diff --git a/app/code/Magento/Checkout/Test/Unit/Controller/Cart/IndexTest.php b/app/code/Magento/Checkout/Test/Unit/Controller/Cart/IndexTest.php new file mode 100644 index 0000000000000000000000000000000000000000..43b5fb8657ded5fc413dc2aaa4f5a627d5af6183 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Unit/Controller/Cart/IndexTest.php @@ -0,0 +1,170 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Checkout\Test\Unit\Controller\Cart; + +use Magento\Checkout\Controller\Cart\Index; + +/** + * Class IndexTest + */ +class IndexTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var Index + */ + protected $controller; + + /** + * @var \Magento\Checkout\Model\Session | \PHPUnit_Framework_MockObject_MockObject + */ + protected $checkoutSession; + + /** + * @var \Magento\Framework\App\Request\Http | \PHPUnit_Framework_MockObject_MockObject + */ + protected $request; + + /** + * @var \Magento\Framework\App\Response\Http | \PHPUnit_Framework_MockObject_MockObject + */ + protected $response; + + /** + * @var \Magento\Quote\Model\Quote | \PHPUnit_Framework_MockObject_MockObject + */ + protected $quote; + + /** + * @var \Magento\Framework\Event\Manager | \PHPUnit_Framework_MockObject_MockObject + */ + protected $eventManager; + + /** + * @var \Magento\Framework\Event\Manager | \PHPUnit_Framework_MockObject_MockObject + */ + protected $objectManagerMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + protected $cart; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + protected $scopeConfig; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + protected $messageManager; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + protected $resultPageFactory; + + /** + * @return void + */ + public function setUp() + { + $this->request = $this->getMock('Magento\Framework\App\Request\Http', [], [], '', false); + $this->response = $this->getMock('Magento\Framework\App\Response\Http', [], [], '', false); + $this->quote = $this->getMock('Magento\Quote\Model\Quote', [], [], '', false); + $this->eventManager = $this->getMock('Magento\Framework\Event\Manager', [], [], '', false); + $this->checkoutSession = $this->getMock('Magento\Checkout\Model\Session', [], [], '', false); + + $this->objectManagerMock = $this->getMock('Magento\Framework\ObjectManager\ObjectManager', [], [], '', false); + + $this->messageManager = $this->getMockBuilder('Magento\Framework\Message\ManagerInterface') + ->disableOriginalConstructor() + ->getMock(); + + $context = $this->getMock('Magento\Framework\App\Action\Context', [], [], '', false); + $context->expects($this->once()) + ->method('getObjectManager') + ->willReturn($this->objectManagerMock); + $context->expects($this->once()) + ->method('getRequest') + ->willReturn($this->request); + $context->expects($this->once()) + ->method('getResponse') + ->willReturn($this->response); + $context->expects($this->once()) + ->method('getEventManager') + ->willReturn($this->eventManager); + $context->expects($this->once()) + ->method('getMessageManager') + ->willReturn($this->messageManager); + + $this->cart = $this->getMockBuilder('Magento\Checkout\Model\Cart') + ->disableOriginalConstructor() + ->getMock(); + $this->scopeConfig = $this->getMockBuilder('Magento\Framework\App\Config\ScopeConfigInterface') + ->disableOriginalConstructor() + ->getMock(); + $this->resultPageFactory = $this->getMockBuilder('Magento\Framework\View\Result\PageFactory') + ->disableOriginalConstructor() + ->setMethods(['create']) + ->getMock(); + + $objectManagerHelper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + + $this->controller = $objectManagerHelper->getObject( + 'Magento\Checkout\Controller\Cart\Index', + [ + 'context' => $context, + 'checkoutSession' => $this->checkoutSession, + 'cart' => $this->cart, + 'scopeConfig' => $this->scopeConfig, + 'resultPageFactory' => $this->resultPageFactory + ] + ); + } + + /** + * @return void + */ + public function testExecuteWithMessages() + { + $layout = $this->getMockBuilder('Magento\Framework\View\Layout') + ->disableOriginalConstructor() + ->getMock(); + $layout->expects($this->once()) + ->method('initMessages'); + + $title = $this->getMockBuilder('Magento\Framework\View\Page\Title') + ->disableOriginalConstructor() + ->getMock(); + $title->expects($this->once()) + ->method('set') + ->with('Shopping Cart'); + + $config = $this->getMockBuilder('Magento\Framework\View\Page\Config') + ->disableOriginalConstructor() + ->getMock(); + $config->expects($this->once()) + ->method('getTitle') + ->willReturn($title); + + $page = $this->getMockBuilder('Magento\Framework\View\Result\Page') + ->disableOriginalConstructor() + ->getMock(); + $page->expects($this->once()) + ->method('getLayout') + ->willReturn($layout); + $page->expects($this->once()) + ->method('getConfig') + ->willReturn($config); + + $this->resultPageFactory->expects($this->once()) + ->method('create') + ->willReturn($page); + $result = $this->controller->execute(); + $this->assertInstanceOf('Magento\Framework\View\Result\Page', $result); + } +} diff --git a/app/code/Magento/Checkout/view/frontend/web/js/opc-checkout-method.js b/app/code/Magento/Checkout/view/frontend/web/js/opc-checkout-method.js index 21b896ecf549ab69eebdc51606ab470afdfa7aa1..bdc635d337b1bd760aa82fa043ec8074b25236e1 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/opc-checkout-method.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/opc-checkout-method.js @@ -234,11 +234,9 @@ define([ if (msg) { if (Array.isArray(msg)) { msg = msg.reduce(function (str, chunk) { - str += '\n' + $.mage.__(chunk); + str += '\n' + chunk; return str; }, ''); - } else { - msg = $.mage.__(msg); } $(this.options.countrySelector).trigger('change'); diff --git a/app/code/Magento/Checkout/view/frontend/web/js/opcheckout.js b/app/code/Magento/Checkout/view/frontend/web/js/opcheckout.js index d8ccb7eecc89cffc5a06899d03e43795c139740d..ac833eb7df2a1ab41c4cf827fa36ccc6b4458de8 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/opcheckout.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/opcheckout.js @@ -198,9 +198,9 @@ define([ $(this.options.countrySelector).trigger('change'); - alert($.mage.__(msg)); + alert(msg); } else { - alert($.mage.__(response.error)); + alert(response.error); } return; diff --git a/app/code/Magento/Core/etc/di.xml b/app/code/Magento/Core/etc/di.xml index 7c0da9d131ee661dffc057d94b4c413292dfd8c5..bb3e14b8fb1be9b9fd1bfd5109410ace0f0dad67 100644 --- a/app/code/Magento/Core/etc/di.xml +++ b/app/code/Magento/Core/etc/di.xml @@ -52,4 +52,25 @@ <argument name="scopeType" xsi:type="const">Magento\Store\Model\ScopeInterface::SCOPE_STORE</argument> </arguments> </type> + <type name="Magento\Framework\View\Asset\PreProcessor\Pool"> + <arguments> + <argument name="preProcessors" xsi:type="array"> + <item name="less" xsi:type="array"> + <item name="css" xsi:type="array"> + <item name="less_css" xsi:type="string">Magento\Framework\Css\PreProcessor\Less</item> + <item name="module_notation" xsi:type="string">Magento\Framework\View\Asset\PreProcessor\ModuleNotation</item> + </item> + <item name="less" xsi:type="array"> + <item name="magento_import" xsi:type="string">Magento\Framework\Less\PreProcessor\Instruction\MagentoImport</item> + <item name="import" xsi:type="string">Magento\Framework\Less\PreProcessor\Instruction\Import</item> + </item> + </item> + <item name="css" xsi:type="array"> + <item name="css" xsi:type="array"> + <item name="module_notation" xsi:type="string">Magento\Framework\View\Asset\PreProcessor\ModuleNotation</item> + </item> + </item> + </argument> + </arguments> + </type> </config> diff --git a/app/code/Magento/Customer/Block/Adminhtml/Edit/Tab/View/PersonalInfo.php b/app/code/Magento/Customer/Block/Adminhtml/Edit/Tab/View/PersonalInfo.php index 7520b9d412947e3b731941d1b43f31547d8c3096..dfd4fcce3fd7b34aad809a63f249975896741d9a 100644 --- a/app/code/Magento/Customer/Block/Adminhtml/Edit/Tab/View/PersonalInfo.php +++ b/app/code/Magento/Customer/Block/Adminhtml/Edit/Tab/View/PersonalInfo.php @@ -7,43 +7,76 @@ namespace Magento\Customer\Block\Adminhtml\Edit\Tab\View; use Magento\Customer\Api\AccountManagementInterface; use Magento\Customer\Controller\RegistryConstants; -use Magento\Customer\Model\AccountManagement; use Magento\Customer\Model\Address\Mapper; use Magento\Framework\Exception\NoSuchEntityException; use Magento\Framework\Stdlib\DateTime\TimezoneInterface; /** - * Adminhtml customer view personal information sales block + * Adminhtml customer view personal information sales block. + * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class PersonalInfo extends \Magento\Backend\Block\Template { /** + * Interval in minutes that shows how long customer will be marked 'Online' + * since his last activity. Used only if it's impossible to get such setting + * from configuration. + */ + const DEFAULT_ONLINE_MINUTES_INTERVAL = 15; + + /** + * Customer + * * @var \Magento\Customer\Api\Data\CustomerInterface */ protected $customer; /** + * Customer log + * + * @var \Magento\Customer\Model\Log + */ + protected $customerLog; + + /** + * Customer logger + * + * @var \Magento\Customer\Model\Logger + */ + protected $customerLogger; + + /** + * Account management + * * @var AccountManagementInterface */ protected $accountManagement; /** + * Customer group repository + * * @var \Magento\Customer\Api\GroupRepositoryInterface */ protected $groupRepository; /** + * Customer data factory + * * @var \Magento\Customer\Api\Data\CustomerInterfaceFactory */ protected $customerDataFactory; /** + * Address helper + * * @var \Magento\Customer\Helper\Address */ protected $addressHelper; /** + * Date time + * * @var \Magento\Framework\Stdlib\DateTime */ protected $dateTime; @@ -56,11 +89,15 @@ class PersonalInfo extends \Magento\Backend\Block\Template protected $coreRegistry; /** + * Address mapper + * * @var Mapper */ protected $addressMapper; /** + * Data object helper + * * @var \Magento\Framework\Api\DataObjectHelper */ protected $dataObjectHelper; @@ -75,6 +112,7 @@ class PersonalInfo extends \Magento\Backend\Block\Template * @param \Magento\Framework\Registry $registry * @param Mapper $addressMapper * @param \Magento\Framework\Api\DataObjectHelper $dataObjectHelper + * @param \Magento\Customer\Model\Logger $customerLogger * @param array $data * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ @@ -88,6 +126,7 @@ class PersonalInfo extends \Magento\Backend\Block\Template \Magento\Framework\Registry $registry, Mapper $addressMapper, \Magento\Framework\Api\DataObjectHelper $dataObjectHelper, + \Magento\Customer\Model\Logger $customerLogger, array $data = [] ) { $this->coreRegistry = $registry; @@ -98,10 +137,14 @@ class PersonalInfo extends \Magento\Backend\Block\Template $this->dateTime = $dateTime; $this->addressMapper = $addressMapper; $this->dataObjectHelper = $dataObjectHelper; + $this->customerLogger = $customerLogger; + parent::__construct($context, $data); } /** + * Retrieve customer object + * * @return \Magento\Customer\Api\Data\CustomerInterface */ public function getCustomer() @@ -118,6 +161,8 @@ class PersonalInfo extends \Magento\Backend\Block\Template } /** + * Retrieve customer id + * * @return string|null */ public function getCustomerId() @@ -125,6 +170,22 @@ class PersonalInfo extends \Magento\Backend\Block\Template return $this->coreRegistry->registry(RegistryConstants::CURRENT_CUSTOMER_ID); } + /** + * Retrieves customer log model + * + * @return \Magento\Customer\Model\Log + */ + protected function getCustomerLog() + { + if (!$this->customerLog) { + $this->customerLog = $this->customerLogger->get( + $this->getCustomer()->getId() + ); + } + + return $this->customerLog; + } + /** * Returns customer's created date in the assigned store * @@ -143,6 +204,8 @@ class PersonalInfo extends \Magento\Backend\Block\Template } /** + * Retrieve store default timezone from configuration + * * @return string */ public function getStoreCreateDateTimezone() @@ -169,6 +232,8 @@ class PersonalInfo extends \Magento\Backend\Block\Template } /** + * Check if account is confirmed + * * @return \Magento\Framework\Phrase */ public function getIsConfirmedStatus() @@ -186,6 +251,8 @@ class PersonalInfo extends \Magento\Backend\Block\Template } /** + * Retrieve store + * * @return null|string */ public function getCreatedInStore() @@ -196,6 +263,8 @@ class PersonalInfo extends \Magento\Backend\Block\Template } /** + * Retrieve billing address html + * * @return \Magento\Framework\Phrase|string */ public function getBillingAddressHtml() @@ -218,6 +287,8 @@ class PersonalInfo extends \Magento\Backend\Block\Template } /** + * Retrieve group name + * * @return string|null */ public function getGroupName() @@ -233,6 +304,8 @@ class PersonalInfo extends \Magento\Backend\Block\Template } /** + * Retrieve customer group by id + * * @param int $groupId * @return \Magento\Customer\Api\Data\GroupInterface|null */ @@ -245,4 +318,106 @@ class PersonalInfo extends \Magento\Backend\Block\Template } return $group; } + + /** + * Returns timezone of the store to which customer assigned. + * + * @return string + */ + public function getStoreLastLoginDateTimezone() + { + return $this->_scopeConfig->getValue( + $this->_localeDate->getDefaultTimezonePath(), + \Magento\Store\Model\ScopeInterface::SCOPE_STORE, + $this->getCustomer()->getStoreId() + ); + } + + /** + * Get customer's current status. + * + * Customer considered 'Offline' in the next cases: + * + * - customer has never been logged in; + * - customer clicked 'Log Out' link\button; + * - predefined interval has passed since customer's last activity. + * + * In all other cases customer considered 'Online'. + * + * @return \Magento\Framework\Phrase + */ + public function getCurrentStatus() + { + $lastLoginTime = $this->getCustomerLog()->getLastLoginAt(); + + // Customer has never been logged in. + if (!$lastLoginTime) { + return __('Offline'); + } + + $lastLogoutTime = $this->getCustomerLog()->getLastLogoutAt(); + + // Customer clicked 'Log Out' link\button. + if ($lastLogoutTime && strtotime($lastLogoutTime) > strtotime($lastLoginTime)) { + return __('Offline'); + } + + // Predefined interval has passed since customer's last activity. + $interval = $this->getOnlineMinutesInterval(); + $currentTimestamp = (new \DateTime())->getTimestamp(); + $lastVisitTime = $this->getCustomerLog()->getLastVisitAt(); + + if ($lastVisitTime && $currentTimestamp - strtotime($lastVisitTime) > $interval * 60) { + return __('Offline'); + } + + return __('Online'); + } + + /** + * Get customer last login date. + * + * @return \Magento\Framework\Phrase|string + */ + public function getLastLoginDate() + { + $date = $this->getCustomerLog()->getLastLoginAt(); + + if ($date) { + return $this->formatDate($date, \IntlDateFormatter::MEDIUM, true); + } + + return __('Never'); + } + + /** + * Returns customer last login date in store's timezone. + * + * @return \Magento\Framework\Phrase|string + */ + public function getStoreLastLoginDate() + { + $date = strtotime($this->getCustomerLog()->getLastLoginAt()); + + if ($date) { + $date = $this->_localeDate->scopeDate($this->getCustomer()->getStoreId(), $date, true); + return $this->formatDate($date, \IntlDateFormatter::MEDIUM, true); + } + + return __('Never'); + } + + /** + * Returns interval that shows how long customer will be considered 'Online'. + * + * @return int Interval in minutes + */ + protected function getOnlineMinutesInterval() + { + $configValue = $this->_scopeConfig->getValue( + 'customer/online_customers/online_minutes_interval', + \Magento\Store\Model\ScopeInterface::SCOPE_STORE + ); + return intval($configValue) > 0 ? intval($configValue) : self::DEFAULT_ONLINE_MINUTES_INTERVAL; + } } diff --git a/app/code/Magento/Customer/Model/AccountManagement.php b/app/code/Magento/Customer/Model/AccountManagement.php index 7fb85ee1caf5ab41cce6309d8843ccacd43f070b..1b9c851c5c3ef0bbb8610ebfb5265bef1b1eeb60 100644 --- a/app/code/Magento/Customer/Model/AccountManagement.php +++ b/app/code/Magento/Customer/Model/AccountManagement.php @@ -30,7 +30,7 @@ use Magento\Framework\Exception\NoSuchEntityException; use Magento\Framework\Exception\State\ExpiredException; use Magento\Framework\Exception\State\InputMismatchException; use Magento\Framework\Exception\State\InvalidTransitionException; -use Psr\Log\LoggerInterface as Logger; +use Psr\Log\LoggerInterface as PsrLogger; use Magento\Framework\Mail\Exception as MailException; use Magento\Framework\Mail\Template\TransportBuilder; use Magento\Framework\Math\Random; @@ -132,7 +132,7 @@ class AccountManagement implements AccountManagementInterface private $url; /** - * @var Logger + * @var PsrLogger */ protected $logger; @@ -215,7 +215,7 @@ class AccountManagement implements AccountManagementInterface * @param CustomerMetadataInterface $customerMetadataService * @param CustomerRegistry $customerRegistry * @param \Magento\Framework\Url $url - * @param Logger $logger + * @param PsrLogger $logger * @param Encryptor $encryptor * @param ConfigShare $configShare * @param StringHelper $stringHelper @@ -243,7 +243,7 @@ class AccountManagement implements AccountManagementInterface CustomerMetadataInterface $customerMetadataService, CustomerRegistry $customerRegistry, \Magento\Framework\Url $url, - Logger $logger, + PsrLogger $logger, Encryptor $encryptor, ConfigShare $configShare, StringHelper $stringHelper, diff --git a/app/code/Magento/Customer/Model/Attribute/Data/Postcode.php b/app/code/Magento/Customer/Model/Attribute/Data/Postcode.php index dfab0a4e277e5280e96c0e2a3df6209393b7682b..247ce01208341bebd1dfbbe7088034771a0be672 100644 --- a/app/code/Magento/Customer/Model/Attribute/Data/Postcode.php +++ b/app/code/Magento/Customer/Model/Attribute/Data/Postcode.php @@ -9,7 +9,7 @@ use Magento\Directory\Helper\Data as DirectoryHelper; use Magento\Eav\Model\AttributeDataFactory; use Magento\Framework\App\RequestInterface; use Magento\Framework\Locale\ResolverInterface; -use Psr\Log\LoggerInterface as Logger; +use Psr\Log\LoggerInterface as PsrLogger; use Magento\Framework\Stdlib\DateTime\TimezoneInterface as MagentoTimezone; /** @@ -25,13 +25,13 @@ class Postcode extends \Magento\Eav\Model\Attribute\Data\AbstractData /** * @param MagentoTimezone $localeDate - * @param Logger $logger + * @param PsrLogger $logger * @param ResolverInterface $localeResolver * @param DirectoryHelper $directoryHelper */ public function __construct( MagentoTimezone $localeDate, - Logger $logger, + PsrLogger $logger, ResolverInterface $localeResolver, DirectoryHelper $directoryHelper ) { @@ -108,6 +108,7 @@ class Postcode extends \Magento\Eav\Model\Attribute\Data\AbstractData * * @param string $format * @return string|array + * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function outputValue($format = AttributeDataFactory::OUTPUT_FORMAT_TEXT) { diff --git a/app/code/Magento/Customer/Model/Log.php b/app/code/Magento/Customer/Model/Log.php new file mode 100644 index 0000000000000000000000000000000000000000..2c65bdcb20cc46472e603af03cd819a08b0a675a --- /dev/null +++ b/app/code/Magento/Customer/Model/Log.php @@ -0,0 +1,96 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Customer\Model; + +/** + * Customer log model. + * + * Contains customer log data. + */ +class Log +{ + /** + * Customer ID. + * + * @var int + */ + protected $customerId; + + /** + * Date and time of customer's last login. + * + * @var string + */ + protected $lastLoginAt; + + /** + * Date and time of customer's last logout. + * + * @var string + */ + protected $lastVisitAt; + + /** + * Date and time of customer's last visit. + * + * @var string + */ + protected $lastLogoutAt; + + /** + * @param int $customerId + * @param string $lastLoginAt + * @param string $lastVisitAt + * @param string $lastLogoutAt + */ + public function __construct($customerId = null, $lastLoginAt = null, $lastVisitAt = null, $lastLogoutAt = null) + { + $this->customerId = $customerId; + $this->lastLoginAt = $lastLoginAt; + $this->lastVisitAt = $lastVisitAt; + $this->lastLogoutAt = $lastLogoutAt; + } + + /** + * Retrieve customer id + * + * @return int + */ + public function getCustomerId() + { + return $this->customerId; + } + + /** + * Retrieve last login date as string + * + * @return string + */ + public function getLastLoginAt() + { + return $this->lastLoginAt; + } + + /** + * Retrieve last visit date as string + * + * @return string + */ + public function getLastVisitAt() + { + return $this->lastVisitAt; + } + + /** + * Retrieve last logout date as string + * + * @return string + */ + public function getLastLogoutAt() + { + return $this->lastLogoutAt; + } +} diff --git a/app/code/Magento/Customer/Model/Logger.php b/app/code/Magento/Customer/Model/Logger.php new file mode 100644 index 0000000000000000000000000000000000000000..e29ceb3cf0c09c805b8eecbf46a05b1356213e6f --- /dev/null +++ b/app/code/Magento/Customer/Model/Logger.php @@ -0,0 +1,118 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Customer\Model; + +/** + * Customer log data logger. + * + * Saves and retrieves customer log data. + */ +class Logger +{ + /** + * Resource instance. + * + * @var \Magento\Framework\App\Resource + */ + protected $resource; + + /** + * @var \Magento\Customer\Model\LogFactory + */ + protected $logFactory; + + /** + * @param \Magento\Framework\App\Resource $resource + * @param \Magento\Customer\Model\LogFactory $logFactory + */ + public function __construct( + \Magento\Framework\App\Resource $resource, + \Magento\Customer\Model\LogFactory $logFactory + ) { + $this->resource = $resource; + $this->logFactory = $logFactory; + } + + /** + * Save (insert new or update existing) log. + * + * @param int $customerId + * @param array $data + * @return $this + * @throws \InvalidArgumentException + */ + public function log($customerId, array $data) + { + $data = array_filter($data); + + if (!$data) { + throw new \InvalidArgumentException("Log data is empty"); + } + + /** @var \Magento\Framework\DB\Adapter\AdapterInterface $adapter */ + $adapter = $this->resource->getConnection('write'); + + $adapter->insertOnDuplicate( + $this->resource->getTableName('customer_log'), + array_merge(['customer_id' => $customerId], $data), + array_keys($data) + ); + + return $this; + } + + /** + * Load log by Customer Id. + * + * @param int $customerId + * @return Log + */ + public function get($customerId = null) + { + $data = (null !== $customerId) ? $this->loadLogData($customerId) : []; + + return $this->logFactory->create( + [ + 'customerId' => isset($data['customer_id']) ? $data['customer_id'] : null, + 'lastLoginAt' => isset($data['last_login_at']) ? $data['last_login_at'] : null, + 'lastLogoutAt' => isset($data['last_logout_at']) ? $data['last_logout_at'] : null, + 'lastVisitAt' => isset($data['last_visit_at']) ? $data['last_visit_at'] : null + ] + ); + } + + /** + * Load customer log data by customer id + * + * @param int $customerId + * @return array + */ + protected function loadLogData($customerId) + { + /** @var \Magento\Framework\DB\Adapter\AdapterInterface $adapter */ + $adapter = $this->resource->getConnection('read'); + + $select = $adapter->select() + ->from( + ['cl' => $this->resource->getTableName('customer_log')] + ) + ->joinLeft( + ['cv' => $this->resource->getTableName('customer_visitor')], + 'cv.customer_id = cl.customer_id', + ['last_visit_at'] + ) + ->where( + 'cl.customer_id = ?', + $customerId + ) + ->order( + 'cv.visitor_id DESC' + ) + ->limit(1); + + return $adapter->fetchRow($select); + } +} diff --git a/app/code/Magento/Customer/Model/Metadata/Form/Postcode.php b/app/code/Magento/Customer/Model/Metadata/Form/Postcode.php index c65d0e7adaa2208981f16ac3de73566ae711570c..0af212b81b191a9c2d8cca6094fafdc6b996865f 100644 --- a/app/code/Magento/Customer/Model/Metadata/Form/Postcode.php +++ b/app/code/Magento/Customer/Model/Metadata/Form/Postcode.php @@ -9,7 +9,7 @@ use Magento\Customer\Api\Data\AttributeMetadataInterface; use Magento\Customer\Model\Metadata\ElementFactory; use Magento\Directory\Helper\Data as DirectoryHelper; use Magento\Framework\Locale\ResolverInterface; -use Psr\Log\LoggerInterface as Logger; +use Psr\Log\LoggerInterface as PsrLogger; use Magento\Framework\Stdlib\DateTime\TimezoneInterface as MagentoTimezone; /** @@ -24,7 +24,7 @@ class Postcode extends AbstractData /** * @param MagentoTimezone $localeDate - * @param Logger $logger + * @param PsrLogger $logger * @param AttributeMetadataInterface $attribute * @param ResolverInterface $localeResolver * @param string $value @@ -34,7 +34,7 @@ class Postcode extends AbstractData */ public function __construct( MagentoTimezone $localeDate, - Logger $logger, + PsrLogger $logger, AttributeMetadataInterface $attribute, ResolverInterface $localeResolver, $value, diff --git a/app/code/Magento/Customer/Model/Observer/Log.php b/app/code/Magento/Customer/Model/Observer/Log.php new file mode 100644 index 0000000000000000000000000000000000000000..539221b5e378f47a537816c4d1e3a14457ab6ea1 --- /dev/null +++ b/app/code/Magento/Customer/Model/Observer/Log.php @@ -0,0 +1,59 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Customer\Model\Observer; + +use Magento\Customer\Model\Logger; +use Magento\Framework\Stdlib\DateTime; +use Magento\Framework\Event\Observer; + +/** + * Customer log observer. + */ +class Log +{ + /** + * Logger of customer's log data. + * + * @var Logger + */ + protected $logger; + + /** + * @param Logger $logger + */ + public function __construct(Logger $logger) + { + $this->logger = $logger; + } + + /** + * Handler for 'customer_login' event. + * + * @param Observer $observer + * @return void + */ + public function logLastLoginAt(Observer $observer) + { + $this->logger->log( + $observer->getEvent()->getCustomer()->getId(), + ['last_login_at' => (new \DateTime())->format(\Magento\Framework\Stdlib\DateTime::DATETIME_PHP_FORMAT)] + ); + } + + /** + * Handler for 'customer_logout' event. + * + * @param Observer $observer + * @return void + */ + public function logLastLogoutAt(Observer $observer) + { + $this->logger->log( + $observer->getEvent()->getCustomer()->getId(), + ['last_logout_at' => (new \DateTime())->format(\Magento\Framework\Stdlib\DateTime::DATETIME_PHP_FORMAT)] + ); + } +} diff --git a/app/code/Magento/Customer/Model/Resource/Visitor.php b/app/code/Magento/Customer/Model/Resource/Visitor.php index 639590bd07e85009d7002a63e1d16f678f875f18..8cc63d45988cad52b67034d5f07a32af0bfbc338 100644 --- a/app/code/Magento/Customer/Model/Resource/Visitor.php +++ b/app/code/Magento/Customer/Model/Resource/Visitor.php @@ -58,6 +58,7 @@ class Visitor extends \Magento\Framework\Model\Resource\Db\AbstractDb protected function _prepareDataForSave(\Magento\Framework\Model\AbstractModel $visitor) { return [ + 'customer_id' => $visitor->getCustomerId(), 'session_id' => $visitor->getSessionId(), 'last_visit_at' => $visitor->getLastVisitAt() ]; diff --git a/app/code/Magento/Customer/Model/Vat.php b/app/code/Magento/Customer/Model/Vat.php index d20d51b2f788155be9e6b62bea29cbe69e5cecad..0930595e6758900822694ea2b2b410ff490999df 100644 --- a/app/code/Magento/Customer/Model/Vat.php +++ b/app/code/Magento/Customer/Model/Vat.php @@ -6,7 +6,7 @@ namespace Magento\Customer\Model; use Magento\Framework\App\Config\ScopeConfigInterface; -use Psr\Log\LoggerInterface as Logger; +use Psr\Log\LoggerInterface as PsrLogger; use Magento\Store\Model\ScopeInterface; /** @@ -68,17 +68,17 @@ class Vat protected $scopeConfig; /** - * @var Logger + * @var PsrLogger */ protected $logger; /** * @param ScopeConfigInterface $scopeConfig - * @param Logger $logger + * @param PsrLogger $logger */ public function __construct( ScopeConfigInterface $scopeConfig, - Logger $logger + PsrLogger $logger ) { $this->scopeConfig = $scopeConfig; $this->logger = $logger; diff --git a/app/code/Magento/Customer/Model/Visitor.php b/app/code/Magento/Customer/Model/Visitor.php index 4fc1c3a8026535a7f3bad674f5c1abea19b7fb16..132fd35acc9d1c66adc5be763fd2214760fb6247 100644 --- a/app/code/Magento/Customer/Model/Visitor.php +++ b/app/code/Magento/Customer/Model/Visitor.php @@ -133,14 +133,15 @@ class Visitor extends \Magento\Framework\Model\AbstractModel if ($this->skipRequestLogging || $this->isModuleIgnored($observer)) { return $this; } + if ($this->session->getVisitorData()) { $this->setData($this->session->getVisitorData()); } + + $this->setLastVisitAt((new \DateTime())->format(\Magento\Framework\Stdlib\DateTime::DATETIME_PHP_FORMAT)); + if (!$this->getId()) { $this->setSessionId($this->session->getSessionId()); - $this->setLastVisitAt( - (new \DateTime())->format(\Magento\Framework\Stdlib\DateTime::DATETIME_PHP_FORMAT) - ); $this->save(); $this->_eventManager->dispatch('visitor_init', ['visitor' => $this]); $this->session->setVisitorData($this->getData()); diff --git a/app/code/Magento/Customer/Setup/UpgradeSchema.php b/app/code/Magento/Customer/Setup/UpgradeSchema.php index 6ebf692405649991e59149a9f47a5ffda6508ce3..2b7a160eb3de8495fa5fa096bb76dd41a3e748e3 100644 --- a/app/code/Magento/Customer/Setup/UpgradeSchema.php +++ b/app/code/Magento/Customer/Setup/UpgradeSchema.php @@ -6,6 +6,8 @@ namespace Magento\Customer\Setup; +use Magento\Framework\DB\Ddl\Table; +use Magento\Framework\DB\Adapter\AdapterInterface; use Magento\Framework\Setup\UpgradeSchemaInterface; use Magento\Framework\Setup\ModuleContextInterface; use Magento\Framework\Setup\SchemaSetupInterface; @@ -17,12 +19,14 @@ class UpgradeSchema implements UpgradeSchemaInterface { /** * {@inheritdoc} + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ public function upgrade(SchemaSetupInterface $setup, ModuleContextInterface $context) { + $setup->startSetup(); + if (version_compare($context->getVersion(), '2.0.0.1') < 0) { $installer = $setup; - $installer->startSetup(); $connection = $installer->getConnection(); $tableNames = [ @@ -57,7 +61,96 @@ class UpgradeSchema implements UpgradeSchemaInterface ); $connection->dropColumn($installer->getTable('customer_entity'), 'entity_type_id'); $connection->dropColumn($installer->getTable('customer_entity'), 'attribute_set_id'); - $installer->endSetup(); } + + if (version_compare($context->getVersion(), '2.0.0.2') < 0) { + /** + * Update 'customer_visitor' table. + */ + $setup->getConnection() + ->addColumn( + $setup->getTable('customer_visitor'), + 'customer_id', + [ + 'type' => Table::TYPE_INTEGER, + 'after' => 'visitor_id', + 'comment' => 'Customer ID' + ] + ); + + $setup->getConnection() + ->addIndex( + $setup->getTable('customer_visitor'), + $setup->getIdxName( + $setup->getTable('customer_visitor'), + ['customer_id'] + ), + 'customer_id' + ); + + /** + * Create 'customer_log' table. + */ + $table = $setup->getConnection() + ->newTable( + $setup->getTable('customer_log') + ) + ->addColumn( + 'log_id', + Table::TYPE_INTEGER, + null, + [ + 'nullable' => false, + 'identity' => true, + 'primary' => true + ], + 'Log ID' + ) + ->addColumn( + 'customer_id', + Table::TYPE_INTEGER, + null, + [ + 'nullable' => false + ], + 'Customer ID' + ) + ->addColumn( + 'last_login_at', + Table::TYPE_TIMESTAMP, + null, + [ + 'nullable' => true, + 'default' => null + ], + 'Last Login Time' + ) + ->addColumn( + 'last_logout_at', + Table::TYPE_TIMESTAMP, + null, + [ + 'nullable' => true, + 'default' => null + ], + 'Last Logout Time' + ) + ->addIndex( + $setup->getIdxName( + $setup->getTable('customer_log'), + ['customer_id'], + AdapterInterface::INDEX_TYPE_UNIQUE + ), + ['customer_id'], + [ + 'type' => AdapterInterface::INDEX_TYPE_UNIQUE + ] + ) + ->setComment('Customer Log Table'); + + $setup->getConnection()->createTable($table); + } + + $setup->endSetup(); } } diff --git a/app/code/Magento/Customer/Test/Unit/Block/Adminhtml/Edit/Tab/View/PersonalInfoTest.php b/app/code/Magento/Customer/Test/Unit/Block/Adminhtml/Edit/Tab/View/PersonalInfoTest.php new file mode 100644 index 0000000000000000000000000000000000000000..a196a2917f99477245f5f7e8cc7fd6b74533dfcb --- /dev/null +++ b/app/code/Magento/Customer/Test/Unit/Block/Adminhtml/Edit/Tab/View/PersonalInfoTest.php @@ -0,0 +1,239 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Customer\Test\Unit\Block\Adminhtml\Edit\Tab\View; + +use Magento\Customer\Block\Adminhtml\Edit\Tab\View\PersonalInfo; +use Magento\Framework\Stdlib\DateTime; + +/** + * Customer personal information template block test. + */ +class PersonalInfoTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var string + */ + protected $defaultTimezone = 'America/Los_Angeles'; + + /** + * @var string + */ + protected $pathToDefaultTimezone = 'path/to/default/timezone'; + + /** + * @var PersonalInfo + */ + protected $block; + + /** + * @var \Magento\Customer\Model\Log|\PHPUnit_Framework_MockObject_MockObject + */ + protected $customerLog; + + /** + * @var \Magento\Framework\Stdlib\DateTime\TimezoneInterface|\PHPUnit_Framework_MockObject_MockObject + */ + protected $localeDate; + + /** + * @var \Magento\Framework\App\Config\ScopeConfigInterface|\PHPUnit_Framework_MockObject_MockObject + */ + protected $scopeConfig; + + /** + * @return void + */ + protected function setUp() + { + $customer = $this->getMock( + 'Magento\Customer\Api\Data\CustomerInterface', + [], + [], + '', + false + ); + $customer->expects($this->any())->method('getId')->willReturn(1); + $customer->expects($this->any())->method('getStoreId')->willReturn(1); + + $customerDataFactory = $this->getMock( + 'Magento\Customer\Api\Data\CustomerInterfaceFactory', + ['create'], + [], + '', + false + ); + $customerDataFactory->expects($this->any())->method('create')->willReturn($customer); + + $backendSession = $this->getMock( + 'Magento\Backend\Model\Session', + ['getCustomerData'], + [], + '', + false + ); + $backendSession->expects($this->any())->method('getCustomerData')->willReturn(['account' => []]); + + $this->customerLog = $this->getMock( + 'Magento\Customer\Model\Log', + ['getLastLoginAt', 'getLastVisitAt', 'getLastLogoutAt'], + [], + '', + false + ); + $this->customerLog->expects($this->any())->method('loadByCustomer')->willReturnSelf(); + + $customerLogger = $this->getMock( + 'Magento\Customer\Model\Logger', + ['get'], + [], + '', + false + ); + $customerLogger->expects($this->any())->method('get')->willReturn($this->customerLog); + + $dateTime = $this->getMock( + 'Magento\Framework\Stdlib\DateTime', + ['now'], + [], + '', + false + ); + $dateTime->expects($this->any())->method('now')->willReturn('2015-03-04 12:00:00'); + + $this->localeDate = $this->getMock( + 'Magento\Framework\Stdlib\DateTime\Timezone', + ['scopeDate', 'formatDateTime', 'getDefaultTimezonePath'], + [], + '', + false + ); + $this->localeDate + ->expects($this->any()) + ->method('getDefaultTimezonePath') + ->willReturn($this->pathToDefaultTimezone); + + $this->scopeConfig = $this->getMock( + 'Magento\Framework\App\Config', + ['getValue'], + [], + '', + false + ); + + $objectManagerHelper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + + $this->block = $objectManagerHelper->getObject( + 'Magento\Customer\Block\Adminhtml\Edit\Tab\View\PersonalInfo', + [ + 'customerDataFactory' => $customerDataFactory, + 'dateTime' => $dateTime, + 'customerLogger' => $customerLogger, + 'localeDate' => $this->localeDate, + 'scopeConfig' => $this->scopeConfig, + 'backendSession' => $backendSession, + ] + ); + } + + /** + * @return void + */ + public function testGetStoreLastLoginDateTimezone() + { + $this->scopeConfig + ->expects($this->once()) + ->method('getValue') + ->with( + $this->pathToDefaultTimezone, + \Magento\Store\Model\ScopeInterface::SCOPE_STORE + ) + ->willReturn($this->defaultTimezone); + + $this->assertEquals($this->defaultTimezone, $this->block->getStoreLastLoginDateTimezone()); + } + + /** + * @param string $status + * @param string|null $lastLoginAt + * @param string|null $lastVisitAt + * @param string|null $lastLogoutAt + * @return void + * @dataProvider getCurrentStatusDataProvider + */ + public function testGetCurrentStatus($status, $lastLoginAt, $lastVisitAt, $lastLogoutAt) + { + $this->customerLog->expects($this->any())->method('getLastLoginAt')->willReturn($lastLoginAt); + $this->customerLog->expects($this->any())->method('getLastVisitAt')->willReturn($lastVisitAt); + $this->customerLog->expects($this->any())->method('getLastLogoutAt')->willReturn($lastLogoutAt); + + $this->assertEquals($status, (string) $this->block->getCurrentStatus()); + } + + /** + * @return array + */ + public function getCurrentStatusDataProvider() + { + return [ + ['Offline', null, null, null], + ['Offline', '2015-03-04 11:00:00', null, '2015-03-04 12:00:00'], + ['Offline', '2015-03-04 11:00:00', '2015-03-04 11:40:00', null], + ['Online', '2015-03-04 11:00:00', (new \DateTime())->format(DateTime::DATETIME_PHP_FORMAT), null] + ]; + } + + /** + * @param string $result + * @param string|null $lastLoginAt + * @dataProvider getLastLoginDateDataProvider + * @return void + */ + public function testGetLastLoginDate($result, $lastLoginAt) + { + $this->customerLog->expects($this->once())->method('getLastLoginAt')->willReturn($lastLoginAt); + $this->localeDate->expects($this->any())->method('formatDateTime')->willReturn($lastLoginAt); + + $this->assertEquals($result, $this->block->getLastLoginDate()); + } + + /** + * @return array + */ + public function getLastLoginDateDataProvider() + { + return [ + ['2015-03-04 12:00:00', '2015-03-04 12:00:00'], + ['Never', null] + ]; + } + + /** + * @param string $result + * @param string|null $lastLoginAt + * @dataProvider getStoreLastLoginDateDataProvider + * @return void + */ + public function testGetStoreLastLoginDate($result, $lastLoginAt) + { + $this->customerLog->expects($this->once())->method('getLastLoginAt')->willReturn($lastLoginAt); + + $this->localeDate->expects($this->any())->method('scopeDate')->will($this->returnValue($lastLoginAt)); + $this->localeDate->expects($this->any())->method('formatDateTime')->willReturn($lastLoginAt); + + $this->assertEquals($result, $this->block->getStoreLastLoginDate()); + } + + /** + * @return array + */ + public function getStoreLastLoginDateDataProvider() + { + return [ + ['2015-03-04 12:00:00', '2015-03-04 12:00:00'], + ['Never', null] + ]; + } +} diff --git a/app/code/Magento/Customer/Test/Unit/Model/LogTest.php b/app/code/Magento/Customer/Test/Unit/Model/LogTest.php new file mode 100644 index 0000000000000000000000000000000000000000..96ce27aef717692871d99e6702bfde5a5314d6a9 --- /dev/null +++ b/app/code/Magento/Customer/Test/Unit/Model/LogTest.php @@ -0,0 +1,79 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Customer\Test\Unit\Model; + +/** + * Customer log model test. + */ +class LogTest extends \PHPUnit_Framework_TestCase +{ + /** + * Customer log model. + * + * @var \Magento\Customer\Model\Log + */ + protected $log; + + /** + * @var array + */ + protected $logData = [ + 'customer_id' => 369, + 'last_login_at' => '2015-03-04 12:00:00', + 'last_visit_at' => '2015-03-04 12:01:00', + 'last_logout_at' => '2015-03-04 12:05:00', + ]; + + /** + * @return void + */ + protected function setUp() + { + $objectManagerHelper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + + $this->log = $objectManagerHelper->getObject( + 'Magento\Customer\Model\Log', + [ + 'customerId' => $this->logData['customer_id'], + 'lastLoginAt' => $this->logData['last_login_at'], + 'lastVisitAt' => $this->logData['last_visit_at'], + 'lastLogoutAt' => $this->logData['last_logout_at'] + ] + ); + } + + /** + * @return void + */ + public function testGetCustomerId() + { + $this->assertEquals($this->logData['customer_id'], $this->log->getCustomerId()); + } + + /** + * @return void + */ + public function testGetLastLoginAt() + { + $this->assertEquals($this->logData['last_login_at'], $this->log->getLastLoginAt()); + } + + /** + * @return void + */ + public function testGetLastVisitAt() + { + $this->assertEquals($this->logData['last_visit_at'], $this->log->getLastVisitAt()); + } + + /** + * @return void + */ + public function testGetLastLogoutAt() + { + $this->assertEquals($this->logData['last_logout_at'], $this->log->getLastLogoutAt()); + } +} diff --git a/app/code/Magento/Customer/Test/Unit/Model/LoggerTest.php b/app/code/Magento/Customer/Test/Unit/Model/LoggerTest.php new file mode 100644 index 0000000000000000000000000000000000000000..88c7f55c22a3eb5ad0c5666d8ab0fcf77b87dac9 --- /dev/null +++ b/app/code/Magento/Customer/Test/Unit/Model/LoggerTest.php @@ -0,0 +1,193 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Customer\Test\Unit\Model; + +/** + * Customer log data logger test. + */ +class LoggerTest extends \PHPUnit_Framework_TestCase +{ + /** + * Customer log data logger. + * + * @var \Magento\Customer\Model\Logger + */ + protected $logger; + + /** + * @var \Magento\Customer\Model\LogFactory|\PHPUnit_Framework_MockObject_MockObject + */ + protected $logFactory; + + /** + * Resource instance. + * + * @var \Magento\Framework\App\Resource|\PHPUnit_Framework_MockObject_MockObject + */ + protected $resource; + + /** + * DB connection instance. + * + * @var \Magento\Framework\DB\Adapter\Pdo|\PHPUnit_Framework_MockObject_MockObject + */ + protected $adapter; + + /** + * @return void + */ + protected function setUp() + { + $this->adapter = $this->getMock( + 'Magento\Framework\DB\Adapter\Pdo', + ['select', 'insertOnDuplicate', 'fetchRow'], + [], + '', + false + ); + $this->resource = $this->getMock('Magento\Framework\App\Resource', [], [], '', false); + $this->logFactory = $this->getMock('\Magento\Customer\Model\LogFactory', ['create'], [], '', false); + + $objectManagerHelper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + + $this->logger = $objectManagerHelper->getObject( + '\Magento\Customer\Model\Logger', + [ + 'resource' => $this->resource, + 'logFactory' => $this->logFactory + ] + ); + } + + /** + * @param int $customerId + * @param array $data + * @dataProvider testLogDataProvider + * @return void + */ + public function testLog($customerId, $data) + { + $tableName = 'customer_log_table_name'; + $data = array_filter($data); + + if (!$data) { + $this->setExpectedException('\InvalidArgumentException', 'Log data is empty'); + $this->logger->log($customerId, $data); + return; + } + + $this->resource->expects($this->once()) + ->method('getConnection') + ->with('write') + ->willReturn($this->adapter); + $this->resource->expects($this->once()) + ->method('getTableName') + ->with('customer_log') + ->willReturn($tableName); + $this->adapter->expects($this->once()) + ->method('insertOnDuplicate') + ->with($tableName, array_merge(['customer_id' => $customerId], $data), array_keys($data)); + + $this->assertEquals($this->logger, $this->logger->log($customerId, $data)); + } + + /** + * @return array + */ + public function testLogDataProvider() + { + return [ + [235, ['last_login_at' => '2015-03-04 12:00:00']], + [235, ['last_login_at' => null]], + ]; + } + + /** + * @param int $customerId + * @param array $data + * @dataProvider testGetDataProvider + * @return void + */ + public function testGet($customerId, $data) + { + $logArguments = [ + 'customerId' => $data['customer_id'], + 'lastLoginAt' => $data['last_login_at'], + 'lastLogoutAt' => $data['last_logout_at'], + 'lastVisitAt' => $data['last_visit_at'] + ]; + + $select = $this->getMock('Magento\Framework\DB\Select', [], [], '', false); + + $select->expects($this->any())->method('from')->willReturnSelf(); + $select->expects($this->any())->method('joinLeft')->willReturnSelf(); + $select->expects($this->any())->method('where')->willReturnSelf(); + $select->expects($this->any())->method('order')->willReturnSelf(); + $select->expects($this->any())->method('limit')->willReturnSelf(); + + $this->adapter->expects($this->any()) + ->method('select') + ->willReturn($select); + + $this->resource->expects($this->once()) + ->method('getConnection') + ->with('read') + ->willReturn($this->adapter); + $this->adapter->expects($this->any()) + ->method('fetchRow') + ->with($select) + ->willReturn($data); + + $log = $this->getMock( + 'Magento\Customer\Model\Log', + [], + $logArguments + ); + + $this->logFactory->expects($this->any()) + ->method('create') + ->with($logArguments) + ->willReturn($log); + + $this->assertEquals($log, $this->logger->get($customerId)); + } + + /** + * @return array + */ + public function testGetDataProvider() + { + return [ + [ + 235, + [ + 'customer_id' => 369, + 'last_login_at' => '2015-03-04 12:00:00', + 'last_visit_at' => '2015-03-04 12:01:00', + 'last_logout_at' => '2015-03-04 12:05:00', + ] + ], + [ + 235, + [ + 'customer_id' => 369, + 'last_login_at' => '2015-03-04 12:00:00', + 'last_visit_at' => '2015-03-04 12:01:00', + 'last_logout_at' => null, + ] + ], + [ + 235, + [ + 'customer_id' => null, + 'last_login_at' => null, + 'last_visit_at' => null, + 'last_logout_at' => null, + ] + ], + ]; + } +} diff --git a/app/code/Magento/Customer/Test/Unit/Model/Observer/LogTest.php b/app/code/Magento/Customer/Test/Unit/Model/Observer/LogTest.php new file mode 100644 index 0000000000000000000000000000000000000000..fcd3ff2c58f76fff1619f3804c0c079eb8c5c397 --- /dev/null +++ b/app/code/Magento/Customer/Test/Unit/Model/Observer/LogTest.php @@ -0,0 +1,90 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Customer\Test\Unit\Model\Observer; + +use Magento\Customer\Model\Logger; +use Magento\Framework\Stdlib\DateTime; +use Magento\Framework\Event\Observer; +use Magento\Customer\Model\Observer\Log; + +/** + * Class LogTest + */ +class LogTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var Log + */ + protected $logObserver; + + /** + * @var Logger | \PHPUnit_Framework_MockObject_MockObject + */ + protected $loggerMock; + + /** + * @return void + */ + public function setUp() + { + $this->loggerMock = $this->getMock('Magento\Customer\Model\Logger', [], [], '', false); + $this->logObserver = new Log($this->loggerMock); + } + + /** + * @return void + */ + public function testLogLastLoginAt() + { + $id = 1; + + $observerMock = $this->getMock('Magento\Framework\Event\Observer', [], [], '', false); + $eventMock = $this->getMock('Magento\Framework\Event', ['getCustomer'], [], '', false); + $customerMock = $this->getMock('Magento\Customer\Model\Customer', [], [], '', false); + + $observerMock->expects($this->once()) + ->method('getEvent') + ->willReturn($eventMock); + $eventMock->expects($this->once()) + ->method('getCustomer') + ->willReturn($customerMock); + $customerMock->expects($this->once()) + ->method('getId') + ->willReturn($id); + + $this->loggerMock->expects($this->once()) + ->method('log'); + + $this->logObserver->logLastLoginAt($observerMock); + } + + /** + * @return void + */ + public function testLogLastLogoutAt() + { + $id = 1; + + $observerMock = $this->getMock('Magento\Framework\Event\Observer', [], [], '', false); + $eventMock = $this->getMock('Magento\Framework\Event', ['getCustomer'], [], '', false); + $customerMock = $this->getMock('Magento\Customer\Model\Customer', [], [], '', false); + + $observerMock->expects($this->once()) + ->method('getEvent') + ->willReturn($eventMock); + $eventMock->expects($this->once()) + ->method('getCustomer') + ->willReturn($customerMock); + $customerMock->expects($this->once()) + ->method('getId') + ->willReturn($id); + + $this->loggerMock->expects($this->once()) + ->method('log'); + + $this->logObserver->logLastLogoutAt($observerMock); + } +} diff --git a/app/code/Magento/Customer/etc/frontend/events.xml b/app/code/Magento/Customer/etc/frontend/events.xml index 81c65b5aa1c648e3cfe5e9f422e16ab4a268bd4e..a91b32c0ea159ab5e69b7d80732252cf72f16814 100644 --- a/app/code/Magento/Customer/etc/frontend/events.xml +++ b/app/code/Magento/Customer/etc/frontend/events.xml @@ -17,6 +17,7 @@ </event> <event name="customer_logout"> <observer name="customer_visitor" instance="Magento\Customer\Model\Visitor" method="bindCustomerLogout" /> + <observer name="customer_log_logout" instance="Magento\Customer\Model\Observer\Log" method="logLastLogoutAt" /> </event> <event name="sales_quote_save_after"> <observer name="customer_visitor" instance="Magento\Customer\Model\Visitor" method="bindQuoteCreate" /> @@ -24,4 +25,7 @@ <event name="checkout_quote_destroy"> <observer name="customer_visitor" instance="Magento\Customer\Model\Visitor" method="bindQuoteDestroy" /> </event> + <event name="customer_login"> + <observer name="customer_log_login" instance="Magento\Customer\Model\Observer\Log" method="logLastLoginAt" /> + </event> </config> diff --git a/app/code/Magento/Customer/etc/module.xml b/app/code/Magento/Customer/etc/module.xml index 33f923e05015b2ac6618f497ca5ea129be68bb74..973c14db73d61034756a46694f3abab1856ed480 100644 --- a/app/code/Magento/Customer/etc/module.xml +++ b/app/code/Magento/Customer/etc/module.xml @@ -6,7 +6,7 @@ */ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../lib/internal/Magento/Framework/Module/etc/module.xsd"> - <module name="Magento_Customer" setup_version="2.0.0.1"> + <module name="Magento_Customer" setup_version="2.0.0.2"> <sequence> <module name="Magento_Eav"/> <module name="Magento_Directory"/> diff --git a/app/code/Magento/Customer/view/adminhtml/templates/tab/view/personal_info.phtml b/app/code/Magento/Customer/view/adminhtml/templates/tab/view/personal_info.phtml index 51204820fd18123d88d90b6c07aadda993728468..e87d3005a1dad3e90d4d06b8c67ea73f561627e7 100644 --- a/app/code/Magento/Customer/view/adminhtml/templates/tab/view/personal_info.phtml +++ b/app/code/Magento/Customer/view/adminhtml/templates/tab/view/personal_info.phtml @@ -10,8 +10,11 @@ * Template for block \Magento\Customer\Block\Adminhtml\Edit\Tab\View\Status\PersonalInfo */ -$createDateAdmin = $block->getCreateDate(); -$createDateStore = $block->getStoreCreateDate(); +$lastLoginDateAdmin = $block->getLastLoginDate(); +$lastLoginDateStore = $block->getStoreLastLoginDate(); + +$createDateAdmin = $block->getCreateDate(); +$createDateStore = $block->getStoreCreateDate(); ?> <div class="fieldset-wrapper customer-information"> @@ -21,6 +24,16 @@ $createDateStore = $block->getStoreCreateDate(); <table class="data-table"> <tbody> <?php echo $block->getChildHtml(); ?> + <tr> + <th><?php echo __('Last Logged In:') ?></th> + <td><?php echo $lastLoginDateAdmin ?> (<?php echo $block->getCurrentStatus() ?>)</td> + </tr> + <?php if ($lastLoginDateAdmin != $lastLoginDateStore): ?> + <tr> + <th><?php echo __('Last Logged In (%1):', $block->getStoreLastLoginDateTimezone()) ?></th> + <td><?php echo $lastLoginDateStore ?> (<?php echo $block->getCurrentStatus() ?>)</td> + </tr> + <?php endif; ?> <tr> <th><?php echo __('Confirmed email:') ?></th> <td><?php echo $block->getIsConfirmedStatus() ?></td> diff --git a/app/code/Magento/DesignEditor/view/adminhtml/web/js/dialog.js b/app/code/Magento/DesignEditor/view/adminhtml/web/js/dialog.js index ad58d32bcbe86077a511d6c2971e2e2efbe943ea..d19339d3f2552893755b919cf4232eef13dc86e3 100644 --- a/app/code/Magento/DesignEditor/view/adminhtml/web/js/dialog.js +++ b/app/code/Magento/DesignEditor/view/adminhtml/web/js/dialog.js @@ -99,9 +99,6 @@ define([ * @param {Array.<Object>|Object} buttons */ set: function (title, text, buttons) { - title = $.mage.__(title); - text = $.mage.__(text); - this.text.set(text); this.title.set(title); this.setButtons(buttons); @@ -120,9 +117,6 @@ define([ if ($.type(buttons) !== 'array') { buttons = [buttons]; } - buttons.each(function(button){ - button.text = $.mage.__(button.text) - }); } var hasToAddCancel = (addCancel == undefined && buttons.length <= 1) || addCancel == true; @@ -146,8 +140,6 @@ define([ * @param {number} position */ addButton: function(button, position) { - button.text = $.mage.__(button.text) - var buttons = this.options.buttons; buttons.splice(position, 0, button); this._setOption('buttons', buttons); @@ -166,7 +158,7 @@ define([ position = buttonPointer; } else { //Find 1st button with given title - var title = $.mage.__(buttonPointer); + var title = buttonPointer; this.options.buttons.each(function(button, index) { if (button.text == title) { position = index; diff --git a/app/code/Magento/Log/Block/Adminhtml/Customer/Edit/Tab/View/Status.php b/app/code/Magento/Log/Block/Adminhtml/Customer/Edit/Tab/View/Status.php deleted file mode 100644 index c95e4ba9827f3facf32bf1257a2c4081a48e3a18..0000000000000000000000000000000000000000 --- a/app/code/Magento/Log/Block/Adminhtml/Customer/Edit/Tab/View/Status.php +++ /dev/null @@ -1,155 +0,0 @@ -<?php -/** - * Copyright © 2015 Magento. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\Log\Block\Adminhtml\Customer\Edit\Tab\View; - -/** - * Class Status - * @SuppressWarnings(PHPMD.CouplingBetweenObjects) - */ -class Status extends \Magento\Backend\Block\Template -{ - /** - * @var \Magento\Customer\Api\Data\CustomerInterface - */ - protected $customer; - - /** - * @var \Magento\Log\Model\Customer - */ - protected $customerLog; - - /** - * @var \Magento\Log\Model\Visitor - */ - protected $modelLog; - - /** - * @var \Magento\Log\Model\CustomerFactory - */ - protected $logFactory; - - /** - * @var \Magento\Customer\Api\Data\CustomerInterfaceFactory - */ - protected $customerFactory; - - /** - * @var \Magento\Framework\Api\DataObjectHelper - */ - protected $dataObjectHelper; - - /** - * @param \Magento\Backend\Block\Template\Context $context - * @param \Magento\Log\Model\CustomerFactory $logFactory - * @param \Magento\Log\Model\Log $modelLog - * @param \Magento\Framework\Stdlib\DateTime $dateTime - * @param \Magento\Customer\Api\Data\CustomerInterfaceFactory $customerFactory - * @param \Magento\Framework\Api\DataObjectHelper $dataObjectHelper - * @param array $data - */ - public function __construct( - \Magento\Backend\Block\Template\Context $context, - \Magento\Log\Model\CustomerFactory $logFactory, - \Magento\Log\Model\Log $modelLog, - \Magento\Framework\Stdlib\DateTime $dateTime, - \Magento\Customer\Api\Data\CustomerInterfaceFactory $customerFactory, - \Magento\Framework\Api\DataObjectHelper $dataObjectHelper, - array $data = [] - ) { - $this->logFactory = $logFactory; - $this->modelLog = $modelLog; - $this->dateTime = $dateTime; - $this->customerFactory = $customerFactory; - $this->dataObjectHelper = $dataObjectHelper; - parent::__construct($context, $data); - } - - /** - * @return string - */ - public function getStoreLastLoginDateTimezone() - { - return $this->_scopeConfig->getValue( - $this->_localeDate->getDefaultTimezonePath(), - \Magento\Store\Model\ScopeInterface::SCOPE_STORE, - $this->getCustomer()->getStoreId() - ); - } - - /** - * @return \Magento\Customer\Api\Data\CustomerInterface - */ - public function getCustomer() - { - if (!$this->customer) { - $this->customer = $this->customerFactory->create(); - $this->dataObjectHelper - ->populateWithArray( - $this->customer, - $this->_backendSession->getCustomerData()['account'], - '\Magento\Customer\Api\Data\CustomerInterface' - ); - } - return $this->customer; - } - - /** - * Get customer's current status - * - * @return \Magento\Framework\Phrase - */ - public function getCurrentStatus() - { - $log = $this->getCustomerLog(); - $interval = $this->modelLog->getOnlineMinutesInterval(); - if ($log->getLogoutAt() || - (new \DateTime())->getTimestamp() - strtotime($log->getLastVisitAt()) > $interval * 60 - ) { - return __('Offline'); - } - return __('Online'); - } - - /** - * Get customer last login date - * - * @return \Magento\Framework\Phrase|string - */ - public function getLastLoginDate() - { - $date = $this->getCustomerLog()->getLoginAt(); - if ($date) { - return $this->formatDate($date, \IntlDateFormatter::MEDIUM, true); - } - return __('Never'); - } - - /** - * @return \Magento\Framework\Phrase|string - */ - public function getStoreLastLoginDate() - { - $date = $this->getCustomerLog()->getLoginAtTimestamp(); - if ($date) { - $date = $this->_localeDate->scopeDate($this->getCustomer()->getStoreId(), $date, true); - return $this->formatDate($date, \IntlDateFormatter::MEDIUM, true); - } - return __('Never'); - } - - /** - * Load Customer Log model - * - * @return \Magento\Log\Model\Customer - */ - public function getCustomerLog() - { - if (!$this->customerLog) { - $this->customerLog = $this->logFactory->create()->loadByCustomer($this->getCustomer()->getId()); - } - return $this->customerLog; - } -} diff --git a/app/code/Magento/Log/Test/Unit/Block/Adminhtml/Customer/Edit/Tab/View/StatusTest.php b/app/code/Magento/Log/Test/Unit/Block/Adminhtml/Customer/Edit/Tab/View/StatusTest.php deleted file mode 100644 index c155c22aca514c592d5a87f4b04c3d99ef491e35..0000000000000000000000000000000000000000 --- a/app/code/Magento/Log/Test/Unit/Block/Adminhtml/Customer/Edit/Tab/View/StatusTest.php +++ /dev/null @@ -1,162 +0,0 @@ -<?php -/** - * Copyright © 2015 Magento. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\Log\Test\Unit\Block\Adminhtml\Customer\Edit\Tab\View; - -use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; - -/** - * Class StatusTest - * @package Magento\Log\Block\Adminhtml\Edit\Tab\View - */ -class StatusTest extends \PHPUnit_Framework_TestCase -{ - /** - * @var \Magento\Log\Block\Adminhtml\Customer\Edit\Tab\View\Status - */ - protected $block; - - /** - * @var \PHPUnit_Framework_MockObject_MockObject - */ - protected $logFactory; - - /** - * @var \Magento\Log\Model\Customer|\PHPUnit_Framework_MockObject_MockObject - */ - protected $customerLog; - - /** - * @var \Magento\Framework\Stdlib\DateTime\TimezoneInterface|\PHPUnit_Framework_MockObject_MockObject - */ - protected $localeDate; - - /** - * @var \Magento\Framework\App\Config\ScopeConfigInterface|\PHPUnit_Framework_MockObject_MockObject - */ - protected $scopeConfig; - - protected function setUp() - { - $log = $this->getMock('Magento\Log\Model\Log', ['getOnlineMinutesInterval'], [], '', false); - $log->expects($this->any())->method('getOnlineMinutesInterval')->will($this->returnValue(1)); - - $this->customerLog = $this->getMockBuilder('Magento\Log\Model\Customer')->disableOriginalConstructor() - ->setMethods(['getLoginAt', 'getLoginAtTimestamp', 'loadByCustomer', 'getLogoutAt', 'getLastVisitAt']) - ->getMock(); - $this->customerLog->expects($this->any())->method('loadByCustomer')->will($this->returnSelf()); - - $this->logFactory = $this->getMockBuilder('Magento\Log\Model\CustomerFactory')->setMethods(['create']) - ->disableOriginalConstructor() - ->getMock(); - $this->logFactory->expects($this->any())->method('create')->will($this->returnValue($this->customerLog)); - - $dateTime = $this->getMockBuilder('Magento\Framework\Stdlib\DateTime')->setMethods(['now']) - ->disableOriginalConstructor() - ->getMock(); - $dateTime->expects($this->any())->method('now')->will($this->returnCallback(function () { - return date('Y-m-d H:i:s'); - })); - - $customer = $this->getMock('\Magento\Customer\Api\Data\CustomerInterface'); - $customer->expects($this->any())->method('getId')->will($this->returnValue(1)); - $customer->expects($this->any())->method('getStoreId')->will($this->returnValue(1)); - - $customerFactory = $this->getMockBuilder('\Magento\Customer\Api\Data\CustomerInterfaceFactory') - ->setMethods(['create']) - ->disableOriginalConstructor() - ->getMock(); - $customerFactory->expects($this->any())->method('create') - ->will($this->returnValue($customer)); - - $customerData = ['account' => ['id' => 1, 'store_id' => 1]]; - $backendSession = $this->getMockBuilder('\Magento\Backend\Model\Session') - ->setMethods(['getCustomerData'])->disableOriginalConstructor()->getMock(); - $backendSession->expects($this->any())->method('getCustomerData')->will($this->returnValue($customerData)); - - $this->localeDate = $this->getMockBuilder('Magento\Framework\Stdlib\DateTime\Timezone') - ->setMethods(['scopeDate', 'formatDateTime', 'getDefaultTimezonePath']) - ->disableOriginalConstructor()->getMock(); - $this->localeDate->expects($this->any())->method('getDefaultTimezonePath') - ->will($this->returnValue('path/to/default/timezone')); - - $this->scopeConfig = $this->getMockBuilder('Magento\Framework\App\Config') - ->setMethods(['getValue']) - ->disableOriginalConstructor()->getMock(); - - $objectManagerHelper = new ObjectManagerHelper($this); - $this->block = $objectManagerHelper->getObject( - 'Magento\Log\Block\Adminhtml\Customer\Edit\Tab\View\Status', - [ - 'logFactory' => $this->logFactory, - 'localeDate' => $this->localeDate, - 'scopeConfig' => $this->scopeConfig, - 'modelLog' => $log, - 'dateTime' => $dateTime, - 'customerFactory' => $customerFactory, - 'backendSession' => $backendSession - ] - ); - } - - public function testGetCustomerLog() - { - $this->logFactory->expects($this->once())->method('create')->will($this->returnValue($this->customerLog)); - $this->assertSame($this->customerLog, $this->block->getCustomerLog()); - } - - public function testGetCurrentStatusOffline() - { - $date = date('Y-m-d H:i:s'); - $this->customerLog->expects($this->any())->method('getLogoutAt')->will($this->returnValue($date)); - $this->assertEquals('Offline', $this->block->getCurrentStatus()); - } - - public function testGetCurrentStatusOnline() - { - $date = date('Y-m-d H:i:s'); - $this->customerLog->expects($this->any())->method('getLogoutAt')->will($this->returnValue(0)); - $this->customerLog->expects($this->any())->method('getLastVisitAt')->will($this->returnValue($date)); - $this->assertEquals('Online', $this->block->getCurrentStatus()); - } - - public function testGetLastLoginDate() - { - $date = date('Y-m-d H:i:s'); - $this->customerLog->expects($this->any())->method('getLoginAt')->will($this->returnValue($date)); - $this->localeDate->expects($this->once())->method('formatDateTime')->will($this->returnValue($date)); - $this->assertEquals($date, $this->block->getLastLoginDate()); - } - - public function testAfterGetLastLoginDateNever() - { - $this->assertEquals('Never', $this->block->getLastLoginDate()); - } - - public function testGetStoreLastLoginDate() - { - $date = date('Y-m-d H:i:s'); - $time = strtotime($date); - - $this->localeDate->expects($this->once())->method('scopeDate')->will($this->returnValue($date)); - $this->localeDate->expects($this->once())->method('formatDateTime')->will($this->returnValue($date)); - - $this->customerLog->expects($this->any())->method('getLoginAtTimestamp')->will($this->returnValue($time)); - $this->assertEquals($date, $this->block->getStoreLastLoginDate()); - } - - public function testGetStoreLastLoginDateNever() - { - $this->assertEquals('Never', $this->block->getStoreLastLoginDate()); - } - - public function testGetStoreLastLoginDateTimezone() - { - $this->scopeConfig->expects($this->once())->method('getValue') - ->with('path/to/default/timezone', 'store', 1) - ->will($this->returnValue('America/Los_Angeles')); - $this->block->getStoreLastLoginDateTimezone(); - } -} diff --git a/app/code/Magento/Log/view/adminhtml/layout/customer_form.xml b/app/code/Magento/Log/view/adminhtml/layout/customer_form.xml deleted file mode 100644 index f53b7241321ea6154c32b08cab9e066644c755d2..0000000000000000000000000000000000000000 --- a/app/code/Magento/Log/view/adminhtml/layout/customer_form.xml +++ /dev/null @@ -1,14 +0,0 @@ -<?xml version="1.0"?> -<!-- -/** - * Copyright © 2015 Magento. All rights reserved. - * See COPYING.txt for license details. - */ ---> -<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../lib/internal/Magento/Framework/View/Layout/etc/page_configuration.xsd"> - <body> - <referenceBlock name="personal_info"> - <block class="Magento\Log\Block\Adminhtml\Customer\Edit\Tab\View\Status" name="view_customer_status" template="customer/status.phtml"/> - </referenceBlock> - </body> -</page> diff --git a/app/code/Magento/Log/view/adminhtml/templates/customer/status.phtml b/app/code/Magento/Log/view/adminhtml/templates/customer/status.phtml deleted file mode 100644 index 3d8c1ac2912e2e312967a447b2908791598aae14..0000000000000000000000000000000000000000 --- a/app/code/Magento/Log/view/adminhtml/templates/customer/status.phtml +++ /dev/null @@ -1,25 +0,0 @@ -<?php -/** - * Copyright © 2015 Magento. All rights reserved. - * See COPYING.txt for license details. - */ - -// @codingStandardsIgnoreFile - -/** - * Template for block \Magento\Log\Block\Adminhtml\Customer\Edit\Tab\View\Status - */ - -$lastLoginDateAdmin = $block->getLastLoginDate(); -$lastLoginDateStore = $block->getStoreLastLoginDate(); -?> -<tr> - <th><?php echo __('Last Logged In:') ?></th> - <td><?php echo $lastLoginDateAdmin ?> (<?php echo $block->getCurrentStatus() ?>)</td> -</tr> -<?php if ($lastLoginDateAdmin != $lastLoginDateStore): ?> -<tr> - <th><?php echo __('Last Logged In (%1):', $block->getStoreLastLoginDateTimezone()) ?></th> - <td><?php echo $lastLoginDateStore ?> (<?php echo $block->getCurrentStatus() ?>)</td> -</tr> -<?php endif; ?> diff --git a/app/code/Magento/Translation/Block/Js.php b/app/code/Magento/Translation/Block/Js.php index dcf8f7156231b72655edf18db7832d47e7c02f16..f79360f534bab736536bdadb8a99d31c3809cb96 100644 --- a/app/code/Magento/Translation/Block/Js.php +++ b/app/code/Magento/Translation/Block/Js.php @@ -6,50 +6,37 @@ namespace Magento\Translation\Block; -use Magento\Framework\Translate\InlineInterface as InlineTranslator; -use Magento\Translation\Model\Js as DataProvider; use Magento\Framework\View\Element\Template; +use Magento\Translation\Model\Js\Config; -class Js extends \Magento\Framework\View\Element\Template +class Js extends Template { /** - * Data provider model - * - * @var DataProvider - */ - protected $dataProvider; - - /** - * Inline translator - * - * @var InlineTranslator + * @var Config */ - protected $translateInline; + protected $config; /** * @param Template\Context $context - * @param DataProvider $dataProvider - * @param InlineTranslator $translateInline + * @param Config $config * @param array $data */ public function __construct( Template\Context $context, - DataProvider $dataProvider, - InlineTranslator $translateInline, + Config $config, array $data = [] ) { parent::__construct($context, $data); - $this->dataProvider = $dataProvider; - $this->translateInline = $translateInline; + $this->config = $config; } /** - * @return string + * Is js translation set to dictionary mode + * + * @return bool */ - public function getTranslatedJson() + public function dictionaryEnabled() { - $data = $this->dataProvider->getTranslateData(); - $this->translateInline->processResponseBody($data); - return \Zend_Json::encode($data); + return $this->config->dictionaryEnabled(); } } diff --git a/app/code/Magento/Translation/Model/Js.php b/app/code/Magento/Translation/Model/Js.php deleted file mode 100644 index ca4bc45b96058712719e24c223c78dec3e8dcd38..0000000000000000000000000000000000000000 --- a/app/code/Magento/Translation/Model/Js.php +++ /dev/null @@ -1,42 +0,0 @@ -<?php -/** - * Copyright © 2015 Magento. All rights reserved. - * See COPYING.txt for license details. - */ - -namespace Magento\Translation\Model; - -class Js -{ - /** - * Translation data - * - * @var string[] - */ - protected $translateData; - - /** - * @param Js\DataProviderInterface[] $dataProviders - */ - public function __construct(array $dataProviders) - { - /** @var $dataProvider Js\DataProviderInterface */ - foreach ($dataProviders as $dataProvider) { - foreach ($dataProvider->getData() as $key => $translatedText) { - if ($key !== $translatedText) { - $this->translateData[$key] = $translatedText; - } - } - } - } - - /** - * Get translated data - * - * @return string[] - */ - public function getTranslateData() - { - return $this->translateData; - } -} diff --git a/app/code/Magento/Translation/Model/Js/Config.php b/app/code/Magento/Translation/Model/Js/Config.php new file mode 100644 index 0000000000000000000000000000000000000000..6dc1abb699d9acdc90d6325fad535150d4b09187 --- /dev/null +++ b/app/code/Magento/Translation/Model/Js/Config.php @@ -0,0 +1,98 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Translation\Model\Js; + +use Magento\Framework\Translate\Js\Config as FrameworkJsConfig; +use Magento\Framework\App\Config\ScopeConfigInterface; + +/** + * Js Translation config + */ +class Config extends FrameworkJsConfig +{ + /** + * Both translation strategies are disabled + */ + const NO_TRANSLATION = 'none'; + + /** + * Strategy when all js files are translated while publishing + */ + const EMBEDDED_STRATEGY = 'embedded'; + + /** + * Strategy when dictionary is generated for dynamic translation + */ + const DICTIONARY_STRATEGY = 'dictionary'; + + /** + * Configuration path to translation strategy + */ + const XML_PATH_STRATEGY = 'dev/js/translate_strategy'; + + /** + * Dictionary file name + */ + const DICTIONARY_FILE_NAME = 'js-translation.json'; + + /** + * Core store config + * + * @var ScopeConfigInterface + */ + protected $scopeConfig; + + /** + * Patterns to match strings for translation + * + * @var string[] + */ + protected $patterns; + + /** + * @param ScopeConfigInterface $scopeConfig + * @param string[] $patterns + */ + public function __construct(ScopeConfigInterface $scopeConfig, array $patterns) + { + $this->scopeConfig = $scopeConfig; + $this->patterns = $patterns; + parent::__construct( + $this->scopeConfig->getValue(self::XML_PATH_STRATEGY) == self::DICTIONARY_STRATEGY, + self::DICTIONARY_FILE_NAME + ); + } + + /** + * Is Embedded Strategy selected + * + * @return bool + */ + public function isEmbeddedStrategy() + { + return ($this->scopeConfig->getValue(self::XML_PATH_STRATEGY) == self::EMBEDDED_STRATEGY); + } + + /** + * Is Dictionary Strategy selected + * + * @return bool + */ + public function dictionaryEnabled() + { + return ($this->scopeConfig->getValue(self::XML_PATH_STRATEGY) == self::DICTIONARY_STRATEGY); + } + + /** + * Retrieve translation patterns + * + * @return string[] + */ + public function getPatterns() + { + return $this->patterns; + } +} diff --git a/app/code/Magento/Translation/Model/Js/Config/Source/Strategy.php b/app/code/Magento/Translation/Model/Js/Config/Source/Strategy.php new file mode 100644 index 0000000000000000000000000000000000000000..30f1b0fe8e9a01fb9838002561820e11986c43cc --- /dev/null +++ b/app/code/Magento/Translation/Model/Js/Config/Source/Strategy.php @@ -0,0 +1,23 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Translation\Model\Js\Config\Source; + +use Magento\Translation\Model\Js\Config; + +class Strategy implements \Magento\Framework\Option\ArrayInterface +{ + /** + * {@inheritdoc} + */ + public function toOptionArray() + { + return [ + ['label' => __('None (Translation is disabled)'), 'value' => Config::NO_TRANSLATION], + ['label' => __('Dictionary (Translation on frontend side)'), 'value' => Config::DICTIONARY_STRATEGY], + ['label' => __('Embedded (Translation on backend side)'), 'value' => Config::EMBEDDED_STRATEGY] + ]; + } +} diff --git a/app/code/Magento/Translation/Model/Js/DataProvider.php b/app/code/Magento/Translation/Model/Js/DataProvider.php index 317dc602a3c7abc55998972f72d9b9f4b8450027..a9a83fcf8d6b35d942c43313f9d6204f2c087577 100644 --- a/app/code/Magento/Translation/Model/Js/DataProvider.php +++ b/app/code/Magento/Translation/Model/Js/DataProvider.php @@ -4,164 +4,109 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - namespace Magento\Translation\Model\Js; +use Magento\Framework\App\Utility\Files; +use Magento\Framework\App\State; +use Magento\Framework\Filesystem; +use Magento\Framework\Filesystem\Directory\ReadInterface; +use Magento\Framework\App\Filesystem\DirectoryList; + +/** + * DataProvider for js translation + */ class DataProvider implements DataProviderInterface { + /** + * Application state + * + * @var State + */ + protected $appState; + + /** + * Js translation configuration + * + * @var Config + */ + protected $config; + + /** + * Files utility + * + * @var Files + */ + protected $filesUtility; + + /** + * Filesystem + * + * @var ReadInterface + */ + protected $rootDirectory; + + /** + * @param State $appState + * @param Config $config + * @param Filesystem $filesystem + * @param Files $filesUtility + */ + public function __construct(State $appState, Config $config, Filesystem $filesystem, Files $filesUtility = null) + { + $this->appState = $appState; + $this->config = $config; + $this->rootDirectory = $filesystem->getDirectoryRead(DirectoryList::ROOT); + $this->filesUtility = (null !== $filesUtility) ? $filesUtility : new Files(BP); + } + /** * Get translation data * + * @param string $themePath * @return string[] - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + * @throws \Exception + * @throws \Magento\Framework\Exception */ - public function getData() + public function getData($themePath) { - return [ - 'Complete' => __('Complete'), - 'Upload Security Error' => __('Upload Security Error'), - 'Upload HTTP Error' => __('Upload HTTP Error'), - 'Upload I/O Error' => __('Upload I/O Error'), - 'SSL Error: Invalid or self-signed certificate' => __('SSL Error: Invalid or self-signed certificate'), - 'TB' => __('TB'), - 'GB' => __('GB'), - 'MB' => __('MB'), - 'kB' => __('kB'), - 'B' => __('B'), - 'Add Products' => __('Add Products'), - 'Add Products By SKU' => __('Add Products By SKU'), - 'Insert Widget...' => __('Insert Widget...'), - 'Please wait, loading...' => __('Please wait, loading...'), - 'HTML tags are not allowed' => __('HTML tags are not allowed'), - 'Please select an option.' => __('Please select an option.'), - 'This is a required field.' => __('This is a required field.'), - 'Please enter a valid number in this field.' => __('Please enter a valid number in this field.'), - 'The value is not within the specified range.' => __('The value is not within the specified range.'), - 'Please use numbers only in this field. Please avoid spaces or other characters such as dots or commas.' => __('Please use numbers only in this field. Please avoid spaces or other characters such as dots or commas.'), - 'Please use letters only (a-z or A-Z) in this field.' => __('Please use letters only (a-z or A-Z) in this field.'), - 'Please use only letters (a-z), numbers (0-9) or underscore(_) in this field, first character should be a letter.' => __('Please use only letters (a-z), numbers (0-9) or underscore(_) in this field, first character should be a letter.'), - 'Please use only letters (a-z or A-Z) or numbers (0-9) only in this field. No spaces or other characters are allowed.' => __('Please use only letters (a-z or A-Z) or numbers (0-9) only in this field. No spaces or other characters are allowed.'), - 'Please use only letters (a-z or A-Z) or numbers (0-9) or spaces and # only in this field.' => __('Please use only letters (a-z or A-Z) or numbers (0-9) or spaces and # only in this field.'), - 'Please enter a valid fax number. For example (123) 456-7890 or 123-456-7890.' => __('Please enter a valid fax number. For example (123) 456-7890 or 123-456-7890.'), - 'Please enter a valid date.' => __('Please enter a valid date.'), - 'The From Date value should be less than or equal to the To Date value.' => __('The From Date value should be less than or equal to the To Date value.'), - 'Please enter a valid email address. For example johndoe@domain.com.' => __('Please enter a valid email address. For example johndoe@domain.com.'), - 'Please use only visible characters and spaces.' => __('Please use only visible characters and spaces.'), - 'Please enter 6 or more characters. Leading or trailing spaces will be ignored.' => __('Please enter 6 or more characters. Leading or trailing spaces will be ignored.'), - 'Please enter 7 or more characters. Password should contain both numeric and alphabetic characters.' => __('Please enter 7 or more characters. Password should contain both numeric and alphabetic characters.'), - 'Please make sure your passwords match.' => __('Please make sure your passwords match.'), - 'Please enter a valid URL. Protocol is required (http://, https:// or ftp://)' => __('Please enter a valid URL. Protocol is required (http://, https:// or ftp://)'), - 'Please enter a valid URL Key. For example "example-page", "example-page.html" or "anotherlevel/example-page".' => __('Please enter a valid URL Key. For example "example-page", "example-page.html" or "anotherlevel/example-page".'), - 'Please enter a valid XML-identifier. For example something_1, block5, id-4.' => __('Please enter a valid XML-identifier. For example something_1, block5, id-4.'), - 'Please enter a valid social security number. For example 123-45-6789.' => __('Please enter a valid social security number. For example 123-45-6789.'), - 'Please enter a valid zip code. For example 90602 or 90602-1234.' => __('Please enter a valid zip code. For example 90602 or 90602-1234.'), - 'Please enter a valid zip code.' => __('Please enter a valid zip code.'), - 'Please use this date format: dd/mm/yyyy. For example 17/03/2006 for the 17th of March, 2006.' => __('Please use this date format: dd/mm/yyyy. For example 17/03/2006 for the 17th of March, 2006.'), - 'Please select one of the above options.' => __('Please select one of the above options.'), - 'Please select one of the options.' => __('Please select one of the options.'), - 'Please select State/Province.' => __('Please select State/Province.'), - 'Please enter a number greater than 0 in this field.' => __('Please enter a number greater than 0 in this field.'), - 'Please enter a number 0 or greater in this field.' => __('Please enter a number 0 or greater in this field.'), - 'Please enter a valid credit card number.' => __('Please enter a valid credit card number.'), - 'Credit card number does not match credit card type.' => __('Credit card number does not match credit card type.'), - 'Card type does not match credit card number.' => __('Card type does not match credit card number.'), - 'Incorrect credit card expiration date.' => __('Incorrect credit card expiration date.'), - 'Please enter a valid credit card verification number.' => __('Please enter a valid credit card verification number.'), - 'Please use only letters (a-z or A-Z), numbers (0-9) or underscore(_) in this field, first character should be a letter.' => __('Please use only letters (a-z or A-Z), numbers (0-9) or underscore(_) in this field, first character should be a letter.'), - 'Please input a valid CSS-length. For example 100px or 77pt or 20em or .5ex or 50%.' => __('Please input a valid CSS-length. For example 100px or 77pt or 20em or .5ex or 50%.'), - 'Text length does not satisfy specified text range.' => __('Text length does not satisfy specified text range.'), - 'Please enter a number lower than 100.' => __('Please enter a number lower than 100.'), - 'Please select a file' => __('Please select a file'), - 'Please enter issue number or start date for switch/solo card type.' => __('Please enter issue number or start date for switch/solo card type.'), - 'This date is a required value.' => __('This date is a required value.'), - 'Please enter a valid day (1-%1).' => __('Please enter a valid day (1-%1).'), - 'Please enter a valid month (1-12).' => __('Please enter a valid month (1-12).'), - 'Please enter a valid year (1900-%1).' => __('Please enter a valid year (1900-%1).'), - 'Please enter a valid full date' => __('Please enter a valid full date'), - 'Allow' => __('Allow'), - 'Activate' => __('Activate'), - 'Reauthorize' => __('Reauthorize'), - 'Cancel' => __('Cancel'), - 'Done' => __('Done'), - 'Save' => __('Save'), - 'File extension not known or unsupported type.' => __('File extension not known or unsupported type.'), - 'Configure Product' => __('Configure Product'), - 'OK' => __('OK'), - 'Gift Options for ' => __('Gift Options for '), - 'New Option' => __('New Option'), - 'Add Products to New Option' => __('Add Products to New Option'), - 'Add Products to Option "%1"' => __('Add Products to Option "%1"'), - 'Add Selected Products' => __('Add Selected Products'), - 'Select type of option.' => __('Select type of option.'), - 'Please add rows to option.' => __('Please add rows to option.'), - 'Select Product' => __('Select Product'), - 'Import' => __('Import'), - 'Please select items.' => __('Please select items.'), - 'Add Products to Group' => __('Add Products to Group'), - 'start typing to search category' => __('start typing to search category'), - 'Choose existing category.' => __('Choose existing category.'), - 'Create Category' => __('Create Category'), - 'Sorry, there was an unknown error.' => __('Sorry, there was an unknown error.'), - 'Something went wrong while loading the theme.' => __('Something went wrong while loading the theme.'), - 'We don\'t recognize or support this file extension type.' => __('We don\'t recognize or support this file extension type.'), - 'Error' => __('Error'), - 'No stores were reassigned.' => __('No stores were reassigned.'), - 'Assign theme to your live store-view:' => __('Assign theme to your live store-view:'), - 'Default title' => __('Default title'), - 'The URL to assign stores is not defined.' => __('The URL to assign stores is not defined.'), - 'No' => __('No'), - 'Yes' => __('Yes'), - 'Some problem with revert action' => __('Some problem with revert action'), - 'Error: unknown error.' => __('Error: unknown error.'), - 'Some problem with save action' => __('Some problem with save action'), - 'Delete' => __('Delete'), - 'Folder' => __('Folder'), - 'Delete Folder' => __('Delete Folder'), - 'Are you sure you want to delete the folder named' => __('Are you sure you want to delete the folder named'), - 'Delete File' => __('Delete File'), - 'Method ' => __('Method '), - 'Please wait...' => __('Please wait...'), - 'Loading...' => __('Loading...'), - 'Translate' => __('Translate'), - 'Submit' => __('Submit'), - 'Close' => __('Close'), - 'Please enter a value less than or equal to %s.' => __('Please enter a value less than or equal to %s.'), - 'Please enter a value greater than or equal to %s.' => __('Please enter a value greater than or equal to %s.'), - 'Maximum length of this field must be equal or less than %1 symbols.' => __('Maximum length of this field must be equal or less than %1 symbols.'), - 'No records found.' => __('No records found.'), - 'Recent items' => __('Recent items'), - 'Show all...' => __('Show all...'), - 'Please enter a date in the past.' => __('Please enter a date in the past.'), - 'Please enter a date between %min and %max.' => __('Please enter a date between %min and %max.'), - 'Please choose to register or to checkout as a guest.' => __('Please choose to register or to checkout as a guest.'), - 'We are not able to ship to the selected shipping address. Please choose another address or edit the current address.' => __('We are not able to ship to the selected shipping address. Please choose another address or edit the current address.'), - 'Please specify a shipping method.' => __('Please specify a shipping method.'), - 'We can\'t complete your order because you don\'t have a payment method available.' => __('We can\'t complete your order because you don\'t have a payment method available.'), - 'Error happened while creating wishlist. Please try again later' => __('Error happened while creating wishlist. Please try again later'), - 'You must select items to move' => __('You must select items to move'), - 'You must select items to copy' => __('You must select items to copy'), - 'You are about to delete your wish list. This action cannot be undone. Are you sure you want to continue?' => __('You are about to delete your wish list. This action cannot be undone. Are you sure you want to continue?'), - 'Please specify payment method.' => __('Please specify payment method.'), - 'Are you sure you want to delete this address?' => __('Are you sure you want to delete this address?'), - 'Use gift registry shipping address' => __('Use gift registry shipping address'), - 'You can change the number of gift registry items on the Gift Registry Info page or directly in your cart, but not while in checkout.' => __('You can change the number of gift registry items on the Gift Registry Info page or directly in your cart, but not while in checkout.'), - 'No confirmation' => __('No confirmation'), - 'Sorry, something went wrong.' => __('Sorry, something went wrong.'), - 'Sorry, something went wrong. Please try again later.' => __('Sorry, something went wrong. Please try again later.'), - 'select all' => __('select all'), - 'unselect all' => __('unselect all'), - 'Please agree to all Terms and Conditions before placing the orders.' => __('Please agree to all Terms and Conditions before placing the orders.'), - 'Please choose to register or to checkout as a guest' => __('Please choose to register or to checkout as a guest'), - 'Your order cannot be completed at this time as there is no shipping methods available for it. Please make necessary changes in your shipping address.' => __('Your order cannot be completed at this time as there is no shipping methods available for it. Please make necessary changes in your shipping address.'), - 'Please specify shipping method.' => __('Please specify shipping method.'), - 'Your order cannot be completed at this time as there is no payment methods available for it.' => __('Your order cannot be completed at this time as there is no payment methods available for it.'), - 'Edit Order' => __('Edit Order'), - 'Ok' => __('Ok'), - 'Please specify at least one search term.' => __('Please specify at least one search term.'), - 'Create New Wish List' => __('Create New Wish List'), - 'Click Details for more required fields.' => __('Click Details for more required fields.'), - 'Incl. Tax' => __('Incl. Tax'), - ]; + $dictionary = []; + + $files = $this->filesUtility->getJsFiles($this->appState->getAreaCode(), $themePath); + foreach ($files as $filePath) { + $content = $this->rootDirectory->readFile($this->rootDirectory->getRelativePath($filePath[0])); + foreach ($this->getPhrases($content) as $phrase) { + $translatedPhrase = (string) __($phrase); + if ($phrase != $translatedPhrase) { + $dictionary[$phrase] = $translatedPhrase; + } + } + } + + return $dictionary; + } + + /** + * Parse content for entries to be translated + * + * @param string $content + * @return string[] + * @throws \Exception + */ + protected function getPhrases($content) + { + $phrases = []; + foreach ($this->config->getPatterns() as $pattern) { + $result = preg_match_all($pattern, $content, $matches); + + if ($result) { + $phrases = array_merge($phrases, $matches[1]); + } + if (false === $result) { + throw new \Exception( + sprintf('Error while generating js translation dictionary: "%s"', error_get_last()) + ); + } + } + return $phrases; } } diff --git a/app/code/Magento/Translation/Model/Js/DataProviderInterface.php b/app/code/Magento/Translation/Model/Js/DataProviderInterface.php index 74231b2ccdd959b4d50454d381121c80c7cb82bd..d9eea098e56b1e4102ba5f49b8882e9b325138f6 100644 --- a/app/code/Magento/Translation/Model/Js/DataProviderInterface.php +++ b/app/code/Magento/Translation/Model/Js/DataProviderInterface.php @@ -10,8 +10,8 @@ interface DataProviderInterface { /** * Get translation data - * + * @param string $themePath * @return string[] */ - public function getData(); + public function getData($themePath); } diff --git a/app/code/Magento/Translation/Model/Js/PreProcessor.php b/app/code/Magento/Translation/Model/Js/PreProcessor.php new file mode 100644 index 0000000000000000000000000000000000000000..d7da55fbd50ef2d263c1b29a682d1460774da2c3 --- /dev/null +++ b/app/code/Magento/Translation/Model/Js/PreProcessor.php @@ -0,0 +1,69 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Translation\Model\Js; + +use Magento\Framework\View\Asset\PreProcessorInterface; +use Magento\Framework\View\Asset\PreProcessor\Chain; +use Magento\Framework\Filesystem; + +/** + * PreProcessor responsible for replacing translation calls in js files to translated strings + */ +class PreProcessor implements PreProcessorInterface +{ + /** + * Javascript translation configuration + * + * @var Config + */ + protected $config; + + /** + * @param Config $config + */ + public function __construct(Config $config) + { + $this->config = $config; + } + + /** + * Transform content and/or content type for the specified preprocessing chain object + * + * @param Chain $chain + * @return void + */ + public function process(Chain $chain) + { + if ($this->config->isEmbeddedStrategy()) { + $chain->setContent($this->translate($chain->getContent())); + } + } + + /** + * Replace translation calls with translation result and return content + * + * @param string $content + * @return string + */ + public function translate($content) + { + foreach ($this->config->getPatterns() as $pattern) { + $content = preg_replace_callback($pattern, [$this, 'replaceCallback'], $content); + } + return $content; + } + + /** + * Replace callback for preg_replace_callback function + * + * @param array $matches + * @return string + */ + protected function replaceCallback($matches) + { + return '"' . __($matches[1]) . '"'; + } +} diff --git a/app/code/Magento/Translation/Model/Json/PreProcessor.php b/app/code/Magento/Translation/Model/Json/PreProcessor.php new file mode 100644 index 0000000000000000000000000000000000000000..79c4ad0ccef842037f191f7f7f41e59f17c7ba8e --- /dev/null +++ b/app/code/Magento/Translation/Model/Json/PreProcessor.php @@ -0,0 +1,71 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Translation\Model\Json; + +use Magento\Framework\View\Asset\PreProcessorInterface; +use Magento\Translation\Model\Js\Config; +use Magento\Translation\Model\Js\DataProviderInterface; +use Magento\Framework\View\Asset\PreProcessor\Chain; +use Magento\Framework\View\Asset\File\FallbackContext; + +/** + * PreProcessor responsible for providing js translation dictionary + */ +class PreProcessor implements PreProcessorInterface +{ + /** + * Js translation configuration + * + * @var Config + */ + protected $config; + + /** + * Translation data provider + * + * @var DataProviderInterface + */ + protected $dataProvider; + + /** + * @param Config $config + * @param DataProviderInterface $dataProvider + */ + public function __construct( + Config $config, + DataProviderInterface $dataProvider + ) { + $this->config = $config; + $this->dataProvider = $dataProvider; + } + + /** + * Transform content and/or content type for the specified preprocessing chain object + * + * @param Chain $chain + * @return void + */ + public function process(Chain $chain) + { + if ($this->isDictionaryPath($chain->getTargetAssetPath())) { + $context = $chain->getAsset()->getContext(); + $themePath = ($context instanceof FallbackContext) ? $context->getThemePath() : '*/*'; + $chain->setContent(json_encode($this->dataProvider->getData($themePath))); + $chain->setContentType('json'); + } + } + + /** + * Is provided path the path to translation dictionary + * + * @param string $path + * @return bool + */ + protected function isDictionaryPath($path) + { + return (strpos($path, $this->config->getDictionaryFileName()) !== false); + } +} diff --git a/app/code/Magento/Translation/Test/Unit/Block/JsTest.php b/app/code/Magento/Translation/Test/Unit/Block/JsTest.php new file mode 100644 index 0000000000000000000000000000000000000000..b929b7304568653dc1fe484408b1c5acae29896e --- /dev/null +++ b/app/code/Magento/Translation/Test/Unit/Block/JsTest.php @@ -0,0 +1,38 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Translation\Test\Unit\Block; + +use Magento\Translation\Block\Js; + +class JsTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var Js + */ + protected $model; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + protected $configMock; + + protected function setUp() + { + $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + $this->configMock = $this->getMockBuilder('Magento\Translation\Model\Js\Config') + ->disableOriginalConstructor() + ->getMock(); + $this->model = $objectManager->getObject('Magento\Translation\Block\Js', ['config' => $this->configMock]); + } + + public function testIsDictionaryStrategy() + { + $this->configMock->expects($this->once()) + ->method('dictionaryEnabled') + ->willReturn(true); + $this->assertTrue($this->model->dictionaryEnabled()); + } +} diff --git a/app/code/Magento/Translation/Test/Unit/Model/Js/Config/Source/StrategyTest.php b/app/code/Magento/Translation/Test/Unit/Model/Js/Config/Source/StrategyTest.php new file mode 100644 index 0000000000000000000000000000000000000000..819d41663235c2739692804db7a98c97b363e207 --- /dev/null +++ b/app/code/Magento/Translation/Test/Unit/Model/Js/Config/Source/StrategyTest.php @@ -0,0 +1,44 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Translation\Test\Unit\Model\Js\Config\Source; + +use Magento\Translation\Model\Js\Config; +use Magento\Translation\Model\Js\Config\Source\Strategy; + +/** + * Class StrategyTest + */ +class StrategyTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var Strategy + */ + protected $model; + + /** + * Set up + * @return void + */ + protected function setUp() + { + $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + $this->model = $objectManager->getObject('Magento\Translation\Model\Js\Config\Source\Strategy'); + } + + /** + * Test for toOptionArray method + * @return void + */ + public function testToOptionArray() + { + $expected = [ + ['label' => __('None (Translation is disabled)'), 'value' => Config::NO_TRANSLATION], + ['label' => 'Dictionary (Translation on frontend side)', 'value' => Config::DICTIONARY_STRATEGY], + ['label' => 'Embedded (Translation on backend side)', 'value' => Config::EMBEDDED_STRATEGY] + ]; + $this->assertEquals($expected, $this->model->toOptionArray()); + } +} diff --git a/app/code/Magento/Translation/Test/Unit/Model/Js/ConfigTest.php b/app/code/Magento/Translation/Test/Unit/Model/Js/ConfigTest.php new file mode 100644 index 0000000000000000000000000000000000000000..a0c1e59c1d3e7ca8dd2eb94a94d8dfacb75ab04f --- /dev/null +++ b/app/code/Magento/Translation/Test/Unit/Model/Js/ConfigTest.php @@ -0,0 +1,64 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Translation\Test\Unit\Model\Js; + +use Magento\Translation\Model\Js\Config; + +class ConfigTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var Config + */ + protected $model; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + protected $scopeMock; + + /** + * @var string + */ + protected $patterns = ['test_pattern']; + + protected function setUp() + { + $this->scopeMock = $this->getMockBuilder('Magento\Framework\App\Config\ScopeConfigInterface') + ->disableOriginalConstructor() + ->getMock(); + $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + $this->model = $objectManager->getObject( + 'Magento\Translation\Model\Js\Config', + [ + 'scopeConfig' => $this->scopeMock, + 'patterns' => $this->patterns + ] + ); + } + + public function testIsEmbeddedStrategy() + { + $this->scopeMock->expects($this->once()) + ->method('getValue') + ->with(Config::XML_PATH_STRATEGY) + ->willReturn(Config::EMBEDDED_STRATEGY); + $this->assertTrue($this->model->isEmbeddedStrategy()); + } + + public function testDictionaryEnabled() + { + $this->scopeMock->expects($this->once()) + ->method('getValue') + ->with(Config::XML_PATH_STRATEGY) + ->willReturn(Config::DICTIONARY_STRATEGY); + $this->assertTrue($this->model->dictionaryEnabled()); + } + + public function testgetPatterns() + { + $this->assertEquals($this->patterns, $this->model->getPatterns()); + } +} diff --git a/app/code/Magento/Translation/Test/Unit/Model/Js/DataProviderTest.php b/app/code/Magento/Translation/Test/Unit/Model/Js/DataProviderTest.php new file mode 100644 index 0000000000000000000000000000000000000000..6067c28723fb4eb764f5e2b26167a984ad9b173e --- /dev/null +++ b/app/code/Magento/Translation/Test/Unit/Model/Js/DataProviderTest.php @@ -0,0 +1,108 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Translation\Test\Unit\Model\Js; + +use Magento\Framework\App\State; +use Magento\Framework\App\Utility\Files; +use Magento\Framework\Filesystem; +use Magento\Framework\Filesystem\Directory\ReadInterface; +use Magento\Framework\App\Filesystem\DirectoryList; +use Magento\Translation\Model\Js\DataProvider; +use Magento\Translation\Model\Js\Config; + +/** + * Class DataProviderTest + */ +class DataProviderTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var DataProvider + */ + protected $model; + + /** + * @var State|\PHPUnit_Framework_MockObject_MockObject + */ + protected $appStateMock; + + /** + * @var Config|\PHPUnit_Framework_MockObject_MockObject + */ + protected $configMock; + + /** + * @var Files|\PHPUnit_Framework_MockObject_MockObject + */ + protected $filesUtilityMock; + + /** + * @var ReadInterface|\PHPUnit_Framework_MockObject_MockObject + */ + protected $rootDirectoryMock; + + /** + * @return void + */ + protected function setUp() + { + $this->appStateMock = $this->getMock('Magento\Framework\App\State', [], [], '', false); + $this->configMock = $this->getMock('Magento\Translation\Model\Js\Config', [], [], '', false); + $this->filesUtilityMock = $this->getMock('Magento\Framework\App\Utility\Files', [], [], '', false); + $filesystem = $this->getMock('Magento\Framework\Filesystem', [], [], '', false); + $this->rootDirectoryMock = $this->getMock('Magento\Framework\Filesystem\Directory\Read', [], [], '', false); + $filesystem->expects($this->once()) + ->method('getDirectoryRead') + ->with(DirectoryList::ROOT) + ->willReturn($this->rootDirectoryMock); + $this->model = new DataProvider( + $this->appStateMock, + $this->configMock, + $filesystem, + $this->filesUtilityMock + ); + } + + /** + * @return void + */ + public function testGetData() + { + $themePath = 'blank'; + $areaCode = 'adminhtml'; + $files = [['path1'], ['path2']]; + + $relativePathMap = [ + ['path1' => 'relativePath1'], + ['path2' => 'relativePath2'] + ]; + $contentsMap = [ + ['relativePath1' => 'content1$.mage.__("hello1")content1'], + ['relativePath2' => 'content2$.mage.__("hello2")content2'] + ]; + + $patterns = ['~\$\.mage\.__\([\'|\"](.+?)[\'|\"]\)~']; + + $this->appStateMock->expects($this->once()) + ->method('getAreaCode') + ->willReturn($areaCode); + $this->filesUtilityMock->expects($this->once()) + ->method('getJsFiles') + ->with($areaCode, $themePath) + ->willReturn($files); + + $this->rootDirectoryMock->expects($this->any()) + ->method('getRelativePath') + ->willReturnMap($relativePathMap); + $this->rootDirectoryMock->expects($this->any()) + ->method('readFile') + ->willReturnMap($contentsMap); + $this->configMock->expects($this->any()) + ->method('getPatterns') + ->willReturn($patterns); + + $this->assertEquals([], $this->model->getData($themePath)); + } +} diff --git a/app/code/Magento/Translation/Test/Unit/Model/Js/PreProcessorTest.php b/app/code/Magento/Translation/Test/Unit/Model/Js/PreProcessorTest.php new file mode 100644 index 0000000000000000000000000000000000000000..2ccfbdafeb7c32842e9460fadc44a7aa61c80389 --- /dev/null +++ b/app/code/Magento/Translation/Test/Unit/Model/Js/PreProcessorTest.php @@ -0,0 +1,52 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Translation\Test\Unit\Model\Js; + +use Magento\Translation\Model\Js\PreProcessor; +use Magento\Translation\Model\Js\Config; + +class PreProcessorTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var PreProcessor + */ + protected $model; + + /** + * @var Config|\PHPUnit_Framework_MockObject_MockObject + */ + protected $configMock; + + protected function setUp() + { + $this->configMock = $this->getMock('Magento\Translation\Model\Js\Config', [], [], '', false); + $this->model = new PreProcessor($this->configMock); + } + + public function testGetData() + { + $chain = $this->getMock('Magento\Framework\View\Asset\PreProcessor\Chain', [], [], '', false); + $originalContent = 'content$.mage.__("hello1")content'; + $translatedContent = 'content"hello1"content'; + $patterns = ['~\$\.mage\.__\([\'|\"](.+?)[\'|\"]\)~']; + + $this->configMock->expects($this->once()) + ->method('isEmbeddedStrategy') + ->willReturn(true); + $chain->expects($this->once()) + ->method('getContent') + ->willReturn($originalContent); + $this->configMock->expects($this->once()) + ->method('getPatterns') + ->willReturn($patterns); + + $chain->expects($this->once()) + ->method('setContent') + ->with($translatedContent); + + $this->model->process($chain); + } +} diff --git a/app/code/Magento/Translation/Test/Unit/Model/Json/PreProcessorTest.php b/app/code/Magento/Translation/Test/Unit/Model/Json/PreProcessorTest.php new file mode 100644 index 0000000000000000000000000000000000000000..7b5e0fb2299b09aeb984eec7b80f43116f22f961 --- /dev/null +++ b/app/code/Magento/Translation/Test/Unit/Model/Json/PreProcessorTest.php @@ -0,0 +1,74 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Translation\Test\Unit\Model\Json; + +use Magento\Translation\Model\Js\Config; +use Magento\Translation\Model\Js\DataProvider; +use Magento\Translation\Model\Json\PreProcessor; + +class PreProcessorTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var PreProcessor + */ + protected $model; + + /** + * @var Config|\PHPUnit_Framework_MockObject_MockObject + */ + protected $configMock; + + /** + * @var DataProvider|\PHPUnit_Framework_MockObject_MockObject + */ + protected $dataProviderMock; + + protected function setUp() + { + $this->configMock = $this->getMock('Magento\Translation\Model\Js\Config', [], [], '', false); + $this->dataProviderMock = $this->getMock('Magento\Translation\Model\Js\DataProvider', [], [], '', false); + $this->model = new PreProcessor($this->configMock, $this->dataProviderMock); + } + + public function testGetData() + { + $chain = $this->getMock('Magento\Framework\View\Asset\PreProcessor\Chain', [], [], '', false); + $asset = $this->getMock('Magento\Framework\View\Asset\File', [], [], '', false); + $context = $this->getMock('Magento\Framework\View\Asset\File\FallbackContext', [], [], '', false); + $fileName = 'js-translation.json'; + $targetPath = 'path/js-translation.json'; + $themePath = '*/*'; + $dictionary = ['hello' => 'bonjour']; + + $chain->expects($this->once()) + ->method('getTargetAssetPath') + ->willReturn($targetPath); + $this->configMock->expects($this->once()) + ->method('getDictionaryFileName') + ->willReturn($fileName); + $chain->expects($this->once()) + ->method('getAsset') + ->willReturn($asset); + $asset->expects($this->once()) + ->method('getContext') + ->willReturn($context); + $context->expects($this->once()) + ->method('getThemePath') + ->willReturn($themePath); + $this->dataProviderMock->expects($this->once()) + ->method('getData') + ->with($themePath) + ->willReturn($dictionary); + $chain->expects($this->once()) + ->method('setContent') + ->with(json_encode($dictionary)); + $chain->expects($this->once()) + ->method('setContentType') + ->with('json'); + + $this->model->process($chain); + } +} diff --git a/app/code/Magento/Translation/etc/adminhtml/system.xml b/app/code/Magento/Translation/etc/adminhtml/system.xml new file mode 100644 index 0000000000000000000000000000000000000000..90d70e824ced6cd42cef19b0d51c39e32e5e08eb --- /dev/null +++ b/app/code/Magento/Translation/etc/adminhtml/system.xml @@ -0,0 +1,20 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../Config/etc/system_file.xsd"> + <system> + <section id="dev"> + <group id="js"> + <field id="translate_strategy" translate="label" type="select" sortOrder="30" showInDefault="1" showInWebsite="0" showInStore="0"> + <label>Translation Strategy</label> + <source_model>Magento\Translation\Model\Js\Config\Source\Strategy</source_model> + <comment>Please put your store into maintenance mode and redeploy static files after changing strategy</comment> + </field> + </group> + </section> + </system> +</config> diff --git a/app/code/Magento/Translation/etc/config.xml b/app/code/Magento/Translation/etc/config.xml index ae18303a6bc7320f54be043c77d802324d4d6d48..19cb337a6b6a804f91d385c21a64995101e8746b 100644 --- a/app/code/Magento/Translation/etc/config.xml +++ b/app/code/Magento/Translation/etc/config.xml @@ -15,6 +15,9 @@ <block_html /> </invalid_caches> </translate_inline> + <js> + <translate_strategy>none</translate_strategy> + </js> </dev> </default> </config> diff --git a/app/code/Magento/Translation/etc/di.xml b/app/code/Magento/Translation/etc/di.xml index 3507d99af6eec6b55fb3c2b7681e76f7510bdf34..c411e653425d197cc7fc4096d6426113ccd94aa1 100644 --- a/app/code/Magento/Translation/etc/di.xml +++ b/app/code/Magento/Translation/etc/di.xml @@ -13,6 +13,8 @@ <preference for="Magento\Framework\Translate\ResourceInterface" type="Magento\Translation\Model\Resource\Translate" /> <preference for="Magento\Framework\Translate\Inline\StateInterface" type="Magento\Framework\Translate\Inline\State" /> <preference for="Magento\Framework\Phrase\RendererInterface" type="Magento\Framework\Phrase\Renderer\Composite" /> + <preference for="Magento\Translation\Model\Js\DataProviderInterface" type="Magento\Translation\Model\Js\DataProvider"/> + <preference for="Magento\Framework\Translate\Js\Config" type="Magento\Translation\Model\Js\Config"/> <type name="Magento\Framework\Translate\Inline"> <arguments> <argument name="templateFileName" xsi:type="string">Magento_Translation::translate_inline.phtml</argument> @@ -52,4 +54,27 @@ </argument> </arguments> </type> + <type name="Magento\Translation\Model\Js\Config"> + <arguments> + <argument name="patterns" xsi:type="array"> + <item name="mage_translation_widget" xsi:type="string">~\$\.mage\.__\([\'|\"](.+?)[\'|\"]\)~</item> + </argument> + </arguments> + </type> + <type name="Magento\Framework\View\Asset\PreProcessor\Pool"> + <arguments> + <argument name="preProcessors" xsi:type="array"> + <item name="js" xsi:type="array"> + <item name="js" xsi:type="array"> + <item name="js_translation" xsi:type="string">Magento\Translation\Model\Js\PreProcessor</item> + </item> + </item> + <item name="json" xsi:type="array"> + <item name="json" xsi:type="array"> + <item name="json_generation" xsi:type="string">Magento\Translation\Model\Json\PreProcessor</item> + </item> + </item> + </argument> + </arguments> + </type> </config> diff --git a/app/code/Magento/Translation/view/base/templates/translate.phtml b/app/code/Magento/Translation/view/base/templates/translate.phtml index be33473e166c7bb81300b8209fe7f11cda7654e3..d6c010e876528c017458f6af3c9734b10e47bce7 100644 --- a/app/code/Magento/Translation/view/base/templates/translate.phtml +++ b/app/code/Magento/Translation/view/base/templates/translate.phtml @@ -3,12 +3,17 @@ * Copyright © 2015 Magento. All rights reserved. * See COPYING.txt for license details. */ +// @codingStandardsIgnoreFile ?> <?php /** @var $block \Magento\Translation\Block\Js */ ?> +<?php if ($block->dictionaryEnabled()): ?> <script> -//<![CDATA[ - require( - ["jquery", "mage/translate"], function($){ $.mage.translate.add( <?php echo $block->getTranslatedJson() ?> ); } - ) -// ]]> +require([ + "jquery", + "mage/translate", + "text!<?php echo Magento\Translation\Model\Js\Config::DICTIONARY_FILE_NAME?>" +], function($, translate, data) { + $.mage.translate.add(JSON.parse(data)); +}); </script> +<?php endif; ?> diff --git a/app/etc/di.xml b/app/etc/di.xml index 48402892228cc368b5457502ed828f9a27e4655d..de5633a6639e278c01403d21d01a133394e846b9 100755 --- a/app/etc/di.xml +++ b/app/etc/di.xml @@ -671,27 +671,6 @@ <argument name="raise_php_limits" xsi:type="boolean">false</argument> </arguments> </type> - <type name="Magento\Framework\View\Asset\PreProcessor\Pool"> - <arguments> - <argument name="preProcessors" xsi:type="array"> - <item name="less" xsi:type="array"> - <item name="css" xsi:type="array"> - <item name="less_css" xsi:type="string">Magento\Framework\Css\PreProcessor\Less</item> - <item name="module_notation" xsi:type="string">Magento\Framework\View\Asset\PreProcessor\ModuleNotation</item> - </item> - <item name="less" xsi:type="array"> - <item name="magento_import" xsi:type="string">Magento\Framework\Less\PreProcessor\Instruction\MagentoImport</item> - <item name="import" xsi:type="string">Magento\Framework\Less\PreProcessor\Instruction\Import</item> - </item> - </item> - <item name="css" xsi:type="array"> - <item name="css" xsi:type="array"> - <item name="module_notation" xsi:type="string">Magento\Framework\View\Asset\PreProcessor\ModuleNotation</item> - </item> - </item> - </argument> - </arguments> - </type> <type name="Magento\Framework\App\DefaultPath\DefaultPath"> <arguments> <argument name="parts" xsi:type="array"> diff --git a/dev/tests/integration/testsuite/Magento/Customer/Model/VisitorTest.php b/dev/tests/integration/testsuite/Magento/Customer/Model/VisitorTest.php index e8bb754254ebeb39ae1329af1c612e69228d99c2..59747a3c427887b59895b20f16e07ce4f9fedc80 100644 --- a/dev/tests/integration/testsuite/Magento/Customer/Model/VisitorTest.php +++ b/dev/tests/integration/testsuite/Magento/Customer/Model/VisitorTest.php @@ -63,18 +63,22 @@ class VisitorTest extends \PHPUnit_Framework_TestCase */ public function testClean() { + $customerIdNow = 1; $lastVisitNow = date('Y-m-d H:i:s', time()); $sessionIdNow = 'asaswljxvgklasdflkjasieasd'; + $customerIdPast = null; $lastVisitPast = date('Y-m-d H:i:s', time() - 172800); $sessionIdPast = 'kui0aa57nqddl8vk7k6ohgi352'; /** @var \Magento\Customer\Model\Visitor $visitor */ $visitor = Bootstrap::getObjectManager()->get('Magento\Customer\Model\Visitor'); + $visitor->setCustomerId($customerIdPast); $visitor->setSessionId($sessionIdPast); $visitor->setLastVisitAt($lastVisitPast); $visitor->save(); $visitorIdPast = $visitor->getId(); $visitor->unsetData(); + $visitor->setCustomerId($customerIdNow); $visitor->setSessionId($sessionIdNow); $visitor->setLastVisitAt($lastVisitNow); $visitor->save(); @@ -87,7 +91,12 @@ class VisitorTest extends \PHPUnit_Framework_TestCase $visitor->unsetData(); $visitor->load($visitorIdNow); $this->assertEquals( - ['visitor_id' => $visitorIdNow, 'session_id' => $sessionIdNow, 'last_visit_at' => $lastVisitNow], + [ + 'visitor_id' => $visitorIdNow, + 'customer_id' => $customerIdNow, + 'session_id' => $sessionIdNow, + 'last_visit_at' => $lastVisitNow + ], $visitor->getData() ); } diff --git a/dev/tests/static/testsuite/Magento/Test/Integrity/Phrase/JsTest.php b/dev/tests/static/testsuite/Magento/Test/Integrity/Phrase/JsTest.php deleted file mode 100644 index 08981d2585ce547dc6d105959a973d8afdfa6167..0000000000000000000000000000000000000000 --- a/dev/tests/static/testsuite/Magento/Test/Integrity/Phrase/JsTest.php +++ /dev/null @@ -1,135 +0,0 @@ -<?php -/** - * Scan javascript files for invocations of mage.__() function, verifies that all the translations - * were output to the page. - * - * Copyright © 2015 Magento. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\Test\Integrity\Phrase; - -use Magento\Tools\I18n\Parser\Adapter; -use Magento\Tools\I18n\Parser\Adapter\Php\Tokenizer; -use Magento\Tools\I18n\Parser\Adapter\Php\Tokenizer\PhraseCollector; - -class JsTest extends \Magento\Test\Integrity\Phrase\AbstractTestCase -{ - /** - * @var \Magento\Tools\I18n\Parser\Adapter\Js - */ - protected $_parser; - - /** @var \Magento\Framework\App\Utility\Files */ - protected $_utilityFiles; - - /** @var \Magento\Tools\I18n\Parser\Adapter\Php\Tokenizer\PhraseCollector */ - protected $_phraseCollector; - - protected function setUp() - { - $this->_parser = new \Magento\Tools\I18n\Parser\Adapter\Js(); - $this->_utilityFiles = \Magento\Framework\App\Utility\Files::init(); - $this->_phraseCollector = new \Magento\Tools\I18n\Parser\Adapter\Php\Tokenizer\PhraseCollector( - new \Magento\Tools\I18n\Parser\Adapter\Php\Tokenizer() - ); - } - - public function testGetPhrasesAdminhtml() - { - $unregisteredMessages = []; - $untranslated = []; - - $registeredPhrases = $this->_getRegisteredPhrases(); - - require_once BP . '/app/code/Magento/Backend/App/Area/FrontNameResolver.php'; - foreach ($this->_getJavascriptPhrases(\Magento\Backend\App\Area\FrontNameResolver::AREA_CODE) as $phrase) { - if (!in_array($phrase['phrase'], $registeredPhrases)) { - $unregisteredMessages[] = sprintf( - "'%s' \n in file %s, line# %s", - $phrase['phrase'], - $phrase['file'], - $phrase['line'] - ); - $untranslated[] = $phrase['phrase']; - } - } - - if (count($unregisteredMessages) > 0) { - $this->fail( - 'There are UI messages in javascript files for adminhtml area ' . - "which requires translations to be output to the page: \n\n" . - implode( - "\n", - $unregisteredMessages - ) - ); - } - } - - public function testGetPhrasesFrontend() - { - $unregisteredMessages = []; - $untranslated = []; - - $registeredPhrases = $this->_getRegisteredPhrases(); - - foreach ($this->_getJavascriptPhrases('frontend') as $phrase) { - if (!in_array($phrase['phrase'], $registeredPhrases)) { - $unregisteredMessages[] = sprintf( - "'%s' \n in file %s, line# %s", - $phrase['phrase'], - $phrase['file'], - $phrase['line'] - ); - $untranslated[] = $phrase['phrase']; - } - } - - if (count($unregisteredMessages) > 0) { - $this->fail( - 'There are UI messages in javascript files for frontend area ' . - "which requires translations to be output to the page: \n\n" . - implode( - "\n", - $unregisteredMessages - ) - ); - } - } - - /** - * Returns an array of phrases that can be used by JS files. - * - * @return string[] - */ - protected function _getRegisteredPhrases() - { - $jsHelperFile = realpath( - __DIR__ . '/../../../../../../../../app/code/Magento/Translation/Model/Js/DataProvider.php' - ); - - $this->_phraseCollector->parse($jsHelperFile); - - $result = []; - foreach ($this->_phraseCollector->getPhrases() as $phrase) { - $result[] = stripcslashes(trim($phrase['phrase'], "'")); - } - return $result; - } - - /** - * Returns an array of phrases used by JavaScript files in a specific area of magento. - * - * @param string $area of magento to search, such as 'frontend' or 'adminthml' - * @return string[] - */ - protected function _getJavascriptPhrases($area) - { - $jsPhrases = []; - foreach ($this->_utilityFiles->getJsFilesForArea($area) as $file) { - $this->_parser->parse($file); - $jsPhrases = array_merge($jsPhrases, $this->_parser->getPhrases()); - } - return $jsPhrases; - } -} diff --git a/dev/tests/static/testsuite/Magento/Test/Legacy/_files/obsolete_classes.php b/dev/tests/static/testsuite/Magento/Test/Legacy/_files/obsolete_classes.php index 1afa25055ddc04de479b612ea4e9666cd1a5f0c4..92b49b9ef7cd70ccc802117e6b8cb936c7da22a0 100644 --- a/dev/tests/static/testsuite/Magento/Test/Legacy/_files/obsolete_classes.php +++ b/dev/tests/static/testsuite/Magento/Test/Legacy/_files/obsolete_classes.php @@ -3104,6 +3104,7 @@ return [ ['Magento\Framework\Module\Updater'], ['Magento\Setup\Module\SetupFactory'], ['Magento\Framework\Module\Updater\SetupFactory'], + ['Magento\Log\Block\Adminhtml\Customer\Edit\Tab\View\Status'], ['Magento\Backend\Model\Config\Source\Yesno', 'Magento\Config\Model\Config\Source\Yesno'], ['Magento\Reports\Model\Resource\Shopcart\Product\Collection'], ['Zend_Locale', '\Locale, \ResourceBundle'], diff --git a/dev/tools/Magento/Tools/View/Deployer.php b/dev/tools/Magento/Tools/View/Deployer.php index fafd45319ff081370c89c805a89bcc870ca235b0..1549724017780a260891f4f386faf09962180a5d 100644 --- a/dev/tools/Magento/Tools/View/Deployer.php +++ b/dev/tools/Magento/Tools/View/Deployer.php @@ -10,6 +10,8 @@ use Magento\Framework\App\ObjectManagerFactory; use Magento\Framework\App\View\Deployment\Version; use Magento\Framework\App\View\Asset\Publisher; use Magento\Framework\App\Utility\Files; +use Magento\Framework\ObjectManagerInterface; +use Magento\Framework\Translate\Js\Config as JsTranslationConfig; /** * A service for deploying Magento static view files for production mode @@ -57,12 +59,23 @@ class Deployer /** @var \Magento\Framework\View\Asset\MinifyService */ protected $minifyService; + /** + * @var ObjectManagerInterface + */ + private $objectManager; + + /** + * @var JsTranslationConfig + */ + protected $jsTranslationConfig; + /** * @param Files $filesUtil * @param Deployer\Log $logger * @param Version\StorageInterface $versionStorage * @param \Magento\Framework\Stdlib\DateTime $dateTime * @param \Magento\Framework\View\Asset\MinifyService $minifyService + * @param JsTranslationConfig $jsTranslationConfig * @param bool $isDryRun */ public function __construct( @@ -71,6 +84,7 @@ class Deployer Version\StorageInterface $versionStorage, \Magento\Framework\Stdlib\DateTime $dateTime, \Magento\Framework\View\Asset\MinifyService $minifyService, + JsTranslationConfig $jsTranslationConfig, $isDryRun = false ) { $this->filesUtil = $filesUtil; @@ -79,6 +93,7 @@ class Deployer $this->dateTime = $dateTime; $this->isDryRun = $isDryRun; $this->minifyService = $minifyService; + $this->jsTranslationConfig = $jsTranslationConfig; } /** @@ -87,6 +102,7 @@ class Deployer * @param ObjectManagerFactory $omFactory * @param array $locales * @return void + * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ public function deploy(ObjectManagerFactory $omFactory, array $locales) { @@ -101,6 +117,7 @@ class Deployer foreach ($areas as $area => $themes) { $this->emulateApplicationArea($area); foreach ($locales as $locale) { + $this->emulateApplicationLocale($locale, $area); foreach ($themes as $themePath) { $this->logger->logMessage("=== {$area} -> {$themePath} -> {$locale} ==="); $this->count = 0; @@ -112,6 +129,15 @@ class Deployer foreach ($libFiles as $filePath) { $this->deployFile($filePath, $area, $themePath, $locale, null); } + if ($this->jsTranslationConfig->dictionaryEnabled()) { + $this->deployFile( + $this->jsTranslationConfig->getDictionaryFileName(), + $area, + $themePath, + $locale, + null + ); + } $this->bundleManager->flush(); $this->logger->logMessage("\nSuccessful: {$this->count} files; errors: {$this->errorCount}\n---\n"); } @@ -176,19 +202,36 @@ class Deployer */ private function emulateApplicationArea($areaCode) { - $objectManager = $this->omFactory->create( + $this->objectManager = $this->omFactory->create( [\Magento\Framework\App\State::PARAM_MODE => \Magento\Framework\App\State::MODE_DEFAULT] ); /** @var \Magento\Framework\App\State $appState */ - $appState = $objectManager->get('Magento\Framework\App\State'); + $appState = $this->objectManager->get('Magento\Framework\App\State'); $appState->setAreaCode($areaCode); /** @var \Magento\Framework\App\ObjectManager\ConfigLoader $configLoader */ - $configLoader = $objectManager->get('Magento\Framework\App\ObjectManager\ConfigLoader'); - $objectManager->configure($configLoader->load($areaCode)); - $this->assetRepo = $objectManager->get('Magento\Framework\View\Asset\Repository'); - $this->assetPublisher = $objectManager->create('Magento\Framework\App\View\Asset\Publisher'); - $this->htmlMinifier = $objectManager->get('Magento\Framework\View\Template\Html\MinifierInterface'); - $this->bundleManager = $objectManager->get('Magento\Framework\View\Asset\Bundle\Manager'); + $configLoader = $this->objectManager->get('Magento\Framework\App\ObjectManager\ConfigLoader'); + $this->objectManager->configure($configLoader->load($areaCode)); + $this->assetRepo = $this->objectManager->get('Magento\Framework\View\Asset\Repository'); + + $this->assetPublisher = $this->objectManager->create('Magento\Framework\App\View\Asset\Publisher'); + $this->htmlMinifier = $this->objectManager->get('Magento\Framework\View\Template\Html\MinifierInterface'); + $this->bundleManager = $this->objectManager->get('Magento\Framework\View\Asset\Bundle\Manager'); + + } + + /** + * Set application locale and load translation for area + * + * @param string $locale + * @param string $area + * @return void + */ + protected function emulateApplicationLocale($locale, $area) + { + /** @var \Magento\Framework\TranslateInterface $translator */ + $translator = $this->objectManager->get('Magento\Framework\TranslateInterface'); + $translator->setLocale($locale); + $translator->loadData($area, true); } /** diff --git a/lib/internal/Magento/Framework/App/Utility/Files.php b/lib/internal/Magento/Framework/App/Utility/Files.php index 97b389f125f278e3d7f67176894ab2ec0905f06c..15869c4be7c49c67c886bab1b14484c2f5b1b5ea 100644 --- a/lib/internal/Magento/Framework/App/Utility/Files.php +++ b/lib/internal/Magento/Framework/App/Utility/Files.php @@ -490,18 +490,18 @@ class Files /** * Returns list of Javascript files in Magento * + * @param string $area + * @param string $themePath + * @param string $namespace + * @param string $module * @return array */ - public function getJsFiles() + public function getJsFiles($area = '*', $themePath = '*/*', $namespace = '*', $module = '*') { - $key = __METHOD__ . $this->_path; + $key = $area . $themePath . $namespace . $module . __METHOD__ . $this->_path; if (isset(self::$_cache[$key])) { return self::$_cache[$key]; } - $namespace = '*'; - $module = '*'; - $area = '*'; - $themePath = '*/*'; $files = self::getFiles( [ "{$this->_path}/app/code/{$namespace}/{$module}/view/{$area}/web", diff --git a/lib/internal/Magento/Framework/Test/Unit/Translate/Js/ConfigTest.php b/lib/internal/Magento/Framework/Test/Unit/Translate/Js/ConfigTest.php new file mode 100644 index 0000000000000000000000000000000000000000..b2dcba665cde41cda8ff2f05642d8c0761d8ca1c --- /dev/null +++ b/lib/internal/Magento/Framework/Test/Unit/Translate/Js/ConfigTest.php @@ -0,0 +1,36 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Framework\Test\Unit\Translate\Js; + +use Magento\Framework\Translate\Js\Config; + +/** + * Class ConfigTest + */ +class ConfigTest extends \PHPUnit_Framework_TestCase +{ + /** + * @return void + */ + public function testDefault() + { + $config = new Config(); + $this->assertFalse($config->dictionaryEnabled()); + $this->assertNull($config->getDictionaryFileName()); + } + + /** + * @return void + */ + public function testCustom() + { + $path = 'path'; + $config = new Config(true, $path); + $this->assertTrue($config->dictionaryEnabled()); + $this->assertEquals($path, $config->getDictionaryFileName()); + } +} diff --git a/lib/internal/Magento/Framework/Test/Unit/View/Asset/SourceTest.php b/lib/internal/Magento/Framework/Test/Unit/View/Asset/SourceTest.php new file mode 100644 index 0000000000000000000000000000000000000000..b1d22dca5d9a9692a2c8fed68235edee03e965f7 --- /dev/null +++ b/lib/internal/Magento/Framework/Test/Unit/View/Asset/SourceTest.php @@ -0,0 +1,285 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +// @codingStandardsIgnoreFile + +namespace Magento\Framework\Test\Unit\View\Asset; + +use Magento\Framework\App\Filesystem\DirectoryList; +use Magento\Framework\Filesystem\DriverPool; +use Magento\Framework\View\Asset\Source; + +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class SourceTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var \Magento\Framework\Filesystem|\PHPUnit_Framework_MockObject_MockObject + */ + private $filesystem; + + /** + * @var \Magento\Framework\Filesystem\Directory\ReadInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $rootDirRead; + + /** + * @var \Magento\Framework\Filesystem\Directory\WriteInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $varDir; + + /** + * @var \Magento\Framework\Filesystem\Directory\WriteInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $staticDirRead; + + /** + * @var \Magento\Framework\View\Asset\PreProcessor\Cache|\PHPUnit_Framework_MockObject_MockObject + */ + private $cache; + + /** + * @var \Magento\Framework\View\Asset\PreProcessor\Pool|\PHPUnit_Framework_MockObject_MockObject + */ + private $preProcessorPool; + + /** + * @var \Magento\Framework\View\Design\FileResolution\Fallback\StaticFile|\PHPUnit_Framework_MockObject_MockObject + */ + private $viewFileResolution; + + /** + * @var \Magento\Framework\View\Design\ThemeInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $theme; + + /** + * @var Source + */ + private $object; + + protected function setUp() + { + $this->cache = $this->getMock( + 'Magento\Framework\View\Asset\PreProcessor\Cache', [], [], '', false + ); + $this->preProcessorPool = $this->getMock( + 'Magento\Framework\View\Asset\PreProcessor\Pool', [], [], '', false + ); + $this->viewFileResolution = $this->getMock( + 'Magento\Framework\View\Design\FileResolution\Fallback\StaticFile', [], [], '', false + ); + $this->theme = $this->getMockForAbstractClass('Magento\Framework\View\Design\ThemeInterface'); + + $themeList = $this->getMockForAbstractClass('Magento\Framework\View\Design\Theme\ListInterface'); + $themeList->expects($this->any()) + ->method('getThemeByFullPath') + ->with('frontend/magento_theme') + ->will($this->returnValue($this->theme)); + + $this->initFilesystem(); + + $this->object = new Source( + $this->cache, + $this->filesystem, + $this->preProcessorPool, + $this->viewFileResolution, + $themeList + ); + } + + public function testGetFileCached() + { + $root = '/root/some/file.ext'; + $expected = '/var/some/file.ext'; + $filePath = 'some/file.ext'; + $this->viewFileResolution->expects($this->once()) + ->method('getFile') + ->with('frontend', $this->theme, 'en_US', $filePath, 'Magento_Module') + ->will($this->returnValue($root)); + $this->rootDirRead->expects($this->once()) + ->method('getRelativePath') + ->with($root) + ->will($this->returnValue($filePath)); + $this->cache->expects($this->once()) + ->method('load') + ->with("some/file.ext:{$filePath}") + ->will($this->returnValue(serialize([DirectoryList::VAR_DIR, $filePath]))); + + $this->varDir->expects($this->once())->method('getAbsolutePath') + ->with($filePath) + ->will($this->returnValue($expected)); + $this->assertSame($expected, $this->object->getFile($this->getAsset())); + } + + /** + * @param string $origFile + * @param string $origPath + * @param string $origContentType + * @param string $origContent + * @param string $isMaterialization + * @dataProvider getFileDataProvider + */ + public function testGetFile($origFile, $origPath, $origContent, $isMaterialization) + { + $filePath = 'some/file.ext'; + $cacheValue = "{$origPath}:{$filePath}"; + $this->viewFileResolution->expects($this->once()) + ->method('getFile') + ->with('frontend', $this->theme, 'en_US', $filePath, 'Magento_Module') + ->will($this->returnValue($origFile)); + $this->rootDirRead->expects($this->once()) + ->method('getRelativePath') + ->with($origFile) + ->will($this->returnValue($origPath)); + $this->cache->expects($this->once()) + ->method('load') + ->will($this->returnValue(false)); + $this->rootDirRead->expects($this->once()) + ->method('readFile') + ->with($origPath) + ->will($this->returnValue($origContent)); + $this->preProcessorPool->expects($this->once()) + ->method('process') + ->will($this->returnCallback([$this, 'chainTestCallback'])); + if ($isMaterialization) { + $this->varDir->expects($this->once()) + ->method('writeFile') + ->with('view_preprocessed/source/some/file.ext', 'processed'); + $this->cache->expects($this->once()) + ->method('save') + ->with( + serialize([DirectoryList::VAR_DIR, 'view_preprocessed/source/some/file.ext']), + $cacheValue + ); + $this->varDir->expects($this->once()) + ->method('getAbsolutePath') + ->with('view_preprocessed/source/some/file.ext')->will($this->returnValue('result')); + } else { + $this->varDir->expects($this->never())->method('writeFile'); + $this->cache->expects($this->once()) + ->method('save') + ->with(serialize([DirectoryList::ROOT, 'source/some/file.ext']), $cacheValue); + $this->rootDirRead->expects($this->once()) + ->method('getAbsolutePath') + ->with('source/some/file.ext') + ->will($this->returnValue('result')); + } + $this->assertSame('result', $this->object->getFile($this->getAsset())); + } + + /** + * @param string $path + * @param string $expected + * @dataProvider getContentTypeDataProvider + */ + public function testGetContentType($path, $expected) + { + $this->assertEquals($expected, $this->object->getContentType($path)); + } + + /** + * @return array + */ + public function getContentTypeDataProvider() + { + return [ + ['', ''], + ['path/file', ''], + ['path/file.ext', 'ext'], + ]; + } + + /** + * A callback for affecting preprocessor chain in the test + * + * @param \Magento\Framework\View\Asset\PreProcessor\Chain $chain + */ + public function chainTestCallback(\Magento\Framework\View\Asset\PreProcessor\Chain $chain) + { + $chain->setContentType('ext'); + $chain->setContent('processed'); + } + + /** + * @return array + */ + public function getFileDataProvider() + { + return [ + ['/root/some/file.ext', 'source/some/file.ext', 'processed', false], + ['/root/some/file.ext', 'source/some/file.ext', 'not_processed', true], + ['/root/some/file.ext2', 'source/some/file.ext2', 'processed', true], + ['/root/some/file.ext2', 'source/some/file.ext2', 'not_processed', true], + ]; + } + + protected function initFilesystem() + { + $this->filesystem = $this->getMock('Magento\Framework\Filesystem', [], [], '', false); + $this->rootDirRead = $this->getMockForAbstractClass('Magento\Framework\Filesystem\Directory\ReadInterface'); + $this->staticDirRead = $this->getMockForAbstractClass('Magento\Framework\Filesystem\Directory\ReadInterface'); + $this->varDir = $this->getMockForAbstractClass('Magento\Framework\Filesystem\Directory\WriteInterface'); + + $readDirMap = [ + [DirectoryList::ROOT, DriverPool::FILE, $this->rootDirRead], + [DirectoryList::STATIC_VIEW, DriverPool::FILE, $this->staticDirRead], + [DirectoryList::VAR_DIR, DriverPool::FILE, $this->varDir], + ]; + + $this->filesystem->expects($this->any()) + ->method('getDirectoryRead') + ->will($this->returnValueMap($readDirMap)); + $this->filesystem->expects($this->any()) + ->method('getDirectoryWrite') + ->with(DirectoryList::VAR_DIR) + ->will($this->returnValue($this->varDir)); + } + + /** + * Create an asset mock + * + * @param bool $isFallback + * @return \Magento\Framework\View\Asset\File|\PHPUnit_Framework_MockObject_MockObject + */ + protected function getAsset($isFallback = true) + { + if ($isFallback) { + $context = new \Magento\Framework\View\Asset\File\FallbackContext( + 'http://example.com/static/', + 'frontend', + 'magento_theme', + 'en_US' + ); + } else { + $context = new \Magento\Framework\View\Asset\File\Context( + 'http://example.com/static/', + DirectoryList::STATIC_VIEW, + '' + ); + } + + $asset = $this->getMock('Magento\Framework\View\Asset\File', [], [], '', false); + $asset->expects($this->any()) + ->method('getContext') + ->will($this->returnValue($context)); + $asset->expects($this->any()) + ->method('getFilePath') + ->will($this->returnValue('some/file.ext')); + $asset->expects($this->any()) + ->method('getPath') + ->will($this->returnValue('some/file.ext')); + $asset->expects($this->any()) + ->method('getModule') + ->will($this->returnValue('Magento_Module')); + $asset->expects($this->any()) + ->method('getContentType') + ->will($this->returnValue('ext')); + + return $asset; + } +} diff --git a/lib/internal/Magento/Framework/Translate/Js/Config.php b/lib/internal/Magento/Framework/Translate/Js/Config.php new file mode 100644 index 0000000000000000000000000000000000000000..17ec1bbba4af1057cf68070bbf0a0640c064641f --- /dev/null +++ b/lib/internal/Magento/Framework/Translate/Js/Config.php @@ -0,0 +1,56 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Framework\Translate\Js; + +/** + * Js Translation config + */ +class Config +{ + /** + * Should the framework generate dictionary file + * + * @var bool + */ + protected $dictionaryEnabled; + + /** + * Name of dictionary json file + * + * @var string + */ + protected $dictionaryFileName; + + /** + * @param bool $dictionaryEnabled + * @param string $dictionaryFileName + */ + public function __construct($dictionaryEnabled = false, $dictionaryFileName = null) + { + $this->dictionaryEnabled = $dictionaryEnabled; + $this->dictionaryFileName = $dictionaryFileName; + } + + /** + * Should the framework generate dictionary file + * + * @return bool + */ + public function dictionaryEnabled() + { + return $this->dictionaryEnabled; + } + + /** + * Name of dictionary json file + * + * @return string + */ + public function getDictionaryFileName() + { + return $this->dictionaryFileName; + } +} diff --git a/lib/internal/Magento/Framework/View/Asset/Repository.php b/lib/internal/Magento/Framework/View/Asset/Repository.php index 4e46b91fba0d65cbbdcafd456b9b6efcb75b4433..fda21b7564074f7412b7430e9927ddef9a64e1bf 100644 --- a/lib/internal/Magento/Framework/View/Asset/Repository.php +++ b/lib/internal/Magento/Framework/View/Asset/Repository.php @@ -152,7 +152,7 @@ class Repository $module = $params['module']; } $isSecure = isset($params['_secure']) ? (bool) $params['_secure'] : null; - $themePath = $this->design->getThemePath($params['themeModel']); + $themePath = isset($params['theme']) ? $params['theme'] : $this->design->getThemePath($params['themeModel']); $context = $this->getFallbackContext( UrlInterface::URL_TYPE_STATIC, $isSecure, diff --git a/lib/internal/Magento/Framework/View/Asset/Source.php b/lib/internal/Magento/Framework/View/Asset/Source.php index 747ab55ab07c3d4952587065da0fa6f47841d33a..2518c6973eaf06b14584ed0d435a0dea5ade18b9 100644 --- a/lib/internal/Magento/Framework/View/Asset/Source.php +++ b/lib/internal/Magento/Framework/View/Asset/Source.php @@ -129,9 +129,6 @@ class Source private function preProcess(LocalInterface $asset) { $sourceFile = $this->findSourceFile($asset); - if (!$sourceFile) { - return false; - } $dirCode = DirectoryList::ROOT; $path = $this->rootDir->getRelativePath($sourceFile); $cacheId = $path . ':' . $asset->getPath(); @@ -141,7 +138,7 @@ class Source } $chain = new \Magento\Framework\View\Asset\PreProcessor\Chain( $asset, - $this->rootDir->readFile($path), + $path ? $this->rootDir->readFile($path) : "", $this->getContentType($path), $path, $this->appMode diff --git a/lib/internal/Magento/Framework/View/Test/Unit/Asset/SourceTest.php b/lib/internal/Magento/Framework/View/Test/Unit/Asset/SourceTest.php index 7c14c7b32ccd265093688328a4959bb8413a90a5..026fbf85e677e1977a0ce9e51b23664c9f848b21 100644 --- a/lib/internal/Magento/Framework/View/Test/Unit/Asset/SourceTest.php +++ b/lib/internal/Magento/Framework/View/Test/Unit/Asset/SourceTest.php @@ -92,24 +92,6 @@ class SourceTest extends \PHPUnit_Framework_TestCase ); } - public function testGetFileNoOriginalFile() - { - $this->viewFileResolution->expects($this->once()) - ->method('getFile') - ->with('frontend', $this->theme, 'en_US', 'some/file.ext', 'Magento_Module') - ->will($this->returnValue(false)); - $this->assertFalse($this->object->getFile($this->getAsset())); - } - - public function testGetFileNoOriginalFileBasic() - { - $this->staticDirRead->expects($this->once()) - ->method('getAbsolutePath') - ->with('some/file.ext') - ->will($this->returnValue(false)); - $this->assertFalse($this->object->getFile($this->getAsset(false))); - } - public function testGetFileCached() { $root = '/root/some/file.ext'; diff --git a/lib/web/jquery/jquery.validate.js b/lib/web/jquery/jquery.validate.js index 58fdf10e665f2de752181fcfc09e1cfc5ecad2c2..85d0621b02244ae568e56509e83f9ad3dde2d626 100644 --- a/lib/web/jquery/jquery.validate.js +++ b/lib/web/jquery/jquery.validate.js @@ -288,22 +288,22 @@ }, messages: { - required: "This field is required.", - remote: "Please fix this field.", - email: "Please enter a valid email address.", - url: "Please enter a valid URL.", - date: "Please enter a valid date.", - dateISO: "Please enter a valid date (ISO).", - number: "Please enter a valid number.", - digits: "Please enter only digits.", - creditcard: "Please enter a valid credit card number.", - equalTo: "Please enter the same value again.", - maxlength: $.validator.format("Please enter no more than {0} characters."), - minlength: $.validator.format("Please enter at least {0} characters."), - rangelength: $.validator.format("Please enter a value between {0} and {1} characters long."), - range: $.validator.format("Please enter a value between {0} and {1}."), - max: $.validator.format("Please enter a value less than or equal to {0}."), - min: $.validator.format("Please enter a value greater than or equal to {0}.") + required: $.mage.__("This field is required."), + remote: $.mage.__("Please fix this field."), + email: $.mage.__("Please enter a valid email address."), + url: $.mage.__("Please enter a valid URL."), + date: $.mage.__("Please enter a valid date."), + dateISO: $.mage.__("Please enter a valid date (ISO)."), + number: $.mage.__("Please enter a valid number."), + digits: $.mage.__("Please enter only digits."), + creditcard: $.mage.__("Please enter a valid credit card number."), + equalTo: $.mage.__("Please enter the same value again."), + maxlength: $.validator.format($.mage.__("Please enter no more than {0} characters.")), + minlength: $.validator.format($.mage.__("Please enter at least {0} characters.")), + rangelength: $.validator.format($.mage.__("Please enter a value between {0} and {1} characters long.")), + range: $.validator.format($.mage.__("Please enter a value between {0} and {1}.")), + max: $.validator.format($.mage.__("Please enter a value less than or equal to {0}.")), + min: $.validator.format($.mage.__("Please enter a value greater than or equal to {0}.")) }, autoCreateRanges: false, @@ -621,6 +621,7 @@ }, defaultMessage: function (element, method) { + var noMessage = $.mage.__("Warning: No message defined for %s"); return this.findDefined( this.customMessage(element.name, method), this.customDataMessage(element, method), @@ -628,7 +629,7 @@ // title is never undefined, so handle empty string as undefined !this.settings.ignoreTitle && element.title || undefined, $.validator.messages[method], - "<strong>Warning: No message defined for " + element.name + "</strong>" + "<strong>" + noMessage.replace('%s', element.name) + "</strong>" ); }, diff --git a/lib/web/mage/decorate.js b/lib/web/mage/decorate.js index 0babea53af81b9f42e7db1bedf2e3266c26a278c..2c629479e497f1ccc31fdf570cf4ea44b3c050d3 100644 --- a/lib/web/mage/decorate.js +++ b/lib/web/mage/decorate.js @@ -111,7 +111,8 @@ } else if (typeof method === 'object' || ! method) { return methods.init.apply(this, arguments); } else { - $.error($.mage.__('Method ' + method + ' does not exist on jQuery.decorate')); + var message = $.mage.__('Method %s does not exist on jQuery.decorate'); + $.error(message.replace('%s', method)); } }; diff --git a/lib/web/mage/translate-inline-vde.js b/lib/web/mage/translate-inline-vde.js index 6c2b3a53b7f927fe0f9ca3679a131568bb9f6038..0e70103b8f06fa3173582e16ed52cd42032d86cb 100644 --- a/lib/web/mage/translate-inline-vde.js +++ b/lib/web/mage/translate-inline-vde.js @@ -127,7 +127,7 @@ */ _checkTranslateEditing: function(event, data) { if (this.isBeingEdited) { - alert($.mage.__(data.alert_message)); + alert(data.alert_message); data.is_being_edited = true; } else { diff --git a/lib/web/mage/validation.js b/lib/web/mage/validation.js index 6e14e33548ddd8d773f3149689b5158c089cd969..7632ce9b84977587975da37348f383f39395c89e 100644 --- a/lib/web/mage/validation.js +++ b/lib/web/mage/validation.js @@ -934,7 +934,8 @@ return true; }, function() { - return $.mage.__('Please enter a value less than or equal to %s.').replace('%s', this.lteToVal); + var message = $.mage.__('Please enter a value less than or equal to %s.'); + return message.replace('%s', this.lteToVal); } ], "greater-than-equals-to": [ @@ -946,7 +947,8 @@ return true; }, function() { - return $.mage.__('Please enter a value greater than or equal to %s.').replace('%s', this.gteToVal); + var message = $.mage.__('Please enter a value greater than or equal to %s.'); + return message.replace('%s', this.gteToVal); } ], "validate-emails": [ @@ -1250,7 +1252,7 @@ var showLabel = $.validator.prototype.showLabel; $.extend(true, $.validator.prototype, { showLabel: function(element, message) { - showLabel.call(this, element, $.mage.__(message)); + showLabel.call(this, element, message); // ARIA (adding aria-invalid & aria-describedby) var label = this.errorsFor(element), diff --git a/lib/web/mage/validation/validation.js b/lib/web/mage/validation/validation.js index 42013cf2ca1b41c1e6a1eb40b22cb9417f8b9d0a..d666d2025c2636cb00b27e5d2af2c8baa584323d 100644 --- a/lib/web/mage/validation/validation.js +++ b/lib/web/mage/validation/validation.js @@ -72,7 +72,8 @@ if (inputDate >= minDate && inputDate <= maxDate) { return true; } - this.dateBetweenErrorMessage = $.mage.__('Please enter a date between %min and %max.').replace('%min', minDate).replace('%max', maxDate); + var message = $.mage.__('Please enter a date between %min and %max.'); + this.dateBetweenErrorMessage = message.replace('%min', minDate).replace('%max', maxDate); return false; }, function () { @@ -107,14 +108,14 @@ return false; } if (year < 1900 || year > curYear) { - this.dobErrorMessage = - $.mage.__('Please enter a valid year (1900-%1).').replace('%1', curYear.toString()); + var validYearMessage = $.mage.__('Please enter a valid year (1900-%1).'); + this.dobErrorMessage = validYearMessage.replace('%1', curYear.toString()); return false; } var validateDayInMonth = new Date(year, month, 0).getDate(); if (day < 1 || day > validateDayInMonth) { - this.dobErrorMessage = - $.mage.__('Please enter a valid day (1-%1).').replace('%1', validateDayInMonth.toString()); + var validDateMessage = $.mage.__('Please enter a valid day (1-%1).'); + this.dobErrorMessage = validDateMessage.replace('%1', validateDayInMonth.toString()); return false; } var today = new Date(),