diff --git a/app/code/Magento/Authorizenet/composer.json b/app/code/Magento/Authorizenet/composer.json index b93cb6688f56d826a8d981518612897aae6f1ac8..f4acc4504604d0625543812ecec21b1d90286e59 100644 --- a/app/code/Magento/Authorizenet/composer.json +++ b/app/code/Magento/Authorizenet/composer.json @@ -12,6 +12,9 @@ "magento/module-catalog": "101.1.*", "magento/framework": "100.2.*" }, + "suggest": { + "magento/module-config": "100.2.*" + }, "type": "magento2-module", "version": "100.2.0-dev", "license": [ diff --git a/app/code/Magento/Authorizenet/etc/di.xml b/app/code/Magento/Authorizenet/etc/di.xml index f5e595fb450e83a60a4ce9b45196018cad335f3d..287cdec6fa0f7f62d68e20bfb49254209ef16ca9 100644 --- a/app/code/Magento/Authorizenet/etc/di.xml +++ b/app/code/Magento/Authorizenet/etc/di.xml @@ -16,4 +16,14 @@ <argument name="storage" xsi:type="object">Magento\Authorizenet\Model\Directpost\Session\Storage</argument> </arguments> </type> + <type name="Magento\Config\Model\Config\Export\ExcludeList"> + <arguments> + <argument name="configs" xsi:type="array"> + <item name="payment/authorizenet_directpost/login" xsi:type="string">1</item> + <item name="payment/authorizenet_directpost/trans_key" xsi:type="string">1</item> + <item name="payment/authorizenet_directpost/trans_md5" xsi:type="string">1</item> + <item name="payment/authorizenet_directpost/merchant_email" xsi:type="string">1</item> + </argument> + </arguments> + </type> </config> diff --git a/app/code/Magento/Backend/Block/Cache/Grid/Massaction/ProductionModeVisibilityChecker.php b/app/code/Magento/Backend/Block/Cache/Grid/Massaction/ProductionModeVisibilityChecker.php new file mode 100644 index 0000000000000000000000000000000000000000..70a125e399ab3034669c011ee24c3877154469e9 --- /dev/null +++ b/app/code/Magento/Backend/Block/Cache/Grid/Massaction/ProductionModeVisibilityChecker.php @@ -0,0 +1,36 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Backend\Block\Cache\Grid\Massaction; + +use Magento\Backend\Block\Widget\Grid\Massaction\VisibilityCheckerInterface; +use Magento\Framework\App\State; + +/** + * Class checks that action can be displayed on massaction list + */ +class ProductionModeVisibilityChecker implements VisibilityCheckerInterface +{ + /** + * @var State + */ + private $state; + + /** + * @param State $state + */ + public function __construct(State $state) + { + $this->state = $state; + } + + /** + * {@inheritdoc} + */ + public function isVisible() + { + return $this->state->getMode() !== State::MODE_PRODUCTION; + } +} diff --git a/app/code/Magento/Backend/Block/Widget/Grid/Massaction.php b/app/code/Magento/Backend/Block/Widget/Grid/Massaction.php index ffe12c6cff46ee17442f7a69293172c02e206d30..c86907cc98042b30f6bac68ea0b0d2977ae87591 100644 --- a/app/code/Magento/Backend/Block/Widget/Grid/Massaction.php +++ b/app/code/Magento/Backend/Block/Widget/Grid/Massaction.php @@ -3,14 +3,11 @@ * Copyright © 2016 Magento. All rights reserved. * See COPYING.txt for license details. */ +namespace Magento\Backend\Block\Widget\Grid; /** * Grid widget massaction default block - * - * @author Magento Core Team <core@magentocommerce.com> */ -namespace Magento\Backend\Block\Widget\Grid; - class Massaction extends \Magento\Backend\Block\Widget\Grid\Massaction\AbstractMassaction { } diff --git a/app/code/Magento/Backend/Block/Widget/Grid/Massaction/AbstractMassaction.php b/app/code/Magento/Backend/Block/Widget/Grid/Massaction/AbstractMassaction.php index 2f3df1377b395de426cc573f4d48016de82d7229..7f697599c7003583d08bc6c2f40ff138971bd4c0 100644 --- a/app/code/Magento/Backend/Block/Widget/Grid/Massaction/AbstractMassaction.php +++ b/app/code/Magento/Backend/Block/Widget/Grid/Massaction/AbstractMassaction.php @@ -5,14 +5,14 @@ */ namespace Magento\Backend\Block\Widget\Grid\Massaction; -use Magento\Framework\View\Element\Template; +use Magento\Backend\Block\Widget\Grid\Massaction\VisibilityCheckerInterface as VisibilityChecker; +use Magento\Framework\DataObject; /** * Grid widget massaction block * * @method \Magento\Quote\Model\Quote setHideFormElement(boolean $value) Hide Form element to prevent IE errors * @method boolean getHideFormElement() - * @author Magento Core Team <core@magentocommerce.com> */ abstract class AbstractMassaction extends \Magento\Backend\Block\Widget { @@ -73,20 +73,21 @@ abstract class AbstractMassaction extends \Magento\Backend\Block\Widget * 'complete' => string, // Only for ajax enabled grid (optional) * 'url' => string, * 'confirm' => string, // text of confirmation of this action (optional) - * 'additional' => string // (optional) + * 'additional' => string, // (optional) + * 'visible' => object // instance of VisibilityCheckerInterface (optional) * ); * * @param string $itemId - * @param array|\Magento\Framework\DataObject $item + * @param array|DataObject $item * @return $this */ public function addItem($itemId, $item) { if (is_array($item)) { - $item = new \Magento\Framework\DataObject($item); + $item = new DataObject($item); } - if ($item instanceof \Magento\Framework\DataObject) { + if ($item instanceof DataObject && $this->isVisible($item)) { $item->setId($itemId); $item->setUrl($this->getUrl($item->getUrl())); $this->_items[$itemId] = $item; @@ -95,6 +96,19 @@ abstract class AbstractMassaction extends \Magento\Backend\Block\Widget return $this; } + /** + * Check that item can be added to list + * + * @param DataObject $item + * @return bool + */ + private function isVisible(DataObject $item) + { + /** @var VisibilityChecker $checker */ + $checker = $item->getData('visible'); + return (!$checker instanceof VisibilityChecker) || $checker->isVisible(); + } + /** * Retrieve massaction item with id $itemId * diff --git a/app/code/Magento/Backend/Block/Widget/Grid/Massaction/VisibilityCheckerInterface.php b/app/code/Magento/Backend/Block/Widget/Grid/Massaction/VisibilityCheckerInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..934c4a84d145f349e028fc5497b9db62b2cdac54 --- /dev/null +++ b/app/code/Magento/Backend/Block/Widget/Grid/Massaction/VisibilityCheckerInterface.php @@ -0,0 +1,18 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Backend\Block\Widget\Grid\Massaction; + +use Magento\Framework\View\Element\Block\ArgumentInterface; + +interface VisibilityCheckerInterface extends ArgumentInterface +{ + /** + * Check that action can be displayed on massaction list + * + * @return bool + */ + public function isVisible(); +} diff --git a/app/code/Magento/Backend/Controller/Adminhtml/Cache/MassDisable.php b/app/code/Magento/Backend/Controller/Adminhtml/Cache/MassDisable.php index 7266775959fc348796fabbb150f9e49b2ae9f7ad..42cbe229815aef16e13005ebdc802ec9b9bbd5c5 100644 --- a/app/code/Magento/Backend/Controller/Adminhtml/Cache/MassDisable.php +++ b/app/code/Magento/Backend/Controller/Adminhtml/Cache/MassDisable.php @@ -8,15 +8,41 @@ namespace Magento\Backend\Controller\Adminhtml\Cache; use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Controller\ResultFactory; +use Magento\Framework\App\State; +use Magento\Framework\App\ObjectManager; +/** + * Controller disables some types of cache + */ class MassDisable extends \Magento\Backend\Controller\Adminhtml\Cache { + /** + * @var State + */ + private $state; + /** * Mass action for cache disabling * * @return \Magento\Backend\Model\View\Result\Redirect */ public function execute() + { + if ($this->getState()->getMode() === State::MODE_PRODUCTION) { + $this->messageManager->addErrorMessage(__('You can\'t change status of cache type(s) in production mode')); + } else { + $this->disableCache(); + } + + return $this->resultFactory->create(ResultFactory::TYPE_REDIRECT)->setPath('adminhtml/*'); + } + + /** + * Disable cache + * + * @return void + */ + private function disableCache() { try { $types = $this->getRequest()->getParam('types'); @@ -41,9 +67,20 @@ class MassDisable extends \Magento\Backend\Controller\Adminhtml\Cache } catch (\Exception $e) { $this->messageManager->addException($e, __('An error occurred while disabling cache.')); } + } + + /** + * Get State Instance + * + * @return State + * @deprecated + */ + private function getState() + { + if ($this->state === null) { + $this->state = ObjectManager::getInstance()->get(State::class); + } - /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */ - $resultRedirect = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT); - return $resultRedirect->setPath('adminhtml/*'); + return $this->state; } } diff --git a/app/code/Magento/Backend/Controller/Adminhtml/Cache/MassEnable.php b/app/code/Magento/Backend/Controller/Adminhtml/Cache/MassEnable.php index 6c8bccfee166a5f746fd138c1b4e043ac1834542..8c4117831e8c8d7463b3112ac495cdede04f13d5 100644 --- a/app/code/Magento/Backend/Controller/Adminhtml/Cache/MassEnable.php +++ b/app/code/Magento/Backend/Controller/Adminhtml/Cache/MassEnable.php @@ -8,15 +8,41 @@ namespace Magento\Backend\Controller\Adminhtml\Cache; use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Controller\ResultFactory; +use Magento\Framework\App\State; +use Magento\Framework\App\ObjectManager; +/** + * Controller enables some types of cache + */ class MassEnable extends \Magento\Backend\Controller\Adminhtml\Cache { + /** + * @var State + */ + private $state; + /** * Mass action for cache enabling * * @return \Magento\Backend\Model\View\Result\Redirect */ public function execute() + { + if ($this->getState()->getMode() === State::MODE_PRODUCTION) { + $this->messageManager->addErrorMessage(__('You can\'t change status of cache type(s) in production mode')); + } else { + $this->enableCache(); + } + + return $this->resultFactory->create(ResultFactory::TYPE_REDIRECT)->setPath('adminhtml/*'); + } + + /** + * Enable cache + * + * @return void + */ + private function enableCache() { try { $types = $this->getRequest()->getParam('types'); @@ -40,9 +66,20 @@ class MassEnable extends \Magento\Backend\Controller\Adminhtml\Cache } catch (\Exception $e) { $this->messageManager->addException($e, __('An error occurred while enabling cache.')); } + } + + /** + * Get State Instance + * + * @return State + * @deprecated + */ + private function getState() + { + if ($this->state === null) { + $this->state = ObjectManager::getInstance()->get(State::class); + } - /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */ - $resultRedirect = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT); - return $resultRedirect->setPath('adminhtml/*'); + return $this->state; } } diff --git a/app/code/Magento/Backend/Test/Unit/Block/Widget/Grid/MassactionTest.php b/app/code/Magento/Backend/Test/Unit/Block/Widget/Grid/MassactionTest.php index 234e9d857549e073cb2fdba1bc39b3e4c7248349..79ecb388873eba21ed9fbfd166a651987194aa52 100644 --- a/app/code/Magento/Backend/Test/Unit/Block/Widget/Grid/MassactionTest.php +++ b/app/code/Magento/Backend/Test/Unit/Block/Widget/Grid/MassactionTest.php @@ -9,6 +9,8 @@ */ namespace Magento\Backend\Test\Unit\Block\Widget\Grid; +use Magento\Backend\Block\Widget\Grid\Massaction\VisibilityCheckerInterface as VisibilityChecker; + class MassactionTest extends \PHPUnit_Framework_TestCase { /** @@ -17,12 +19,12 @@ class MassactionTest extends \PHPUnit_Framework_TestCase protected $_block; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var \Magento\Framework\View\Layout|\PHPUnit_Framework_MockObject_MockObject */ protected $_layoutMock; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var \Magento\Backend\Block\Widget\Grid|\PHPUnit_Framework_MockObject_MockObject */ protected $_gridMock; @@ -32,63 +34,63 @@ class MassactionTest extends \PHPUnit_Framework_TestCase protected $_eventManagerMock; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var \Magento\Backend\Model\Url|\PHPUnit_Framework_MockObject_MockObject */ protected $_urlModelMock; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var \Magento\Framework\App\Request\Http|\PHPUnit_Framework_MockObject_MockObject */ protected $_requestMock; + /** + * @var VisibilityChecker|\PHPUnit_Framework_MockObject_MockObject + */ + private $visibilityCheckerMock; + protected function setUp() { - $this->_gridMock = $this->getMock( - \Magento\Backend\Block\Widget\Grid::class, - ['getId', 'getCollection'], - [], - '', - false - ); - $this->_gridMock->expects($this->any())->method('getId')->will($this->returnValue('test_grid')); - - $this->_layoutMock = $this->getMock( - \Magento\Framework\View\Layout::class, - ['getParentName', 'getBlock', 'helper'], - [], - '', - false, - false - ); + $this->_gridMock = $this->getMockBuilder(\Magento\Backend\Block\Widget\Grid::class) + ->disableOriginalConstructor() + ->disableOriginalClone() + ->setMethods(['getId', 'getCollection']) + ->getMock(); + $this->_gridMock->expects($this->any()) + ->method('getId') + ->willReturn('test_grid'); - $this->_layoutMock->expects( - $this->any() - )->method( - 'getParentName' - )->with( - 'test_grid_massaction' - )->will( - $this->returnValue('test_grid') - ); - $this->_layoutMock->expects( - $this->any() - )->method( - 'getBlock' - )->with( - 'test_grid' - )->will( - $this->returnValue($this->_gridMock) - ); + $this->_layoutMock = $this->getMockBuilder(\Magento\Framework\View\Layout::class) + ->disableOriginalConstructor() + ->disableOriginalClone() + ->setMethods(['getParentName', 'getBlock', 'helper']) + ->getMock(); + $this->_layoutMock->expects($this->any()) + ->method('getParentName') + ->with('test_grid_massaction') + ->willReturn('test_grid'); + $this->_layoutMock->expects($this->any()) + ->method('getBlock') + ->with('test_grid') + ->willReturn($this->_gridMock); + + $this->_requestMock = $this->getMockBuilder(\Magento\Framework\App\Request\Http::class) + ->disableOriginalConstructor() + ->disableOriginalClone() + ->getMock(); - $this->_requestMock = $this->getMock(\Magento\Framework\App\Request\Http::class, [], [], '', false); + $this->_urlModelMock = $this->getMockBuilder(\Magento\Backend\Model\Url::class) + ->disableOriginalConstructor() + ->disableOriginalClone() + ->getMock(); - $this->_urlModelMock = $this->getMock(\Magento\Backend\Model\Url::class, [], [], '', false); + $this->visibilityCheckerMock = $this->getMockBuilder(VisibilityChecker::class) + ->getMockForAbstractClass(); $arguments = [ 'layout' => $this->_layoutMock, 'request' => $this->_requestMock, 'urlBuilder' => $this->_urlModelMock, - 'data' => ['massaction_id_field' => 'test_id', 'massaction_id_filter' => 'test_id'], + 'data' => ['massaction_id_field' => 'test_id', 'massaction_id_filter' => 'test_id'] ]; $objectManagerHelper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); @@ -124,26 +126,24 @@ class MassactionTest extends \PHPUnit_Framework_TestCase } /** - * @param $itemId - * @param $item + * @param string $itemId + * @param \Magento\Framework\DataObject $item * @param $expectedItem \Magento\Framework\DataObject - * @dataProvider itemsDataProvider + * @dataProvider itemsProcessingDataProvider */ public function testItemsProcessing($itemId, $item, $expectedItem) { - $this->_urlModelMock->expects( - $this->any() - )->method( - 'getBaseUrl' - )->will( - $this->returnValue('http://localhost/index.php') - ); + $this->_urlModelMock->expects($this->any()) + ->method('getBaseUrl') + ->willReturn('http://localhost/index.php'); $urlReturnValueMap = [ ['*/*/test1', [], 'http://localhost/index.php/backend/admin/test/test1'], ['*/*/test2', [], 'http://localhost/index.php/backend/admin/test/test2'], ]; - $this->_urlModelMock->expects($this->any())->method('getUrl')->will($this->returnValueMap($urlReturnValueMap)); + $this->_urlModelMock->expects($this->any()) + ->method('getUrl') + ->willReturnMap($urlReturnValueMap); $this->_block->addItem($itemId, $item); $this->assertEquals(1, $this->_block->getCount()); @@ -157,7 +157,10 @@ class MassactionTest extends \PHPUnit_Framework_TestCase $this->assertNull($this->_block->getItem($itemId)); } - public function itemsDataProvider() + /** + * @return array + */ + public function itemsProcessingDataProvider() { return [ [ @@ -186,22 +189,17 @@ class MassactionTest extends \PHPUnit_Framework_TestCase } /** - * @param $param - * @param $expectedJson - * @param $expected + * @param string $param + * @param string $expectedJson + * @param array $expected * @dataProvider selectedDataProvider */ public function testSelected($param, $expectedJson, $expected) { - $this->_requestMock->expects( - $this->any() - )->method( - 'getParam' - )->with( - $this->_block->getFormFieldNameInternal() - )->will( - $this->returnValue($param) - ); + $this->_requestMock->expects($this->any()) + ->method('getParam') + ->with($this->_block->getFormFieldNameInternal()) + ->willReturn($param); $this->assertEquals($expectedJson, $this->_block->getSelectedJson()); $this->assertEquals($expected, $this->_block->getSelected()); @@ -262,6 +260,9 @@ class MassactionTest extends \PHPUnit_Framework_TestCase $this->assertEquals($result, $this->_block->getGridIdsJson()); } + /** + * @return array + */ public function dataProviderGetGridIdsJsonWithUseSelectAll() { return [ @@ -279,4 +280,71 @@ class MassactionTest extends \PHPUnit_Framework_TestCase ], ]; } + + /** + * @param string $itemId + * @param array|\Magento\Framework\DataObject $item + * @param int $count + * @param bool $withVisibilityChecker + * @param bool $isVisible + * @dataProvider addItemDataProvider + */ + public function testAddItem($itemId, $item, $count, $withVisibilityChecker, $isVisible) + { + $this->visibilityCheckerMock->expects($this->any()) + ->method('isVisible') + ->willReturn($isVisible); + + if ($withVisibilityChecker) { + $item['visible'] = $this->visibilityCheckerMock; + } + + $urlReturnValueMap = [ + ['*/*/test1', [], 'http://localhost/index.php/backend/admin/test/test1'], + ['*/*/test2', [], 'http://localhost/index.php/backend/admin/test/test2'], + ]; + $this->_urlModelMock->expects($this->any()) + ->method('getUrl') + ->willReturnMap($urlReturnValueMap); + + $this->_block->addItem($itemId, $item); + $this->assertEquals($count, $this->_block->getCount()); + } + + /** + * @return array + */ + public function addItemDataProvider() + { + return [ + [ + 'itemId' => 'test1', + 'item' => ['label' => 'Test 1', 'url' => '*/*/test1'], + 'count' => 1, + 'withVisibilityChecker' => false, + '$isVisible' => false, + ], + [ + 'itemId' => 'test2', + 'item' => ['label' => 'Test 2', 'url' => '*/*/test2'], + 'count' => 1, + 'withVisibilityChecker' => false, + 'isVisible' => true, + ], + [ + 'itemId' => 'test1', + 'item' => ['label' => 'Test 1. Hide', 'url' => '*/*/test1'], + 'count' => 0, + 'withVisibilityChecker' => true, + 'isVisible' => false, + ], + [ + 'itemId' => 'test2', + 'item' => ['label' => 'Test 2. Does not hide', 'url' => '*/*/test2'], + 'count' => 1, + 'withVisibilityChecker' => true, + 'isVisible' => true, + ] + ]; + } } diff --git a/app/code/Magento/Backend/Test/Unit/Controller/Adminhtml/Cache/MassDisableTest.php b/app/code/Magento/Backend/Test/Unit/Controller/Adminhtml/Cache/MassDisableTest.php new file mode 100644 index 0000000000000000000000000000000000000000..b2fc808b0e237b8bbeb5b323fb173ecff67d8071 --- /dev/null +++ b/app/code/Magento/Backend/Test/Unit/Controller/Adminhtml/Cache/MassDisableTest.php @@ -0,0 +1,224 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Backend\Test\Unit\Controller\Adminhtml\Cache; + +use PHPUnit_Framework_MockObject_MockObject as MockObject; +use Magento\Backend\Controller\Adminhtml\Cache\MassDisable; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; +use Magento\Framework\App\State; +use Magento\Backend\App\Action\Context; +use Magento\Framework\Message\ManagerInterface as MessageManager; +use Magento\Framework\Controller\ResultFactory; +use Magento\Backend\Model\View\Result\Redirect; +use Magento\Framework\App\RequestInterface as Request; +use Magento\Framework\App\Cache\TypeListInterface as CacheTypeList; +use Magento\Framework\App\Cache\StateInterface as CacheState; + +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class MassDisableTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var MassDisable + */ + private $controller; + + /** + * @var State|MockObject + */ + private $stateMock; + + /** + * @var MessageManager|MockObject + */ + private $messageManagerMock; + + /** + * @var Redirect|MockObject + */ + private $redirectMock; + + /** + * @var Request|MockObject + */ + private $requestMock; + + /** + * @var CacheTypeList|MockObject + */ + private $cacheTypeListMock; + + /** + * @var CacheState|MockObject + */ + private $cacheStateMock; + + protected function setUp() + { + $objectManagerHelper = new ObjectManagerHelper($this); + + $this->stateMock = $this->getMockBuilder(State::class) + ->disableOriginalConstructor() + ->disableOriginalClone() + ->getMock(); + + $this->messageManagerMock = $this->getMockBuilder(MessageManager::class) + ->getMockForAbstractClass(); + + $this->requestMock = $this->getMockBuilder(Request::class) + ->getMockForAbstractClass(); + + $this->cacheTypeListMock = $this->getMockBuilder(CacheTypeList::class) + ->getMockForAbstractClass(); + + $this->cacheStateMock = $this->getMockBuilder(CacheState::class) + ->getMockForAbstractClass(); + + $this->redirectMock = $this->getMockBuilder(Redirect::class) + ->disableOriginalConstructor() + ->disableOriginalClone() + ->getMock(); + $this->redirectMock->expects($this->once()) + ->method('setPath') + ->with('adminhtml/*') + ->willReturnSelf(); + $resultFactoryMock = $this->getMockBuilder(ResultFactory::class) + ->disableOriginalConstructor() + ->disableOriginalClone() + ->getMock(); + $resultFactoryMock->expects($this->once()) + ->method('create') + ->with(ResultFactory::TYPE_REDIRECT) + ->willReturn($this->redirectMock); + + $contextMock = $this->getMockBuilder(Context::class) + ->disableOriginalConstructor() + ->disableOriginalClone() + ->getMock(); + $contextMock->expects($this->once()) + ->method('getMessageManager') + ->willReturn($this->messageManagerMock); + $contextMock->expects($this->once()) + ->method('getResultFactory') + ->willReturn($resultFactoryMock); + $contextMock->expects($this->once()) + ->method('getRequest') + ->willReturn($this->requestMock); + + $this->controller = $objectManagerHelper->getObject( + MassDisable::class, + [ + 'context' => $contextMock, + 'cacheTypeList' => $this->cacheTypeListMock, + 'cacheState' => $this->cacheStateMock + ] + ); + $objectManagerHelper->setBackwardCompatibleProperty($this->controller, 'state', $this->stateMock); + } + + public function testExecuteInProductionMode() + { + $this->stateMock->expects($this->once()) + ->method('getMode') + ->willReturn(State::MODE_PRODUCTION); + + $this->messageManagerMock->expects($this->once()) + ->method('addErrorMessage') + ->with('You can\'t change status of cache type(s) in production mode', null) + ->willReturnSelf(); + + $this->assertSame($this->redirectMock, $this->controller->execute()); + } + + public function testExecuteInvalidTypeCache() + { + $this->stateMock->expects($this->once()) + ->method('getMode') + ->willReturn(State::MODE_DEVELOPER); + + $this->cacheTypeListMock->expects($this->once()) + ->method('getTypes') + ->willReturn([ + 'pageCache' => [ + 'id' => 'pageCache', + 'label' => 'Cache of Page' + ] + ]); + + $this->requestMock->expects($this->once()) + ->method('getParam') + ->with('types') + ->willReturn(['someCache']); + + $this->messageManagerMock->expects($this->once()) + ->method('addError') + ->with('Specified cache type(s) don\'t exist: someCache') + ->willReturnSelf(); + + $this->assertSame($this->redirectMock, $this->controller->execute()); + } + + public function testExecuteWithException() + { + $exception = new \Exception(); + + $this->stateMock->expects($this->once()) + ->method('getMode') + ->willReturn(State::MODE_DEVELOPER); + + $this->requestMock->expects($this->once()) + ->method('getParam') + ->willThrowException($exception); + + $this->messageManagerMock->expects($this->once()) + ->method('addException') + ->with($exception, 'An error occurred while disabling cache.') + ->willReturnSelf(); + + $this->assertSame($this->redirectMock, $this->controller->execute()); + } + + public function testExecuteSuccess() + { + $cacheType = 'pageCache'; + + $this->stateMock->expects($this->once()) + ->method('getMode') + ->willReturn(State::MODE_DEVELOPER); + + $this->cacheTypeListMock->expects($this->once()) + ->method('getTypes') + ->willReturn([ + 'pageCache' => [ + 'id' => 'pageCache', + 'label' => 'Cache of Page' + ] + ]); + + $this->requestMock->expects($this->once()) + ->method('getParam') + ->with('types') + ->willReturn([$cacheType]); + + $this->cacheStateMock->expects($this->once()) + ->method('isEnabled') + ->with($cacheType) + ->willReturn(true); + $this->cacheStateMock->expects($this->once()) + ->method('setEnabled') + ->with($cacheType, false); + $this->cacheStateMock->expects($this->once()) + ->method('persist'); + + $this->messageManagerMock->expects($this->once()) + ->method('addSuccess') + ->with('1 cache type(s) disabled.') + ->willReturnSelf(); + + $this->assertSame($this->redirectMock, $this->controller->execute()); + } +} diff --git a/app/code/Magento/Backend/Test/Unit/Controller/Adminhtml/Cache/MassEnableTest.php b/app/code/Magento/Backend/Test/Unit/Controller/Adminhtml/Cache/MassEnableTest.php new file mode 100644 index 0000000000000000000000000000000000000000..8c1b9f1718ab924379cf3e5440a942c0be5502d7 --- /dev/null +++ b/app/code/Magento/Backend/Test/Unit/Controller/Adminhtml/Cache/MassEnableTest.php @@ -0,0 +1,224 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Backend\Test\Unit\Controller\Adminhtml\Cache; + +use PHPUnit_Framework_MockObject_MockObject as MockObject; +use Magento\Backend\Controller\Adminhtml\Cache\MassEnable; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; +use Magento\Framework\App\State; +use Magento\Backend\App\Action\Context; +use Magento\Framework\Message\ManagerInterface as MessageManager; +use Magento\Framework\Controller\ResultFactory; +use Magento\Backend\Model\View\Result\Redirect; +use Magento\Framework\App\RequestInterface as Request; +use Magento\Framework\App\Cache\TypeListInterface as CacheTypeList; +use Magento\Framework\App\Cache\StateInterface as CacheState; + +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class MassEnableTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var MassEnable + */ + private $controller; + + /** + * @var State|MockObject + */ + private $stateMock; + + /** + * @var MessageManager|MockObject + */ + private $messageManagerMock; + + /** + * @var Redirect|MockObject + */ + private $redirectMock; + + /** + * @var Request|MockObject + */ + private $requestMock; + + /** + * @var CacheTypeList|MockObject + */ + private $cacheTypeListMock; + + /** + * @var CacheState|MockObject + */ + private $cacheStateMock; + + protected function setUp() + { + $objectManagerHelper = new ObjectManagerHelper($this); + + $this->stateMock = $this->getMockBuilder(State::class) + ->disableOriginalConstructor() + ->disableOriginalClone() + ->getMock(); + + $this->messageManagerMock = $this->getMockBuilder(MessageManager::class) + ->getMockForAbstractClass(); + + $this->requestMock = $this->getMockBuilder(Request::class) + ->getMockForAbstractClass(); + + $this->cacheTypeListMock = $this->getMockBuilder(CacheTypeList::class) + ->getMockForAbstractClass(); + + $this->cacheStateMock = $this->getMockBuilder(CacheState::class) + ->getMockForAbstractClass(); + + $this->redirectMock = $this->getMockBuilder(Redirect::class) + ->disableOriginalConstructor() + ->disableOriginalClone() + ->getMock(); + $this->redirectMock->expects($this->once()) + ->method('setPath') + ->with('adminhtml/*') + ->willReturnSelf(); + $resultFactoryMock = $this->getMockBuilder(ResultFactory::class) + ->disableOriginalConstructor() + ->disableOriginalClone() + ->getMock(); + $resultFactoryMock->expects($this->once()) + ->method('create') + ->with(ResultFactory::TYPE_REDIRECT) + ->willReturn($this->redirectMock); + + $contextMock = $this->getMockBuilder(Context::class) + ->disableOriginalConstructor() + ->disableOriginalClone() + ->getMock(); + $contextMock->expects($this->once()) + ->method('getMessageManager') + ->willReturn($this->messageManagerMock); + $contextMock->expects($this->once()) + ->method('getResultFactory') + ->willReturn($resultFactoryMock); + $contextMock->expects($this->once()) + ->method('getRequest') + ->willReturn($this->requestMock); + + $this->controller = $objectManagerHelper->getObject( + MassEnable::class, + [ + 'context' => $contextMock, + 'cacheTypeList' => $this->cacheTypeListMock, + 'cacheState' => $this->cacheStateMock + ] + ); + $objectManagerHelper->setBackwardCompatibleProperty($this->controller, 'state', $this->stateMock); + } + + public function testExecuteInProductionMode() + { + $this->stateMock->expects($this->once()) + ->method('getMode') + ->willReturn(State::MODE_PRODUCTION); + + $this->messageManagerMock->expects($this->once()) + ->method('addErrorMessage') + ->with('You can\'t change status of cache type(s) in production mode', null) + ->willReturnSelf(); + + $this->assertSame($this->redirectMock, $this->controller->execute()); + } + + public function testExecuteInvalidTypeCache() + { + $this->stateMock->expects($this->once()) + ->method('getMode') + ->willReturn(State::MODE_DEVELOPER); + + $this->cacheTypeListMock->expects($this->once()) + ->method('getTypes') + ->willReturn([ + 'pageCache' => [ + 'id' => 'pageCache', + 'label' => 'Cache of Page' + ] + ]); + + $this->requestMock->expects($this->once()) + ->method('getParam') + ->with('types') + ->willReturn(['someCache']); + + $this->messageManagerMock->expects($this->once()) + ->method('addError') + ->with('Specified cache type(s) don\'t exist: someCache') + ->willReturnSelf(); + + $this->assertSame($this->redirectMock, $this->controller->execute()); + } + + public function testExecuteWithException() + { + $exception = new \Exception(); + + $this->stateMock->expects($this->once()) + ->method('getMode') + ->willReturn(State::MODE_DEVELOPER); + + $this->requestMock->expects($this->once()) + ->method('getParam') + ->willThrowException($exception); + + $this->messageManagerMock->expects($this->once()) + ->method('addException') + ->with($exception, 'An error occurred while enabling cache.') + ->willReturnSelf(); + + $this->assertSame($this->redirectMock, $this->controller->execute()); + } + + public function testExecuteSuccess() + { + $cacheType = 'pageCache'; + + $this->stateMock->expects($this->once()) + ->method('getMode') + ->willReturn(State::MODE_DEVELOPER); + + $this->cacheTypeListMock->expects($this->once()) + ->method('getTypes') + ->willReturn([ + 'pageCache' => [ + 'id' => 'pageCache', + 'label' => 'Cache of Page' + ] + ]); + + $this->requestMock->expects($this->once()) + ->method('getParam') + ->with('types') + ->willReturn([$cacheType]); + + $this->cacheStateMock->expects($this->once()) + ->method('isEnabled') + ->with($cacheType) + ->willReturn(false); + $this->cacheStateMock->expects($this->once()) + ->method('setEnabled') + ->with($cacheType, true); + $this->cacheStateMock->expects($this->once()) + ->method('persist'); + + $this->messageManagerMock->expects($this->once()) + ->method('addSuccess') + ->with('1 cache type(s) enabled.') + ->willReturnSelf(); + + $this->assertSame($this->redirectMock, $this->controller->execute()); + } +} diff --git a/app/code/Magento/Backend/etc/di.xml b/app/code/Magento/Backend/etc/di.xml index 8b52d08da48fbbd8fd1cf450bb8a2db66ca75b62..c0c5a0ec5b8a7ff217fcf0bb1772cb932f14bc55 100644 --- a/app/code/Magento/Backend/etc/di.xml +++ b/app/code/Magento/Backend/etc/di.xml @@ -213,4 +213,22 @@ </argument> </arguments> </type> + <type name="Magento\Config\Model\Config\Export\ExcludeList"> + <arguments> + <argument name="configs" xsi:type="array"> + <item name="trans_email/ident_general/name" xsi:type="string">1</item> + <item name="trans_email/ident_general/email" xsi:type="string">1</item> + <item name="trans_email/ident_sales/name" xsi:type="string">1</item> + <item name="trans_email/ident_sales/email" xsi:type="string">1</item> + <item name="trans_email/ident_support/name" xsi:type="string">1</item> + <item name="trans_email/ident_support/email" xsi:type="string">1</item> + <item name="trans_email/ident_custom1/name" xsi:type="string">1</item> + <item name="trans_email/ident_custom1/email" xsi:type="string">1</item> + <item name="trans_email/ident_custom2/name" xsi:type="string">1</item> + <item name="trans_email/ident_custom2/email" xsi:type="string">1</item> + <item name="admin/url/custom" xsi:type="string">1</item> + <item name="admin/url/custom_path" xsi:type="string">1</item> + </argument> + </arguments> + </type> </config> diff --git a/app/code/Magento/Backend/view/adminhtml/layout/adminhtml_cache_block.xml b/app/code/Magento/Backend/view/adminhtml/layout/adminhtml_cache_block.xml index decc26f331c8298d16b4a76d4a74d82c5808c651..98f9ca89ba18accc9013c7b0851e39abafa517b2 100644 --- a/app/code/Magento/Backend/view/adminhtml/layout/adminhtml_cache_block.xml +++ b/app/code/Magento/Backend/view/adminhtml/layout/adminhtml_cache_block.xml @@ -23,10 +23,12 @@ <item name="enable" xsi:type="array"> <item name="label" xsi:type="string" translate="true">Enable</item> <item name="url" xsi:type="string">adminhtml/*/massEnable</item> + <item name="visible" xsi:type="object">Magento\Backend\Block\Cache\Grid\Massaction\ProductionModeVisibilityChecker</item> </item> <item name="disable" xsi:type="array"> <item name="label" xsi:type="string" translate="true">Disable</item> <item name="url" xsi:type="string">adminhtml/*/massDisable</item> + <item name="visible" xsi:type="object">Magento\Backend\Block\Cache\Grid\Massaction\ProductionModeVisibilityChecker</item> </item> <item name="refresh" xsi:type="array"> <item name="label" xsi:type="string" translate="true">Refresh</item> diff --git a/app/code/Magento/Braintree/etc/di.xml b/app/code/Magento/Braintree/etc/di.xml index d051ef78cfcd22424fa359e47be3efc0ee8dcc2b..5417c96ba677204a0c3c3ffc69cefdc27f148ee3 100644 --- a/app/code/Magento/Braintree/etc/di.xml +++ b/app/code/Magento/Braintree/etc/di.xml @@ -365,7 +365,7 @@ </arguments> </virtualType> <!-- END PayPal commands --> - + <!-- Value handlers infrastructure --> <type name="Magento\Braintree\Gateway\Response\VaultDetailsHandler"> <arguments> @@ -452,7 +452,7 @@ </arguments> </virtualType> <!-- END PayPal value handlers infrastructure --> - + <!-- Void Command --> <virtualType name="BraintreeVoidCommand" type="Magento\Payment\Gateway\Command\GatewayCommand"> <arguments> @@ -544,4 +544,16 @@ </arguments> </type> <!-- END Settlement Report Section --> + <type name="Magento\Config\Model\Config\Export\ExcludeList"> + <arguments> + <argument name="configs" xsi:type="array"> + <item name="payment/braintree/merchant_id" xsi:type="string">1</item> + <item name="payment/braintree/public_key" xsi:type="string">1</item> + <item name="payment/braintree/private_key" xsi:type="string">1</item> + <item name="payment/braintree/merchant_account_id" xsi:type="string">1</item> + <item name="payment/braintree/kount_id" xsi:type="string">1</item> + <item name="payment/braintree_paypal/merchant_name_override" xsi:type="string">1</item> + </argument> + </arguments> + </type> </config> diff --git a/app/code/Magento/Checkout/etc/di.xml b/app/code/Magento/Checkout/etc/di.xml index a2243b33a04edc0af4dc71ed79b802eac68f4cc2..81a430d52c49933cf70013f8856a96f5b6c26377 100644 --- a/app/code/Magento/Checkout/etc/di.xml +++ b/app/code/Magento/Checkout/etc/di.xml @@ -42,4 +42,11 @@ </argument> </arguments> </type> + <type name="Magento\Config\Model\Config\Export\ExcludeList"> + <arguments> + <argument name="configs" xsi:type="array"> + <item name="checkout/payment_failed/copy_to" xsi:type="string">1</item> + </argument> + </arguments> + </type> </config> diff --git a/app/code/Magento/Config/App/Config/Source/DumpConfigSourceAggregated.php b/app/code/Magento/Config/App/Config/Source/DumpConfigSourceAggregated.php new file mode 100644 index 0000000000000000000000000000000000000000..80567d0504ee9c31d8cadef957c1d12dad9f1508 --- /dev/null +++ b/app/code/Magento/Config/App/Config/Source/DumpConfigSourceAggregated.php @@ -0,0 +1,152 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Config\App\Config\Source; + +use Magento\Config\Model\Config\Export\ExcludeList; +use Magento\Framework\App\Config\ConfigSourceInterface; +use Magento\Framework\App\Config\ScopeConfigInterface; + +/** + * Class DumpConfigSourceAggregated aggregates configurations from all available sources + */ +class DumpConfigSourceAggregated implements DumpConfigSourceInterface +{ + /** + * @var ExcludeList + */ + private $excludeList; + + /** + * @var ConfigSourceInterface[] + */ + private $sources; + + /** + * @var array + */ + private $excludedFields; + + /** + * @var array + */ + private $data; + + /** + * @param ExcludeList $excludeList + * @param array $sources + */ + public function __construct(ExcludeList $excludeList, array $sources = []) + { + $this->excludeList = $excludeList; + $this->sources = $sources; + } + + /** + * Retrieve aggregated configuration from all available sources. + * + * @param string $path + * @return array + */ + public function get($path = '') + { + $path = (string)$path; + $data = []; + + if (isset($this->data[$path])) { + return $this->data[$path]; + } + + $this->sortSources(); + + foreach ($this->sources as $sourceConfig) { + /** @var ConfigSourceInterface $source */ + $source = $sourceConfig['source']; + $data = array_replace_recursive($data, $source->get($path)); + } + + $this->excludedFields = []; + $this->filterChain($path, $data); + + return $this->data[$path] = $data; + } + + /** + * Recursive filtering of sensitive data + * + * @param string $path + * @param array $data + * @return void + */ + private function filterChain($path, &$data) + { + foreach ($data as $subKey => &$subData) { + $newPath = $path ? $path . '/' . $subKey : $subKey; + $filteredPath = $this->filterPath($newPath); + + if ( + $filteredPath + && !is_array($data[$subKey]) + && $this->excludeList->isPresent($filteredPath) + ) { + $this->excludedFields[$newPath] = $filteredPath; + + unset($data[$subKey]); + } elseif (is_array($subData)) { + $this->filterChain($newPath, $subData); + } + } + } + + /** + * Eliminating scope info from path + * + * @param string $path + * @return null|string + */ + private function filterPath($path) + { + $parts = explode('/', $path); + + // Check if there are enough parts to recognize scope + if (count($parts) < 3) { + return null; + } + + if ($parts[0] === ScopeConfigInterface::SCOPE_TYPE_DEFAULT) { + unset($parts[0]); + } else { + unset($parts[0], $parts[1]); + } + + return implode('/', $parts); + } + + /** + * Sort sources ASC from higher priority to lower + * + * @return void + */ + private function sortSources() + { + uasort($this->sources, function ($firstItem, $secondItem) { + return $firstItem['sortOrder'] > $secondItem['sortOrder']; + }); + } + + /** + * Retrieves list of field paths were excluded from config dump + * @return array + */ + public function getExcludedFields() + { + $this->get(); + + $fields = array_values($this->excludedFields); + $fields = array_unique($fields); + + return $fields; + } +} diff --git a/app/code/Magento/Config/App/Config/Source/DumpConfigSourceInterface.php b/app/code/Magento/Config/App/Config/Source/DumpConfigSourceInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..cf0ce492b7d5265a5445a3fbe3f4e6d68ec384a4 --- /dev/null +++ b/app/code/Magento/Config/App/Config/Source/DumpConfigSourceInterface.php @@ -0,0 +1,21 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Config\App\Config\Source; + +use Magento\Framework\App\Config\ConfigSourceInterface; + +/** + * Interface DumpConfigSourceInterface + */ +interface DumpConfigSourceInterface extends ConfigSourceInterface +{ + /** + * Retrieves list of field paths were excluded from config dump + * + * @return array + */ + public function getExcludedFields(); +} diff --git a/app/code/Magento/Config/App/Config/Type/System.php b/app/code/Magento/Config/App/Config/Type/System.php index 4a3c6da8379153a240e4869592fcef31788df608..a0fc0f1f10ebdfbbcd78695ee223e48efdd21b7a 100644 --- a/app/code/Magento/Config/App/Config/Type/System.php +++ b/app/code/Magento/Config/App/Config/Type/System.php @@ -6,18 +6,10 @@ namespace Magento\Config\App\Config\Type; use Magento\Framework\App\Config\ConfigTypeInterface; -use Magento\Framework\App\Config\ConfigSourceInterface; -use Magento\Framework\App\Config\Spi\PostProcessorInterface; -use Magento\Framework\Cache\FrontendInterface; use Magento\Framework\DataObject; -use Magento\Framework\Serialize\Serializer\Serialize; -use Magento\Framework\Serialize\SerializerInterface; -use Magento\Store\Model\Config\Processor\Fallback; /** * Class process source, cache them and retrieve value by path - * - * @package Magento\Config\App\Config\Type */ class System implements ConfigTypeInterface { @@ -26,7 +18,7 @@ class System implements ConfigTypeInterface const CONFIG_TYPE = 'system'; /** - * @var ConfigSourceInterface + * @var \Magento\Framework\App\Config\ConfigSourceInterface */ private $source; @@ -36,12 +28,17 @@ class System implements ConfigTypeInterface private $data; /** - * @var PostProcessorInterface + * @var \Magento\Framework\App\Config\Spi\PostProcessorInterface */ private $postProcessor; /** - * @var FrontendInterface + * @var \Magento\Framework\App\Config\Spi\PreProcessorInterface + */ + private $preProcessor; + + /** + * @var \Magento\Framework\Cache\FrontendInterface */ private $cache; @@ -51,34 +48,36 @@ class System implements ConfigTypeInterface private $cachingNestedLevel; /** - * @var Fallback + * @var \Magento\Store\Model\Config\Processor\Fallback */ private $fallback; /** - * @var Serialize + * @var \Magento\Framework\Serialize\SerializerInterface */ private $serializer; /** - * System constructor. - * @param ConfigSourceInterface $source - * @param PostProcessorInterface $postProcessor - * @param Fallback $fallback - * @param FrontendInterface $cache + * @param \Magento\Framework\App\Config\ConfigSourceInterface $source + * @param \Magento\Framework\App\Config\Spi\PostProcessorInterface $postProcessor + * @param \Magento\Store\Model\Config\Processor\Fallback $fallback + * @param \Magento\Framework\Cache\FrontendInterface $cache + * @param \Magento\Framework\Serialize\SerializerInterface $serializer + * @param \Magento\Framework\App\Config\Spi\PreProcessorInterface $preProcessor * @param int $cachingNestedLevel - * @param Serialize $serializer */ public function __construct( - ConfigSourceInterface $source, - PostProcessorInterface $postProcessor, - Fallback $fallback, - FrontendInterface $cache, - Serialize $serializer, + \Magento\Framework\App\Config\ConfigSourceInterface $source, + \Magento\Framework\App\Config\Spi\PostProcessorInterface $postProcessor, + \Magento\Store\Model\Config\Processor\Fallback $fallback, + \Magento\Framework\Cache\FrontendInterface $cache, + \Magento\Framework\Serialize\SerializerInterface $serializer, + \Magento\Framework\App\Config\Spi\PreProcessorInterface $preProcessor, $cachingNestedLevel = 1 ) { $this->source = $source; $this->postProcessor = $postProcessor; + $this->preProcessor = $preProcessor; $this->cache = $cache; $this->cachingNestedLevel = $cachingNestedLevel; $this->fallback = $fallback; @@ -96,7 +95,9 @@ class System implements ConfigTypeInterface if (!$this->data) { $data = $this->cache->load(self::CONFIG_TYPE); if (!$data) { - $data = $this->fallback->process($this->source->get()); + $data = $this->preProcessor->process($this->source->get()); + $this->data = new DataObject($data); + $data = $this->fallback->process($data); $this->data = new DataObject($data); //Placeholder processing need system config - so we need to save intermediate result $data = $this->postProcessor->process($data); diff --git a/app/code/Magento/Config/Block/System/Config/Form.php b/app/code/Magento/Config/Block/System/Config/Form.php index d1a0da2a700a3f628c83ac2d61029bc915cd3ba6..f0ad7e4a28b1a365b6596f7175ae34434e09e59c 100644 --- a/app/code/Magento/Config/Block/System/Config/Form.php +++ b/app/code/Magento/Config/Block/System/Config/Form.php @@ -7,8 +7,10 @@ namespace Magento\Config\Block\System\Config; use Magento\Config\App\Config\Type\System; use Magento\Config\Model\Config\Reader\Source\Deployed\SettingChecker; +use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Framework\App\DeploymentConfig; use Magento\Framework\App\ObjectManager; +use Magento\Framework\DataObject; /** * System config form block @@ -331,29 +333,9 @@ class Form extends \Magento\Backend\Block\Widget\Form\Generic $fieldPrefix = '', $labelPrefix = '' ) { - $inherit = true; - $data = $this->getAppConfigDataValue($path); - if ($data === null) { - if (array_key_exists($path, $this->_configData)) { - $data = $this->_configData[$path]; - $inherit = false; + $inherit = !array_key_exists($path, $this->_configData); + $data = $this->getFieldData($field, $path); - if ($field->hasBackendModel()) { - $backendModel = $field->getBackendModel(); - $backendModel->setPath($path) - ->setValue($data) - ->setWebsite($this->getWebsiteCode()) - ->setStore($this->getStoreCode()) - ->afterLoad(); - $data = $backendModel->getValue(); - } - - } elseif ($field->getConfigPath() !== null) { - $data = $this->getConfigValue($field->getConfigPath()); - } else { - $data = $this->getConfigValue($path); - } - } $fieldRendererClass = $field->getFrontendModel(); if ($fieldRendererClass) { $fieldRenderer = $this->_layout->getBlockSingleton($fieldRendererClass); @@ -373,9 +355,7 @@ class Form extends \Magento\Backend\Block\Widget\Form\Generic $sharedClass = $this->_getSharedCssClass($field); $requiresClass = $this->_getRequiresCssClass($field, $fieldPrefix); - $isReadOnly = $this->getSettingChecker()->isReadOnly($path, $this->getScope(), $this->getScopeCode()); - $canUseDefault = $this->canUseDefaultValue($field->showInDefault()); - $canUseWebsite = $this->canUseWebsiteValue($field->showInWebsite()); + $isReadOnly = $this->getSettingChecker()->isReadOnly($path, $this->getScope(), $this->getStringScopeCode()); $formField = $fieldset->addField( $elementId, $field->getType(), @@ -392,8 +372,8 @@ class Form extends \Magento\Backend\Block\Widget\Form\Generic 'scope' => $this->getScope(), 'scope_id' => $this->getScopeId(), 'scope_label' => $this->getScopeLabel($field), - 'can_use_default_value' => $canUseDefault, - 'can_use_website_value' => $canUseWebsite, + 'can_use_default_value' => $this->canUseDefaultValue($field->showInDefault()), + 'can_use_website_value' => $this->canUseWebsiteValue($field->showInWebsite()), 'can_restore_to_default' => $this->isCanRestoreToDefault($field->canRestore()), 'disabled' => $isReadOnly, 'is_disable_inheritance' => $isReadOnly @@ -413,6 +393,74 @@ class Form extends \Magento\Backend\Block\Widget\Form\Generic $formField->setRenderer($fieldRenderer); } + /** + * Get data of field by path + * + * @param \Magento\Config\Model\Config\Structure\Element\Field $field + * @param string $path + * @return mixed|null|string + */ + private function getFieldData(\Magento\Config\Model\Config\Structure\Element\Field $field, $path) + { + $data = $this->getAppConfigDataValue($path); + + $placeholderValue = $this->getSettingChecker()->getPlaceholderValue( + $path, + $this->getScope(), + $this->getStringScopeCode() + ); + + if ($placeholderValue) { + $data = $placeholderValue; + } + if ($data === null) { + if (array_key_exists($path, $this->_configData)) { + $data = $this->_configData[$path]; + + if ($field->hasBackendModel()) { + $backendModel = $field->getBackendModel(); + $backendModel->setPath($path) + ->setValue($data) + ->setWebsite($this->getWebsiteCode()) + ->setStore($this->getStoreCode()) + ->afterLoad(); + $data = $backendModel->getValue(); + } + + } elseif ($field->getConfigPath() !== null) { + $data = $this->getConfigValue($field->getConfigPath()); + } else { + $data = $this->getConfigValue($path); + } + } + + return $data; + } + + /** + * Retrieve Scope string code + * + * @return string + */ + private function getStringScopeCode() + { + $scopeCode = $this->getData('scope_string_code'); + + if (null === $scopeCode) { + if ($this->getStoreCode()) { + $scopeCode = $this->_storeManager->getStore($this->getStoreCode())->getCode(); + } elseif ($this->getWebsiteCode()) { + $scopeCode = $this->_storeManager->getWebsite($this->getWebsiteCode())->getCode(); + } else { + $scopeCode = ''; + } + + $this->setData('scope_string_code', $scopeCode); + } + + return $scopeCode; + } + /** * Populate dependencies block * @@ -748,14 +796,13 @@ class Form extends \Magento\Backend\Block\Widget\Form\Generic { $appConfig = $this->getAppConfig()->get(System::CONFIG_TYPE); $scope = $this->getScope(); - $scopeId = $this->getScopeId(); - if ($scope === 'default') { - $data = isset($appConfig[$scope][$path]) ? $appConfig[$scope][$path] : null; + $scopeCode = $this->getStringScopeCode(); + + if ($scope === ScopeConfigInterface::SCOPE_TYPE_DEFAULT) { + $data = new DataObject(isset($appConfig[$scope]) ? $appConfig[$scope] : []); } else { - $data = isset($appConfig[$scope][$scopeId][$path]) - ? $appConfig[$scope][$scopeId][$path] - : null; + $data = new DataObject(isset($appConfig[$scope][$scopeCode]) ? $appConfig[$scope][$scopeCode] : []); } - return $data; + return $data->getData($path); } } diff --git a/app/code/Magento/Config/Model/Config/Export/Comment.php b/app/code/Magento/Config/Model/Config/Export/Comment.php new file mode 100644 index 0000000000000000000000000000000000000000..ae0431c82daa031523f464ea1539209ca324996c --- /dev/null +++ b/app/code/Magento/Config/Model/Config/Export/Comment.php @@ -0,0 +1,59 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Config\Model\Config\Export; + +use Magento\Config\App\Config\Source\DumpConfigSourceInterface; +use Magento\Config\Model\Placeholder\PlaceholderFactory; +use Magento\Config\Model\Placeholder\PlaceholderInterface; +use Magento\Framework\App\Config\CommentInterface; + +/** + * Class Comment. Is used to retrieve comment for config dump file + */ +class Comment implements CommentInterface +{ + /** + * @var PlaceholderInterface + */ + private $placeholder; + + /** + * @var DumpConfigSourceInterface + */ + private $source; + + /** + * @param PlaceholderFactory $placeholderFactory + * @param DumpConfigSourceInterface $source + */ + public function __construct( + PlaceholderFactory $placeholderFactory, + DumpConfigSourceInterface $source + ) { + $this->placeholder = $placeholderFactory->create(PlaceholderFactory::TYPE_ENVIRONMENT); + $this->source = $source; + } + + /** + * Retrieves comments for config export file. + * + * @return string + */ + public function get() + { + $comment = ''; + $fields = $this->source->getExcludedFields(); + foreach ($fields as $path) { + $comment .= "\n" . $this->placeholder->generate($path) . ' for ' . $path ; + } + if ($comment) { + $comment = 'The configuration file doesn\'t contain sensitive data for security reasons. ' + . 'Sensitive data can be stored in the following environment variables:' + . $comment; + } + return $comment; + } +} diff --git a/app/code/Magento/Config/Model/Config/Export/ExcludeList.php b/app/code/Magento/Config/Model/Config/Export/ExcludeList.php new file mode 100644 index 0000000000000000000000000000000000000000..f3c10b4100ed3a068b56873553c96e2e5852073b --- /dev/null +++ b/app/code/Magento/Config/Model/Config/Export/ExcludeList.php @@ -0,0 +1,55 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Config\Model\Config\Export; + +use Magento\Framework\App\Config\ScopeConfigInterface; + +/** + * Class ExcludeList contains list of config fields which should be excluded from config export file + */ +class ExcludeList +{ + /** + * @var array + */ + private $configs; + + /** + * @param array $configs + */ + public function __construct(array $configs = []) + { + $this->configs = $configs; + } + + /** + * Check whether config item is excluded from export + * + * @param string $path + * @return bool + */ + public function isPresent($path) + { + return !empty($this->configs[$path]) ; + } + + /** + * Retrieves all excluded field paths for export + * + * @return array + */ + public function get() + { + return array_keys( + array_filter( + $this->configs, + function ($value) { + return filter_var($value, FILTER_VALIDATE_BOOLEAN); + } + ) + ); + } +} diff --git a/app/code/Magento/Config/Model/Config/Processor/EnvironmentPlaceholder.php b/app/code/Magento/Config/Model/Config/Processor/EnvironmentPlaceholder.php new file mode 100644 index 0000000000000000000000000000000000000000..efbe888f2ebd62e0376e567e4046080af6ba8658 --- /dev/null +++ b/app/code/Magento/Config/Model/Config/Processor/EnvironmentPlaceholder.php @@ -0,0 +1,70 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Config\Model\Config\Processor; + +use Magento\Config\Model\Placeholder\PlaceholderFactory; +use Magento\Config\Model\Placeholder\PlaceholderInterface; +use Magento\Framework\App\Config\Spi\PreProcessorInterface; +use Magento\Framework\Stdlib\ArrayManager; + +/** + * Allows to extract configurations from environment variables. + */ +class EnvironmentPlaceholder implements PreProcessorInterface +{ + /** + * @var PlaceholderFactory + */ + private $placeholderFactory; + + /** + * @var ArrayManager + */ + private $arrayManager; + + /** + * @var PlaceholderInterface + */ + private $placeholder; + + /** + * @param PlaceholderFactory $placeholderFactory + * @param ArrayManager $arrayManager + */ + public function __construct( + PlaceholderFactory $placeholderFactory, + ArrayManager $arrayManager + ) { + $this->placeholderFactory = $placeholderFactory; + $this->arrayManager = $arrayManager; + $this->placeholder = $placeholderFactory->create(PlaceholderFactory::TYPE_ENVIRONMENT); + } + + /** + * Method extracts environment variables. + * If environment variable is matching the desired rule - it's being used as value. + * + * {@inheritdoc} + */ + public function process(array $config) + { + $environmentVariables = $_ENV; + + foreach ($environmentVariables as $template => $value) { + if (!$this->placeholder->isApplicable($template)) { + continue; + } + + $config = $this->arrayManager->set( + $this->placeholder->restore($template), + $config, + $value + ); + } + + return $config; + } +} diff --git a/app/code/Magento/Config/Model/Config/Reader/Source/Deployed/SettingChecker.php b/app/code/Magento/Config/Model/Config/Reader/Source/Deployed/SettingChecker.php index 48b82086ad8b10d3b0241d2502b6199ab2584ab1..7e673401c7348c0721d917b5417c9940ceae16e3 100644 --- a/app/code/Magento/Config/Model/Config/Reader/Source/Deployed/SettingChecker.php +++ b/app/code/Magento/Config/Model/Config/Reader/Source/Deployed/SettingChecker.php @@ -6,10 +6,11 @@ namespace Magento\Config\Model\Config\Reader\Source\Deployed; use Magento\Config\Model\Config\Reader; -use Magento\Framework\App\Config\ScopeCodeResolver; use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Framework\App\DeploymentConfig; -use Magento\Framework\App\ObjectManager; +use Magento\Config\Model\Placeholder\PlaceholderInterface; +use Magento\Config\Model\Placeholder\PlaceholderFactory; +use Magento\Framework\App\Config\ScopeCodeResolver; /** * Class for checking settings that defined in config file @@ -21,6 +22,11 @@ class SettingChecker */ private $config; + /** + * @var PlaceholderInterface + */ + private $placeholder; + /** * @var ScopeCodeResolver */ @@ -28,45 +34,86 @@ class SettingChecker /** * @param DeploymentConfig $config + * @param PlaceholderFactory $placeholderFactory * @param ScopeCodeResolver $scopeCodeResolver */ public function __construct( DeploymentConfig $config, + PlaceholderFactory $placeholderFactory, ScopeCodeResolver $scopeCodeResolver ) { $this->config = $config; $this->scopeCodeResolver = $scopeCodeResolver; + $this->placeholder = $placeholderFactory->create(PlaceholderFactory::TYPE_ENVIRONMENT); } /** - * Resolve path by scope and scope code + * Check that setting defined in deployed configuration * + * @param string $path * @param string $scope - * @param string $scopeCode - * @return string + * @param string|null $scopeCode + * @return boolean */ - private function resolvePath($scope, $scopeCode) + public function isReadOnly($path, $scope, $scopeCode = null) { - $scopePath = 'system/' . $scope; + $config = $this->getEnvValue( + $this->placeholder->generate($path, $scope, $scopeCode) + ); - if ($scope != ScopeConfigInterface::SCOPE_TYPE_DEFAULT) { - $scopePath .= '/' . $this->scopeCodeResolver->resolve($scope, $scopeCode); + if (null === $config) { + $config = $this->config->get($this->resolvePath($scope, $scopeCode) . "/" . $path); } - return $scopePath; + return $config !== null; } /** - * Check that setting defined in deployed configuration + * Check that there is value for generated placeholder + * + * Placeholder is generated from values of $path, $scope and $scopeCode * * @param string $path * @param string $scope * @param string $scopeCode - * @return boolean + * @param string|null $scopeCode + * @return string|null */ - public function isReadOnly($path, $scope, $scopeCode) + public function getPlaceholderValue($path, $scope, $scopeCode = null) { - $config = $this->config->get($this->resolvePath($scope, $scopeCode) . "/" . $path); - return $config !== null; + return $this->getEnvValue($this->placeholder->generate($path, $scope, $scopeCode)); + } + + /** + * Retrieve value of environment variable by placeholder + * + * @param string $placeholder + * @return string|null + */ + public function getEnvValue($placeholder) + { + if ($this->placeholder->isApplicable($placeholder) && isset($_ENV[$placeholder])) { + return $_ENV[$placeholder]; + } + + return null; + } + + /** + * Resolve path by scope and scope code + * + * @param string $scope + * @param string $scopeCode + * @return string + */ + private function resolvePath($scope, $scopeCode) + { + $scopePath = 'system/' . $scope; + + if ($scope != ScopeConfigInterface::SCOPE_TYPE_DEFAULT) { + $scopePath .= '/' . $this->scopeCodeResolver->resolve($scope, $scopeCode); + } + + return $scopePath; } } diff --git a/app/code/Magento/Config/Model/Placeholder/Environment.php b/app/code/Magento/Config/Model/Placeholder/Environment.php new file mode 100644 index 0000000000000000000000000000000000000000..96ffadc96c6f9a958ebc61e6626bcc02ac178237 --- /dev/null +++ b/app/code/Magento/Config/Model/Placeholder/Environment.php @@ -0,0 +1,76 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Config\Model\Placeholder; + +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\App\DeploymentConfig; + +/** + * Class is used to work with placeholders for environment variables names based on config paths + */ +class Environment implements PlaceholderInterface +{ + /** + * @const string Prefix for placeholder + */ + const PREFIX = 'CONFIG__'; + + /** + * @var DeploymentConfig + */ + private $deploymentConfig; + + /** + * @param DeploymentConfig $deploymentConfig + */ + public function __construct(DeploymentConfig $deploymentConfig) + { + $this->deploymentConfig = $deploymentConfig; + } + + /** + * Generates placeholder like CONFIG__DEFAULT__TEST__TEST_VALUE + * + * @inheritdoc + */ + public function generate($path, $scopeType = ScopeConfigInterface::SCOPE_TYPE_DEFAULT, $scopeCode = null) + { + $parts = $scopeType ? [$scopeType] : []; + + if ($scopeType !== ScopeConfigInterface::SCOPE_TYPE_DEFAULT && $scopeCode) { + $parts[] = $scopeCode; + } + + $parts[] = $path; + + $template = implode('__', $parts); + $template = str_replace('/', '__', $template); + $template = static::PREFIX . $template; + $template = strtoupper($template); + + return $template; + } + + /** + * @inheritdoc + */ + public function restore($template) + { + $template = preg_replace('/^' . static::PREFIX . '/', '', $template); + $template = str_replace('__', '/', $template); + $template = strtolower($template); + + return $template; + } + + /** + * @inheritdoc + */ + public function isApplicable($placeholder) + { + return 1 === preg_match('/^' . static::PREFIX . '([a-zA-Z]+)([a-zA-Z0-9_])*$/', $placeholder); + } +} diff --git a/app/code/Magento/Config/Model/Placeholder/PlaceholderFactory.php b/app/code/Magento/Config/Model/Placeholder/PlaceholderFactory.php new file mode 100644 index 0000000000000000000000000000000000000000..3f88bc2a289c4b32e47f24ef43223a868ea99267 --- /dev/null +++ b/app/code/Magento/Config/Model/Placeholder/PlaceholderFactory.php @@ -0,0 +1,59 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Config\Model\Placeholder; + +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\ObjectManagerInterface; + +class PlaceholderFactory +{ + /** + * @const string Environment type + */ + const TYPE_ENVIRONMENT = 'environment'; + + /** + * @var ObjectManagerInterface + */ + private $objectManager; + + /** + * @var array + */ + private $types; + + /** + * @param ObjectManagerInterface $objectManager + * @param array $types + */ + public function __construct(ObjectManagerInterface $objectManager, array $types = []) + { + $this->objectManager = $objectManager; + $this->types = $types; + } + + /** + * Create placeholder + * + * @param string $type + * @return PlaceholderInterface + * @throws LocalizedException + */ + public function create($type) + { + if (!isset($this->types[$type])) { + throw new LocalizedException(__('There is no defined type ' . $type)); + } + + $object = $this->objectManager->create($this->types[$type]); + + if (!$object instanceof PlaceholderInterface) { + throw new LocalizedException(__('Object is not instance of ' . PlaceholderInterface::class)); + } + + return $object; + } +} diff --git a/app/code/Magento/Config/Model/Placeholder/PlaceholderInterface.php b/app/code/Magento/Config/Model/Placeholder/PlaceholderInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..286eb0034a55063b8dcbf35cc13474ef144fd656 --- /dev/null +++ b/app/code/Magento/Config/Model/Placeholder/PlaceholderInterface.php @@ -0,0 +1,40 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Config\Model\Placeholder; + +use Magento\Framework\App\Config\ScopeConfigInterface; + +/** + * Interface PlaceholderInterface + */ +interface PlaceholderInterface +{ + /** + * Generating placeholder from value + * + * @param string $path + * @param string $scopeType + * @param string $scopeCode + * @return string + */ + public function generate($path, $scopeType = ScopeConfigInterface::SCOPE_TYPE_DEFAULT, $scopeCode = null); + + /** + * Restoring path parts from template. + * + * @param string $template + * @return string + */ + public function restore($template); + + /** + * Check whether provided string is placeholder + * + * @param string $placeholder + * @return bool + */ + public function isApplicable($placeholder); +} diff --git a/app/code/Magento/Config/Test/Unit/App/Config/Source/DumpConfigSourceAggregatedTest.php b/app/code/Magento/Config/Test/Unit/App/Config/Source/DumpConfigSourceAggregatedTest.php new file mode 100644 index 0000000000000000000000000000000000000000..9c6aef37540e5d69f071b8f4f9ccb465f69233ff --- /dev/null +++ b/app/code/Magento/Config/Test/Unit/App/Config/Source/DumpConfigSourceAggregatedTest.php @@ -0,0 +1,171 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Config\Test\Unit\App\Config\Source; + +use Magento\Config\App\Config\Source\DumpConfigSourceAggregated; +use Magento\Config\Model\Config\Export\ExcludeList; +use Magento\Framework\App\Config\ConfigSourceInterface; + +class DumpConfigSourceAggregatedTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var ConfigSourceInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $sourceMock; + + /** + * @var ConfigSourceInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $sourceMockTwo; + + /** + * @var DumpConfigSourceAggregated + */ + private $model; + + /** + * @var ExcludeList|\PHPUnit_Framework_MockObject_MockObject + */ + private $excludeListMock; + + public function setUp() + { + $this->sourceMock = $this->getMockBuilder(ConfigSourceInterface::class) + ->getMockForAbstractClass(); + $this->sourceMockTwo = $this->getMockBuilder(ConfigSourceInterface::class) + ->getMockForAbstractClass(); + $this->excludeListMock = $this->getMockBuilder(ExcludeList::class) + ->disableOriginalConstructor() + ->getMock(); + + $sources = [ + [ + 'source' => $this->sourceMockTwo, + 'sortOrder' => 100 + ], + [ + 'source' => $this->sourceMock, + 'sortOrder' => 10 + ], + + ]; + + $this->model = new DumpConfigSourceAggregated($this->excludeListMock, $sources); + } + + public function testGet() + { + $path = ''; + $data = [ + 'default' => [ + 'web' => [ + 'unsecure' => [ + 'base_url' => 'http://test.local', + ], + 'secure' => [ + 'base_url' => 'https://test.local', + ] + ] + ], + 'test' => [ + 'test' => [ + 'test1' => [ + 'test2' => [ + 'test3' => 5, + ] + ] + ] + ] + ]; + + $this->sourceMock->expects($this->once()) + ->method('get') + ->with($path) + ->willReturn($data); + $this->sourceMockTwo->expects($this->once()) + ->method('get') + ->with($path) + ->willReturn(['key' => 'value2']); + $this->excludeListMock->expects($this->any()) + ->method('isPresent') + ->willReturnMap([ + ['web/unsecure/base_url', false], + ['web/secure/base_url', true], + ['test1/test2/test/3', false] + ]); + + $this->assertEquals( + [ + 'test' => [ + 'test' => [ + 'test1' => [ + 'test2' => [ + 'test3' => 5, + ] + ] + ], + ], + 'key' => 'value2', + 'default' => [ + 'web' => [ + 'unsecure' => [ + 'base_url' => 'http://test.local', + ], + 'secure' => [] + ] + ], + ], + $this->model->get($path) + ); + } + + public function testGetExcludedFields() + { + $path = ''; + $data = [ + 'default' => [ + 'web' => [ + 'unsecure' => [ + 'base_url' => 'http://test.local', + ], + 'secure' => [ + 'base_url' => 'https://test.local', + ] + ] + ], + 'test' => [ + 'test' => [ + 'test1' => [ + 'test2' => [ + 'test3' => 5, + ] + ] + ] + ] + ]; + + $this->sourceMock->expects($this->once()) + ->method('get') + ->with($path) + ->willReturn($data); + $this->sourceMockTwo->expects($this->once()) + ->method('get') + ->with($path) + ->willReturn(['key' => 'value2']); + $this->excludeListMock->expects($this->any()) + ->method('isPresent') + ->willReturnMap([ + ['web/unsecure/base_url', false], + ['web/secure/base_url', true], + ['test1/test2/test/3', false] + ]); + + $this->assertEquals( + ['web/secure/base_url'], + $this->model->getExcludedFields() + ); + } +} diff --git a/app/code/Magento/Config/Test/Unit/App/Config/Type/SystemTest.php b/app/code/Magento/Config/Test/Unit/App/Config/Type/SystemTest.php index be541228bf6d88a5ad7e459ba77a34e896c863a1..8409e96db552e61a8ee3a8a4ec788d457d84ba5c 100644 --- a/app/code/Magento/Config/Test/Unit/App/Config/Type/SystemTest.php +++ b/app/code/Magento/Config/Test/Unit/App/Config/Type/SystemTest.php @@ -8,9 +8,9 @@ namespace Magento\Config\Test\Unit\App\Config\Type; use Magento\Config\App\Config\Type\System; use Magento\Framework\App\Config\ConfigSourceInterface; use Magento\Framework\App\Config\Spi\PostProcessorInterface; -use Magento\Framework\App\ObjectManager; +use Magento\Framework\App\Config\Spi\PreProcessorInterface; use Magento\Framework\Cache\FrontendInterface; -use Magento\Framework\Serialize\Serializer\Serialize; +use Magento\Framework\Serialize\SerializerInterface; use Magento\Store\Model\Config\Processor\Fallback; /** @@ -29,6 +29,11 @@ class SystemTest extends \PHPUnit_Framework_TestCase */ private $postProcessor; + /** + * @var PreProcessorInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $preProcessor; + /** * @var Fallback|\PHPUnit_Framework_MockObject_MockObject */ @@ -45,7 +50,7 @@ class SystemTest extends \PHPUnit_Framework_TestCase private $configType; /** - * @var Serialize|\PHPUnit_Framework_MockObject_MockObject + * @var SerializerInterface|\PHPUnit_Framework_MockObject_MockObject */ private $serializer; @@ -60,7 +65,9 @@ class SystemTest extends \PHPUnit_Framework_TestCase ->getMock(); $this->cache = $this->getMockBuilder(FrontendInterface::class) ->getMockForAbstractClass(); - $this->serializer = $this->getMockBuilder(Serialize::class) + $this->preProcessor = $this->getMockBuilder(PreProcessorInterface::class) + ->getMockForAbstractClass(); + $this->serializer = $this->getMockBuilder(SerializerInterface::class) ->disableOriginalConstructor() ->getMock(); $this->configType = new System( @@ -68,7 +75,8 @@ class SystemTest extends \PHPUnit_Framework_TestCase $this->postProcessor, $this->fallback, $this->cache, - $this->serializer + $this->serializer, + $this->preProcessor ); } @@ -112,6 +120,10 @@ class SystemTest extends \PHPUnit_Framework_TestCase ->method('process') ->with($data) ->willReturnArgument(0); + $this->preProcessor->expects($this->once()) + ->method('process') + ->with($data) + ->willReturnArgument(0); $this->postProcessor->expects($this->once()) ->method('process') ->with($data) diff --git a/app/code/Magento/Config/Test/Unit/Block/System/Config/FormTest.php b/app/code/Magento/Config/Test/Unit/Block/System/Config/FormTest.php index 2c671914f264b04ec34508d919a23c67bf64abad..5c7bf92f954285571bc6620a1726d2ccfd9432f4 100644 --- a/app/code/Magento/Config/Test/Unit/Block/System/Config/FormTest.php +++ b/app/code/Magento/Config/Test/Unit/Block/System/Config/FormTest.php @@ -10,6 +10,7 @@ namespace Magento\Config\Test\Unit\Block\System\Config; use Magento\Config\Model\Config\Reader\Source\Deployed\SettingChecker; use Magento\Framework\App\DeploymentConfig; +use Magento\Store\Model\StoreManagerInterface; /** * Test System config form block @@ -72,6 +73,11 @@ class FormTest extends \PHPUnit_Framework_TestCase */ protected $_fieldsetFactoryMock; + /** + * @var StoreManagerInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $storeManagerMock; + /** * @return void * @SuppressWarnings(PHPMD.ExcessiveMethodLength) @@ -161,6 +167,9 @@ class FormTest extends \PHPUnit_Framework_TestCase false ); + $this->storeManagerMock = $this->getMockBuilder(StoreManagerInterface::class) + ->getMockForAbstractClass(); + $helper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); $context = $helper->getObject( @@ -168,7 +177,8 @@ class FormTest extends \PHPUnit_Framework_TestCase [ 'scopeConfig' => $this->_coreConfigMock, 'request' => $requestMock, - 'urlBuilder' => $this->_urlModelMock + 'urlBuilder' => $this->_urlModelMock, + 'storeManager' => $this->storeManagerMock ] ); @@ -423,6 +433,7 @@ class FormTest extends \PHPUnit_Framework_TestCase * @param string|null $configPath * @param bool $inherit * @param string $expectedValue + * @param string|null $placeholderValue * @param int $hasBackendModel * * @dataProvider initFieldsDataProvider @@ -434,6 +445,7 @@ class FormTest extends \PHPUnit_Framework_TestCase $configPath, $inherit, $expectedValue, + $placeholderValue, $hasBackendModel ) { // Parameters initialization @@ -503,6 +515,18 @@ class FormTest extends \PHPUnit_Framework_TestCase $this->returnValue($configValue) ); + /** @var \Magento\Store\Api\Data\StoreInterface|\PHPUnit_Framework_MockObject_MockObject $storeMock */ + $storeMock = $this->getMockBuilder(\Magento\Store\Api\Data\StoreInterface::class) + ->getMockForAbstractClass(); + $storeMock->expects($this->once()) + ->method('getCode') + ->willReturn('store_code'); + + $this->storeManagerMock->expects($this->atLeastOnce()) + ->method('getStore') + ->with('store_code') + ->willReturn($storeMock); + // Field mock configuration $fieldMock = $this->getMock( \Magento\Config\Model\Config\Structure\Element\Field::class, @@ -596,17 +620,20 @@ class FormTest extends \PHPUnit_Framework_TestCase $fieldMock->expects($this->once())->method('populateInput'); - - $settingChecker = $this->getMockBuilder(SettingChecker::class) + $settingCheckerMock = $this->getMockBuilder(SettingChecker::class) ->disableOriginalConstructor() ->getMock(); - $settingChecker->expects($this->once()) + $settingCheckerMock->expects($this->once()) ->method('isReadOnly') ->willReturn(false); - $reflection = new \ReflectionClass(get_class($this->object)); - $reflectionProperty = $reflection->getProperty('settingChecker'); - $reflectionProperty->setAccessible(true); - $reflectionProperty->setValue($this->object, $settingChecker); + + $settingCheckerMock->expects($this->once()) + ->method('getPlaceholderValue') + ->willReturn($placeholderValue); + + $helper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + + $helper->setBackwardCompatibleProperty($this->object, 'settingChecker', $settingCheckerMock); $this->object->initFields($fieldsetMock, $groupMock, $sectionMock, $fieldPrefix, $labelPrefix); } @@ -617,8 +644,9 @@ class FormTest extends \PHPUnit_Framework_TestCase public function initFieldsDataProvider() { return [ - [['section1/group1/field1' => 'some_value'], false, null, false, 'some_value', 1], - [[], 'Config Value', 'some/config/path', true, 'Config Value', 0] + [['section1/group1/field1' => 'some_value'], false, null, false, 'some_value', null, 1], + [[], 'Config Value', 'some/config/path', true, 'Config Value', null, 0], + [[], 'Config Value', 'some/config/path', true, 'Placeholder Value', 'Placeholder Value', 0] ]; } } diff --git a/app/code/Magento/Config/Test/Unit/Model/Config/Export/CommentTest.php b/app/code/Magento/Config/Test/Unit/Model/Config/Export/CommentTest.php new file mode 100644 index 0000000000000000000000000000000000000000..c8b10bcf4ddcd5393a62307c873646f87d1419e8 --- /dev/null +++ b/app/code/Magento/Config/Test/Unit/Model/Config/Export/CommentTest.php @@ -0,0 +1,88 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Config\Test\Unit\Model\Config\Export; + +use Magento\Config\Model\Config\Export\Comment; +use Magento\Config\App\Config\Source\DumpConfigSourceInterface; +use Magento\Config\Model\Placeholder\PlaceholderFactory; +use Magento\Config\Model\Placeholder\PlaceholderInterface; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; + +class CommentTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var DumpConfigSourceInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $configSourceMock; + + /** + * @var PlaceholderInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $placeholderMock; + + /** + * @var Comment + */ + private $model; + + protected function setUp() + { + $objectManager = new ObjectManager($this); + + $this->placeholderMock = $this->getMockBuilder(PlaceholderInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + + $placeholderFactoryMock = $this->getMockBuilder(PlaceholderFactory::class) + ->setMethods(['create']) + ->disableOriginalConstructor() + ->getMock(); + $placeholderFactoryMock->expects($this->once()) + ->method('create') + ->with(PlaceholderFactory::TYPE_ENVIRONMENT) + ->willReturn($this->placeholderMock); + + $this->configSourceMock = $this->getMockBuilder(DumpConfigSourceInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + + $this->model = $objectManager->getObject( + Comment::class, + [ + 'placeholderFactory' => $placeholderFactoryMock, + 'source' => $this->configSourceMock + ] + ); + } + + public function testGetEmpty() + { + $this->configSourceMock->expects($this->once()) + ->method('getExcludedFields') + ->willReturn([]); + $this->assertEmpty($this->model->get()); + } + + public function testGet() + { + $path = 'one/two'; + $placeholder = 'one__two'; + $expectedResult = 'The configuration file doesn\'t contain sensitive data for security reasons. ' + . 'Sensitive data can be stored in the following environment variables:' + . "\n$placeholder for $path"; + + $this->configSourceMock->expects($this->once()) + ->method('getExcludedFields') + ->willReturn([$path]); + + $this->placeholderMock->expects($this->once()) + ->method('generate') + ->with($path) + ->willReturn($placeholder); + + $this->assertEquals($expectedResult, $this->model->get()); + } +} diff --git a/app/code/Magento/Config/Test/Unit/Model/Config/Export/ExcludeListTest.php b/app/code/Magento/Config/Test/Unit/Model/Config/Export/ExcludeListTest.php new file mode 100644 index 0000000000000000000000000000000000000000..3156ad1ec549325839bbf9470c6396d101ee557f --- /dev/null +++ b/app/code/Magento/Config/Test/Unit/Model/Config/Export/ExcludeListTest.php @@ -0,0 +1,40 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Config\Test\Unit\Model\Config\Export; + +use Magento\Config\Model\Config\Export\ExcludeList; + +class ExcludeListTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var ExcludeList + */ + private $model; + + protected function setUp() + { + $this->model = new ExcludeList( + [ + 'web/unsecure/base_url' => '', + 'web/test/test_value' => '0', + 'web/test/test_sensitive' => '1', + ] + ); + } + + public function testGet() + { + $this->assertEquals(['web/test/test_sensitive'], $this->model->get()); + } + + public function testIsPresent() + { + $this->assertFalse($this->model->isPresent('some/new/path')); + $this->assertFalse($this->model->isPresent('web/unsecure/base_url')); + $this->assertFalse($this->model->isPresent('web/test/test_value')); + $this->assertTrue($this->model->isPresent('web/test/test_sensitive')); + } +} diff --git a/app/code/Magento/Config/Test/Unit/Model/Config/Processor/EnvironmentPlaceholderTest.php b/app/code/Magento/Config/Test/Unit/Model/Config/Processor/EnvironmentPlaceholderTest.php new file mode 100644 index 0000000000000000000000000000000000000000..25f9f6b3cb83222dd9c196114729e8b10465072b --- /dev/null +++ b/app/code/Magento/Config/Test/Unit/Model/Config/Processor/EnvironmentPlaceholderTest.php @@ -0,0 +1,131 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Config\Test\Unit\Model\Config\Processor; + +use Magento\Config\Model\Config\Processor\EnvironmentPlaceholder; +use Magento\Config\Model\Placeholder\PlaceholderFactory; +use Magento\Config\Model\Placeholder\PlaceholderInterface; +use Magento\Framework\Stdlib\ArrayManager; +use \PHPUnit_Framework_MockObject_MockObject as Mock; + +class EnvironmentPlaceholderTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var EnvironmentPlaceholder + */ + private $model; + + /** + * @var PlaceholderFactory|Mock + */ + private $placeholderFactoryMock; + + /** + * @var ArrayManager|Mock + */ + private $arrayManagerMock; + + /** + * @var PlaceholderInterface|Mock + */ + private $placeholderMock; + + /** + * @var array + */ + private $env; + + protected function setUp() + { + $this->placeholderFactoryMock = $this->getMockBuilder(PlaceholderFactory::class) + ->disableOriginalConstructor() + ->getMock(); + $this->arrayManagerMock = $this->getMockBuilder(ArrayManager::class) + ->disableOriginalConstructor() + ->getMock(); + $this->placeholderMock = $this->getMockBuilder(PlaceholderInterface::class) + ->getMockForAbstractClass(); + $this->env = $_ENV; + + $this->placeholderFactoryMock->expects($this->any()) + ->method('create') + ->with(PlaceholderFactory::TYPE_ENVIRONMENT) + ->willReturn($this->placeholderMock); + + $this->model = new EnvironmentPlaceholder( + $this->placeholderFactoryMock, + $this->arrayManagerMock + ); + } + + public function testProcess() + { + $_ENV = array_merge( + $this->env, + [ + 'CONFIG_DEFAULT_TEST' => 1, + 'CONFIG_DEFAULT_TEST2' => 2, + 'BAD_CONFIG' => 3, + ] + ); + + $this->placeholderMock->expects($this->any()) + ->method('isApplicable') + ->willReturnMap( + [ + ['CONFIG_DEFAULT_TEST', true], + ['CONFIG_DEFAULT_TEST2', true], + ['BAD_CONFIG', false], + ] + ); + $this->placeholderMock->expects($this->any()) + ->method('restore') + ->willReturnMap( + [ + ['CONFIG_DEFAULT_TEST', 'default/test'], + ['CONFIG_DEFAULT_TEST2', 'default/test2'], + ] + ); + $this->arrayManagerMock->expects($this->any()) + ->method('set') + ->willReturnMap( + [ + ['default/test', [], 1, '/', ['default' => ['test' => 1]]], + [ + 'default/test2', + [ + 'default' => [ + 'test' => 1 + ] + ], + 2, + '/', + [ + 'default' => [ + 'test' => 1, + 'test2' => 2 + ] + ], + ] + ] + ); + + $this->assertSame( + [ + 'default' => [ + 'test' => 1, + 'test2' => 2 + ] + ], + $this->model->process([]) + ); + } + + protected function tearDown() + { + $_ENV = $this->env; + } +} diff --git a/app/code/Magento/Config/Test/Unit/Model/Config/Reader/Source/Deployed/SettingCheckerTest.php b/app/code/Magento/Config/Test/Unit/Model/Config/Reader/Source/Deployed/SettingCheckerTest.php index 2e746eae410f414788a7b5277e331be7075e6655..75bfab85616b588975175abe8976a8e57ec65e04 100644 --- a/app/code/Magento/Config/Test/Unit/Model/Config/Reader/Source/Deployed/SettingCheckerTest.php +++ b/app/code/Magento/Config/Test/Unit/Model/Config/Reader/Source/Deployed/SettingCheckerTest.php @@ -7,21 +7,30 @@ namespace Magento\Config\Test\Unit\Model\Config\Reader\Source\Deployed; use Magento\Config\Model\Config\Reader; use Magento\Config\Model\Config\Reader\Source\Deployed\SettingChecker; -use Magento\Framework\App\Config\ScopeCodeResolver; use Magento\Framework\App\Config; use Magento\Framework\App\DeploymentConfig; +use Magento\Config\Model\Placeholder\PlaceholderInterface; +use Magento\Config\Model\Placeholder\PlaceholderFactory; /** * Test class for checking settings that defined in config file - * - * @package Magento\Config\Test\Unit\Model\Config\Reader\Source\Deployed */ class SettingCheckerTest extends \PHPUnit_Framework_TestCase { /** * @var Config|\PHPUnit_Framework_MockObject_MockObject */ - private $config; + private $configMock; + + /** + * @var PlaceholderInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $placeholderMock; + + /** + * @var Config\ScopeCodeResolver|\PHPUnit_Framework_MockObject_MockObject + */ + private $scopeCodeResolverMock; /** * @var SettingChecker @@ -29,43 +38,109 @@ class SettingCheckerTest extends \PHPUnit_Framework_TestCase private $checker; /** - * @var ScopeCodeResolver | \PHPUnit_Framework_MockObject_MockObject + * @var array */ - private $scopeCodeResolver; + private $env; public function setUp() { - $this->config = $this->getMockBuilder(DeploymentConfig::class) + $this->configMock = $this->getMockBuilder(DeploymentConfig::class) ->disableOriginalConstructor() ->getMock(); - $this->scopeCodeResolver = $this->getMockBuilder(ScopeCodeResolver::class) + $this->placeholderMock = $this->getMockBuilder(PlaceholderInterface::class) + ->getMockForAbstractClass(); + $this->scopeCodeResolverMock = $this->getMockBuilder(Config\ScopeCodeResolver::class) ->disableOriginalConstructor() ->getMock(); - $this->checker = new SettingChecker($this->config, $this->scopeCodeResolver); + $placeholderFactoryMock = $this->getMockBuilder(PlaceholderFactory::class) + ->disableOriginalConstructor() + ->getMock(); + $this->env = $_ENV; + + $placeholderFactoryMock->expects($this->once()) + ->method('create') + ->with(PlaceholderFactory::TYPE_ENVIRONMENT) + ->willReturn($this->placeholderMock); + + $this->checker = new SettingChecker($this->configMock, $placeholderFactoryMock, $this->scopeCodeResolverMock); } - public function testIsDefined() + /** + * @param string $path + * @param string $scope + * @param string $scopeCode + * @param string|null $confValue + * @param array $variables + * @param bool $expectedResult + * @dataProvider isReadonlyDataProvider + */ + public function testIsReadonly($path, $scope, $scopeCode, $confValue, array $variables, $expectedResult) { - $path = 'general/web/locale'; - $scope = 'website'; - $scopeCode = 'myWebsite'; - $scopeCodeId = '4'; + $this->placeholderMock->expects($this->once()) + ->method('isApplicable') + ->willReturn(true); + $this->placeholderMock->expects($this->once()) + ->method('generate') + ->with($path, $scope, $scopeCode) + ->willReturn('SOME_PLACEHOLDER'); + $this->scopeCodeResolverMock->expects($this->any()) + ->method('resolve') + ->willReturnMap( + [ + ['website', 'myWebsite', ($scopeCode ? $scopeCode : '')] + ] + ); + + $_ENV = array_merge($this->env, $variables); - $this->config->expects($this->once()) + $this->configMock->expects($this->any()) ->method('get') - ->willReturn([ - $scope => [ - $scopeCode => [ - $path => 'value' - ], + ->willReturnMap([ + [ + 'system/' . $scope . "/" . ($scopeCode ? $scopeCode . '/' : '') . $path, + null, + $confValue ], ]); - $this->scopeCodeResolver->expects($this->once()) - ->method('resolve') - ->with($scope, $scopeCodeId) - ->willReturn($scopeCode); + $this->assertSame($expectedResult, $this->checker->isReadOnly($path, $scope, $scopeCode)); + } - $this->assertTrue($this->checker->isReadOnly($path, $scope, $scopeCodeId)); + /** + * @return array + */ + public function isReadonlyDataProvider() + { + return [ + [ + 'path' => 'general/web/locale', + 'scope' => 'website', + 'scopeCode' => 'myWebsite', + 'confValue' => 'value', + 'variables' => [], + 'expectedResult' => true, + ], + [ + 'path' => 'general/web/locale', + 'scope' => 'website', + 'scopeCode' => 'myWebsite', + 'confValue' => null, + 'variables' => ['SOME_PLACEHOLDER' => 'value'], + 'expectedResult' => true, + ], + [ + 'path' => 'general/web/locale', + 'scope' => 'website', + 'scopeCode' => 'myWebsite', + 'confValue' => null, + 'variables' => [], + 'expectedResult' => false, + ] + ]; + } + + protected function tearDown() + { + $_ENV = $this->env; } } diff --git a/app/code/Magento/Config/Test/Unit/Model/Placeholder/EnvironmentTest.php b/app/code/Magento/Config/Test/Unit/Model/Placeholder/EnvironmentTest.php new file mode 100644 index 0000000000000000000000000000000000000000..64b4e6e37fb94867854da15f8e0f72d9b0ccbf3c --- /dev/null +++ b/app/code/Magento/Config/Test/Unit/Model/Placeholder/EnvironmentTest.php @@ -0,0 +1,131 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Config\Test\Unit\Model\Placeholder; + +use Magento\Config\Model\Placeholder\Environment; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\App\DeploymentConfig; +use Magento\Framework\Config\ConfigOptionsListConstants; +use \PHPUnit_Framework_MockObject_MockObject as Mock; + +/** + * Class EnvironmentTest + */ +class EnvironmentTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var Environment + */ + private $model; + + /** + * @var DeploymentConfig|Mock + */ + private $deploymentConfigMock; + + protected function setUp() + { + $this->deploymentConfigMock = $this->getMockBuilder(DeploymentConfig::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->model = new Environment( + $this->deploymentConfigMock + ); + } + + /** + * @param string $path + * @param string $scope + * @param string $scopeId + * @param string $expected + * @dataProvider getGenerateDataProvider + */ + public function testGenerate($path, $scope, $scopeId, $expected) + { + $this->assertSame( + $expected, + $this->model->generate($path, $scope, $scopeId) + ); + } + + public function getGenerateDataProvider() + { + return [ + [ + 'web/unsecure/base_url', + ScopeConfigInterface::SCOPE_TYPE_DEFAULT, + null, + Environment::PREFIX . 'DEFAULT__WEB__UNSECURE__BASE_URL' + ], + [ + 'web/unsecure/base_url', + 'web', + 'test', + Environment::PREFIX . 'WEB__TEST__WEB__UNSECURE__BASE_URL' + ], + [ + 'web/unsecure/base_url', + 'web', + null, + Environment::PREFIX . 'WEB__WEB__UNSECURE__BASE_URL' + ], + ]; + } + + /** + * @param string $placeholder + * @param bool $expected + * @dataProvider getIsPlaceholderDataProvider + */ + public function testIsApplicable($placeholder, $expected) + { + $this->assertSame( + $expected, + $this->model->isApplicable($placeholder) + ); + } + + /** + * @return array + */ + public function getIsPlaceholderDataProvider() + { + return [ + [Environment::PREFIX . 'TEST', true], + ['TEST', false], + [Environment::PREFIX . 'TEST_test', true], + [Environment::PREFIX . '-:A', false], + [Environment::PREFIX . '_A', false], + [Environment::PREFIX . 'A@#$', false] + ]; + } + + /** + * @param string $template + * @param string $expected + * @dataProvider restoreDataProvider + */ + public function testRestore($template, $expected) + { + $this->assertSame( + $expected, + $this->model->restore($template) + ); + } + + /** + * @return array + */ + public function restoreDataProvider() + { + return [ + [Environment::PREFIX . 'TEST__CONFIG', 'test/config'], + [Environment::PREFIX . 'TEST__CONFIG__VALUE', 'test/config/value'], + [Environment::PREFIX . 'TEST__CONFIG_VALUE', 'test/config_value'], + ]; + } +} diff --git a/app/code/Magento/Config/Test/Unit/Model/Placeholder/PlaceholderFactoryTest.php b/app/code/Magento/Config/Test/Unit/Model/Placeholder/PlaceholderFactoryTest.php new file mode 100644 index 0000000000000000000000000000000000000000..e8f646e984187ed49ffc7df0798c8efa689ba767 --- /dev/null +++ b/app/code/Magento/Config/Test/Unit/Model/Placeholder/PlaceholderFactoryTest.php @@ -0,0 +1,76 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Config\Test\Unit\Model\Placeholder; + +use Magento\Config\Model\Placeholder\Environment; +use Magento\Config\Model\Placeholder\PlaceholderFactory; +use Magento\Framework\ObjectManagerInterface; + +class PlaceholderFactoryTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var PlaceholderFactory + */ + private $model; + + /** + * @var ObjectManagerInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $objectManagerMock; + + /** + * @var Environment|\PHPUnit_Framework_MockObject_MockObject + */ + private $environmentMock; + + protected function setUp() + { + $this->objectManagerMock = $this->getMockBuilder(ObjectManagerInterface::class) + ->getMockForAbstractClass(); + $this->environmentMock = $this->getMockBuilder(Environment::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->model = new PlaceholderFactory( + $this->objectManagerMock, + [ + PlaceholderFactory::TYPE_ENVIRONMENT => Environment::class, + 'wrongClass' => \stdClass::class, + ] + ); + } + + public function testCreate() + { + $this->objectManagerMock->expects($this->once()) + ->method('create') + ->with(Environment::class) + ->willReturn($this->environmentMock); + + $this->assertInstanceOf( + Environment::class, + $this->model->create(PlaceholderFactory::TYPE_ENVIRONMENT) + ); + } + + /** + * @expectedException \Magento\Framework\Exception\LocalizedException + * @expectedExceptionMessage There is no defined type dummyClass + */ + public function testCreateNonExisted() + { + $this->model->create('dummyClass'); + } + + /** + * @expectedException \Magento\Framework\Exception\LocalizedException + * @expectedExceptionMessage Object is not instance of Magento\Config\Model\Placeholder\PlaceholderInterface + */ + public function testCreateWrongImplementation() + { + $this->model->create('wrongClass'); + } +} diff --git a/app/code/Magento/Config/etc/di.xml b/app/code/Magento/Config/etc/di.xml index 4f9eae24b55f611db54f3f5423c3944f7d93a7f6..3e2fa4fbecf6da0a712a1df313eb5b63812a5a20 100644 --- a/app/code/Magento/Config/etc/di.xml +++ b/app/code/Magento/Config/etc/di.xml @@ -81,6 +81,8 @@ <argument name="source" xsi:type="object">systemConfigSourceAggregatedProxy</argument> <argument name="postProcessor" xsi:type="object">systemConfigPostProcessorCompositeProxy</argument> <argument name="cache" xsi:type="object">Magento\Framework\App\Cache\Type\Config</argument> + <argument name="preProcessor" xsi:type="object">systemConfigPreProcessorComposite</argument> + <argument name="serializer" xsi:type="object">Magento\Framework\Serialize\Serializer\Serialize</argument> </arguments> </type> <virtualType name="modulesDataProviderProxy" type="Magento\Framework\App\Config\InitialConfigSource\Proxy"> @@ -113,6 +115,13 @@ </argument> </arguments> </virtualType> + <virtualType name="systemConfigPreProcessorComposite" type="Magento\Framework\App\Config\PreProcessorComposite"> + <arguments> + <argument name="processors" xsi:type="array"> + <item name="environmentPlaceholder" xsi:type="object">Magento\Config\Model\Config\Processor\EnvironmentPlaceholder</item> + </argument> + </arguments> + </virtualType> <virtualType name="systemConfigSourceAggregated" type="Magento\Framework\App\Config\ConfigSourceAggregated"> <arguments> <argument name="sources" xsi:type="array"> @@ -138,15 +147,15 @@ <argument name="fileKey" xsi:type="const">Magento\Framework\Config\File\ConfigFilePool::APP_CONFIG</argument> </arguments> </virtualType> - <virtualType name="appDumpSystemSource" type="Magento\Framework\App\Config\ConfigSourceAggregated"> + <virtualType name="appDumpSystemSource" type="Magento\Config\App\Config\Source\DumpConfigSourceAggregated"> <arguments> <argument name="sources" xsi:type="array"> - <item name="initial" xsi:type="array"> - <item name="source" xsi:type="object">systemConfigInitialDataProvider</item> - <item name="sortOrder" xsi:type="string">10</item> - </item> <item name="dynamic" xsi:type="array"> <item name="source" xsi:type="object">Magento\Config\App\Config\Source\RuntimeConfigSource</item> + <item name="sortOrder" xsi:type="string">100</item> + </item> + <item name="initial" xsi:type="array"> + <item name="source" xsi:type="object">systemConfigInitialDataProvider</item> <item name="sortOrder" xsi:type="string">1000</item> </item> </argument> @@ -158,8 +167,21 @@ <item name="system" xsi:type="array"> <item name="source" xsi:type="object">appDumpSystemSource</item> <item name="namespace" xsi:type="const">Magento\Config\App\Config\Type\System::CONFIG_TYPE</item> + <item name="comment" xsi:type="object">Magento\Config\Model\Config\Export\Comment</item> </item> </argument> </arguments> </type> + <type name="Magento\Config\Model\Config\Export\Comment"> + <arguments> + <argument name="source" xsi:type="object">appDumpSystemSource</argument> + </arguments> + </type> + <type name="Magento\Config\Model\Placeholder\PlaceholderFactory"> + <arguments> + <argument name="types" xsi:type="array"> + <item name="environment" xsi:type="string">Magento\Config\Model\Placeholder\Environment</item> + </argument> + </arguments> + </type> </config> diff --git a/app/code/Magento/Contact/etc/di.xml b/app/code/Magento/Contact/etc/di.xml new file mode 100644 index 0000000000000000000000000000000000000000..95cd40cb55e3171e025e3482a290168799395212 --- /dev/null +++ b/app/code/Magento/Contact/etc/di.xml @@ -0,0 +1,16 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> + <type name="Magento\Config\Model\Config\Export\ExcludeList"> + <arguments> + <argument name="configs" xsi:type="array"> + <item name="contact/email/recipient_email" xsi:type="string">1</item> + </argument> + </arguments> + </type> +</config> diff --git a/app/code/Magento/Cron/Console/Command/CronInstallCommand.php b/app/code/Magento/Cron/Console/Command/CronInstallCommand.php new file mode 100644 index 0000000000000000000000000000000000000000..2835244599d38943fea5b2162814bbd6b9edb32e --- /dev/null +++ b/app/code/Magento/Cron/Console/Command/CronInstallCommand.php @@ -0,0 +1,79 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Cron\Console\Command; + +use Magento\Framework\Crontab\CrontabManagerInterface; +use Magento\Framework\Crontab\TasksProviderInterface; +use Magento\Framework\Exception\LocalizedException; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Magento\Framework\Console\Cli; +use Symfony\Component\Console\Input\InputOption; + +/** + * CronInstallCommand installs Magento cron tasks + */ +class CronInstallCommand extends Command +{ + /** + * @var CrontabManagerInterface + */ + private $crontabManager; + + /** + * @var TasksProviderInterface + */ + private $tasksProvider; + + /** + * @param CrontabManagerInterface $crontabManager + * @param TasksProviderInterface $tasksProvider + */ + public function __construct( + CrontabManagerInterface $crontabManager, + TasksProviderInterface $tasksProvider + ) { + $this->crontabManager = $crontabManager; + $this->tasksProvider = $tasksProvider; + + parent::__construct(); + } + + /** + * {@inheritdoc} + */ + protected function configure() + { + $this->setName('cron:install') + ->setDescription('Generates and installs crontab for current user') + ->addOption('force', 'f', InputOption::VALUE_NONE, 'Force install tasks'); + + parent::configure(); + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + if ($this->crontabManager->getTasks() && !$input->getOption('force')) { + $output->writeln('<error>Crontab has already been generated and saved</error>'); + return Cli::RETURN_FAILURE; + } + + try { + $this->crontabManager->saveTasks($this->tasksProvider->getTasks()); + } catch (LocalizedException $e) { + $output->writeln('<error>' . $e->getMessage() . '</error>'); + return Cli::RETURN_FAILURE; + } + + $output->writeln('<info>Crontab has been generated and saved</info>'); + + return Cli::RETURN_SUCCESS; + } +} diff --git a/app/code/Magento/Cron/Console/Command/CronRemoveCommand.php b/app/code/Magento/Cron/Console/Command/CronRemoveCommand.php new file mode 100644 index 0000000000000000000000000000000000000000..11c666a556f899e9d7c6358c09291ee9df904998 --- /dev/null +++ b/app/code/Magento/Cron/Console/Command/CronRemoveCommand.php @@ -0,0 +1,62 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Cron\Console\Command; + +use Magento\Framework\Crontab\CrontabManagerInterface; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Magento\Framework\Console\Cli; +use Magento\Framework\Exception\LocalizedException; + +/** + * CronRemoveCommand removes Magento cron tasks + */ +class CronRemoveCommand extends Command +{ + /** + * @var CrontabManagerInterface + */ + private $crontabManager; + + /** + * @param CrontabManagerInterface $crontabManager + */ + public function __construct(CrontabManagerInterface $crontabManager) + { + $this->crontabManager = $crontabManager; + + parent::__construct(); + } + + /** + * {@inheritdoc} + */ + protected function configure() + { + $this->setName('cron:remove') + ->setDescription('Removes tasks from crontab'); + + parent::configure(); + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + try { + $this->crontabManager->removeTasks(); + } catch (LocalizedException $e) { + $output->writeln('<error>' . $e->getMessage() . '</error>'); + return Cli::RETURN_FAILURE; + } + + $output->writeln('<info>Magento cron tasks have been removed</info>'); + + return Cli::RETURN_SUCCESS; + } +} diff --git a/app/code/Magento/Cron/Test/Unit/Console/Command/CronInstallCommandTest.php b/app/code/Magento/Cron/Test/Unit/Console/Command/CronInstallCommandTest.php new file mode 100644 index 0000000000000000000000000000000000000000..09949cf78dc8f267fe2bb668d91f2b28032ed8c1 --- /dev/null +++ b/app/code/Magento/Cron/Test/Unit/Console/Command/CronInstallCommandTest.php @@ -0,0 +1,126 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Cron\Test\Unit\Console\Command; + +use Symfony\Component\Console\Tester\CommandTester; +use Magento\Cron\Console\Command\CronInstallCommand; +use Magento\Framework\Crontab\CrontabManagerInterface; +use Magento\Framework\Crontab\TasksProviderInterface; +use Magento\Framework\Console\Cli; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Phrase; + +class CronInstallCommandTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var CrontabManagerInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $crontabManagerMock; + + /** + * @var TasksProviderInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $tasksProviderMock; + + /** + * @var CommandTester + */ + private $commandTester; + + /** + * @return void + */ + protected function setUp() + { + $this->crontabManagerMock = $this->getMockBuilder(CrontabManagerInterface::class) + ->getMockForAbstractClass(); + $this->tasksProviderMock = $this->getMockBuilder(TasksProviderInterface::class) + ->getMockForAbstractClass(); + + $this->commandTester = new CommandTester( + new CronInstallCommand($this->crontabManagerMock, $this->tasksProviderMock) + ); + } + + /** + * @return void + */ + public function testExecuteAlreadyInstalled() + { + $this->crontabManagerMock->expects($this->once()) + ->method('getTasks') + ->willReturn([['* * * * * /bin/php /var/run.php']]); + $this->tasksProviderMock->expects($this->never()) + ->method('getTasks'); + + $this->commandTester->execute([]); + $this->assertEquals( + 'Crontab has already been generated and saved' . PHP_EOL, + $this->commandTester->getDisplay() + ); + $this->assertEquals(Cli::RETURN_FAILURE, $this->commandTester->getStatusCode()); + } + + /** + * @return void + */ + public function testExecuteWithException() + { + $this->crontabManagerMock->expects($this->once()) + ->method('getTasks') + ->willReturn([]); + $this->tasksProviderMock->expects($this->once()) + ->method('getTasks') + ->willReturn([]); + $this->crontabManagerMock->expects($this->once()) + ->method('saveTasks') + ->willThrowException(new LocalizedException(new Phrase('Some error'))); + + $this->commandTester->execute([]); + $this->assertEquals( + 'Some error' . PHP_EOL, + $this->commandTester->getDisplay() + ); + $this->assertEquals(Cli::RETURN_FAILURE, $this->commandTester->getStatusCode()); + } + + /** + * @param array $existingTasks + * @param array $options + * @return void + * @dataProvider executeDataProvider + */ + public function testExecute($existingTasks, $options) + { + $this->crontabManagerMock->expects($this->once()) + ->method('getTasks') + ->willReturn($existingTasks); + $this->tasksProviderMock->expects($this->once()) + ->method('getTasks') + ->willReturn([]); + $this->crontabManagerMock->expects($this->once()) + ->method('saveTasks') + ->with([]); + + $this->commandTester->execute($options); + $this->assertEquals( + 'Crontab has been generated and saved' . PHP_EOL, + $this->commandTester->getDisplay() + ); + $this->assertEquals(Cli::RETURN_SUCCESS, $this->commandTester->getStatusCode()); + } + + /** + * @return array + */ + public function executeDataProvider() + { + return [ + ['existingTasks' => [], 'options' => []], + ['existingTasks' => ['* * * * * /bin/php /var/www/run.php'], 'options' => ['-f'=> true]] + ]; + } +} diff --git a/app/code/Magento/Cron/Test/Unit/Console/Command/CronRemoveCommandTest.php b/app/code/Magento/Cron/Test/Unit/Console/Command/CronRemoveCommandTest.php new file mode 100644 index 0000000000000000000000000000000000000000..cd017b26d758578482bffa137a3f221650619bae --- /dev/null +++ b/app/code/Magento/Cron/Test/Unit/Console/Command/CronRemoveCommandTest.php @@ -0,0 +1,72 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Cron\Test\Unit\Console\Command; + +use Symfony\Component\Console\Tester\CommandTester; +use Magento\Cron\Console\Command\CronRemoveCommand; +use Magento\Framework\Crontab\CrontabManagerInterface; +use Magento\Framework\Console\Cli; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Phrase; + +class CronRemoveCommandTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var CrontabManagerInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $crontabManagerMock; + + /** + * @var CommandTester + */ + private $commandTester; + + /** + * @return void + */ + protected function setUp() + { + $this->crontabManagerMock = $this->getMockBuilder(CrontabManagerInterface::class) + ->getMockForAbstractClass(); + + $this->commandTester = new CommandTester( + new CronRemoveCommand($this->crontabManagerMock) + ); + } + + /** + * @return void + */ + public function testExecute() + { + $this->crontabManagerMock->expects($this->once()) + ->method('RemoveTasks'); + + $this->commandTester->execute([]); + $this->assertEquals( + 'Magento cron tasks have been removed' . PHP_EOL, + $this->commandTester->getDisplay() + ); + $this->assertEquals(Cli::RETURN_SUCCESS, $this->commandTester->getStatusCode()); + } + + /** + * @return void + */ + public function testExecuteFailed() + { + $this->crontabManagerMock->expects($this->once()) + ->method('RemoveTasks') + ->willThrowException(new LocalizedException(new Phrase('Some error'))); + + $this->commandTester->execute([]); + $this->assertEquals( + 'Some error' . PHP_EOL, + $this->commandTester->getDisplay() + ); + $this->assertEquals(Cli::RETURN_FAILURE, $this->commandTester->getStatusCode()); + } +} diff --git a/app/code/Magento/Cron/etc/di.xml b/app/code/Magento/Cron/etc/di.xml index d5624e96765c58bd699eebc29c14c7fe76037b7a..6abc9096f24012309a3b56cb0b84912d3a8d1e26 100644 --- a/app/code/Magento/Cron/etc/di.xml +++ b/app/code/Magento/Cron/etc/di.xml @@ -8,6 +8,8 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> <preference for="Magento\Cron\Model\ConfigInterface" type="Magento\Cron\Model\Config" /> <preference for="Magento\Framework\Shell\CommandRendererInterface" type="Magento\Framework\Shell\CommandRenderer" /> + <preference for="Magento\Framework\Crontab\CrontabManagerInterface" type="Magento\Framework\Crontab\CrontabManager" /> + <preference for="Magento\Framework\Crontab\TasksProviderInterface" type="Magento\Framework\Crontab\TasksProvider" /> <type name="Magento\Config\Model\Config\Structure\Converter"> <plugin name="cron_backend_config_structure_converter_plugin" type="Magento\Cron\Model\Backend\Config\Structure\Converter" /> </type> @@ -28,6 +30,8 @@ <arguments> <argument name="commands" xsi:type="array"> <item name="cronCommand" xsi:type="object">Magento\Cron\Console\Command\CronCommand</item> + <item name="cronInstall" xsi:type="object">Magento\Cron\Console\Command\CronInstallCommand</item> + <item name="cronRemove" xsi:type="object">Magento\Cron\Console\Command\CronRemoveCommand</item> </argument> </arguments> </type> @@ -38,4 +42,24 @@ </argument> </arguments> </type> + <type name="Magento\Framework\Crontab\CrontabManager"> + <arguments> + <argument name="shell" xsi:type="object">Magento\Framework\App\Shell</argument> + </arguments> + </type> + <type name="Magento\Framework\Crontab\TasksProviderInterface"> + <arguments> + <argument name="tasks" xsi:type="array"> + <item name="cronMagento" xsi:type="array"> + <item name="command" xsi:type="string">{magentoRoot}bin/magento cron:run | grep -v "Ran jobs by schedule" >> {magentoLog}magento.cron.log</item> + </item> + <item name="cronUpdate" xsi:type="array"> + <item name="command" xsi:type="string">{magentoRoot}update/cron.php >> {magentoLog}update.cron.log</item> + </item> + <item name="cronSetup" xsi:type="array"> + <item name="command" xsi:type="string">{magentoRoot}bin/magento setup:cron:run >> {magentoLog}setup.cron.log</item> + </item> + </argument> + </arguments> + </type> </config> diff --git a/app/code/Magento/Deploy/Console/Command/App/ApplicationDumpCommand.php b/app/code/Magento/Deploy/Console/Command/App/ApplicationDumpCommand.php index b4e4fef8fb2f941eaa56cc33b2df65f783fa0413..40b262e3e4f5176f87d306be8babb5cfb2ba1898 100644 --- a/app/code/Magento/Deploy/Console/Command/App/ApplicationDumpCommand.php +++ b/app/code/Magento/Deploy/Console/Command/App/ApplicationDumpCommand.php @@ -5,7 +5,7 @@ */ namespace Magento\Deploy\Console\Command\App; -use Magento\Framework\App\Config\Reader\Source\SourceInterface; +use Magento\Framework\App\Config\ConfigSourceInterface; use Magento\Framework\App\DeploymentConfig\Writer; use Magento\Framework\Config\File\ConfigFilePool; use Magento\Framework\Console\Cli; @@ -24,7 +24,7 @@ class ApplicationDumpCommand extends Command private $writer; /** - * @var SourceInterface[] + * @var ConfigSourceInterface[] */ private $sources; @@ -64,20 +64,29 @@ class ApplicationDumpCommand extends Command protected function execute(InputInterface $input, OutputInterface $output) { $dump = []; + $comments = []; foreach ($this->sources as $sourceData) { - /** @var SourceInterface $source */ + /** @var ConfigSourceInterface $source */ $source = $sourceData['source']; $namespace = $sourceData['namespace']; $dump[$namespace] = $source->get(); + if (!empty($sourceData['comment'])) { + $comments[$namespace] = is_string($sourceData['comment']) + ? $sourceData['comment'] + : $sourceData['comment']->get(); + } } - $this->writer ->saveConfig( [ConfigFilePool::APP_CONFIG => $dump], true, - ConfigFilePool::LOCAL + ConfigFilePool::LOCAL, + $comments ); + if (!empty($comments)) { + $output->writeln($comments); + } $output->writeln('<info>Done.</info>'); - return Cli::RETURN_SUCCESS; + return Cli::RETURN_SUCCESS; } } diff --git a/app/code/Magento/Developer/etc/di.xml b/app/code/Magento/Developer/etc/di.xml index 80433242853b85378a31b50133716e8399084c29..2362aaa0780debfcdc0e55b270ed733dc1237e5b 100644 --- a/app/code/Magento/Developer/etc/di.xml +++ b/app/code/Magento/Developer/etc/di.xml @@ -252,4 +252,11 @@ </argument> </arguments> </type> + <type name="Magento\Config\Model\Config\Export\ExcludeList"> + <arguments> + <argument name="configs" xsi:type="array"> + <item name="dev/restrict/allow_ips" xsi:type="string">1</item> + </argument> + </arguments> + </type> </config> diff --git a/app/code/Magento/Dhl/etc/di.xml b/app/code/Magento/Dhl/etc/di.xml index a215c2e82182535e14e659d20cf1f7db2317e915..cbe795791147cc03de7eb060c84108c6cbeb53fa 100644 --- a/app/code/Magento/Dhl/etc/di.xml +++ b/app/code/Magento/Dhl/etc/di.xml @@ -9,4 +9,13 @@ <type name="Magento\Checkout\Block\Cart\LayoutProcessor"> <plugin name="checkout_cart_shipping_dhl" type="Magento\Dhl\Model\Plugin\Checkout\Block\Cart\Shipping"/> </type> + <type name="Magento\Config\Model\Config\Export\ExcludeList"> + <arguments> + <argument name="configs" xsi:type="array"> + <item name="carriers/dhl/id" xsi:type="string">1</item> + <item name="carriers/dhl/password" xsi:type="string">1</item> + <item name="carriers/dhl/account" xsi:type="string">1</item> + </argument> + </arguments> + </type> </config> diff --git a/app/code/Magento/Directory/etc/di.xml b/app/code/Magento/Directory/etc/di.xml index f868197e60593cb59292a060b473f1d87d55820e..b4da40d119fe3e2269d1d33f47c5e2009a3379b1 100644 --- a/app/code/Magento/Directory/etc/di.xml +++ b/app/code/Magento/Directory/etc/di.xml @@ -47,4 +47,12 @@ <preference for="Magento\Directory\Api\CountryInformationAcquirerInterface" type="Magento\Directory\Model\CountryInformationAcquirer" /> <preference for="Magento\Directory\Api\Data\CountryInformationInterface" type="Magento\Directory\Model\Data\CountryInformation" /> <preference for="Magento\Directory\Api\Data\RegionInformationInterface" type="Magento\Directory\Model\Data\RegionInformation" /> + + <type name="Magento\Config\Model\Config\Export\ExcludeList"> + <arguments> + <argument name="configs" xsi:type="array"> + <item name="currency/import/error_email" xsi:type="string">1</item> + </argument> + </arguments> + </type> </config> diff --git a/app/code/Magento/NewRelicReporting/etc/di.xml b/app/code/Magento/NewRelicReporting/etc/di.xml index caabf89be1871b05d55040d34f3b4b0e5b7a9120..6e3a24be91982e9f14b54228144157165094b1a5 100644 --- a/app/code/Magento/NewRelicReporting/etc/di.xml +++ b/app/code/Magento/NewRelicReporting/etc/di.xml @@ -12,4 +12,14 @@ <argument name="fullModuleList" xsi:type="object">Magento\Framework\Module\FullModuleList</argument> </arguments> </type> + <type name="Magento\Config\Model\Config\Export\ExcludeList"> + <arguments> + <argument name="configs" xsi:type="array"> + <item name="newrelicreporting/general/account_id" xsi:type="string">1</item> + <item name="newrelicreporting/general/app_id" xsi:type="string">1</item> + <item name="newrelicreporting/general/api" xsi:type="string">1</item> + <item name="newrelicreporting/general/insights_insert_key" xsi:type="string">1</item> + </argument> + </arguments> + </type> </config> diff --git a/app/code/Magento/Paypal/etc/di.xml b/app/code/Magento/Paypal/etc/di.xml index c5cbdbdc51331c95a269581a97a8ee3a94c25e0f..940876451fd591984937faeae1deef5ac1e4ea60 100644 --- a/app/code/Magento/Paypal/etc/di.xml +++ b/app/code/Magento/Paypal/etc/di.xml @@ -182,4 +182,32 @@ <argument name="paymentTokenFactory" xsi:type="object">Magento\Vault\Model\CreditCardTokenFactory</argument> </arguments> </type> + <type name="Magento\Config\Model\Config\Export\ExcludeList"> + <arguments> + <argument name="configs" xsi:type="array"> + <item name="paypal/general/business_account" xsi:type="string">1</item> + <item name="paypal/wpp/api_password" xsi:type="string">1</item> + <item name="paypal/wpp/api_signature" xsi:type="string">1</item> + <item name="paypal/wpp/api_username" xsi:type="string">1</item> + <item name="paypal/wpp/api_cert" xsi:type="string">1</item> + <item name="paypal/wpp/proxy_host" xsi:type="string">1</item> + <item name="paypal/wpp/proxy_port" xsi:type="string">1</item> + <item name="payment/paypal_express/merchant_id" xsi:type="string">1</item> + <item name="payment/paypal_express_bml/publisher_id" xsi:type="string">1</item> + <item name="paypal/fetch_reports/ftp_password" xsi:type="string">1</item> + <item name="paypal/fetch_reports/ftp_login" xsi:type="string">1</item> + <item name="paypal/fetch_reports/ftp_path" xsi:type="string">1</item> + <item name="paypal/fetch_reports/ftp_ip" xsi:type="string">1</item> + <item name="payment/payflow_advanced/user" xsi:type="string">1</item> + <item name="payment/payflow_advanced/pwd" xsi:type="string">1</item> + <item name="payment/payflow_link/user" xsi:type="string">1</item> + <item name="payment/payflow_link/pwd" xsi:type="string">1</item> + <item name="payment/payflowpro/user" xsi:type="string">1</item> + <item name="payment/payflowpro/pwd" xsi:type="string">1</item> + <item name="payment/payflowpro/partner" xsi:type="string">1</item> + <item name="payment/payflowpro/proxy_host" xsi:type="string">1</item> + <item name="payment/payflowpro/proxy_port" xsi:type="string">1</item> + </argument> + </arguments> + </type> </config> diff --git a/app/code/Magento/ProductAlert/composer.json b/app/code/Magento/ProductAlert/composer.json index 9c63c6958a465852ea3148cab659102880b07630..4ed6a998b084eb25a93ec939ebe20c70c6a4e5c2 100644 --- a/app/code/Magento/ProductAlert/composer.json +++ b/app/code/Magento/ProductAlert/composer.json @@ -9,6 +9,9 @@ "magento/module-customer": "100.2.*", "magento/framework": "100.2.*" }, + "suggest": { + "magento/module-config": "100.2.*" + }, "type": "magento2-module", "version": "100.2.0-dev", "license": [ diff --git a/app/code/Magento/ProductAlert/etc/di.xml b/app/code/Magento/ProductAlert/etc/di.xml index 734b0f6695778bc38744f185da7b58eb0faf50a1..d5c81adce46f0c66f10d5b60226f0525e3fd4c54 100644 --- a/app/code/Magento/ProductAlert/etc/di.xml +++ b/app/code/Magento/ProductAlert/etc/di.xml @@ -13,4 +13,12 @@ </argument> </arguments> </type> + + <type name="Magento\Config\Model\Config\Export\ExcludeList"> + <arguments> + <argument name="configs" xsi:type="array"> + <item name="catalog/productalert_cron/error_email" xsi:type="string">1</item> + </argument> + </arguments> + </type> </config> diff --git a/app/code/Magento/ProductVideo/composer.json b/app/code/Magento/ProductVideo/composer.json index 08d21e4c2abbfb167aed2ca24c0ba012d6b2e8c8..726104c084677dfcb69e3721adfe63f4d71a7ecd 100644 --- a/app/code/Magento/ProductVideo/composer.json +++ b/app/code/Magento/ProductVideo/composer.json @@ -12,7 +12,8 @@ "magento/magento-composer-installer": "*" }, "suggest": { - "magento/module-customer": "100.2.*" + "magento/module-customer": "100.2.*", + "magento/module-config": "100.2.*" }, "type": "magento2-module", "version": "100.2.0-dev", diff --git a/app/code/Magento/ProductVideo/etc/di.xml b/app/code/Magento/ProductVideo/etc/di.xml index 7242a9d48ce1e3c0da66a3f4d5953cda705132cd..9aad01caaf72b65bc1a49951cb84c2f28a2de5e0 100644 --- a/app/code/Magento/ProductVideo/etc/di.xml +++ b/app/code/Magento/ProductVideo/etc/di.xml @@ -46,4 +46,11 @@ <type name="Magento\Catalog\Model\ResourceModel\Product\Gallery"> <plugin name="external_video_media_resource_backend" type="Magento\ProductVideo\Model\Plugin\ExternalVideoResourceBackend" /> </type> + <type name="Magento\Config\Model\Config\Export\ExcludeList"> + <arguments> + <argument name="configs" xsi:type="array"> + <item name="catalog/product_video/youtube_api_key" xsi:type="string">1</item> + </argument> + </arguments> + </type> </config> diff --git a/app/code/Magento/Sales/etc/di.xml b/app/code/Magento/Sales/etc/di.xml index 4e2487d9dcada2eaf224f8cc30f65fc8fa4a4fc8..eb392fc83d8580e0cea6803a43c5f1a44f27fd58 100644 --- a/app/code/Magento/Sales/etc/di.xml +++ b/app/code/Magento/Sales/etc/di.xml @@ -956,4 +956,18 @@ <type name="Magento\Sales\Model\ResourceModel\Order\Handler\Address"> <plugin name="addressUpdate" type="Magento\Sales\Model\Order\Invoice\Plugin\AddressUpdate"/> </type> + <type name="Magento\Config\Model\Config\Export\ExcludeList"> + <arguments> + <argument name="configs" xsi:type="array"> + <item name="sales_email/order/copy_to" xsi:type="string">1</item> + <item name="sales_email/order_comment/copy_to" xsi:type="string">1</item> + <item name="sales_email/invoice/copy_to" xsi:type="string">1</item> + <item name="sales_email/invoice_comment/copy_to" xsi:type="string">1</item> + <item name="sales_email/shipment/copy_to" xsi:type="string">1</item> + <item name="sales_email/shipment_comment/copy_to" xsi:type="string">1</item> + <item name="sales_email/creditmemo/copy_to" xsi:type="string">1</item> + <item name="sales_email/creditmemo_comment/copy_to" xsi:type="string">1</item> + </argument> + </arguments> + </type> </config> diff --git a/app/code/Magento/Shipping/composer.json b/app/code/Magento/Shipping/composer.json index 03f51bbd95e0ff5f65b88e565cfbe77102a44760..6f3f5c3925fa5431446d0c40ccf1e4bf902afede 100644 --- a/app/code/Magento/Shipping/composer.json +++ b/app/code/Magento/Shipping/composer.json @@ -21,7 +21,8 @@ }, "suggest": { "magento/module-fedex": "100.2.*", - "magento/module-ups": "100.2.*" + "magento/module-ups": "100.2.*", + "magento/module-config": "100.2.*" }, "type": "magento2-module", "version": "100.2.0-dev", diff --git a/app/code/Magento/Shipping/etc/di.xml b/app/code/Magento/Shipping/etc/di.xml index 1fe0657bf1337a1712c2aef7b614db6a45ea853f..44e138d6c9ac6f5c102727458edfe4d34fe4a9c5 100644 --- a/app/code/Magento/Shipping/etc/di.xml +++ b/app/code/Magento/Shipping/etc/di.xml @@ -9,4 +9,16 @@ <preference for="Magento\Quote\Model\Quote\Address\RateCollectorInterface" type="Magento\Shipping\Model\Shipping" /> <preference for="Magento\Shipping\Model\CarrierFactoryInterface" type="Magento\Shipping\Model\CarrierFactory" /> <preference for="Magento\Shipping\Model\Carrier\Source\GenericInterface" type="\Magento\Shipping\Model\Carrier\Source\GenericDefault" /> + <type name="Magento\Config\Model\Config\Export\ExcludeList"> + <arguments> + <argument name="configs" xsi:type="array"> + <item name="shipping/origin/country_id" xsi:type="string">1</item> + <item name="shipping/origin/region_id" xsi:type="string">1</item> + <item name="shipping/origin/postcode" xsi:type="string">1</item> + <item name="shipping/origin/city" xsi:type="string">1</item> + <item name="shipping/origin/street_line1" xsi:type="string">1</item> + <item name="shipping/origin/street_line2" xsi:type="string">1</item> + </argument> + </arguments> + </type> </config> diff --git a/app/code/Magento/Sitemap/composer.json b/app/code/Magento/Sitemap/composer.json index 9f556178fc2cccfd070c272a89164e438f1d33e8..307691a67d2e24617f381803d8a3f96a2c010d1b 100644 --- a/app/code/Magento/Sitemap/composer.json +++ b/app/code/Magento/Sitemap/composer.json @@ -12,6 +12,9 @@ "magento/module-media-storage": "100.2.*", "magento/framework": "100.2.*" }, + "suggest": { + "magento/module-config": "100.2.*" + }, "type": "magento2-module", "version": "100.2.0-dev", "license": [ diff --git a/app/code/Magento/Sitemap/etc/di.xml b/app/code/Magento/Sitemap/etc/di.xml index 7ce1fdee7b5b6a1de7947da82c2a1b93bf470bfc..dfe34a25fb7ba37766ae0247194df1cc85bdee28 100644 --- a/app/code/Magento/Sitemap/etc/di.xml +++ b/app/code/Magento/Sitemap/etc/di.xml @@ -11,4 +11,11 @@ <argument name="resource" xsi:type="object">Magento\Sitemap\Model\ResourceModel\Sitemap</argument> </arguments> </type> + <type name="Magento\Config\Model\Config\Export\ExcludeList"> + <arguments> + <argument name="configs" xsi:type="array"> + <item name="sitemap/generate/error_email" xsi:type="string">1</item> + </argument> + </arguments> + </type> </config> diff --git a/app/code/Magento/Theme/Model/Design/Config/DataProvider.php b/app/code/Magento/Theme/Model/Design/Config/DataProvider.php index c433b02fab240d736637bf41b571c2eb75d193bc..977295c1f80c466899284f0e8e18c373a6007445 100644 --- a/app/code/Magento/Theme/Model/Design/Config/DataProvider.php +++ b/app/code/Magento/Theme/Model/Design/Config/DataProvider.php @@ -5,9 +5,13 @@ */ namespace Magento\Theme\Model\Design\Config; +use Magento\Framework\App\Config\ScopeCodeResolver; +use Magento\Framework\App\ObjectManager; use Magento\Theme\Model\ResourceModel\Design\Config\Collection; use Magento\Theme\Model\ResourceModel\Design\Config\CollectionFactory; use Magento\Ui\DataProvider\AbstractDataProvider; +use Magento\Config\Model\Config\Reader\Source\Deployed\SettingChecker; +use Magento\Framework\App\RequestInterface; class DataProvider extends AbstractDataProvider { @@ -31,6 +35,21 @@ class DataProvider extends AbstractDataProvider */ private $metadataLoader; + /** + * @var SettingChecker + */ + private $settingChecker; + + /** + * @var RequestInterface + */ + private $request; + + /** + * @var ScopeCodeResolver + */ + private $scopeCodeResolver; + /** * @param string $name * @param string $primaryFieldName @@ -78,4 +97,84 @@ class DataProvider extends AbstractDataProvider $this->loadedData = $this->dataLoader->getData(); return $this->loadedData; } + + /** + * {@inheritdoc} + */ + public function getMeta() + { + $meta = parent::getMeta(); + if (!isset($meta['other_settings']['children'])) { + return $meta; + } + + $request = $this->getRequest()->getParams(); + if (!isset($request['scope'])) { + return $meta; + } + + $scope = $request['scope']; + $scopeCode = $this->getScopeCodeResolver()->resolve( + $scope, + isset($request['scope_id']) ? $request['scope_id'] : null + ); + + foreach ($meta['other_settings']['children'] as $settingGroupName => &$settingGroup) { + foreach ($settingGroup['children'] as $fieldName => &$field) { + $path = sprintf( + 'design/%s/%s', + $settingGroupName, + preg_replace('/^' . $settingGroupName . '_/', '', $fieldName) + ); + $isReadOnly = $this->getSettingChecker()->isReadOnly( + $path, + $scope, + $scopeCode + ); + + if ($isReadOnly) { + $field['arguments']['data']['config']['disabled'] = true; + $field['arguments']['data']['config']['is_disable_inheritance'] = true; + } + } + } + + return $meta; + } + + /** + * @deprecated + * @return ScopeCodeResolver + */ + private function getScopeCodeResolver() + { + if ($this->scopeCodeResolver === null) { + $this->scopeCodeResolver = ObjectManager::getInstance()->get(ScopeCodeResolver::class); + } + return $this->scopeCodeResolver; + } + + /** + * @deprecated + * @return SettingChecker + */ + private function getSettingChecker() + { + if ($this->settingChecker === null) { + $this->settingChecker = ObjectManager::getInstance()->get(SettingChecker::class); + } + return $this->settingChecker; + } + + /** + * @deprecated + * @return RequestInterface + */ + private function getRequest() + { + if ($this->request === null) { + $this->request = ObjectManager::getInstance()->get(RequestInterface::class); + } + return $this->request; + } } diff --git a/app/code/Magento/Theme/Test/Unit/Model/Design/Config/DataProviderTest.php b/app/code/Magento/Theme/Test/Unit/Model/Design/Config/DataProviderTest.php index 3ba61da79b5ca00073a14e11e7ab14c46b7af031..a5db6156efea32c56009620d9a8f2110f070bfe1 100644 --- a/app/code/Magento/Theme/Test/Unit/Model/Design/Config/DataProviderTest.php +++ b/app/code/Magento/Theme/Test/Unit/Model/Design/Config/DataProviderTest.php @@ -5,11 +5,18 @@ */ namespace Magento\Theme\Test\Unit\Model\Design\Config; +use Magento\Config\Model\Config\Reader\Source\Deployed\SettingChecker; +use Magento\Framework\App\Config\ScopeCodeResolver; +use Magento\Framework\App\RequestInterface; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; use Magento\Theme\Model\Design\Config\DataLoader; use Magento\Theme\Model\Design\Config\DataProvider; use Magento\Theme\Model\Design\Config\MetadataLoader; use Magento\Theme\Model\ResourceModel\Design\Config\Collection; +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ class DataProviderTest extends \PHPUnit_Framework_TestCase { /** @@ -32,8 +39,29 @@ class DataProviderTest extends \PHPUnit_Framework_TestCase */ protected $collection; + /** + * @var ObjectManager + */ + private $objectManager; + + /** + * @var RequestInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $requestMock; + + /** + * @var ScopeCodeResolver|\PHPUnit_Framework_MockObject_MockObject + */ + private $scopeCodeResolverMock; + + /** + * @var SettingChecker|\PHPUnit_Framework_MockObject_MockObject + */ + private $settingCheckerMock; + protected function setUp() { + $this->objectManager = new ObjectManager($this); $this->dataLoader = $this->getMockBuilder(\Magento\Theme\Model\Design\Config\DataProvider\DataLoader::class) ->disableOriginalConstructor() ->getMock(); @@ -56,6 +84,16 @@ class DataProviderTest extends \PHPUnit_Framework_TestCase ->method('create') ->willReturn($this->collection); + $this->requestMock = $this->getMockBuilder(RequestInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->scopeCodeResolverMock = $this->getMockBuilder(ScopeCodeResolver::class) + ->disableOriginalConstructor() + ->getMock(); + $this->settingCheckerMock = $this->getMockBuilder(SettingChecker::class) + ->disableOriginalConstructor() + ->getMock(); + $this->model = new DataProvider( 'scope', 'scope', @@ -64,6 +102,21 @@ class DataProviderTest extends \PHPUnit_Framework_TestCase $this->metadataLoader, $collectionFactory ); + $this->objectManager->setBackwardCompatibleProperty( + $this->model, + 'request', + $this->requestMock + ); + $this->objectManager->setBackwardCompatibleProperty( + $this->model, + 'scopeCodeResolver', + $this->scopeCodeResolverMock + ); + $this->objectManager->setBackwardCompatibleProperty( + $this->model, + 'settingChecker', + $this->settingCheckerMock + ); } public function testGetData() @@ -78,4 +131,119 @@ class DataProviderTest extends \PHPUnit_Framework_TestCase $this->assertEquals($data, $this->model->getData()); } + + /** + * @param array $inputMeta + * @param array $expectedMeta + * @param array $request + * @dataProvider getMetaDataProvider + */ + public function testGetMeta(array $inputMeta, array $expectedMeta, array $request) + { + $this->requestMock->expects($this->any()) + ->method('getParams') + ->willReturn($request); + $this->scopeCodeResolverMock->expects($this->any()) + ->method('resolve') + ->with('stores', 1) + ->willReturn('default'); + $this->settingCheckerMock->expects($this->any()) + ->method('isReadOnly') + ->withConsecutive( + ['design/head/welcome', 'stores', 'default'], + ['design/head/logo', 'stores', 'default'], + ['design/head/head', 'stores', 'default'] + ) + ->willReturnOnConsecutiveCalls( + true, + false, + true + ); + + $this->objectManager->setBackwardCompatibleProperty( + $this->model, + 'meta', + $inputMeta + ); + + $this->assertSame($expectedMeta, $this->model->getMeta()); + } + + /** + * @return array + */ + public function getMetaDataProvider() + { + return [ + [ + [ + 'option1' + ], + [ + 'option1' + ], + [ + 'scope' => 'default' + ] + ], + [ + [ + 'other_settings' => [ + 'children' => [ + 'head' => [ + 'children' => [ + 'head_welcome' => [ + + ], + 'head_logo' => [ + + ], + 'head_head' => [ + + ] + ] + ] + ] + ] + ], + [ + 'other_settings' => [ + 'children' => [ + 'head' => [ + 'children' => [ + 'head_welcome' => [ + 'arguments' => [ + 'data' => [ + 'config' => [ + 'disabled' => true, + 'is_disable_inheritance' => true, + ] + ] + ] + ], + 'head_logo' => [ + + ], + 'head_head' => [ + 'arguments' => [ + 'data' => [ + 'config' => [ + 'disabled' => true, + 'is_disable_inheritance' => true, + ] + ] + ] + ] + ] + ] + ] + ] + ], + [ + 'scope' => 'stores', + 'scope_id' => 1 + ] + ] + ]; + } } diff --git a/app/code/Magento/Ups/composer.json b/app/code/Magento/Ups/composer.json index c20dd28ba88876a70c04ea49038b94f82d3d9b06..e3cf2bce1813a0d6625f0b5dce2ac4cf0d8b5640 100644 --- a/app/code/Magento/Ups/composer.json +++ b/app/code/Magento/Ups/composer.json @@ -12,6 +12,9 @@ "magento/module-quote": "100.2.*", "magento/framework": "100.2.*" }, + "suggest": { + "magento/module-config": "100.2.*" + }, "type": "magento2-module", "version": "100.2.0-dev", "license": [ diff --git a/app/code/Magento/Ups/etc/di.xml b/app/code/Magento/Ups/etc/di.xml new file mode 100644 index 0000000000000000000000000000000000000000..324349a82994fdadec4d4db33cdb66f28029b137 --- /dev/null +++ b/app/code/Magento/Ups/etc/di.xml @@ -0,0 +1,21 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> + <type name="Magento\Config\Model\Config\Export\ExcludeList"> + <arguments> + <argument name="configs" xsi:type="array"> + <item name="carriers/ups/username" xsi:type="string">1</item> + <item name="carriers/ups/password" xsi:type="string">1</item> + <item name="carriers/ups/access_license_number" xsi:type="string">1</item> + <item name="carriers/ups/tracking_xml_url" xsi:type="string">1</item> + <item name="carriers/ups/gateway_xml_url" xsi:type="string">1</item> + <item name="carriers/ups/shipper_number" xsi:type="string">1</item> + </argument> + </arguments> + </type> +</config> diff --git a/app/code/Magento/Usps/etc/di.xml b/app/code/Magento/Usps/etc/di.xml new file mode 100644 index 0000000000000000000000000000000000000000..450a24ad8b9f734ec85d1640ea1df8bf75e12225 --- /dev/null +++ b/app/code/Magento/Usps/etc/di.xml @@ -0,0 +1,19 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> + <type name="Magento\Config\Model\Config\Export\ExcludeList"> + <arguments> + <argument name="configs" xsi:type="array"> + <item name="carriers/usps/userid" xsi:type="string">1</item> + <item name="carriers/usps/password" xsi:type="string">1</item> + <item name="carriers/usps/gateway_url" xsi:type="string">1</item> + <item name="carriers/usps/gateway_secure_url" xsi:type="string">1</item> + </argument> + </arguments> + </type> +</config> diff --git a/app/etc/di.xml b/app/etc/di.xml index e9767eccb28114ffa66bc8bc84829d92b1620e99..1b347574f4b2a2d3f010cb9a712da6cd6c7dbec2 100755 --- a/app/etc/di.xml +++ b/app/etc/di.xml @@ -433,7 +433,7 @@ </virtualType> <virtualType name="layoutObjectArgumentInterpreter" type="Magento\Framework\View\Layout\Argument\Interpreter\DataObject"> <arguments> - <argument name="expectedClass" xsi:type="string">Magento\Framework\Data\CollectionDataSourceInterface</argument> + <argument name="expectedClass" xsi:type="string">Magento\Framework\View\Element\Block\ArgumentInterface</argument> </arguments> </virtualType> <type name="Magento\Framework\View\Layout\Argument\Interpreter\NamedParams"> diff --git a/dev/tests/integration/testsuite/Magento/Backend/Block/Widget/Grid/MassactionTest.php b/dev/tests/integration/testsuite/Magento/Backend/Block/Widget/Grid/MassactionTest.php index 1bbf40ca6dc72999e4d2bd0c65987d25ee2e6f2a..8c0a7dd201d62abd7e823de4c31c2e9aec57f954 100644 --- a/dev/tests/integration/testsuite/Magento/Backend/Block/Widget/Grid/MassactionTest.php +++ b/dev/tests/integration/testsuite/Magento/Backend/Block/Widget/Grid/MassactionTest.php @@ -3,11 +3,10 @@ * Copyright © 2016 Magento. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - namespace Magento\Backend\Block\Widget\Grid; +use Magento\TestFramework\App\State; + /** * @magentoAppArea adminhtml * @magentoComponentsDir Magento/Backend/Block/_files/design @@ -25,18 +24,43 @@ class MassactionTest extends \PHPUnit_Framework_TestCase */ protected $_layout; + /** + * @var \Magento\Framework\ObjectManagerInterface + */ + private $objectManager; + + /** + * @var string + */ + private $mageMode; + protected function setUp() { - $this->markTestIncomplete('MAGETWO-6406'); - parent::setUp(); - $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + + $this->mageMode = $this->objectManager->get(State::class)->getMode(); + /** @var \Magento\Theme\Model\Theme\Registration $registration */ - $registration = $objectManager->get(\Magento\Theme\Model\Theme\Registration::class); + $registration = $this->objectManager->get(\Magento\Theme\Model\Theme\Registration::class); $registration->register(); - $objectManager->get(\Magento\Framework\View\DesignInterface::class)->setDesignTheme('BackendTest/test_default'); - $this->_layout = $objectManager->create( + $this->objectManager->get(\Magento\Framework\View\DesignInterface::class) + ->setDesignTheme('BackendTest/test_default'); + } + + protected function tearDown() + { + $this->objectManager->get(State::class)->setMode($this->mageMode); + } + + /** + * @param string $mageMode + */ + private function loadLayout($mageMode = State::MODE_DEVELOPER) + { + $this->objectManager->get(State::class)->setMode($mageMode); + $this->_layout = $this->objectManager->create( \Magento\Framework\View\LayoutInterface::class, ['area' => 'adminhtml'] ); @@ -48,20 +72,14 @@ class MassactionTest extends \PHPUnit_Framework_TestCase $this->assertNotFalse($this->_block, 'Could not load the block for testing'); } - /** - * @covers \Magento\Backend\Block\Widget\Grid\Massaction::getItems - * @covers \Magento\Backend\Block\Widget\Grid\Massaction::getCount - * @covers \Magento\Backend\Block\Widget\Grid\Massaction::getItemsJson - * @covers \Magento\Backend\Block\Widget\Grid\Massaction::isAvailable - */ public function testMassactionDefaultValues() { + $this->loadLayout(); + /** @var $blockEmpty \Magento\Backend\Block\Widget\Grid\Massaction */ - $blockEmpty = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get( - \Magento\Framework\View\LayoutInterface::class - )->createBlock( - \Magento\Backend\Block\Widget\Grid\Massaction::class - ); + $blockEmpty = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() + ->get(\Magento\Framework\View\LayoutInterface::class) + ->createBlock(\Magento\Backend\Block\Widget\Grid\Massaction::class); $this->assertEmpty($blockEmpty->getItems()); $this->assertEquals(0, $blockEmpty->getCount()); $this->assertSame('[]', $blockEmpty->getItemsJson()); @@ -71,6 +89,8 @@ class MassactionTest extends \PHPUnit_Framework_TestCase public function testGetJavaScript() { + $this->loadLayout(); + $javascript = $this->_block->getJavaScript(); $expectedItemFirst = '#"option_id1":{"label":"Option One",' . @@ -86,6 +106,8 @@ class MassactionTest extends \PHPUnit_Framework_TestCase public function testGetJavaScriptWithAddedItem() { + $this->loadLayout(); + $input = [ 'id' => 'option_id3', 'label' => 'Option Three', @@ -100,20 +122,49 @@ class MassactionTest extends \PHPUnit_Framework_TestCase $this->assertRegExp($expected, $this->_block->getJavaScript()); } - public function testGetCount() + /** + * @param string $mageMode + * @param int $expectedCount + * @dataProvider getCountDataProvider + */ + public function testGetCount($mageMode, $expectedCount) { - $this->assertEquals(2, $this->_block->getCount()); + $this->loadLayout($mageMode); + $this->assertEquals($expectedCount, $this->_block->getCount()); + } + + /** + * @return array + */ + public function getCountDataProvider() + { + return [ + [ + 'mageMode' => State::MODE_DEVELOPER, + 'expectedCount' => 3, + ], + [ + 'mageMode' => State::MODE_DEFAULT, + 'expectedCount' => 3, + ], + [ + 'mageMode' => State::MODE_PRODUCTION, + 'expectedCount' => 2, + ], + ]; } /** - * @param $itemId - * @param $expectedItem + * @param string $itemId + * @param array $expectedItem * @dataProvider getItemsDataProvider */ public function testGetItems($itemId, $expectedItem) { + $this->loadLayout(); + $items = $this->_block->getItems(); - $this->assertCount(2, $items); + $this->assertCount(3, $items); $this->assertArrayHasKey($itemId, $items); $actualItem = $items[$itemId]; @@ -149,19 +200,29 @@ class MassactionTest extends \PHPUnit_Framework_TestCase 'selected' => false, 'blockname' => '' ] + ], + [ + 'option_id3', + [ + 'id' => 'option_id3', + 'label' => 'Option Three', + 'url' => '#http:\/\/localhost\/index\.php\/(?:key\/([\w\d]+)\/)?#', + 'selected' => false, + 'blockname' => '' + ] ] ]; } public function testGridContainsMassactionColumn() { + $this->loadLayout(); $this->_layout->getBlock('admin.test.grid')->toHtml(); - $gridMassactionColumn = $this->_layout->getBlock( - 'admin.test.grid' - )->getColumnSet()->getChildBlock( - 'massaction' - ); + $gridMassactionColumn = $this->_layout->getBlock('admin.test.grid') + ->getColumnSet() + ->getChildBlock('massaction'); + $this->assertNotNull($gridMassactionColumn, 'Massaction column does not exist in the grid column set'); $this->assertInstanceOf( \Magento\Backend\Block\Widget\Grid\Column::class, diff --git a/dev/tests/integration/testsuite/Magento/Backend/Block/_files/design/adminhtml/Magento/test_default/Magento_Backend/layout/layout_test_grid_handle.xml b/dev/tests/integration/testsuite/Magento/Backend/Block/_files/design/adminhtml/Magento/test_default/Magento_Backend/layout/layout_test_grid_handle.xml index 5f9a80a5a3dbda9a86915c99aefce1066074ffb6..c072532a1c27c3471602abbb2442611fa49c6a59 100644 --- a/dev/tests/integration/testsuite/Magento/Backend/Block/_files/design/adminhtml/Magento/test_default/Magento_Backend/layout/layout_test_grid_handle.xml +++ b/dev/tests/integration/testsuite/Magento/Backend/Block/_files/design/adminhtml/Magento/test_default/Magento_Backend/layout/layout_test_grid_handle.xml @@ -6,60 +6,68 @@ */ --> <layout xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> - <block class="Magento\Backend\Block\Widget\Grid" name="admin.test.grid" Output="1"> + <block class="Magento\Backend\Block\Widget\Grid" name="admin.test.grid"> <arguments> - <dataSource type="object">Magento\Framework\Data\Collection</dataSource> + <argument name="dataSource" xsi:type="object">Magento\Framework\Data\Collection</argument> </arguments> - <block class="Magento\Backend\Block\Widget\Grid\ColumnSet" as="grid.columnSet" name="admin.test.grid.columnSet" Output="1"> - <block class="Magento\Backend\Block\Widget\Grid\Column" as="product_name" Output="1"> + <block class="Magento\Backend\Block\Widget\Grid\ColumnSet" as="grid.columnSet" name="admin.test.grid.columnSet"> + <block class="Magento\Backend\Block\Widget\Grid\Column" as="product_name"> <arguments> - <header>Product name 1</header> - <index>product_name</index> - <type>text</type> + <argument name="header" xsi:type="string" translate="true">Product name 1</argument> + <argument name="id" xsi:type="string">product_name</argument> + <argument name="index" xsi:type="string">product_name</argument> + <argument name="type" xsi:type="string">text</argument> </arguments> </block> - <block class="Magento\Backend\Block\Widget\Grid\Column" as="description" output="1"> + <block class="Magento\Backend\Block\Widget\Grid\Column" as="description"> <arguments> - <header>User Description</header> - <index>description</index> - <type>text</type> + <argument name="header" xsi:type="string" translate="true">User Description</argument> + <argument name="id" xsi:type="string">description</argument> + <argument name="index" xsi:type="string">description</argument> + <argument name="type" xsi:type="string">text</argument> </arguments> </block> - <block class="Magento\Backend\Block\Widget\Grid\Column" as="qty" output="1"> + <block class="Magento\Backend\Block\Widget\Grid\Column" as="qty"> <arguments> - <header>Qty</header> - <index>qty</index> - <type>number</type> - <width>60px</width> + <argument name="header" xsi:type="string" translate="true">Qty</argument> + <argument name="id" xsi:type="string">qty</argument> + <argument name="index" xsi:type="string">qty</argument> + <argument name="type" xsi:type="string">number</argument> + <argument name="width" xsi:type="string">60</argument> </arguments> </block> - <block class="Magento\Backend\Block\Widget\Grid\Column" as="added_at" output="1"> + <block class="Magento\Backend\Block\Widget\Grid\Column" as="added_at"> <arguments> - <header>Date Added</header> - <index>added_at</index> - <gmtoffset>1</gmtoffset> - <type>date</type> + <argument name="header" xsi:type="string" translate="true">Date Added</argument> + <argument name="id" xsi:type="string">added_at</argument> + <argument name="index" xsi:type="string">added_at</argument> + <argument name="type" xsi:type="string">date</argument> + <argument name="gmtoffset" xsi:type="string">1</argument> </arguments> </block> </block> - <block class="Magento\Backend\Block\Widget\Grid\Massaction" as="grid.massaction" name='admin.test.grid.massaction' output="1"> + <block class="Magento\Backend\Block\Widget\Grid\Massaction" as="grid.massaction" name='admin.test.grid.massaction'> <arguments> - <massaction_id_field>test_id</massaction_id_field> - <massaction_id_filter>test_id</massaction_id_filter> - <form_field_name>test</form_field_name> - <use_select_all>1</use_select_all> - <options> - <option_id1> - <label>Option One</label> - <url>*/*/option1</url> - <complete>Test</complete> - </option_id1> - <option_id2> - <label>Option Two</label> - <url>*/*/option2</url> - <confirm>Are you sure?</confirm> - </option_id2> - </options> + <argument name="massaction_id_field" xsi:type="string">test_id</argument> + <argument name="form_field_name" xsi:type="string">test_id</argument> + <argument name="use_select_all" xsi:type="string">1</argument> + <argument name="options" xsi:type="array"> + <item name="option_id1" xsi:type="array"> + <item name="label" xsi:type="string" translate="true">Option One</item> + <item name="url" xsi:type="string">*/*/option1</item> + <item name="complete" xsi:type="string">Test</item> + </item> + <item name="option_id2" xsi:type="array"> + <item name="label" xsi:type="string" translate="true">Option Two</item> + <item name="url" xsi:type="string">*/*/option2</item> + <item name="confirm" xsi:type="string">Are you sure?</item> + </item> + <item name="option_id3" xsi:type="array"> + <item name="label" xsi:type="string" translate="true">Option Three</item> + <item name="url" xsi:type="string">*/*/option3</item> + <item name="visible" xsi:type="object">Magento\Backend\Block\Cache\Grid\Massaction\ProductionModeVisibilityChecker</item> + </item> + </argument> </arguments> </block> </block> diff --git a/dev/tests/integration/testsuite/Magento/Backend/Controller/Adminhtml/Cache/MassActionTest.php b/dev/tests/integration/testsuite/Magento/Backend/Controller/Adminhtml/Cache/MassActionTest.php index e9fbb76f513d545eb4d710423bfd58a0cbb3d03e..a1034643ae3dcd6064caea87cf13f052db967f0f 100644 --- a/dev/tests/integration/testsuite/Magento/Backend/Controller/Adminhtml/Cache/MassActionTest.php +++ b/dev/tests/integration/testsuite/Magento/Backend/Controller/Adminhtml/Cache/MassActionTest.php @@ -3,13 +3,13 @@ * Copyright © 2016 Magento. All rights reserved. * See COPYING.txt for license details. */ - namespace Magento\Backend\Controller\Adminhtml\Cache; use Magento\Framework\App\Cache\State; use Magento\TestFramework\Helper\Bootstrap; use Magento\Framework\Config\File\ConfigFilePool; use Magento\Framework\App\Filesystem\DirectoryList; +use Magento\TestFramework\App\State as AppState; class MassActionTest extends \Magento\TestFramework\TestCase\AbstractBackendController { @@ -20,6 +20,11 @@ class MassActionTest extends \Magento\TestFramework\TestCase\AbstractBackendCont */ private static $typesConfig; + /** + * @var string + */ + private $mageState; + public static function setUpBeforeClass() { /** @var \Magento\Framework\App\DeploymentConfig $config */ @@ -27,8 +32,15 @@ class MassActionTest extends \Magento\TestFramework\TestCase\AbstractBackendCont self::$typesConfig = $config->get(State::CACHE_KEY); } + protected function setUp() + { + parent::setUp(); + $this->mageState = Bootstrap::getObjectManager()->get(AppState::class)->getMode(); + } + protected function tearDown() { + Bootstrap::getObjectManager()->get(AppState::class)->setMode($this->mageState); /** @var $cacheState \Magento\Framework\App\Cache\StateInterface */ $cacheState = Bootstrap::getObjectManager()->get(\Magento\Framework\App\Cache\StateInterface::class); foreach (self::$typesConfig as $type => $value) { @@ -42,7 +54,7 @@ class MassActionTest extends \Magento\TestFramework\TestCase\AbstractBackendCont * @dataProvider massActionsDataProvider * @param array $typesToEnable */ - public function testMassEnableAction($typesToEnable = []) + public function testMassEnableActionDeveloperMode($typesToEnable = []) { $this->setAll(false); @@ -53,16 +65,33 @@ class MassActionTest extends \Magento\TestFramework\TestCase\AbstractBackendCont if (in_array($cacheType, $typesToEnable)) { $this->assertEquals(1, $cacheState, "Type '{$cacheType}' has not been enabled"); } else { - $this->assertEquals(0, $cacheState, "Type '{$cacheType}' has not been enabled"); + $this->assertEquals(0, $cacheState, "Type '{$cacheType}' must remain disabled"); } } } + /** + * @dataProvider massActionsDataProvider + * @param array $typesToEnable + */ + public function testMassEnableActionProductionMode($typesToEnable = []) + { + Bootstrap::getObjectManager()->get(AppState::class)->setMode(AppState::MODE_PRODUCTION); + $this->setAll(false); + + $this->getRequest()->setParams(['types' => $typesToEnable]); + $this->dispatch('backend/admin/cache/massEnable'); + + foreach ($this->getCacheStates() as $cacheType => $cacheState) { + $this->assertEquals(0, $cacheState, "Type '{$cacheType}' must remain disabled"); + } + } + /** * @dataProvider massActionsDataProvider * @param array $typesToDisable */ - public function testMassDisableAction($typesToDisable = []) + public function testMassDisableActionDeveloperMode($typesToDisable = []) { $this->setAll(true); @@ -78,6 +107,23 @@ class MassActionTest extends \Magento\TestFramework\TestCase\AbstractBackendCont } } + /** + * @dataProvider massActionsDataProvider + * @param array $typesToDisable + */ + public function testMassDisableActionProductionMode($typesToDisable = []) + { + Bootstrap::getObjectManager()->get(AppState::class)->setMode(AppState::MODE_PRODUCTION); + $this->setAll(true); + + $this->getRequest()->setParams(['types' => $typesToDisable]); + $this->dispatch('backend/admin/cache/massDisable'); + + foreach ($this->getCacheStates() as $cacheType => $cacheState) { + $this->assertEquals(1, $cacheState, "Type '{$cacheType}' must remain enabled"); + } + } + /** * Retrieve cache states (enabled/disabled) information * diff --git a/dev/tests/integration/testsuite/Magento/Config/Model/Config/Processor/EnvironmentPlaceholderTest.php b/dev/tests/integration/testsuite/Magento/Config/Model/Config/Processor/EnvironmentPlaceholderTest.php new file mode 100644 index 0000000000000000000000000000000000000000..1e2b6aacc931446cee8f42accb17d684c4bb413c --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Config/Model/Config/Processor/EnvironmentPlaceholderTest.php @@ -0,0 +1,103 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Config\Model\Config\Processor; + +use Magento\Framework\ObjectManagerInterface; + +class EnvironmentPlaceholderTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var ObjectManagerInterface + */ + private $objectManager; + + /** + * @var EnvironmentPlaceholder + */ + private $model; + + /** + * @var array + */ + private $env = []; + + protected function setUp() + { + $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + $this->model = $this->objectManager->get(EnvironmentPlaceholder::class); + $this->env = $_ENV; + } + + public function testProcess() + { + $_ENV = array_merge( + $_ENV, + [ + 'CONFIG__DEFAULT__WEB__UNSECURE__BASE_URL' => 'http://expected.local', + 'CONFIG__TEST__TEST__DESIGN__HEADER__WELCOME' => 'Expected header', + 'TEST__TEST__WEB__SECURE__BASE_URL' => 'http://wrong_pattern.local', + 'CONFIG__DEFAULT__GENERAL__REGION__DISPLAY_ALL' => 1 + ] + ); + $expected = [ + 'default' => [ + 'web' => [ + 'unsecure' => [ + 'base_url' => 'http://expected.local' + ], + 'secure' => [ + 'base_url' => 'https://original.local' + ] + ], + 'general' => [ + 'region' => [ + 'display_all' => 1 + ], + ], + ], + 'test' => [ + 'test' => [ + 'design' => [ + 'header' => [ + 'welcome' => 'Expected header' + ] + ], + ], + ] + ]; + $config = [ + 'default' => [ + 'web' => [ + 'unsecure' => [ + 'base_url' => 'http://original.local', + ], + 'secure' => [ + 'base_url' => 'https://original.local' + ] + ] + ], + 'test' => [ + 'test' => [ + 'design' => [ + 'header' => [ + 'welcome' => 'Original header' + ] + ], + ], + ] + ]; + + $this->assertSame( + $expected, + $this->model->process($config) + ); + } + + protected function tearDown() + { + $_ENV = $this->env; + } +} diff --git a/dev/tests/integration/testsuite/Magento/Deploy/Console/Command/App/ApplicationDumpCommandTest.php b/dev/tests/integration/testsuite/Magento/Deploy/Console/Command/App/ApplicationDumpCommandTest.php index a229b64bb7dd10ef29acd5e824dfb5a9b28bfcc8..170d186e20af812a1c40bc4937559c6d5342942a 100644 --- a/dev/tests/integration/testsuite/Magento/Deploy/Console/Command/App/ApplicationDumpCommandTest.php +++ b/dev/tests/integration/testsuite/Magento/Deploy/Console/Command/App/ApplicationDumpCommandTest.php @@ -7,7 +7,6 @@ namespace Magento\Deploy\Console\Command\App; use Magento\Framework\App\DeploymentConfig; use Magento\Framework\App\Filesystem\DirectoryList; -use Magento\Framework\App\ObjectManager; use Magento\Framework\Config\File\ConfigFilePool; use Magento\Framework\Filesystem\DriverPool; use Magento\Framework\ObjectManagerInterface; @@ -18,37 +17,73 @@ use Symfony\Component\Console\Output\OutputInterface; class ApplicationDumpCommandTest extends \PHPUnit_Framework_TestCase { /** - * @var ApplicationDumpCommand + * @var ObjectManagerInterface */ - private $command; + private $objectManager; /** - * @var ObjectManagerInterface + * @var DeploymentConfig\Reader */ - private $objectManager; + private $reader; public function setUp() { - $this->command = Bootstrap::getObjectManager()->get(ApplicationDumpCommand::class); $this->objectManager = Bootstrap::getObjectManager(); + $this->reader = $this->objectManager->get(DeploymentConfig\Reader::class); } + /** + * @magentoDbIsolation enabled + * @magentoDataFixture Magento/Deploy/_files/config_data.php + */ public function testExecute() { - $inputMock = $this->getMock(InputInterface::class); + $this->objectManager->configure([ + \Magento\Config\Model\Config\Export\ExcludeList::class => [ + 'arguments' => [ + 'configs' => [ + 'web/test/test_value_1' => '', + 'web/test/test_value_2' => '', + 'web/test/test_sensitive' => '1', + ], + ], + ], + ]); + + $comment = 'The configuration file doesn\'t contain sensitive data for security reasons. ' + . 'Sensitive data can be stored in the following environment variables:' + . "\nCONFIG__DEFAULT__WEB__TEST__TEST_SENSITIVE for web/test/test_sensitive"; $outputMock = $this->getMock(OutputInterface::class); - $outputMock->expects($this->once()) + $outputMock->expects($this->at(0)) + ->method('writeln') + ->with(['system' => $comment]); + $outputMock->expects($this->at(1)) ->method('writeln') ->with('<info>Done.</info>'); - $this->assertEquals(0, $this->command->run($inputMock, $outputMock)); + + /** @var ApplicationDumpCommand command */ + $command = $this->objectManager->create(ApplicationDumpCommand::class); + $command->run($this->getMock(InputInterface::class), $outputMock); + + $config = $this->reader->loadConfigFile(ConfigFilePool::APP_CONFIG, $this->getFileName()); + + $this->assertArrayHasKey( + 'test_value_1', + $config['system']['default']['web']['test'] + ); + $this->assertArrayHasKey( + 'test_value_2', + $config['system']['default']['web']['test'] + ); + $this->assertArrayNotHasKey( + 'test_sensitive', + $config['system']['default']['web']['test'] + ); } public function tearDown() { - /** @var ConfigFilePool $configFilePool */ - $configFilePool = $this->objectManager->get(ConfigFilePool::class); - $filePool = $configFilePool->getInitialFilePools(); - $file = $filePool[ConfigFilePool::LOCAL][ConfigFilePool::APP_CONFIG]; + $file = $this->getFileName(); /** @var DirectoryList $dirList */ $dirList = $this->objectManager->get(DirectoryList::class); $path = $dirList->getPath(DirectoryList::CONFIG); @@ -61,4 +96,16 @@ class ApplicationDumpCommandTest extends \PHPUnit_Framework_TestCase $deploymentConfig = $this->objectManager->get(DeploymentConfig::class); $deploymentConfig->resetData(); } + + /** + * @return string + */ + private function getFileName() + { + /** @var ConfigFilePool $configFilePool */ + $configFilePool = $this->objectManager->get(ConfigFilePool::class); + $filePool = $configFilePool->getInitialFilePools(); + + return $filePool[ConfigFilePool::LOCAL][ConfigFilePool::APP_CONFIG]; + } } diff --git a/dev/tests/integration/testsuite/Magento/Deploy/_files/config_data.php b/dev/tests/integration/testsuite/Magento/Deploy/_files/config_data.php new file mode 100644 index 0000000000000000000000000000000000000000..bd8e69262e1e03f98b3ce41735bbf2264e234292 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Deploy/_files/config_data.php @@ -0,0 +1,33 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +$configData = [ + 'default' => [ + '' => [ + 'web/test/test_value_1' => 'http://local2.test/', + 'web/test/test_value_2' => 5, + 'web/test/test_sensitive' => 10, + ] + ], +]; + +$objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); +$configFactory = $objectManager->create(\Magento\Config\Model\Config\Factory::class); + +foreach ($configData as $scope => $data) { + foreach ($data as $scopeCode => $scopeData) { + foreach ($scopeData as $path => $value) { + $config = $configFactory->create(); + $config->setCope($scope); + + if ($scopeCode) { + $config->setScopeCode($scopeCode); + } + + $config->setDataByPath($path, $value); + $config->save(); + } + } +} diff --git a/lib/internal/Magento/Framework/App/Config/CommentInterface.php b/lib/internal/Magento/Framework/App/Config/CommentInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..902d63668f368912208ed38c9d19ebb985af8b31 --- /dev/null +++ b/lib/internal/Magento/Framework/App/Config/CommentInterface.php @@ -0,0 +1,21 @@ +<?php +/** + * Provide access to data. Each Source can be responsible for each storage, where config data can be placed + * + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Framework\App\Config; + +/** + * Interface CommentInterface + */ +interface CommentInterface +{ + /** + * Retrieve comment for configuration data. + * + * @return string + */ + public function get(); +} diff --git a/lib/internal/Magento/Framework/App/Config/PreProcessorComposite.php b/lib/internal/Magento/Framework/App/Config/PreProcessorComposite.php new file mode 100644 index 0000000000000000000000000000000000000000..10c355d290dfc43f818fa3799ad8b46807d7e6cd --- /dev/null +++ b/lib/internal/Magento/Framework/App/Config/PreProcessorComposite.php @@ -0,0 +1,40 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Framework\App\Config; + +use Magento\Framework\App\Config\Spi\PreProcessorInterface; + +/** + * Class PreProcessorComposite + */ +class PreProcessorComposite implements PreProcessorInterface +{ + /** + * @var PreProcessorInterface[] + */ + private $processors = []; + + /** + * @param PreProcessorInterface[] $processors + */ + public function __construct(array $processors = []) + { + $this->processors = $processors; + } + + /** + * @inheritdoc + */ + public function process(array $config) + { + /** @var PreProcessorInterface $processor */ + foreach ($this->processors as $processor) { + $config = $processor->process($config); + } + + return $config; + } +} diff --git a/lib/internal/Magento/Framework/App/Config/Spi/PreProcessorInterface.php b/lib/internal/Magento/Framework/App/Config/Spi/PreProcessorInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..1be0783d7f7deb9ec393a40eff2e2d0df3197281 --- /dev/null +++ b/lib/internal/Magento/Framework/App/Config/Spi/PreProcessorInterface.php @@ -0,0 +1,20 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Framework\App\Config\Spi; + +/** + * Allows to use custom callbacks and functions before applying fallback + */ +interface PreProcessorInterface +{ + /** + * Pre-processing of config + * + * @param array $config + * @return array + */ + public function process(array $config); +} diff --git a/lib/internal/Magento/Framework/App/DeploymentConfig/Reader.php b/lib/internal/Magento/Framework/App/DeploymentConfig/Reader.php index a1635fc4b2670d3749adbc8ac7bb88bab31904cf..d5a224d1b50b221d0afcf7c6127325d702c5217d 100644 --- a/lib/internal/Magento/Framework/App/DeploymentConfig/Reader.php +++ b/lib/internal/Magento/Framework/App/DeploymentConfig/Reader.php @@ -149,7 +149,9 @@ class Reader foreach ($initialFilePools as $initialFiles) { if (isset($initialFiles[$fileKey]) && $fileDriver->isExists($path . '/' . $initialFiles[$fileKey])) { $fileBuffer = include $path . '/' . $initialFiles[$fileKey]; - $result = array_replace_recursive($result, $fileBuffer); + if (is_array($fileBuffer)) { + $result = array_replace_recursive($result, $fileBuffer); + } } } } @@ -159,6 +161,13 @@ class Reader $result = array_replace_recursive($result, $fileBuffer); } + if ($fileDriver->isExists($path . '/' . $pathConfig)) { + $configResult = include $path . '/' . $pathConfig; + if (is_array($configResult)) { + $result = array_replace_recursive($result, $configResult); + } + } + return $result; } diff --git a/lib/internal/Magento/Framework/App/DeploymentConfig/Writer.php b/lib/internal/Magento/Framework/App/DeploymentConfig/Writer.php index 6cead0305a6c6ada899060a2a7debdbfe71f6f1a..2d8e7a14aaf55519d33376f1ee09f80b9b46cec6 100644 --- a/lib/internal/Magento/Framework/App/DeploymentConfig/Writer.php +++ b/lib/internal/Magento/Framework/App/DeploymentConfig/Writer.php @@ -94,10 +94,11 @@ class Writer * @param array $data * @param bool $override * @param string $pool + * @param array $comments * @return void * @throws FileSystemException */ - public function saveConfig(array $data, $override = false, $pool = null) + public function saveConfig(array $data, $override = false, $pool = null, array $comments = []) { foreach ($data as $fileKey => $config) { $paths = $pool ? $this->configFilePool->getPathsByPool($pool) : $this->configFilePool->getPaths(); @@ -112,7 +113,7 @@ class Writer } } - $contents = $this->formatter->format($config); + $contents = $this->formatter->format($config, $comments); try { $writeFilePath = $paths[$fileKey]; $this->filesystem->getDirectoryWrite(DirectoryList::CONFIG)->writeFile($writeFilePath, $contents); diff --git a/lib/internal/Magento/Framework/App/DeploymentConfig/Writer/FormatterInterface.php b/lib/internal/Magento/Framework/App/DeploymentConfig/Writer/FormatterInterface.php index 24e31074501f3aae16a12bed0473ccfd43d45ac0..640bcb61d2031abd4d671e6f02de0636b252b16d 100644 --- a/lib/internal/Magento/Framework/App/DeploymentConfig/Writer/FormatterInterface.php +++ b/lib/internal/Magento/Framework/App/DeploymentConfig/Writer/FormatterInterface.php @@ -12,7 +12,8 @@ interface FormatterInterface * Format deployment configuration * * @param array $data + * @param array $comments * @return string */ - public function format($data); + public function format($data, array $comments = []); } diff --git a/lib/internal/Magento/Framework/App/DeploymentConfig/Writer/PhpFormatter.php b/lib/internal/Magento/Framework/App/DeploymentConfig/Writer/PhpFormatter.php index 1ab99358cef1adacff5dc2dabc1ea884885e5d95..91bd906e3ca4d83438188e3131ba5e9537af939c 100644 --- a/lib/internal/Magento/Framework/App/DeploymentConfig/Writer/PhpFormatter.php +++ b/lib/internal/Magento/Framework/App/DeploymentConfig/Writer/PhpFormatter.php @@ -12,10 +12,26 @@ namespace Magento\Framework\App\DeploymentConfig\Writer; class PhpFormatter implements FormatterInterface { /** + * Format deployment configuration. + * If $comments is present, each item will be added + * as comment to the corresponding section + * * {@inheritdoc} */ - public function format($data) + public function format($data, array $comments = []) { + if (!empty($comments) && is_array($data)) { + $elements = []; + foreach ($data as $key => $value) { + $comment = ' '; + if (!empty($comments[$key])) { + $comment = " /**\n * " . str_replace("\n", "\n * ", var_export($comments[$key], true)) . "\n */\n"; + } + $space = is_array($value) ? " \n" : ' '; + $elements[] = $comment . var_export($key, true) . ' =>' . $space . var_export($value, true); + } + return "<?php\nreturn array (\n" . implode(",\n", str_replace("\n", "\n ", $elements)) . "\n);\n"; + } return "<?php\nreturn " . var_export($data, true) . ";\n"; } } diff --git a/lib/internal/Magento/Framework/App/Test/Unit/Config/PreProcessorCompositeTest.php b/lib/internal/Magento/Framework/App/Test/Unit/Config/PreProcessorCompositeTest.php new file mode 100644 index 0000000000000000000000000000000000000000..291bef1266134f7956919a2c831af4a7fd4715e2 --- /dev/null +++ b/lib/internal/Magento/Framework/App/Test/Unit/Config/PreProcessorCompositeTest.php @@ -0,0 +1,40 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Framework\App\Test\Unit\Config; + +use Magento\Framework\App\Config\PreProcessorComposite; +use Magento\Framework\App\Config\Spi\PreProcessorInterface; + +class PreProcessorCompositeTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var PreProcessorComposite + */ + private $model; + + /** + * @var PreProcessorInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $preProcessorMock; + + protected function setUp() + { + $this->preProcessorMock = $this->getMockBuilder(PreProcessorInterface::class) + ->getMockForAbstractClass(); + + $this->model = new PreProcessorComposite([$this->preProcessorMock]); + } + + public function testProcess() + { + $this->preProcessorMock->expects($this->once()) + ->method('process') + ->with(['test' => 'data']) + ->willReturn(['test' => 'data2']); + + $this->assertSame(['test' => 'data2'], $this->model->process(['test' => 'data'])); + } +} diff --git a/lib/internal/Magento/Framework/App/Test/Unit/DeploymentConfig/Writer/PhpFormatterTest.php b/lib/internal/Magento/Framework/App/Test/Unit/DeploymentConfig/Writer/PhpFormatterTest.php index a9b5bc04a12768f8de76e5cdc514e226fb4af05b..2ab8b209272f1cc882c14fb1a2dd9a15c22605f4 100644 --- a/lib/internal/Magento/Framework/App/Test/Unit/DeploymentConfig/Writer/PhpFormatterTest.php +++ b/lib/internal/Magento/Framework/App/Test/Unit/DeploymentConfig/Writer/PhpFormatterTest.php @@ -10,10 +10,128 @@ use \Magento\Framework\App\DeploymentConfig\Writer\PhpFormatter; class PhpFormatterTest extends \PHPUnit_Framework_TestCase { - public function testFormat() + /** + * @dataProvider formatWithCommentDataProvider + * @param string|array $data + * @param array $comments + * @param string $expectedResult + */ + public function testFormat($data, $comments, $expectedResult) { $formatter = new PhpFormatter(); - $data = 'test'; - $this->assertEquals("<?php\nreturn 'test';\n", $formatter->format($data)); + $this->assertEquals($expectedResult, $formatter->format($data, $comments)); + } + + /** + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ + public function formatWithCommentDataProvider() + { + $array = [ + 'ns1' => [ + 's1' => [ + 's11', + 's12' + ], + 's2' => [ + 's21', + 's22' + ], + ], + 'ns2' => [ + 's1' => [ + 's11' + ], + ], + 'ns3' => 'just text', + 'ns4' => 'just text' + ]; + $comments1 = ['ns2' => 'comment for namespace 2']; + $comments2 = [ + 'ns1' => 'comment for namespace 1', + 'ns2' => "comment for namespace 2.\nNext comment for namespace 2", + 'ns3' => 'comment for namespace 3', + 'ns4' => 'comment for namespace 4', + 'ns5' => 'comment for unexisted namespace 5', + ]; + $expectedResult1 = <<<TEXT +<?php +return array ( + 'ns1' => + array ( + 's1' => + array ( + 0 => 's11', + 1 => 's12', + ), + 's2' => + array ( + 0 => 's21', + 1 => 's22', + ), + ), + /** + * 'comment for namespace 2' + */ + 'ns2' => + array ( + 's1' => + array ( + 0 => 's11', + ), + ), + 'ns3' => 'just text', + 'ns4' => 'just text' +); + +TEXT; + $expectedResult2 = <<<TEXT +<?php +return array ( + /** + * 'comment for namespace 1' + */ + 'ns1' => + array ( + 's1' => + array ( + 0 => 's11', + 1 => 's12', + ), + 's2' => + array ( + 0 => 's21', + 1 => 's22', + ), + ), + /** + * 'comment for namespace 2. + * Next comment for namespace 2' + */ + 'ns2' => + array ( + 's1' => + array ( + 0 => 's11', + ), + ), + /** + * 'comment for namespace 3' + */ + 'ns3' => 'just text', + /** + * 'comment for namespace 4' + */ + 'ns4' => 'just text' +); + +TEXT; + return [ + ['string', [], "<?php\nreturn 'string';\n"], + ['string', ['comment'], "<?php\nreturn 'string';\n"], + [$array, [], "<?php\nreturn " . var_export($array, true) . ";\n"], + [$array, $comments1, $expectedResult1], + [$array, $comments2, $expectedResult2], + ]; } } diff --git a/lib/internal/Magento/Framework/Crontab/CrontabManager.php b/lib/internal/Magento/Framework/Crontab/CrontabManager.php new file mode 100644 index 0000000000000000000000000000000000000000..2b597b8eb638fb21af887ab7b47490f5533fafae --- /dev/null +++ b/lib/internal/Magento/Framework/Crontab/CrontabManager.php @@ -0,0 +1,199 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Framework\Crontab; + +use Magento\Framework\ShellInterface; +use Magento\Framework\Phrase; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Filesystem; +use Magento\Framework\App\Filesystem\DirectoryList; + +/** + * Manager works with cron tasks + */ +class CrontabManager implements CrontabManagerInterface +{ + /** + * @var ShellInterface + */ + private $shell; + + /** + * @var Filesystem + */ + private $filesystem; + + /** + * @param ShellInterface $shell + * @param Filesystem $filesystem + */ + public function __construct( + ShellInterface $shell, + Filesystem $filesystem + ) { + $this->shell = $shell; + $this->filesystem = $filesystem; + } + + /** + * {@inheritdoc} + */ + public function getTasks() + { + $this->checkSupportedOs(); + $content = $this->getCrontabContent(); + $pattern = '!(' . self::TASKS_BLOCK_START . ')(.*?)(' . self::TASKS_BLOCK_END . ')!s'; + + if (preg_match($pattern, $content, $matches)) { + $tasks = trim($matches[2], PHP_EOL); + $tasks = explode(PHP_EOL, $tasks); + return $tasks; + } + + return []; + } + + /** + * {@inheritdoc} + */ + public function saveTasks(array $tasks) + { + $this->checkSupportedOs(); + $baseDir = $this->filesystem->getDirectoryRead(DirectoryList::ROOT)->getAbsolutePath(); + $logDir = $this->filesystem->getDirectoryRead(DirectoryList::LOG)->getAbsolutePath(); + + if (!$tasks) { + throw new LocalizedException(new Phrase('List of tasks is empty')); + } + + foreach ($tasks as $key => $task) { + if (empty($task['expression'])) { + $tasks[$key]['expression'] = '* * * * *'; + } + + if (empty($task['command'])) { + throw new LocalizedException(new Phrase('Command should not be empty')); + } + + $tasks[$key]['command'] = str_replace( + ['{magentoRoot}', '{magentoLog}'], + [$baseDir, $logDir], + $task['command'] + ); + } + + $content = $this->getCrontabContent(); + $content = $this->cleanMagentoSection($content); + $content = $this->generateSection($content, $tasks); + + $this->save($content); + } + + /** + * {@inheritdoc} + * @throws LocalizedException + */ + public function removeTasks() + { + $this->checkSupportedOs(); + $content = $this->getCrontabContent(); + $content = $this->cleanMagentoSection($content); + $this->save($content); + } + + /** + * Generate Magento Tasks Section + * + * @param string $content + * @param array $tasks + * @return string + */ + private function generateSection($content, $tasks = []) + { + if ($tasks) { + $content .= self::TASKS_BLOCK_START . PHP_EOL; + foreach ($tasks as $task) { + $content .= $task['expression'] . ' ' . PHP_BINARY . ' '. $task['command'] . PHP_EOL; + } + $content .= self::TASKS_BLOCK_END . PHP_EOL; + } + + return $content; + } + + /** + * Clean Magento Tasks Section in crontab content + * + * @param string $content + * @return string + */ + private function cleanMagentoSection($content) + { + $content = preg_replace( + '!' . preg_quote(self::TASKS_BLOCK_START) . '.*?' . preg_quote(self::TASKS_BLOCK_END . PHP_EOL) . '!s', + '', + $content + ); + + return $content; + } + + /** + * Get crontab content without Magento Tasks Section + * + * In case of some exceptions the empty content is returned + * + * @return string + */ + private function getCrontabContent() + { + try { + $content = (string)$this->shell->execute('crontab -l'); + } catch (LocalizedException $e) { + return ''; + } + + return $content; + } + + /** + * Save crontab + * + * @param string $content + * @return void + * @throws LocalizedException + */ + private function save($content) + { + $content = str_replace('%', '%%', $content); + + try { + $this->shell->execute('echo "' . $content . '" | crontab -'); + } catch (LocalizedException $e) { + throw new LocalizedException( + new Phrase('Error during saving of crontab: %1', [$e->getPrevious()->getMessage()]), + $e + ); + } + } + + /** + * Check that OS is supported + * + * If OS is not supported then no possibility to work with crontab + * + * @return void + * @throws LocalizedException + */ + private function checkSupportedOs() + { + if (stripos(PHP_OS, 'WIN') === 0) { + throw new LocalizedException( + new Phrase('Your operation system is not supported to work with this command') + ); + } + } +} diff --git a/lib/internal/Magento/Framework/Crontab/CrontabManagerInterface.php b/lib/internal/Magento/Framework/Crontab/CrontabManagerInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..c00ab41d8b873a46dc27abbb1af37a23b7e7fd86 --- /dev/null +++ b/lib/internal/Magento/Framework/Crontab/CrontabManagerInterface.php @@ -0,0 +1,43 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Framework\Crontab; + +use Magento\Framework\Exception\LocalizedException; + +interface CrontabManagerInterface +{ + /**#@+ + * Constants for wrapping Magento section in crontab + */ + const TASKS_BLOCK_START = '#~ MAGENTO START'; + const TASKS_BLOCK_END = '#~ MAGENTO END'; + /**#@-*/ + + /** + * Get list of Magento Tasks + * + * @return array + * @throws LocalizedException + */ + public function getTasks(); + + /** + * Save Magento Tasks to crontab + * + * @param array $tasks + * @return void + * @throws LocalizedException + */ + public function saveTasks(array $tasks); + + /** + * Remove Magento Tasks form crontab + * + * @return void + * @throws LocalizedException + */ + public function removeTasks(); +} diff --git a/lib/internal/Magento/Framework/Crontab/README.md b/lib/internal/Magento/Framework/Crontab/README.md new file mode 100644 index 0000000000000000000000000000000000000000..bfbf194715dc8f9f359137ecb79be5a1adbd5c5e --- /dev/null +++ b/lib/internal/Magento/Framework/Crontab/README.md @@ -0,0 +1,12 @@ +Library for working with crontab + +The library has the next interfaces: +* CrontabManagerInterface +* TasksProviderInterface + +*CrontabManagerInterface* provides working with crontab: +* *getTasks* - get list of Magento cron tasks from crontab +* *saveTasks* - save Magento cron tasks to crontab +* *removeTasks* - remove Magento cron tasks from crontab + +*TasksProviderInterface* has only one method *getTasks*. This interface provides transportation the list of tasks from DI \ No newline at end of file diff --git a/lib/internal/Magento/Framework/Crontab/TasksProvider.php b/lib/internal/Magento/Framework/Crontab/TasksProvider.php new file mode 100644 index 0000000000000000000000000000000000000000..94524fd2bbd19b4395a8ba36daaa0f43c2b6c648 --- /dev/null +++ b/lib/internal/Magento/Framework/Crontab/TasksProvider.php @@ -0,0 +1,33 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Framework\Crontab; + +/** + * TasksProvider collects list of tasks + */ +class TasksProvider implements TasksProviderInterface +{ + /** + * @var array + */ + private $tasks = []; + + /** + * @param array $tasks + */ + public function __construct(array $tasks = []) + { + $this->tasks = $tasks; + } + + /** + * {@inheritdoc} + */ + public function getTasks() + { + return $this->tasks; + } +} diff --git a/lib/internal/Magento/Framework/Crontab/TasksProviderInterface.php b/lib/internal/Magento/Framework/Crontab/TasksProviderInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..bb02c30797be4cbf015c5858ac1c88825bde54a7 --- /dev/null +++ b/lib/internal/Magento/Framework/Crontab/TasksProviderInterface.php @@ -0,0 +1,16 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Framework\Crontab; + +interface TasksProviderInterface +{ + /** + * Get list of tasks + * + * @return array + */ + public function getTasks(); +} diff --git a/lib/internal/Magento/Framework/Crontab/Test/Unit/CrontabManagerTest.php b/lib/internal/Magento/Framework/Crontab/Test/Unit/CrontabManagerTest.php new file mode 100644 index 0000000000000000000000000000000000000000..7cafd386c629a4b48350d5ca4937b58fd49da4b6 --- /dev/null +++ b/lib/internal/Magento/Framework/Crontab/Test/Unit/CrontabManagerTest.php @@ -0,0 +1,333 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Framework\Crontab\Test\Unit; + +use Magento\Framework\Crontab\CrontabManager; +use Magento\Framework\Crontab\CrontabManagerInterface; +use Magento\Framework\ShellInterface; +use Magento\Framework\Phrase; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Filesystem; +use Magento\Framework\App\Filesystem\DirectoryList; +use Magento\Framework\Filesystem\Directory\ReadInterface; +use Magento\Framework\Filesystem\DriverPool; + +class CrontabManagerTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var ShellInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $shellMock; + + /** + * @var Filesystem|\PHPUnit_Framework_MockObject_MockObject + */ + private $filesystemMock; + + /** + * @var CrontabManager + */ + private $crontabManager; + + /** + * @return void + */ + protected function setUp() + { + $this->shellMock = $this->getMockBuilder(ShellInterface::class) + ->getMockForAbstractClass(); + $this->filesystemMock = $this->getMockBuilder(Filesystem::class) + ->disableOriginalClone() + ->disableOriginalConstructor() + ->getMock(); + + $this->crontabManager = new CrontabManager($this->shellMock, $this->filesystemMock); + } + + /** + * @return void + */ + public function testGetTasksNoCrontab() + { + $exception = new \Exception('crontab: no crontab for user'); + $localizedException = new LocalizedException(new Phrase('Some error'), $exception); + + $this->shellMock->expects($this->once()) + ->method('execute') + ->with('crontab -l', []) + ->willThrowException($localizedException); + + $this->assertEquals([], $this->crontabManager->getTasks()); + } + + /** + * @param string $content + * @param array $tasks + * @return void + * @dataProvider getTasksDataProvider + */ + public function testGetTasks($content, $tasks) + { + $this->shellMock->expects($this->once()) + ->method('execute') + ->with('crontab -l', []) + ->willReturn($content); + + $this->assertEquals($tasks, $this->crontabManager->getTasks()); + } + + /** + * @return array + */ + public function getTasksDataProvider() + { + return [ + [ + 'content' => '* * * * * /bin/php /var/www/cron.php' . PHP_EOL + . CrontabManagerInterface::TASKS_BLOCK_START . PHP_EOL + . '* * * * * /bin/php /var/www/magento/bin/magento cron:run' . PHP_EOL + . CrontabManagerInterface::TASKS_BLOCK_END . PHP_EOL, + 'tasks' => ['* * * * * /bin/php /var/www/magento/bin/magento cron:run'], + ], + [ + 'content' => '* * * * * /bin/php /var/www/cron.php' . PHP_EOL + . CrontabManagerInterface::TASKS_BLOCK_START . PHP_EOL + . '* * * * * /bin/php /var/www/magento/bin/magento cron:run' . PHP_EOL + . '* * * * * /bin/php /var/www/magento/bin/magento setup:cron:run' . PHP_EOL + . CrontabManagerInterface::TASKS_BLOCK_END . PHP_EOL, + 'tasks' => [ + '* * * * * /bin/php /var/www/magento/bin/magento cron:run', + '* * * * * /bin/php /var/www/magento/bin/magento setup:cron:run', + ], + ], + [ + 'content' => '* * * * * /bin/php /var/www/cron.php' . PHP_EOL, + 'tasks' => [], + ], + [ + 'content' => '', + 'tasks' => [], + ], + ]; + } + + /** + * @return void + * @expectedException \Magento\Framework\Exception\LocalizedException + * @expectedExceptionMessage Shell error + */ + public function testRemoveTasksWithException() + { + $exception = new \Exception('Shell error'); + $localizedException = new LocalizedException(new Phrase('Some error'), $exception); + + $this->shellMock->expects($this->at(0)) + ->method('execute') + ->with('crontab -l', []) + ->willReturn(''); + + $this->shellMock->expects($this->at(1)) + ->method('execute') + ->with('echo "" | crontab -', []) + ->willThrowException($localizedException); + + $this->crontabManager->removeTasks(); + } + + /** + * @param string $contentBefore + * @param string $contentAfter + * @return void + * @dataProvider removeTasksDataProvider + */ + public function testRemoveTasks($contentBefore, $contentAfter) + { + $this->shellMock->expects($this->at(0)) + ->method('execute') + ->with('crontab -l', []) + ->willReturn($contentBefore); + + $this->shellMock->expects($this->at(1)) + ->method('execute') + ->with('echo "' . $contentAfter . '" | crontab -', []); + + $this->crontabManager->removeTasks(); + } + + /** + * @return array + */ + public function removeTasksDataProvider() + { + return [ + [ + 'contentBefore' => '* * * * * /bin/php /var/www/cron.php' . PHP_EOL + . CrontabManagerInterface::TASKS_BLOCK_START . PHP_EOL + . '* * * * * /bin/php /var/www/magento/bin/magento cron:run' . PHP_EOL + . CrontabManagerInterface::TASKS_BLOCK_END . PHP_EOL, + 'contentAfter' => '* * * * * /bin/php /var/www/cron.php' . PHP_EOL + ], + [ + 'contentBefore' => '* * * * * /bin/php /var/www/cron.php' . PHP_EOL + . CrontabManagerInterface::TASKS_BLOCK_START . PHP_EOL + . '* * * * * /bin/php /var/www/magento/bin/magento cron:run' . PHP_EOL + . '* * * * * /bin/php /var/www/magento/bin/magento setup:cron:run' . PHP_EOL + . CrontabManagerInterface::TASKS_BLOCK_END . PHP_EOL, + 'contentAfter' => '* * * * * /bin/php /var/www/cron.php' . PHP_EOL + ], + [ + 'contentBefore' => '* * * * * /bin/php /var/www/cron.php' . PHP_EOL, + 'contentAfter' => '* * * * * /bin/php /var/www/cron.php' . PHP_EOL + ], + [ + 'contentBefore' => '', + 'contentAfter' => '' + ], + ]; + } + + /** + * @return void + * @expectedException \Magento\Framework\Exception\LocalizedException + * @expectedExceptionMessage List of tasks is empty + */ + public function testSaveTasksWithEmptyTasksList() + { + $baseDirMock = $this->getMockBuilder(ReadInterface::class) + ->getMockForAbstractClass(); + $baseDirMock->expects($this->once()) + ->method('getAbsolutePath') + ->willReturn('/var/www/magento2/'); + $logDirMock = $this->getMockBuilder(ReadInterface::class) + ->getMockForAbstractClass(); + $logDirMock->expects($this->once()) + ->method('getAbsolutePath') + ->willReturn('/var/www/magento2/var/log/'); + + $this->filesystemMock->expects($this->any()) + ->method('getDirectoryRead') + ->willReturnMap([ + [DirectoryList::ROOT, DriverPool::FILE, $baseDirMock], + [DirectoryList::LOG, DriverPool::FILE, $logDirMock], + ]); + + $this->crontabManager->saveTasks([]); + } + + /** + * @return void + * @expectedException \Magento\Framework\Exception\LocalizedException + * @expectedExceptionMessage Command should not be empty + */ + public function testSaveTasksWithoutCommand() + { + $baseDirMock = $this->getMockBuilder(ReadInterface::class) + ->getMockForAbstractClass(); + $baseDirMock->expects($this->once()) + ->method('getAbsolutePath') + ->willReturn('/var/www/magento2/'); + $logDirMock = $this->getMockBuilder(ReadInterface::class) + ->getMockForAbstractClass(); + $logDirMock->expects($this->once()) + ->method('getAbsolutePath') + ->willReturn('/var/www/magento2/var/log/'); + + $this->filesystemMock->expects($this->any()) + ->method('getDirectoryRead') + ->willReturnMap([ + [DirectoryList::ROOT, DriverPool::FILE, $baseDirMock], + [DirectoryList::LOG, DriverPool::FILE, $logDirMock], + ]); + + $this->crontabManager->saveTasks([ + 'myCron' => ['expression' => '* * * * *'] + ]); + } + + /** + * @param array $tasks + * @param string $content + * @param string $contentToSave + * @return void + * @dataProvider saveTasksDataProvider + */ + public function testSaveTasks($tasks, $content, $contentToSave) + { + $baseDirMock = $this->getMockBuilder(ReadInterface::class) + ->getMockForAbstractClass(); + $baseDirMock->expects($this->once()) + ->method('getAbsolutePath') + ->willReturn('/var/www/magento2/'); + $logDirMock = $this->getMockBuilder(ReadInterface::class) + ->getMockForAbstractClass(); + $logDirMock->expects($this->once()) + ->method('getAbsolutePath') + ->willReturn('/var/www/magento2/var/log/'); + + $this->filesystemMock->expects($this->any()) + ->method('getDirectoryRead') + ->willReturnMap([ + [DirectoryList::ROOT, DriverPool::FILE, $baseDirMock], + [DirectoryList::LOG, DriverPool::FILE, $logDirMock], + ]); + + $this->shellMock->expects($this->at(0)) + ->method('execute') + ->with('crontab -l', []) + ->willReturn($content); + + $this->shellMock->expects($this->at(1)) + ->method('execute') + ->with('echo "' . $contentToSave . '" | crontab -', []); + + $this->crontabManager->saveTasks($tasks); + } + + /** + * @return array + */ + public function saveTasksDataProvider() + { + $content = '* * * * * /bin/php /var/www/cron.php' . PHP_EOL + . CrontabManagerInterface::TASKS_BLOCK_START . PHP_EOL + . '* * * * * /bin/php /var/www/magento/bin/magento cron:run' . PHP_EOL + . CrontabManagerInterface::TASKS_BLOCK_END . PHP_EOL; + + return [ + [ + 'tasks' => [ + ['expression' => '* * * * *', 'command' => 'run.php'] + ], + 'content' => $content, + 'contentToSave' => '* * * * * /bin/php /var/www/cron.php' . PHP_EOL + . CrontabManagerInterface::TASKS_BLOCK_START . PHP_EOL + . '* * * * * ' . PHP_BINARY . ' run.php' . PHP_EOL + . CrontabManagerInterface::TASKS_BLOCK_END . PHP_EOL, + ], + [ + 'tasks' => [ + ['expression' => '1 2 3 4 5', 'command' => 'run.php'] + ], + 'content' => $content, + 'contentToSave' => '* * * * * /bin/php /var/www/cron.php' . PHP_EOL + . CrontabManagerInterface::TASKS_BLOCK_START . PHP_EOL + . '1 2 3 4 5 ' . PHP_BINARY . ' run.php' . PHP_EOL + . CrontabManagerInterface::TASKS_BLOCK_END . PHP_EOL, + ], + [ + 'tasks' => [ + ['command' => '{magentoRoot}run.php >> {magentoLog}cron.log'] + ], + 'content' => $content, + 'contentToSave' => '* * * * * /bin/php /var/www/cron.php' . PHP_EOL + . CrontabManagerInterface::TASKS_BLOCK_START . PHP_EOL + . '* * * * * ' . PHP_BINARY . ' /var/www/magento2/run.php >>' + . ' /var/www/magento2/var/log/cron.log' . PHP_EOL + . CrontabManagerInterface::TASKS_BLOCK_END . PHP_EOL, + ], + ]; + } +} diff --git a/lib/internal/Magento/Framework/Crontab/Test/Unit/TasksProviderTest.php b/lib/internal/Magento/Framework/Crontab/Test/Unit/TasksProviderTest.php new file mode 100644 index 0000000000000000000000000000000000000000..80be47cda5ef4973ad1999968dd636da0bd1757a --- /dev/null +++ b/lib/internal/Magento/Framework/Crontab/Test/Unit/TasksProviderTest.php @@ -0,0 +1,34 @@ +<?php + +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Framework\Crontab\Test\Unit; + +use Magento\Framework\Crontab\TasksProvider; + +class TasksProviderTest extends \PHPUnit_Framework_TestCase +{ + /** + * @return void + */ + public function testTasksProviderEmpty() + { + /** @var $tasksProvider $tasksProvider */ + $tasksProvider = new TasksProvider(); + $this->assertSame([], $tasksProvider->getTasks()); + } + + public function testTasksProvider() + { + $tasks = [ + 'magentoCron' => ['expressin' => '* * * * *', 'command' => 'bin/magento cron:run'], + 'magentoSetup' => ['command' => 'bin/magento setup:cron:run'], + ]; + + /** @var $tasksProvider $tasksProvider */ + $tasksProvider = new TasksProvider($tasks); + $this->assertSame($tasks, $tasksProvider->getTasks()); + } +} diff --git a/lib/internal/Magento/Framework/Data/CollectionDataSourceInterface.php b/lib/internal/Magento/Framework/Data/CollectionDataSourceInterface.php index eeff60c1f61c887ef87439b680e8d73fbf6791f2..4bdf29fd1c8fc167af98cc79fd1d20c9696c7b50 100644 --- a/lib/internal/Magento/Framework/Data/CollectionDataSourceInterface.php +++ b/lib/internal/Magento/Framework/Data/CollectionDataSourceInterface.php @@ -5,9 +5,11 @@ */ namespace Magento\Framework\Data; +use Magento\Framework\View\Element\Block\ArgumentInterface; + /** * Interface CollectionDataSourceInterface */ -interface CollectionDataSourceInterface +interface CollectionDataSourceInterface extends ArgumentInterface { } diff --git a/lib/internal/Magento/Framework/View/Element/Block/ArgumentInterface.php b/lib/internal/Magento/Framework/View/Element/Block/ArgumentInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..123a75946ba775c0e1565e7689201fde8f8c9f0b --- /dev/null +++ b/lib/internal/Magento/Framework/View/Element/Block/ArgumentInterface.php @@ -0,0 +1,14 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Framework\View\Element\Block; + +/** + * Block argument interface. + * All objects that are injected to block arguments should implement this interface. + */ +interface ArgumentInterface +{ +} diff --git a/lib/internal/Magento/Framework/View/Test/Unit/Layout/Argument/Interpreter/ObjectTest.php b/lib/internal/Magento/Framework/View/Test/Unit/Layout/Argument/Interpreter/ObjectTest.php index a8f2374cc0ce11efd32b7c6e0419db5c3a799086..8de579730b154035d02b38fbdb8d07ef3315d194 100644 --- a/lib/internal/Magento/Framework/View/Test/Unit/Layout/Argument/Interpreter/ObjectTest.php +++ b/lib/internal/Magento/Framework/View/Test/Unit/Layout/Argument/Interpreter/ObjectTest.php @@ -34,16 +34,11 @@ class ObjectTest extends \PHPUnit_Framework_TestCase public function testEvaluate() { - $input = ['value' => self::EXPECTED_CLASS]; - $this->_objectManager->expects( - $this->once() - )->method( - 'create' - )->with( - self::EXPECTED_CLASS - )->will( - $this->returnValue($this) - ); + $input = ['name' => 'dataSource', 'value' => self::EXPECTED_CLASS]; + $this->_objectManager->expects($this->once()) + ->method('create') + ->with(self::EXPECTED_CLASS) + ->willReturn($this); $actual = $this->_model->evaluate($input); $this->assertSame($this, $actual); @@ -56,17 +51,18 @@ class ObjectTest extends \PHPUnit_Framework_TestCase { $this->setExpectedException($expectedException, $expectedExceptionMessage); $self = $this; - $this->_objectManager->expects($this->any())->method('create')->will( - $this->returnCallback( - function ($className) use ($self) { - return $self->getMock($className); - } - ) + $this->_objectManager->expects($this->any())->method('create')->willReturnCallback( + function ($className) use ($self) { + return $self->getMock($className); + } ); $this->_model->evaluate($input); } + /** + * @return array + */ public function evaluateWrongClassDataProvider() { return [