diff --git a/dev/tests/performance/testsuite/reusable/admin_login.jmx b/dev/tests/performance/testsuite/reusable/admin_login.jmx
new file mode 100644
index 0000000000000..939997aac5cba
--- /dev/null
+++ b/dev/tests/performance/testsuite/reusable/admin_login.jmx
@@ -0,0 +1,229 @@
+
+
+
+
+
+ Reusable scenario to log-in to admin backend. Needs: HOST, ADMIN_PATH, ADMIN_USERNAME, ADMIN_PASSWORD, Http Cookie Manager
+ false
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+ ${HOST}
+
+
+
+ http
+
+ ${ADMIN_PATH}
+ GET
+ true
+ false
+ true
+ false
+ Java
+ false
+
+
+
+
+
+ Log in to Admin Panel
+
+ Assertion.response_data
+ false
+ 2
+
+
+
+ false
+ form_key
+ <input name="form_key" type="hidden" value="([^'"]+)" />
+ $1$
+
+ 1
+
+
+
+
+ ^.+$
+
+ Assertion.response_data
+ false
+ 1
+ variable
+ form_key
+
+
+
+
+
+
+
+ false
+ ${ADMIN_USERNAME}
+ =
+ true
+ login[username]
+
+
+ false
+ ${ADMIN_PASSWORD}
+ =
+ true
+ login[password]
+
+
+
+ ${HOST}
+
+
+
+ http
+
+ ${ADMIN_PATH}
+ POST
+ true
+ false
+ true
+ false
+ Java
+ false
+
+
+
+
+
+ Logged in as ${ADMIN_USERNAME}
+
+ Assertion.response_data
+ false
+ 2
+
+
+
+
+
+ stopthread
+
+ false
+ 1
+
+ 1
+ 1
+ 1304708488000
+ 1304708488000
+ false
+
+
+ Not included into exported fragment, just for purpose of testing this scenario
+
+
+
+
+ WorkBench
+ Admin - Login (Test Plan)
+ Admin - Login (Test Fragment)
+
+
+
+
+
+ false
+
+
+
+
+
+ HOST
+ ${__P(host,localhost)}
+ =
+
+
+ ADMIN_PATH
+ ${__P(path,/)}${__P(admin_frontname,backend)}/
+ =
+
+
+ ADMIN_USERNAME
+ ${__P(admin_username,admin)}
+ =
+
+
+ ADMIN_PASSWORD
+ ${__P(admin_password,123123q)}
+ =
+
+
+
+
+
+
+ false
+
+ saveConfig
+
+
+ true
+ true
+ true
+
+ true
+ true
+ true
+ true
+ false
+ true
+ true
+ false
+ false
+ true
+ false
+ false
+ false
+ false
+ false
+ 0
+ true
+
+
+
+
+
+
+
+
diff --git a/dev/tests/static/testsuite/Php/_files/whitelist/core.txt b/dev/tests/static/testsuite/Php/_files/whitelist/core.txt
index 667e040416a49..70dbef97cb694 100644
--- a/dev/tests/static/testsuite/Php/_files/whitelist/core.txt
+++ b/dev/tests/static/testsuite/Php/_files/whitelist/core.txt
@@ -18,6 +18,7 @@ app/code/core/Mage/Core/Model/Layout/Update.php
app/code/core/Mage/Core/Model/Design/Fallback
app/code/core/Mage/Core/Model/Design/Fallback.php
app/code/core/Mage/Core/Model/Design/FallbackInterface.php
+app/code/core/Mage/Core/Model/Resource/Setup/Migration.php
app/code/core/Mage/DesignEditor
app/code/core/Mage/ImportExport/Model/Import/Entity/CustomerComposite.php
app/code/core/Mage/ImportExport/Model/Import/Entity/Product/Option.php
diff --git a/dev/tests/unit/testsuite/Mage/Core/Model/Resource/Setup/MigrationTest.php b/dev/tests/unit/testsuite/Mage/Core/Model/Resource/Setup/MigrationTest.php
new file mode 100644
index 0000000000000..1e97d7fb6342d
--- /dev/null
+++ b/dev/tests/unit/testsuite/Mage/Core/Model/Resource/Setup/MigrationTest.php
@@ -0,0 +1,270 @@
+_actualUpdateResult);
+ unset($this->_actualWhere);
+ unset($this->_selectMock);
+ }
+
+ /**
+ * Retrieve all necessary objects mocks which used inside customer storage
+ *
+ * @param int $tableRowsCount
+ * @param array $tableData
+ * @param array $aliasesMap
+ *
+ * @return array
+ */
+ protected function _getModelDependencies($tableRowsCount = 0, $tableData = array(), $aliasesMap = array())
+ {
+ $autoload = $this->getMock('Magento_Autoload', array('classExists'), array(), '', false);
+ $autoload->expects($this->any())
+ ->method('classExists')
+ ->will($this->returnCallback(array($this, 'classExistCallback')));
+
+ $this->_selectMock = $this->getMock('Varien_Db_Select', array(), array(), '', false);
+ $this->_selectMock->expects($this->any())
+ ->method('from')
+ ->will($this->returnSelf());
+ $this->_selectMock->expects($this->any())
+ ->method('where')
+ ->will($this->returnCallback(array($this, 'whereCallback')));
+
+ $adapterMock = $this->getMock('Varien_Db_Adapter_Pdo_Mysql',
+ array('select', 'update', 'fetchAll', 'fetchOne'), array(), '', false
+ );
+ $adapterMock->expects($this->any())
+ ->method('select')
+ ->will($this->returnValue($this->_selectMock));
+ $adapterMock->expects($this->any())
+ ->method('update')
+ ->will($this->returnCallback(array($this, 'updateCallback')));
+ $adapterMock->expects($this->any())
+ ->method('fetchAll')
+ ->will($this->returnValue($tableData));
+ $adapterMock->expects($this->any())
+ ->method('fetchOne')
+ ->will($this->returnValue($tableRowsCount));
+
+ return array(
+ 'resource_config' => 'not_used',
+ 'connection_config' => 'not_used',
+ 'module_config' => 'not_used',
+ 'base_dir' => 'not_used',
+ 'path_to_map_file' => 'not_used',
+ 'connection' => $adapterMock,
+ 'autoload' => $autoload,
+ 'core_helper' => new Mage_Core_Helper_Data(),
+ 'aliases_map' => $aliasesMap
+ );
+ }
+
+ /**
+ * Callback for Magento_Autoload::classExist
+ *
+ * @return bool
+ */
+ public function classExistCallback()
+ {
+ return true;
+ }
+
+ /**
+ * Callback for Varien_Db_Select::update
+ *
+ * @param string $table
+ * @param array $bind
+ * @param array $where
+ */
+ public function updateCallback($table, array $bind, $where)
+ {
+ $fields = array_keys($bind);
+ $replacements = array_values($bind);
+
+ $this->_actualUpdateResult[] = array(
+ 'table' => $table,
+ 'field' => $fields[0],
+ 'to' => $replacements[0],
+ 'from' => $where
+ );
+ }
+
+ /**
+ * Callback for Varien_Db_Select::where
+ *
+ * @param string $condition
+ * @return PHPUnit_Framework_MockObject_MockObject|Varien_Db_Select
+ */
+ public function whereCallback($condition)
+ {
+ if (null === $this->_actualWhere) {
+ $this->_actualWhere = array();
+ }
+ if (!empty($condition) && false === strpos($condition, ' IS NOT NULL')
+ && !in_array($condition, $this->_actualWhere)
+ ) {
+ $this->_actualWhere[] = $condition;
+ }
+ return $this->_selectMock;
+ }
+
+ /**
+ * @covers Mage_Core_Model_Resource_Setup_Migration::appendClassAliasReplace
+ */
+ public function testAppendClassAliasReplace()
+ {
+ $setupModel = new Mage_Core_Model_Resource_Setup_Migration('core_setup', $this->_getModelDependencies());
+
+ $setupModel->appendClassAliasReplace('tableName', 'fieldName', 'entityType', 'fieldContentType',
+ array('pk_field1', 'pk_field2'), 'additionalWhere'
+ );
+
+ $expectedRulesList = array (
+ 'tableName' => array(
+ 'fieldName' => array(
+ 'entity_type' => 'entityType',
+ 'content_type' => 'fieldContentType',
+ 'pk_fields' => array('pk_field1', 'pk_field2'),
+ 'additional_where' => 'additionalWhere'
+ )
+ )
+ );
+
+ $this->assertAttributeEquals($expectedRulesList, '_replaceRules', $setupModel);
+ }
+
+ /**
+ * @dataProvider updateClassAliasesDataProvider
+ * @covers Mage_Core_Model_Resource_Setup_Migration::doUpdateClassAliases
+ * @covers Mage_Core_Model_Resource_Setup_Migration::_updateClassAliasesInTable
+ * @covers Mage_Core_Model_Resource_Setup_Migration::_getRowsCount
+ * @covers Mage_Core_Model_Resource_Setup_Migration::_applyFieldRule
+ * @covers Mage_Core_Model_Resource_Setup_Migration::_updateRowsData
+ * @covers Mage_Core_Model_Resource_Setup_Migration::_getTableData
+ * @covers Mage_Core_Model_Resource_Setup_Migration::_getReplacement
+ * @covers Mage_Core_Model_Resource_Setup_Migration::_getCorrespondingClassName
+ * @covers Mage_Core_Model_Resource_Setup_Migration::_getModelReplacement
+ * @covers Mage_Core_Model_Resource_Setup_Migration::_getPatternReplacement
+ * @covers Mage_Core_Model_Resource_Setup_Migration::_getClassName
+ * @covers Mage_Core_Model_Resource_Setup_Migration::_isFactoryName
+ * @covers Mage_Core_Model_Resource_Setup_Migration::_getModuleName
+ * @covers Mage_Core_Model_Resource_Setup_Migration::_getCompositeModuleName
+ * @covers Mage_Core_Model_Resource_Setup_Migration::_getAliasFromMap
+ * @covers Mage_Core_Model_Resource_Setup_Migration::_pushToMap
+ * @covers Mage_Core_Model_Resource_Setup_Migration::_getAliasesMap
+ * @covers Mage_Core_Model_Resource_Setup_Migration::_getAliasInSerializedStringReplacement
+ * @covers Mage_Core_Model_Resource_Setup_Migration::_parseSerializedString
+ */
+ public function testDoUpdateClassAliases($replaceRules, $tableData, $expected, $aliasesMap = array())
+ {
+ $this->_actualUpdateResult = array();
+
+ $tableRowsCount = count($tableData);
+
+ $setupModel = new Mage_Core_Model_Resource_Setup_Migration(
+ 'core_setup',
+ $this->_getModelDependencies($tableRowsCount, $tableData, $aliasesMap)
+ );
+
+ foreach ($replaceRules as $replaceRule) {
+ call_user_func_array(array($setupModel, 'appendClassAliasReplace'), $replaceRule);
+ }
+
+ $setupModel->doUpdateClassAliases();
+
+ $this->assertEquals($expected['updates'], $this->_actualUpdateResult);
+
+ if (isset($expected['where'])) {
+ $this->assertEquals($expected['where'], $this->_actualWhere);
+ }
+
+ if (isset($expected['aliases_map'])) {
+ $this->assertAttributeEquals($expected['aliases_map'], '_aliasesMap', $setupModel);
+ }
+ }
+
+ /**
+ * Data provider for updating class aliases
+ *
+ * @return array
+ */
+ public function updateClassAliasesDataProvider()
+ {
+ return array(
+ 'plain text replace model' => include __DIR__ . '/_files/data_content_plain_model.php',
+ 'plain text replace resource' => include __DIR__ . '/_files/data_content_plain_resource.php',
+ 'plain text replace with pk field' => include __DIR__ . '/_files/data_content_plain_pk_fields.php',
+ 'xml replace' => include __DIR__ . '/_files/data_content_xml.php',
+ 'wiki markup replace' => include __DIR__ . '/_files/data_content_wiki.php',
+ 'serialized php replace' => include __DIR__ . '/_files/data_content_serialized.php',
+ );
+ }
+
+ /**
+ * @covers Mage_Core_Model_Resource_Setup_Migration::getCompositeModules
+ */
+ public function testGetCompositeModules()
+ {
+ $compositeModules = Mage_Core_Model_Resource_Setup_Migration::getCompositeModules();
+ $this->assertInternalType('array', $compositeModules);
+ $this->assertNotEmpty($compositeModules);
+ foreach ($compositeModules as $classAlias => $className) {
+ $this->assertInternalType('string', $classAlias);
+ $this->assertInternalType('string', $className);
+ $this->assertNotEmpty($classAlias);
+ $this->assertNotEmpty($className);
+ }
+ }
+}
diff --git a/dev/tests/unit/testsuite/Mage/Core/Model/Resource/Setup/_files/data_content_plain_model.php b/dev/tests/unit/testsuite/Mage/Core/Model/Resource/Setup/_files/data_content_plain_model.php
new file mode 100644
index 0000000000000..95b03ff80e9f6
--- /dev/null
+++ b/dev/tests/unit/testsuite/Mage/Core/Model/Resource/Setup/_files/data_content_plain_model.php
@@ -0,0 +1,91 @@
+ array(
+ array(
+ 'table',
+ 'field',
+ Mage_Core_Model_Resource_Setup_Migration::ENTITY_TYPE_MODEL,
+ Mage_Core_Model_Resource_Setup_Migration::FIELD_CONTENT_TYPE_PLAIN,
+ )
+ ),
+ '$tableData' => array(
+ array('field' => 'customer/customer'),
+ array('field' => 'customer/attribute_data_postcode'),
+ array('field' => 'customer/attribute_data_postcode::someMethod'),
+ array('field' => 'catalogSearch/session'),
+ array('field' => 'catalogSearch/session::someMethod'),
+ array('field' => 'Mage_Customer_Model_Customer'),
+ ),
+ '$expected' => array(
+ 'updates' => array(
+ array(
+ 'table' => 'table',
+ 'field' => 'field',
+ 'to' => 'Mage_Customer_Model_Customer_FROM_MAP',
+ 'from' => array('`field` = ?' => 'customer/customer')
+ ),
+ array(
+ 'table' => 'table',
+ 'field' => 'field',
+ 'to' => 'Mage_Customer_Model_Attribute_Data_Postcode',
+ 'from' => array('`field` = ?' => 'customer/attribute_data_postcode')
+ ),
+ array(
+ 'table' => 'table',
+ 'field' => 'field',
+ 'to' => 'Mage_Customer_Model_Attribute_Data_Postcode::someMethod',
+ 'from' => array('`field` = ?' => 'customer/attribute_data_postcode::someMethod')
+ ),
+ array(
+ 'table' => 'table',
+ 'field' => 'field',
+ 'to' => 'Mage_CatalogSearch_Model_Session',
+ 'from' => array('`field` = ?' => 'catalogSearch/session')
+ ),
+ array(
+ 'table' => 'table',
+ 'field' => 'field',
+ 'to' => 'Mage_CatalogSearch_Model_Session::someMethod',
+ 'from' => array('`field` = ?' => 'catalogSearch/session::someMethod')
+ ),
+ ),
+ 'aliases_map' => array(
+ Mage_Core_Model_Resource_Setup_Migration::ENTITY_TYPE_MODEL => array(
+ 'customer/customer' => 'Mage_Customer_Model_Customer_FROM_MAP',
+ 'customer/attribute_data_postcode' => 'Mage_Customer_Model_Attribute_Data_Postcode',
+ 'catalogSearch/session' => 'Mage_CatalogSearch_Model_Session',
+ ),
+ )
+ ),
+ '$aliasesMap' => array(
+ Mage_Core_Model_Resource_Setup_Migration::ENTITY_TYPE_MODEL => array(
+ 'customer/customer' => 'Mage_Customer_Model_Customer_FROM_MAP'
+ )
+ )
+);
diff --git a/dev/tests/unit/testsuite/Mage/Core/Model/Resource/Setup/_files/data_content_plain_pk_fields.php b/dev/tests/unit/testsuite/Mage/Core/Model/Resource/Setup/_files/data_content_plain_pk_fields.php
new file mode 100644
index 0000000000000..d359a24eb6f23
--- /dev/null
+++ b/dev/tests/unit/testsuite/Mage/Core/Model/Resource/Setup/_files/data_content_plain_pk_fields.php
@@ -0,0 +1,56 @@
+ array(
+ array(
+ 'table',
+ 'collection',
+ Mage_Core_Model_Resource_Setup_Migration::ENTITY_TYPE_RESOURCE,
+ Mage_Core_Model_Resource_Setup_Migration::FIELD_CONTENT_TYPE_PLAIN,
+ array('pk_field1', 'pk_field2')
+ )
+ ),
+ '$tableData' => array(
+ array('collection' => 'customer/attribute_collection', 'pk_field1' => 'pk_value1', 'pk_field2' => 'pk_value2'),
+ ),
+ '$expected' => array(
+ 'updates' => array(
+ array(
+ 'table' => 'table',
+ 'field' => 'collection',
+ 'to' => 'Mage_Customer_Model_Resource_Attribute_Collection',
+ 'from' => array('`pk_field1` = ?' => 'pk_value1', '`pk_field2` = ?' => 'pk_value2')
+ ),
+ ),
+ 'aliases_map' => array(
+ Mage_Core_Model_Resource_Setup_Migration::ENTITY_TYPE_RESOURCE => array(
+ 'customer/attribute_collection' => 'Mage_Customer_Model_Resource_Attribute_Collection'
+ ),
+ )
+ ),
+);
diff --git a/dev/tests/unit/testsuite/Mage/Core/Model/Resource/Setup/_files/data_content_plain_resource.php b/dev/tests/unit/testsuite/Mage/Core/Model/Resource/Setup/_files/data_content_plain_resource.php
new file mode 100644
index 0000000000000..7e2817ba23119
--- /dev/null
+++ b/dev/tests/unit/testsuite/Mage/Core/Model/Resource/Setup/_files/data_content_plain_resource.php
@@ -0,0 +1,60 @@
+ array(
+ array(
+ 'table',
+ 'collection',
+ Mage_Core_Model_Resource_Setup_Migration::ENTITY_TYPE_RESOURCE,
+ Mage_Core_Model_Resource_Setup_Migration::FIELD_CONTENT_TYPE_PLAIN,
+ array(),
+ 'flag = 1'
+ )
+ ),
+ '$tableData' => array(
+ array('collection' => 'customer/attribute_collection'),
+ ),
+ '$expected' => array(
+ 'updates' => array(
+ array(
+ 'table' => 'table',
+ 'field' => 'collection',
+ 'to' => 'Mage_Customer_Model_Resource_Attribute_Collection',
+ 'from' => array('`collection` = ?' => 'customer/attribute_collection')
+ ),
+ ),
+ 'where' => array(
+ 'flag = 1'
+ ),
+ 'aliases_map' => array(
+ Mage_Core_Model_Resource_Setup_Migration::ENTITY_TYPE_RESOURCE => array(
+ 'customer/attribute_collection' => 'Mage_Customer_Model_Resource_Attribute_Collection'
+ ),
+ )
+ ),
+);
diff --git a/dev/tests/unit/testsuite/Mage/Core/Model/Resource/Setup/_files/data_content_serialized.php b/dev/tests/unit/testsuite/Mage/Core/Model/Resource/Setup/_files/data_content_serialized.php
new file mode 100644
index 0000000000000..a0b6b3c4eda04
--- /dev/null
+++ b/dev/tests/unit/testsuite/Mage/Core/Model/Resource/Setup/_files/data_content_serialized.php
@@ -0,0 +1,64 @@
+ array(
+ array(
+ 'table',
+ 'field',
+ Mage_Core_Model_Resource_Setup_Migration::ENTITY_TYPE_MODEL,
+ Mage_Core_Model_Resource_Setup_Migration::FIELD_CONTENT_TYPE_SERIALIZED
+ )
+ ),
+ '$tableData' => array(
+ array('field' => 'a:1:{s:5:"model";s:34:"catalogrule/rule_condition_combine";}'),
+ array('field' => 'a:1:{s:5:"model";s:21:"catalogSearch/session";}'),
+ array('field' => 'a:1:{s:5:"model";s:16:"some random text";}'),
+ ),
+ '$expected' => array(
+ 'updates' => array(
+ array(
+ 'table' => 'table',
+ 'field' => 'field',
+ 'to' => 'a:1:{s:5:"model";s:45:"Mage_CatalogRule_Model_Rule_Condition_Combine";}',
+ 'from' => array('`field` = ?' => 'a:1:{s:5:"model";s:34:"catalogrule/rule_condition_combine";}')
+ ),
+ array(
+ 'table' => 'table',
+ 'field' => 'field',
+ 'to' => 'a:1:{s:5:"model";s:32:"Mage_CatalogSearch_Model_Session";}',
+ 'from' => array('`field` = ?' => 'a:1:{s:5:"model";s:21:"catalogSearch/session";}')
+ ),
+ ),
+ 'aliases_map' => array(
+ Mage_Core_Model_Resource_Setup_Migration::ENTITY_TYPE_MODEL => array(
+ 'catalogrule/rule_condition_combine' => 'Mage_CatalogRule_Model_Rule_Condition_Combine',
+ 'catalogSearch/session' => 'Mage_CatalogSearch_Model_Session',
+ )
+ )
+ ),
+);
diff --git a/dev/tests/unit/testsuite/Mage/Core/Model/Resource/Setup/_files/data_content_wiki.php b/dev/tests/unit/testsuite/Mage/Core/Model/Resource/Setup/_files/data_content_wiki.php
new file mode 100644
index 0000000000000..f27b43c253c5a
--- /dev/null
+++ b/dev/tests/unit/testsuite/Mage/Core/Model/Resource/Setup/_files/data_content_wiki.php
@@ -0,0 +1,64 @@
+ array(
+ array(
+ 'table',
+ 'field',
+ Mage_Core_Model_Resource_Setup_Migration::ENTITY_TYPE_BLOCK,
+ Mage_Core_Model_Resource_Setup_Migration::FIELD_CONTENT_TYPE_WIKI
+ )
+ ),
+ '$tableData' => array(
+ array('field' => '{{widget type="productalert/product_view"}}
'),
+ array('field' => '{{widget type="catalogSearch/result"}}
'),
+ array('field' => 'Some HTML code
'),
+ ),
+ '$expected' => array(
+ 'updates' => array(
+ array(
+ 'table' => 'table',
+ 'field' => 'field',
+ 'to' => '{{widget type="Mage_ProductAlert_Block_Product_View"}}
',
+ 'from' => array('`field` = ?' => '{{widget type="productalert/product_view"}}
')
+ ),
+ array(
+ 'table' => 'table',
+ 'field' => 'field',
+ 'to' => '{{widget type="Mage_CatalogSearch_Block_Result"}}
',
+ 'from' => array('`field` = ?' => '{{widget type="catalogSearch/result"}}
')
+ ),
+ ),
+ 'aliases_map' => array(
+ Mage_Core_Model_Resource_Setup_Migration::ENTITY_TYPE_BLOCK => array(
+ 'productalert/product_view' => 'Mage_ProductAlert_Block_Product_View',
+ 'catalogSearch/result' => 'Mage_CatalogSearch_Block_Result',
+ )
+ )
+ ),
+);
diff --git a/dev/tests/unit/testsuite/Mage/Core/Model/Resource/Setup/_files/data_content_xml.php b/dev/tests/unit/testsuite/Mage/Core/Model/Resource/Setup/_files/data_content_xml.php
new file mode 100644
index 0000000000000..c78b5a0ad938d
--- /dev/null
+++ b/dev/tests/unit/testsuite/Mage/Core/Model/Resource/Setup/_files/data_content_xml.php
@@ -0,0 +1,64 @@
+ array(
+ array(
+ 'table',
+ 'field',
+ Mage_Core_Model_Resource_Setup_Migration::ENTITY_TYPE_BLOCK,
+ Mage_Core_Model_Resource_Setup_Migration::FIELD_CONTENT_TYPE_XML,
+ )
+ ),
+ '$tableData' => array(
+ array('field' => ''),
+ array('field' => ''),
+ array('field' => ''),
+ ),
+ '$expected' => array(
+ 'updates' => array(
+ array(
+ 'table' => 'table',
+ 'field' => 'field',
+ 'to' => '',
+ 'from' => array('`field` = ?' => '')
+ ),
+ array(
+ 'table' => 'table',
+ 'field' => 'field',
+ 'to' => '',
+ 'from' => array('`field` = ?' => '')
+ ),
+ ),
+ 'aliases_map' => array(
+ Mage_Core_Model_Resource_Setup_Migration::ENTITY_TYPE_BLOCK => array(
+ 'catalog/product_new' => 'Mage_Catalog_Block_Product_New',
+ 'catalogSearch/result' => 'Mage_CatalogSearch_Block_Result',
+ )
+ )
+ ),
+);
diff --git a/dev/tests/unit/testsuite/Mage/Sales/Model/Config/OrderTest.php b/dev/tests/unit/testsuite/Mage/Sales/Model/Config/OrderTest.php
new file mode 100644
index 0000000000000..de6ec5e4dcea8
--- /dev/null
+++ b/dev/tests/unit/testsuite/Mage/Sales/Model/Config/OrderTest.php
@@ -0,0 +1,118 @@
+getMockForAbstractClass('Mage_Sales_Model_Config_Ordered');
+
+ $method = new ReflectionMethod($mock, '_getSortedCollectorCodes');
+ $method->setAccessible(true);
+ $actualResult = $method->invoke($mock, $totalConfig);
+ $this->assertEquals($expectedResult, $actualResult);
+ }
+
+ public function getSortedCollectorCodesDataProvider()
+ {
+ $ambiguousCases = self::ambiguousTotalsDataProvider();
+ return array_merge(array(
+ 'core totals' => array(
+ require __DIR__ . '/_files/core_totals_config.php',
+ array(
+ 'nominal', 'subtotal', 'freeshipping', 'tax_subtotal', 'shipping', 'tax_shipping', 'discount',
+ 'tax', 'grand_total', 'msrp', 'weee',
+ )
+ ),
+ 'custom totals' => array(
+ require __DIR__ . '/_files/custom_totals_config.php',
+ array(
+ 'nominal', 'own_subtotal', 'own_total1', 'own_total2', 'subtotal', 'freeshipping', 'tax_subtotal',
+ 'shipping', 'tax_shipping', 'discount', 'handling', 'handling_tax', 'tax', 'grand_total', 'msrp',
+ 'weee',
+ )
+ ),
+ ), $ambiguousCases);
+ }
+
+ /**
+ * @dataProvider ambiguousTotalsDataProvider
+ * @expectedException Magento_Exception
+ */
+ public function testValidateCollectorDeclarations($config)
+ {
+ Mage_Sales_Model_Config_Ordered::validateCollectorDeclarations($config);
+ }
+
+ public function ambiguousTotalsDataProvider()
+ {
+ return array(
+ '"before" ambiguity 1' => array(
+ array(
+ 'total_one' => array('before' => array('total_two'), 'after' => array()),
+ 'total_two' => array('before' => array('total_one'), 'after' => array()),
+ ),
+ array('total_one', 'total_two'),
+ ),
+ '"before" ambiguity 2' => array(
+ array(
+ 'total_two' => array('before' => array('total_one'), 'after' => array()),
+ 'total_one' => array('before' => array('total_two'), 'after' => array()),
+ ),
+ array('total_two', 'total_one'),
+ ),
+ '"after" ambiguity 1' => array(
+ array(
+ 'total_one' => array('before' => array(), 'after' => array('total_two')),
+ 'total_two' => array('before' => array(), 'after' => array('total_one')),
+ ),
+ array('total_one', 'total_two'),
+ ),
+ '"after" ambiguity 2' => array(
+ array(
+ 'total_two' => array('before' => array(), 'after' => array('total_one')),
+ 'total_one' => array('before' => array(), 'after' => array('total_two')),
+ ),
+ array('total_two', 'total_one'),
+ ),
+ 'combined contradiction to itself' => array(
+ array(
+ 'one' => array('before' => array('two'), 'after' => array('two')),
+ 'two' => array('before' => array(), 'after' => array()),
+ ),
+ array('one', 'two'),
+ ),
+ 'combined contradiction across declarations' => array(
+ array(
+ 'one' => array('before' => array('two'), 'after' => array()),
+ 'two' => array('before' => array(), 'after' => array('three')),
+ 'three' => array('before' => array(), 'after' => array('two')),
+ ),
+ array('one', 'two', 'three'),
+ ),
+ );
+ }
+}
diff --git a/dev/tests/unit/testsuite/Mage/Sales/Model/Config/_files/core_totals_config.php b/dev/tests/unit/testsuite/Mage/Sales/Model/Config/_files/core_totals_config.php
new file mode 100644
index 0000000000000..439cd2ccaa3f2
--- /dev/null
+++ b/dev/tests/unit/testsuite/Mage/Sales/Model/Config/_files/core_totals_config.php
@@ -0,0 +1,77 @@
+ array(
+ 'before' => array('subtotal'),
+ 'after' => array(),
+ ),
+ 'subtotal' => array(
+ 'after' => array('nominal'),
+ 'before' => array('grand_total'),
+ ),
+ 'shipping' => array(
+ 'after' => array('subtotal', 'freeshipping', 'tax_subtotal'),
+ 'before' => array('grand_total'),
+ ),
+ 'grand_total' => array(
+ 'after' => array('subtotal'),
+ 'before' => array(),
+ ),
+ 'msrp' => array(
+ 'after' => array(),
+ 'before' => array(),
+ ),
+ // Totals declared in Mage_SalesRule
+ 'freeshipping' => array(
+ 'after' => array('subtotal'),
+ 'before' => array('tax_subtotal', 'shipping'),
+ ),
+ 'discount' => array(
+ 'after' => array('subtotal', 'shipping'),
+ 'before' => array('grand_total'),
+ ),
+ // Totals declared in Mage_Tax
+ 'tax_subtotal' => array(
+ 'after' => array('freeshipping'),
+ 'before' => array('tax', 'discount'),
+ ),
+ 'tax_shipping' => array(
+ 'after' => array('shipping'),
+ 'before' => array('tax', 'discount'),
+ ),
+ 'tax' => array(
+ 'after' => array('subtotal', 'shipping'),
+ 'before' => array('grand_total'),
+ ),
+ // Totals declared in Mage_Weee
+ 'weee' => array(
+ 'after' => array('subtotal', 'tax', 'discount', 'grand_total', 'shipping'),
+ 'before' => array(),
+ ),
+);
diff --git a/dev/tests/unit/testsuite/Mage/Sales/Model/Config/_files/custom_totals_config.php b/dev/tests/unit/testsuite/Mage/Sales/Model/Config/_files/custom_totals_config.php
new file mode 100644
index 0000000000000..2213e330b8efe
--- /dev/null
+++ b/dev/tests/unit/testsuite/Mage/Sales/Model/Config/_files/custom_totals_config.php
@@ -0,0 +1,51 @@
+ array(
+ 'after' => array('shipping'),
+ 'before' => array('tax'),
+ ),
+ 'handling_tax' => array(
+ 'after' => array('tax_shipping'),
+ 'before' => array('tax'),
+ ),
+ 'own_subtotal' => array(
+ 'after' => array('nominal'),
+ 'before' => array('subtotal'),
+ ),
+ 'own_total1' => array(
+ 'after' => array('nominal'),
+ 'before' => array('subtotal'),
+ ),
+ 'own_total2' => array(
+ 'after' => array('nominal'),
+ 'before' => array('subtotal'),
+ ),
+);
+return $result;
diff --git a/dev/tests/unit/testsuite/Magento/Data/GraphTest.php b/dev/tests/unit/testsuite/Magento/Data/GraphTest.php
new file mode 100644
index 0000000000000..151f4caf7d6f2
--- /dev/null
+++ b/dev/tests/unit/testsuite/Magento/Data/GraphTest.php
@@ -0,0 +1,123 @@
+ array(
+ array(1, 2, 2), array()
+ ),
+ 'self-link' => array(
+ array(1, 2), array(array(1, 2), array(2, 2))
+ ),
+ 'broken reference "from"' => array(
+ array(1, 2), array(array(1, 2), array(3, 1))
+ ),
+ 'broken reference "to"' => array(
+ array(1, 2), array(array(1, 2), array(1, 3))
+ ),
+ );
+ }
+
+ /**
+ * Exceptions are covered by testConstructorError()
+ */
+ public function testAddRelation()
+ {
+ $model = new Magento_Data_Graph(array(1, 2, 3), array(array(1, 2), array(2, 3)));
+ $this->assertEquals(array(1 => array(2 => 2), 2 => array(3 => 3)), $model->getRelations());
+ $this->assertSame($model, $model->addRelation(3, 1));
+ $this->assertEquals(array(1 => array(2 => 2), 2 => array(3 => 3), 3 => array(1 => 1)), $model->getRelations());
+ }
+
+ public function testGetRelations()
+ {
+ // directional case is covered by testAddRelation()
+
+ // inverse
+ $model = new Magento_Data_Graph(array(1, 2, 3), array(array(1, 2), array(2, 3)));
+ $this->assertEquals(
+ array(2 => array(1 => 1), 3 => array(2 => 2)), $model->getRelations(Magento_Data_Graph::INVERSE)
+ );
+
+ // non-directional
+ $this->assertEquals(
+ array(1 => array(2 => 2), 2 => array(1 => 1, 3 => 3), 3 => array(2 => 2)),
+ $model->getRelations(Magento_Data_Graph::NON_DIRECTIONAL)
+ );
+ }
+
+ public function testFindCycle()
+ {
+ $nodes = array(1, 2, 3, 4);
+ $model = new Magento_Data_Graph($nodes, array(
+ array(1, 2), array(2, 3), array(3, 4),
+ ));
+ $this->assertEquals(array(), $model->findCycle());
+
+ $model = new Magento_Data_Graph($nodes, array(
+ array(1, 2), array(2, 3), array(3, 4), array(4, 2)
+ ));
+ $this->assertEquals(array(), $model->findCycle(1));
+ $cycle = $model->findCycle();
+ sort($cycle);
+ $this->assertEquals(array(2, 2, 3, 4), $cycle);
+ $this->assertEquals(array(3, 4, 2, 3), $model->findCycle(3));
+ }
+
+ public function testDfs()
+ {
+ $model = new Magento_Data_Graph(array(1, 2, 3, 4, 5), array(array(1, 2), array(2, 3), array(4, 5)));
+
+ // directional
+ $this->assertEquals(array(1, 2, 3), $model->dfs(1, 3));
+ $this->assertEquals(array(), $model->dfs(3, 1));
+ $this->assertEquals(array(4, 5), $model->dfs(4, 5));
+ $this->assertEquals(array(), $model->dfs(1, 5));
+
+ // inverse
+ $this->assertEquals(array(3, 2, 1), $model->dfs(3, 1, Magento_Data_Graph::INVERSE));
+
+ // non-directional
+ $model = new Magento_Data_Graph(array(1, 2, 3), array(array(2, 1), array(2, 3)));
+ $this->assertEquals(array(), $model->dfs(1, 3, Magento_Data_Graph::DIRECTIONAL));
+ $this->assertEquals(array(), $model->dfs(3, 1, Magento_Data_Graph::INVERSE));
+ $this->assertEquals(array(1, 2, 3), $model->dfs(1, 3, Magento_Data_Graph::NON_DIRECTIONAL));
+ }
+}
diff --git a/dev/tests/unit/testsuite/Magento/ShellTest.php b/dev/tests/unit/testsuite/Magento/ShellTest.php
index 281414d324545..5e564abbe1c6b 100644
--- a/dev/tests/unit/testsuite/Magento/ShellTest.php
+++ b/dev/tests/unit/testsuite/Magento/ShellTest.php
@@ -40,27 +40,36 @@ public function testGetSetVerbose()
}
/**
- * @dataProvider executeDataProvider
* @param string $phpCommand
- * @param bool $isVerbose
- * @param string $expectedOutput
- * @param string $expectedResult
+ * @param string $expectedRaw
+ * @param string $expectedFull
+ * @dataProvider executeDataProvider
*/
- public function testExecute($phpCommand, $isVerbose, $expectedOutput, $expectedResult = '')
+ public function testExecute($phpCommand, $expectedRaw, $expectedFull)
{
- $this->expectOutputString($expectedOutput);
- $shell = new Magento_Shell($isVerbose);
- $actualResult = $shell->execute('php -r %s', array($phpCommand));
- $this->assertEquals($expectedResult, $actualResult);
+ // non-verbose
+ $shell = new Magento_Shell();
+ $rawResult = $shell->execute('php -r %s', array($phpCommand), $fullResult);
+ $this->assertEquals($expectedRaw, $rawResult);
+ $this->assertEquals($expectedFull, $fullResult);
+
+ // verbose
+ $this->expectOutputString($expectedFull);
+ $shell = new Magento_Shell(true);
+ $shell->execute('php -r %s', array($phpCommand));
}
public function executeDataProvider()
{
+ $quote = escapeshellarg('\'""');
+ $quote = $quote[0];
return array(
- 'capture STDOUT' => array('echo 27182;', false, '', '27182'),
- 'print STDOUT' => array('echo 27182;', true, '27182' . PHP_EOL, '27182'),
- 'capture STDERR' => array('fwrite(STDERR, 27182);', false, '', '27182'),
- 'print STDERR' => array('fwrite(STDERR, 27182);', true, '27182' . PHP_EOL, '27182'),
+ 'STDOUT' => array('echo 27182;', array('27182'),
+ "php -r {$quote}echo 27182;{$quote} 2>&1" . PHP_EOL . '27182' . PHP_EOL . PHP_EOL
+ ),
+ 'STDERR' => array('fwrite(STDERR, 27183);', array('27183'),
+ "php -r {$quote}fwrite(STDERR, 27183);{$quote} 2>&1" . PHP_EOL . '27183' . PHP_EOL . PHP_EOL
+ ),
);
}
@@ -76,20 +85,19 @@ public function testExecuteFailure()
}
/**
- * @dataProvider executeDataProvider
* @param string $phpCommand
- * @param bool $isVerbose
- * @param string $expectedOutput
- * @param string $expectedError
+ * @param string $expectedRaw
+ * @param string $expectedFull
+ * @dataProvider executeDataProvider
*/
- public function testExecuteFailureDetails($phpCommand, $isVerbose, $expectedOutput, $expectedError = '')
+ public function testExecuteFailureDetails($phpCommand, $expectedRaw, $expectedFull)
{
try {
/* Force command to return non-zero exit code */
- $this->testExecute($phpCommand . ' exit(42);', $isVerbose, $expectedOutput);
+ $this->testExecute($phpCommand . ' exit(42);', $expectedRaw, $expectedFull);
} catch (Magento_Exception $e) {
$this->assertInstanceOf('Exception', $e->getPrevious());
- $this->assertEquals($expectedError, $e->getPrevious()->getMessage());
+ $this->assertEquals(implode(PHP_EOL, $expectedRaw), $e->getPrevious()->getMessage());
$this->assertEquals(42, $e->getPrevious()->getCode());
}
}
diff --git a/dev/tests/unit/testsuite/Varien/Io/FileTest.php b/dev/tests/unit/testsuite/Varien/Io/FileTest.php
new file mode 100644
index 0000000000000..abaf5d891cb1b
--- /dev/null
+++ b/dev/tests/unit/testsuite/Varien/Io/FileTest.php
@@ -0,0 +1,108 @@
+_prepare();
+ if (substr(PHP_OS, 0, 3) == 'WIN') {
+ $this->markTestSkipped("chmod may not work for Windows");
+ }
+ $permsBefore = 0700;
+ $expected = 0777;
+ $this->assertEquals($permsBefore, fileperms($this->_dir) & $permsBefore,
+ "Wrong permissions set for " . $this->_dir);
+ $this->assertEquals($permsBefore, fileperms($this->_file) & $permsBefore,
+ "Wrong permissions set for " . $this->_file);
+ Varien_Io_File::chmodRecursive($this->_dir, $expected);
+ $this->assertEquals($expected, fileperms($this->_dir) & $expected,
+ "Directory permissions were changed incorrectly.");
+ $this->assertEquals($expected, fileperms($this->_file) & $expected,
+ "File permissions were changed incorrectly.");
+ } catch (Exception $e) {
+ }
+
+ $this->_cleanup();
+ if (isset($e)) {
+ throw $e;
+ }
+ }
+
+ public function testRmdirRecursive()
+ {
+ try {
+ $this->_prepare();
+ $this->assertFileExists($this->_file);
+ Varien_Io_File::rmdirRecursive($this->_dir);
+ $this->assertFileNotExists($this->_dir);
+ } catch (Exception $e) {
+ }
+
+ $this->_cleanup();
+ if (isset($e)) {
+ throw $e;
+ }
+ }
+
+ /**
+ * Create files for tests
+ */
+ protected function _prepare()
+ {
+ $this->_dir = __DIR__ . DIRECTORY_SEPARATOR . '_files' . DIRECTORY_SEPARATOR . 'directory';
+ $this->_file = $this->_dir . DIRECTORY_SEPARATOR . 'file.txt';
+ @mkdir($this->_dir, 0700, true);
+ if (@touch($this->_file)) {
+ chmod($this->_file, 0700);
+ }
+ }
+
+ /**
+ * Remove fixture files
+ */
+ protected function _cleanup()
+ {
+ if (file_exists($this->_file)) {
+ @unlink($this->_file);
+ }
+ if (file_exists($this->_dir)) {
+ @rmdir($this->_dir);
+ }
+ }
+}
diff --git a/dev/tools/migration/aliases_map/composite_modules_ce.php b/dev/tools/migration/aliases_map/composite_modules_ce.php
new file mode 100644
index 0000000000000..e0ddb48bcf530
--- /dev/null
+++ b/dev/tools/migration/aliases_map/composite_modules_ce.php
@@ -0,0 +1,36 @@
+./get_aliases_map.php -- [-ph]
+ Build Magento 1 Aliases To Magento 2 Classes Names.
+ Additional parameters:
+ -p path to code scope of magento instance
+ -h print usage
+
+USAGE
+);
+
+$options = getopt('p:h');
+
+if (isset($options['h'])) {
+ print USAGE;
+ exit(0);
+}
+
+require_once realpath(dirname(dirname(dirname(__DIR__)))) . '/dev/tests/static/framework/bootstrap.php';
+require_once realpath(dirname(dirname(dirname(__DIR__)))) . '/lib/Zend/Json.php';
+
+$magentoBaseDir = dirname(__DIR__) . '/../../';
+if (isset($options['p'])) {
+ $magentoBaseDir = $options['p'];
+}
+
+$utilityFiles = new Utility_Files($magentoBaseDir);
+$map = array();
+$compositeModules = getFilesCombinedArray(dirname(__FILE__) . '/aliases_map', '/^composite_modules_.*\.php$/');
+// PHP code
+foreach ($utilityFiles->getPhpFiles(true, true, true, false) as $file) {
+ $content = file_get_contents($file);
+ $classes = Legacy_ClassesTest::collectPhpCodeClasses($content);
+ if ($classes) {
+ $factoryNames = array_filter($classes, 'isFactoryName');
+ foreach ($factoryNames as $factoryName) {
+ list($module, $name) = getModuleName($factoryName, $compositeModules);
+ $patterns = array(
+ '::getModel(\'%s\'' => 'Model',
+ '::getSingleton(\'%s\'' => 'Model',
+ '::getResourceModel(\'%s\'' => 'Model_Resource',
+ '::getResourceSingleton(\'%s\'' => 'Model_Resource',
+ 'addBlock(\'%s\'' => 'Block',
+ 'createBlock(\'%s\'' => 'Block',
+ 'getBlockClassName(\'%s\'' => 'Block',
+ 'getBlockSingleton(\'%s\'' => 'Block'
+ );
+
+ foreach ($patterns as $pattern => $classType) {
+ if (isPatternExist($content, $pattern, $factoryName)) {
+ if (!isset($map[$classType])) {
+ $map[$classType] = array();
+ }
+
+ $map[$classType][$factoryName] = getClassName($module, $classType, $name);
+ }
+ }
+ }
+ }
+}
+
+// layouts
+$classType = 'Block';
+$layouts = $utilityFiles->getLayoutFiles(array(), false);
+foreach ($layouts as $file) {
+ $xml = simplexml_load_file($file);
+ $classes = Utility_Classes::collectLayoutClasses($xml);
+ $factoryNames = array_filter($classes, 'isFactoryName');
+ if (!$factoryNames) {
+ continue;
+ }
+ foreach ($factoryNames as $factoryName) {
+ list($module, $name) = getModuleName($factoryName, $compositeModules);
+ $map[$classType][$factoryName] = getClassName($module, $classType, $name);
+ }
+}
+
+echo Zend_Json::prettyPrint(Zend_Json::encode($map));
+
+/**
+ * Get combined array from similar files by pattern
+ *
+ * @param string $dirPath
+ * @param string $filePattern
+ * @return array
+ */
+function getFilesCombinedArray($dirPath, $filePattern)
+{
+ $result = array();
+ $directoryIterator = new DirectoryIterator($dirPath);
+ $patternIterator = new RegexIterator($directoryIterator, $filePattern);
+
+ foreach ($patternIterator as $fileInfo) {
+ $arrayFromFile = include_once($fileInfo->getPathname());
+ $result = array_merge($result, $arrayFromFile);
+ }
+ return $result;
+}
+
+/**
+ * Check if pattern exist in file content
+ *
+ * @param string $content
+ * @param string $pattern
+ * @param string $alias
+ * @return bool
+ */
+function isPatternExist($content, $pattern, $alias)
+{
+ $search = sprintf($pattern, $alias);
+ return strpos($content, $search) !== false;
+}
+
+/**
+ * Build class name supported in magento 2
+ *
+ * @param string $module
+ * @param string $type
+ * @param string $name
+ * @return string|bool
+ */
+function getClassName($module, $type, $name = null)
+{
+ if (empty($name)) {
+ if ('Helper' !== $type) {
+ return false;
+ }
+ $name = 'data';
+ }
+
+ return implode('_', array_map('ucfirst', explode('_', $module . '_' . $type . '_' . $name)));
+}
+
+/**
+ * Whether the given class name is a factory name
+ *
+ * @param string $class
+ * @return bool
+ */
+function isFactoryName($class)
+{
+ return false !== strpos($class, '/') || preg_match('/^[a-z\d]+(_[A-Za-z\d]+)?$/', $class);
+}
+
+/**
+ * Transform factory name into a pair of module and name
+ *
+ * @param string $factoryName
+ * @return array
+ */
+function getModuleName($factoryName, $compositeModules = array())
+{
+ if (false !== strpos($factoryName, '/')) {
+ list($module, $name) = explode('/', $factoryName);
+ } else {
+ $module = $factoryName;
+ $name = false;
+ }
+ if (array_key_exists($module, $compositeModules)) {
+ $module = $compositeModules[$module];
+ } elseif (false === strpos($module, '_')) {
+ $module = "Mage_{$module}";
+ }
+ return array($module, $name);
+}
diff --git a/lib/Magento/Data/Graph.php b/lib/Magento/Data/Graph.php
new file mode 100644
index 0000000000000..636f748cba6da
--- /dev/null
+++ b/lib/Magento/Data/Graph.php
@@ -0,0 +1,195 @@
+_assertNode($node, false);
+ $this->_nodes[$node] = $node;
+ }
+ foreach ($relations as $pair) {
+ list($from, $to) = $pair;
+ $this->addRelation($from, $to);
+ }
+ }
+
+ /**
+ * Set a relation between nodes
+ *
+ * @param string|int $from
+ * @param string|int $to
+ * @return Magento_Data_Graph
+ * @throws InvalidArgumentException
+ */
+ public function addRelation($from, $to)
+ {
+ if ($from == $to) {
+ throw new InvalidArgumentException("Graph node '{$from}' is linked to itself.");
+ }
+ $this->_assertNode($from, true);
+ $this->_assertNode($to, true);
+ $this->_from[$from][$to] = $to;
+ $this->_to[$to][$from] = $from;
+ return $this;
+ }
+
+ /**
+ * Export relations between nodes. Can return inverse relations
+ *
+ * @param int $mode
+ * @return array
+ * @throws InvalidArgumentException
+ */
+ public function getRelations($mode = self::DIRECTIONAL)
+ {
+ switch ($mode) {
+ case self::DIRECTIONAL:
+ return $this->_from;
+ case self::INVERSE:
+ return $this->_to;
+ case self::NON_DIRECTIONAL:
+ $graph = $this->_from;
+ foreach ($this->_to as $idTo => $relations) {
+ foreach ($relations as $idFrom) {
+ $graph[$idTo][$idFrom] = $idFrom;
+ }
+ }
+ return $graph;
+ default:
+ throw new InvalidArgumentException("Unknown search mode: '{$mode}'");
+ }
+ }
+
+ /**
+ * Find a cycle in the graph
+ *
+ * Returns first found cycle
+ * Optionally may specify a node to return a cycle if it is in any
+ *
+ * @param string|int $node
+ * @return array
+ */
+ public function findCycle($node = null)
+ {
+ $nodes = (null === $node) ? $this->_nodes : array($node);
+ $result = array();
+ foreach ($nodes as $node) {
+ $result = $this->dfs($node, $node);
+ if ($result) {
+ break;
+ }
+ }
+ return $result;
+ }
+
+ /**
+ * "Depth-first search" of a path between nodes
+ *
+ * Returns path as array of nodes or empty array if path does not exist.
+ * Only first found path is returned. It will be not necessary the shortest or optimal in any way.
+ *
+ * @param string|int $from
+ * @param string|int $to
+ * @param int $mode
+ * @return array
+ */
+ public function dfs($from, $to, $mode = self::DIRECTIONAL)
+ {
+ $this->_assertNode($from, true);
+ $this->_assertNode($to, true);
+ return $this->_dfs($from, $to, $this->getRelations($mode));
+ }
+
+ /**
+ * Recursive sub-routine of dfs()
+ *
+ * @param string|int $from
+ * @param string|int $to
+ * @param array $graph
+ * @param array &$visited
+ * @param array $stack
+ * @return array
+ * @link http://en.wikipedia.org/wiki/Depth-first_search
+ */
+ protected function _dfs($from, $to, $graph, &$visited = array(), $stack = array())
+ {
+ $stack[] = $from;
+ $visited[$from] = $from;
+ if (isset($graph[$from][$to])) {
+ $stack[] = $to;
+ return $stack;
+ }
+ if (isset($graph[$from])) {
+ foreach ($graph[$from] as $node) {
+ if (!isset($visited[$node])) {
+ $result = $this->_dfs($node, $to, $graph, $visited, $stack);
+ if ($result) {
+ return $result;
+ }
+ }
+ }
+ }
+ return array();
+ }
+
+ /**
+ * Verify existence or non-existence of a node
+ *
+ * @param string|int $node
+ * @param bool $mustExist
+ * @throws InvalidArgumentException according to assertion rules
+ */
+ protected function _assertNode($node, $mustExist)
+ {
+ if (isset($this->_nodes[$node])) {
+ if (!$mustExist) {
+ throw new InvalidArgumentException("Graph node '{$node}' already exists'.");
+ }
+ } else {
+ if ($mustExist) {
+ throw new InvalidArgumentException("Graph node '{$node}' does not exist.");
+ }
+ }
+ }
+}
diff --git a/lib/Magento/Shell.php b/lib/Magento/Shell.php
index 43f7b8f841523..61cefd0d9c25e 100644
--- a/lib/Magento/Shell.php
+++ b/lib/Magento/Shell.php
@@ -73,23 +73,30 @@ public function getVerbose()
*
* @param string $command Command with optional argument markers '%s'
* @param array $arguments Argument values to substitute markers with
- * @return string
- * @throws Magento_Exception
+ * @param string &$fullOutput A string to dump all actual output
+ * @return array raw output from exec() PHP-function (the second argument)
+ * @throws Magento_Exception if exit code is other than zero
*/
- public function execute($command, array $arguments = array())
+ public function execute($command, array $arguments = array(), &$fullOutput = '')
{
$arguments = array_map('escapeshellarg', $arguments);
- $command = vsprintf($command, $arguments);
- /* Output errors to STDOUT instead of STDERR */
- exec("$command 2>&1", $output, $exitCode);
- $output = implode(PHP_EOL, $output);
+ $rawCommand = vsprintf("{$command} 2>&1", $arguments); // Output errors to STDOUT instead of STDERR
+ $output = $rawCommand . PHP_EOL;
+ $fullOutput .= $output;
if ($this->_isVerbose) {
- echo $output . PHP_EOL;
+ echo $output;
+ }
+ exec($rawCommand, $rawOutput, $exitCode);
+ $rawOutputStr = implode(PHP_EOL, $rawOutput);
+ $output = $rawOutputStr . PHP_EOL . PHP_EOL;
+ $fullOutput .= $output;
+ if ($this->_isVerbose) {
+ echo $output;
}
if ($exitCode) {
- $commandError = new Exception($output, $exitCode);
+ $commandError = new Exception($rawOutputStr, $exitCode);
throw new Magento_Exception("Command `$command` returned non-zero exit code.", 0, $commandError);
}
- return $output;
+ return $rawOutput;
}
}
diff --git a/lib/Varien/Data/Form/Element/Editor.php b/lib/Varien/Data/Form/Element/Editor.php
index e6c219cc2ad33..3c09a52dafff4 100644
--- a/lib/Varien/Data/Form/Element/Editor.php
+++ b/lib/Varien/Data/Form/Element/Editor.php
@@ -107,10 +107,8 @@ public function getElementHtml()
. $this->serialize($this->getHtmlAttributes()) . ' >' . $this->getEscapedValue() . ''
. $js . '