├── README.md ├── modman ├── phpunit.xml.dist └── src ├── app ├── code │ └── community │ │ └── Hackathon │ │ └── DerivedAttributes │ │ ├── Block │ │ └── Adminhtml │ │ │ ├── Entity.php │ │ │ ├── Entity │ │ │ ├── Customer │ │ │ │ └── Grid.php │ │ │ ├── Form.php │ │ │ ├── Massaction.php │ │ │ ├── Product │ │ │ │ └── Grid.php │ │ │ └── Tabs.php │ │ │ ├── Rule.php │ │ │ └── Rule │ │ │ ├── Edit.php │ │ │ ├── Edit │ │ │ └── Form.php │ │ │ └── Grid.php │ │ ├── Helper │ │ ├── Data.php │ │ └── Rule │ │ │ └── Director.php │ │ ├── Model │ │ ├── Bridge │ │ │ ├── Entity.php │ │ │ ├── EntityIterator.php │ │ │ ├── RuleLogger.php │ │ │ └── RuleRepository.php │ │ ├── Massupdater.php │ │ ├── Observer.php │ │ ├── Resource │ │ │ ├── Rule.php │ │ │ └── Rule │ │ │ │ └── Collection.php │ │ ├── Rule.php │ │ └── Source │ │ │ ├── Abstract.php │ │ │ ├── Attribute.php │ │ │ ├── Condition.php │ │ │ └── Generator.php │ │ ├── Test │ │ ├── Block │ │ │ ├── Entity.php │ │ │ └── Rule.php │ │ ├── Case │ │ │ └── Controller │ │ │ │ └── Dom.php │ │ ├── Constraint │ │ │ └── DomQuery.php │ │ ├── Controller │ │ │ ├── EntityController.php │ │ │ └── RuleController.php │ │ ├── Model │ │ │ ├── Bridge │ │ │ │ ├── Entity.php │ │ │ │ └── RuleRepository.php │ │ │ ├── Massupdater.php │ │ │ ├── Massupdater │ │ │ │ └── providers │ │ │ │ │ └── testProductMassUpdateByStoreView.yaml │ │ │ ├── Rule.php │ │ │ └── Rule │ │ │ │ └── providers │ │ │ │ └── rulesShouldBeAppliedOnModelSave.yaml │ │ ├── expectations │ │ │ ├── attributes.yaml │ │ │ └── rulesets.yaml │ │ └── fixtures │ │ │ ├── products.yaml │ │ │ ├── rules.yaml │ │ │ └── stores.yaml │ │ ├── controllers │ │ └── Adminhtml │ │ │ └── DerivedAttributes │ │ │ ├── EntityController.php │ │ │ └── RuleController.php │ │ ├── etc │ │ ├── adminhtml.xml │ │ └── config.xml │ │ └── sql │ │ └── derivedattributes_setup │ │ ├── mysql4-install-0.1.0.php │ │ ├── upgrade-0.1.0-0.1.1.php │ │ ├── upgrade-0.1.0-0.1.2.php │ │ ├── upgrade-0.1.1-0.1.2.php │ │ └── upgrade-0.1.2-0.1.3.php ├── design │ └── adminhtml │ │ └── base │ │ └── default │ │ └── layout │ │ └── hackathon_derivedattributes.xml └── etc │ └── modules │ └── Hackathon_DerivedAttributes.xml └── lib └── Hackathon └── DerivedAttributes ├── Attribute.php ├── BridgeInterface ├── EntityInterface.php ├── EntityIteratorInterface.php ├── RuleLoggerInterface.php └── RuleRepositoryInterface.php ├── Rule.php ├── RuleBuilder.php ├── RuleSet.php ├── RuleSetsByStore.php ├── Service ├── Condition │ ├── AlwaysCondition.php │ └── BooleanAttributeCondition.php ├── Generator │ └── TemplateGenerator.php └── Manager.php ├── ServiceInterface ├── ConditionInterface.php ├── FilterInterface.php └── GeneratorInterface.php ├── Store.php ├── StoreSet.php └── Updater.php /README.md: -------------------------------------------------------------------------------- 1 | Status: WIP 2 | 3 | Build status (master): [![Build Status](https://travis-ci.org/magento-hackathon/DerivedAttributes.svg?branch=master)](https://travis-ci.org/magento-hackathon/DerivedAttributes) 4 | 5 | DerivedAttributes (e.g. for product feed export) 6 | ================================================ 7 | 8 | (Product Attribute, Customer Attributes) 9 | 10 | Installation Instructions 11 | ------------------------- 12 | 13 | ### Option 1: Via Composer 14 | 1. Add required repositories to project `composer.json`: 15 | 16 | "repositories": [ 17 | { 18 | "type": "vcs", 19 | "url": "https://github.com/magento-hackathon/DerivedAttributes.git" 20 | }, 21 | { 22 | "type": "vcs", 23 | "url": "https://github.com/integer-net/IntegerNet_GridMassActionPager" 24 | } 25 | ] 26 | 27 | 2. Install via composer: `composer require magento-hackathon/derived-attributes` 28 | 29 | ### Option 2: Manually 30 | 1. Download hackathon-derivedattributes.tar.gz or hackathon-derivedattributes.zip from the [Releases page](https://github.com/magento-hackathon/DerivedAttributes/releases) 31 | 2. Extract contained directory into your Magento installation. It contains all dependencies, no additional downloads necessary. 32 | 33 | 34 | Add custom conditions and generators 35 | ----------- 36 | 37 | Custom conditions and generators must implement the interfaces listed below. 38 | To add them, create an observer for the `derivedattribute_new_rulemanager` event: 39 | 40 | 41 | 42 | 43 | 44 | singleton 45 | Your_Module_Model_Observer 46 | addDerivedAttributesPlugin 47 | 48 | 49 | 50 | 51 | 52 | In this observer: 53 | 54 | class Your_Module_Model_Observer 55 | { 56 | public function addDerivedAttributesPlugin(Varien_Event_Observer $observer) 57 | { 58 | /** @var \Hackathon\DerivedAttributes\Service\Manager $ruleManager */ 59 | $ruleManager = $observer->getRuleManager(); 60 | $ruleManager->addConditionType('your_unique_condition_identifier', Mage::getModel('your_module/your_condition_class')); 61 | $ruleManager->addGeneratorType('your_unique_generator_identifier', Mage::getModel('your_module/your_generator_class')); 62 | } 63 | } 64 | 65 | ## Interfaces 66 | 67 | ### GeneratorInterface 68 | 69 | GeneratorInterface configure(string $data) 70 | string getData() 71 | mixed generateAttributeValue(EntityInterface $entity) 72 | string getTitle() 73 | string getDescription() 74 | 75 | ### ConditionInterface (ex. impl. BooleanAttributeCondition) 76 | 77 | ConditionInterface configure(string $data) 78 | string getData() 79 | boolean match(EntityInterface $entity) 80 | string getTitle() 81 | string getDescription() 82 | 83 | 84 | 85 | Transitivity of DerivedAttributes 86 | --- 87 | There is NO magic for "derived of derivedAttributes". Just rule priority. 88 | 89 | 90 | License 91 | ------- 92 | [OSL - Open Software Licence 3.0](http://opensource.org/licenses/osl-3.0.php) 93 | -------------------------------------------------------------------------------- /modman: -------------------------------------------------------------------------------- 1 | src/app/code/community/Hackathon/DerivedAttributes app/code/community/Hackathon/DerivedAttributes 2 | src/app/etc/modules/Hackathon_DerivedAttributes.xml app/etc/modules/Hackathon_DerivedAttributes.xml 3 | src/lib/Hackathon/DerivedAttributes lib/Hackathon/DerivedAttributes 4 | src/app/design/adminhtml/base/default/layout/hackathon_derivedattributes.xml app/design/adminhtml/base/default/layout/hackathon_derivedattributes.xml -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 15 | ./test/ 16 | 17 | 18 | 19 | 20 | 21 | ./src/ 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/app/code/community/Hackathon/DerivedAttributes/Block/Adminhtml/Entity.php: -------------------------------------------------------------------------------- 1 | _objectId = 'entity_id'; 7 | $this->_controller = 'adminhtml_entity'; 8 | $this->_headerText = $this->__('Apply Rules'); 9 | 10 | parent::__construct(); 11 | 12 | $this->removeButton('reset') 13 | ->removeButton('delete') 14 | ->removeButton('save'); 15 | } 16 | 17 | public function getBackUrl() 18 | { 19 | return $this->getUrl('*/derivedAttributes_rule/index'); 20 | } 21 | 22 | 23 | } -------------------------------------------------------------------------------- /src/app/code/community/Hackathon/DerivedAttributes/Block/Adminhtml/Entity/Customer/Grid.php: -------------------------------------------------------------------------------- 1 | <@fschmengler> 7 | * @category Hackathon 8 | * @package Hackathon_DerivedAttributes 9 | */ 10 | 11 | /** 12 | * @package Hackathon_DerivedAttributes 13 | */ 14 | class Hackathon_DerivedAttributes_Block_Adminhtml_Entity_Customer_Grid extends Mage_Adminhtml_Block_Customer_Grid 15 | { 16 | protected function _prepareMassaction() 17 | { 18 | $this->setMassactionIdField('entity_id'); 19 | $this->getMassactionBlock()->setFormFieldName('entity_ids'); 20 | $this->getMassactionBlock()->setUseAjax(true); 21 | $this->setNoFilterMassactionColumn(true); 22 | 23 | $this->getMassactionBlock()->addItem('apply', array( 24 | 'label'=> Mage::helper('catalog')->__('Apply Rules'), 25 | 'url' => $this->getUrl('*/*/applyRules'), 26 | 'confirm' => Mage::helper('catalog')->__('Are you sure?'), 27 | 'complete' => 'integerNetGridMassActionPager', 28 | 'additional' => $this->_getAdditionalMassactionBlock(), 29 | )); 30 | 31 | $this->getMassactionBlock()->addItem('dryrun', array( 32 | 'label'=> Mage::helper('catalog')->__('Apply Rules (dry run)'), 33 | 'url' => $this->getUrl('*/*/dryRun'), 34 | 'complete' => 'integerNetGridMassActionPager', 35 | 'additional' => $this->_getAdditionalMassactionBlock()->setDryRun(true), 36 | )); 37 | 38 | return $this; 39 | } 40 | 41 | protected function _getAdditionalMassactionBlock() 42 | { 43 | /** @var Hackathon_DerivedAttributes_Block_Adminhtml_Entity_Massaction $block */ 44 | $block = $this->getLayout()->createBlock('derivedattributes/adminhtml_entity_massaction'); 45 | $block->setEntityType('customer'); 46 | return $block; 47 | } 48 | 49 | protected function _prepareColumns() 50 | { 51 | parent::_prepareColumns(); 52 | $this->removeColumn('action'); 53 | return $this; 54 | } 55 | 56 | public function getGridUrl() 57 | { 58 | return $this->getUrl('*/*/customerGrid', array('_current'=>true)); 59 | } 60 | 61 | public function getRowUrl($row) 62 | { 63 | return '#'; 64 | } 65 | 66 | } -------------------------------------------------------------------------------- /src/app/code/community/Hackathon/DerivedAttributes/Block/Adminhtml/Entity/Form.php: -------------------------------------------------------------------------------- 1 | setId('ruleApplyForm'); 9 | } 10 | 11 | protected function _prepareForm() 12 | { 13 | $form = new Varien_Data_Form(array( 14 | 'id' => 'apply_rules_form', 15 | 'action' => $this->getData('action'), 16 | 'method' => 'post' 17 | )); 18 | $form->addFieldset('apply_rules_entities', array( 19 | 'legend' => $this->__('Select Entities') 20 | )); 21 | $fieldset = $form->addFieldset('apply_rules_stores', array( 22 | 'legend' => $this->__('Select Stores') 23 | )); 24 | if (Mage::app()->isSingleStoreMode()) { 25 | $storeId = Mage::app()->getStore(true)->getId(); 26 | $fieldset->addField('store_id', 'hidden', array( 27 | 'name' => 'store_id[]', 28 | 'value' => $storeId 29 | )); 30 | //TODO make fieldset invisible? 31 | } else { 32 | $field = $fieldset->addField('store_id', 'multiselect', array( 33 | 'name' => 'store_id[]', 34 | 'label' => $this->__('Store'), 35 | 'title' => $this->__('Store'), 36 | 'required' => true, 37 | 'values' => Mage::getSingleton('adminhtml/system_store')->getStoreValuesForForm(false, true), 38 | 'onchange' => '$(\'massaction_store_id\').setValue(this.getValue())', 39 | )); 40 | $field->setValue('0'); 41 | $renderer = $this->getLayout()->createBlock('adminhtml/store_switcher_form_renderer_fieldset_element'); 42 | $field->setRenderer($renderer); 43 | } 44 | $this->setForm($form); 45 | return parent::_prepareForm(); 46 | } 47 | 48 | } -------------------------------------------------------------------------------- /src/app/code/community/Hackathon/DerivedAttributes/Block/Adminhtml/Entity/Massaction.php: -------------------------------------------------------------------------------- 1 | false, 17 | ]); 18 | $form->addField('massaction_entity_type', 'hidden', array( 19 | 'name' => 'entity_type', 20 | 'value' => $this->getEntityType(), 21 | 'no_span' => true, 22 | )); 23 | $form->addField('massaction_store_id', 'multiselect', array( 24 | 'name' => 'store_id[]', 25 | 'required' => true, 26 | 'values' => Mage::getSingleton('adminhtml/system_store')->getStoreValuesForForm(false, true), 27 | 'no_span' => true, 28 | 'style' => 'display:none', 29 | )); 30 | if ($this->getDryRun()) { 31 | $form->addField('massaction_dry_run', 'hidden', array( 32 | 'name' => 'dry_run', 33 | 'value' => '1', 34 | 'no_span' => true, 35 | )); 36 | } 37 | $massActionId = $this->getParentBlock()->getMassaction()->getHtmlId(); 38 | $updateHiddenFieldJs = << 40 | \$('{$massActionId}').observe('change', function() { 41 | \$('massaction_store_id').setValue(\$('store_id').getValue()) 42 | }); 43 | 44 | HTML; 45 | return $form->toHtml() . $updateHiddenFieldJs; 46 | 47 | } 48 | } -------------------------------------------------------------------------------- /src/app/code/community/Hackathon/DerivedAttributes/Block/Adminhtml/Entity/Product/Grid.php: -------------------------------------------------------------------------------- 1 | <@fschmengler> 7 | * @category Hackathon 8 | * @package Hackathon_DerivedAttributes 9 | */ 10 | 11 | /** 12 | * @package Hackathon_DerivedAttributes 13 | */ 14 | class Hackathon_DerivedAttributes_Block_Adminhtml_Entity_Product_Grid extends Mage_Adminhtml_Block_Catalog_Product_Grid 15 | { 16 | public function __construct() 17 | { 18 | parent::__construct(); 19 | $this->setId('productDerivedAttributesGrid'); 20 | } 21 | 22 | 23 | protected function _prepareMassaction() 24 | { 25 | $this->setMassactionIdField('entity_id'); 26 | $this->getMassactionBlock()->setFormFieldName('entity_ids'); 27 | $this->getMassactionBlock()->setUseAjax(true); 28 | $this->setNoFilterMassactionColumn(true); 29 | 30 | $this->getMassactionBlock()->addItem('apply', array( 31 | 'label'=> Mage::helper('catalog')->__('Apply Rules'), 32 | 'url' => $this->getUrl('*/*/applyRules'), 33 | 'confirm' => Mage::helper('catalog')->__('Are you sure?'), 34 | 'complete' => 'integerNetGridMassActionPager', 35 | 'additional' => $this->_getAdditionalMassactionBlock(), 36 | )); 37 | 38 | $this->getMassactionBlock()->addItem('dryrun', array( 39 | 'label'=> Mage::helper('catalog')->__('Apply Rules (dry run)'), 40 | 'url' => $this->getUrl('*/*/applyRules'), 41 | 'complete' => 'integerNetGridMassActionPager', 42 | 'additional' => $this->_getAdditionalMassactionBlock()->setDryRun(true), 43 | )); 44 | 45 | return $this; 46 | } 47 | 48 | protected function _getAdditionalMassactionBlock() 49 | { 50 | /** @var Hackathon_DerivedAttributes_Block_Adminhtml_Entity_Massaction $block */ 51 | $block = $this->getLayout()->createBlock('derivedattributes/adminhtml_entity_massaction'); 52 | $block->setEntityType(Mage_Catalog_Model_Product::ENTITY); 53 | return $block; 54 | } 55 | 56 | protected function _prepareColumns() 57 | { 58 | parent::_prepareColumns(); 59 | $this->removeColumn('action'); 60 | return $this; 61 | } 62 | 63 | public function getGridUrl() 64 | { 65 | return $this->getUrl('*/*/productGrid', array('_current'=>true)); 66 | } 67 | 68 | public function getRowUrl($row) 69 | { 70 | return '#'; 71 | } 72 | 73 | } -------------------------------------------------------------------------------- /src/app/code/community/Hackathon/DerivedAttributes/Block/Adminhtml/Entity/Tabs.php: -------------------------------------------------------------------------------- 1 | setId('derivedattributes_entity_tabs'); 8 | $this->setDestElementId('apply_rules_entities'); 9 | $this->setTitle(Mage::helper('derivedattributes')->__('Apply Rules')); 10 | } 11 | 12 | protected function _beforeToHtml() 13 | { 14 | $productGridBlock = $this->getLayout()->createBlock( 15 | 'derivedattributes/adminhtml_entity_product_grid', 16 | 'derivedattributes_entity_product_grid' 17 | ); 18 | $this->addTab('product', array( 19 | 'label' => $this->__('Products'), 20 | 'content' => $productGridBlock->toHtml(), 21 | )); 22 | 23 | $customerGridBlock = $this->getLayout()->createBlock( 24 | 'derivedattributes/adminhtml_entity_customer_grid', 25 | 'derivedattributes_entity_customer_grid' 26 | ); 27 | $this->addTab('customers', array( 28 | 'label' => Mage::helper('customer')->__('Customers'), 29 | 'content' => $customerGridBlock->toHtml(), 30 | )); 31 | 32 | $this->_updateActiveTab(); 33 | return parent::_beforeToHtml(); 34 | } 35 | 36 | protected function _updateActiveTab() 37 | { 38 | $tabId = $this->getRequest()->getParam('tab'); 39 | if( $tabId ) { 40 | $tabId = preg_replace("#{$this->getId()}_#", '', $tabId); 41 | if($tabId) { 42 | $this->setActiveTab($tabId); 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/app/code/community/Hackathon/DerivedAttributes/Block/Adminhtml/Rule.php: -------------------------------------------------------------------------------- 1 | _blockGroup = 'derivedattributes'; 7 | $this->_controller = 'adminhtml_rule'; 8 | $this->_headerText = $this->__('Derived Attribute Rules'); 9 | $this->_addButtonLabel = $this->__('Add New Rule'); 10 | parent::__construct(); 11 | 12 | $this->addButton('derivedattributes_rule_apply', array( 13 | 'label' => $this->__('Apply Rules'), 14 | 'onclick' => 'setLocation(\'' . $this->getUrl('*/derivedAttributes_entity/index') .'\')', 15 | 'class' => 'save', 16 | )); 17 | } 18 | } -------------------------------------------------------------------------------- /src/app/code/community/Hackathon/DerivedAttributes/Block/Adminhtml/Rule/Edit.php: -------------------------------------------------------------------------------- 1 | _objectId = 'id'; 14 | $this->_blockGroup = 'derivedattributes'; 15 | $this->_controller = 'adminhtml_rule'; 16 | 17 | parent::__construct(); 18 | 19 | $this->_addButton('save_and_continue_edit', array( 20 | 'class' => 'save', 21 | 'label' => $this->__('Save and Continue Edit'), 22 | 'onclick' => 'editForm.submit($(\'edit_form\').action + \'back/edit/\')', 23 | ), 10); 24 | } 25 | 26 | /** 27 | * Getter for form header text 28 | * 29 | * @return string 30 | */ 31 | public function getHeaderText() 32 | { 33 | $rule = Mage::registry('current_derived_attribute_rule'); 34 | if ($rule->getRuleId()) { 35 | return $this->__("Edit Rule '%s'", $this->escapeHtml($rule->getName())); 36 | } 37 | else { 38 | return $this->__('New Rule'); 39 | } 40 | } 41 | /** 42 | * Get form submit URL 43 | * 44 | * @return string 45 | */ 46 | public function getFormActionUrl() 47 | { 48 | return $this->getUrl('*/*/save'); 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/app/code/community/Hackathon/DerivedAttributes/Block/Adminhtml/Rule/Edit/Form.php: -------------------------------------------------------------------------------- 1 | setId('derivedattributes_rule_form'); 9 | $this->setTitle($this->__('Rule Information')); 10 | } 11 | 12 | protected function _prepareForm() 13 | { 14 | $form = new Varien_Data_Form(array('id' => 'edit_form', 'action' => $this->getData('action'), 'method' => 'post')); 15 | $form->setUseContainer(true); 16 | $this->setForm($form); 17 | 18 | $fieldset = $form->addFieldset('base_fieldset', 19 | array('legend' => $this->__('General Information')) 20 | ); 21 | 22 | $model = Mage::registry('current_derived_attribute_rule'); 23 | if ($model->getId()) { 24 | $fieldset->addField('rule_id', 'hidden', array( 25 | 'name' => 'rule_id', 26 | )); 27 | } 28 | 29 | $fieldset->addField('name', 'text', array( 30 | 'name' => 'name', 31 | 'label' => $this->__('Rule Name'), 32 | 'title' => $this->__('Rule Name'), 33 | 'required' => true, 34 | )); 35 | 36 | $fieldset->addField('description', 'textarea', array( 37 | 'name' => 'description', 38 | 'label' => $this->__('Description'), 39 | 'title' => $this->__('Description'), 40 | 'style' => 'height: 100px;', 41 | )); 42 | 43 | $fieldset->addField('active', 'select', array( 44 | 'label' => $this->__('Status'), 45 | 'title' => $this->__('Status'), 46 | 'name' => 'active', 47 | 'required' => true, 48 | 'options' => array( 49 | '1' => $this->__('Active'), 50 | '0' => $this->__('Inactive'), 51 | ), 52 | )); 53 | 54 | if (Mage::app()->isSingleStoreMode()) { 55 | $storeId = Mage::app()->getStore(true)->getId(); 56 | $fieldset->addField('store_id', 'hidden', array( 57 | 'name' => 'store_id[]', 58 | 'value' => $storeId 59 | )); 60 | $model->setStoreId($storeId); 61 | } else { 62 | $field = $fieldset->addField('store_id', 'multiselect', array( 63 | 'name' => 'store_id[]', 64 | 'label' => $this->__('Store'), 65 | 'title' => $this->__('Store'), 66 | 'required' => true, 67 | 'values' => Mage::getSingleton('adminhtml/system_store')->getStoreValuesForForm(false, true) 68 | )); 69 | $renderer = $this->getLayout()->createBlock('adminhtml/store_switcher_form_renderer_fieldset_element'); 70 | $field->setRenderer($renderer); 71 | } 72 | 73 | $fieldset->addField('attribute_id', 'select', array( 74 | 'label' => $this->__('Attribute'), 75 | 'title' => $this->__('Attribute'), 76 | 'name' => 'attribute_id', 77 | 'required' => true, 78 | 'values' => Mage::getModel('derivedattributes/source_attribute')->toOptionArray(true) 79 | )); 80 | 81 | $fieldset = $form->addFieldset('generator_fieldset', 82 | array('legend' => $this->__('Generator')) 83 | ); 84 | 85 | $fieldset->addField('generator_type', 'select', array( 86 | 'name' => 'generator_type', 87 | 'label' => $this->__('Generator Type'), 88 | 'title' => $this->__('Generator Type'), 89 | 'values' => Mage::getModel('derivedattributes/source_generator')->toOptionArray(true) 90 | )); 91 | 92 | //TODO show description for currently selected generator to explain generator_data format 93 | $fieldset->addField('generator_data', 'textarea', array( 94 | 'name' => 'generator_data', 95 | 'label' => $this->__('Generator Data'), 96 | 'title' => $this->__('Generator Data'), 97 | 'style' => 'height: 100px;', 98 | )); 99 | 100 | $fieldset = $form->addFieldset('condition_fieldset', 101 | array('legend' => $this->__('Condition')) 102 | ); 103 | 104 | $fieldset->addField('condition_type', 'select', array( 105 | 'name' => 'condition_type', 106 | 'label' => $this->__('Condition Type'), 107 | 'title' => $this->__('Condition Type'), 108 | 'values' => Mage::getModel('derivedattributes/source_condition')->toOptionArray(true) 109 | )); 110 | 111 | //TODO show description for currently selected condition to explain condition_data format 112 | $fieldset->addField('condition_data', 'textarea', array( 113 | 'name' => 'condition_data', 114 | 'label' => $this->__('Condition Data'), 115 | 'title' => $this->__('Condition Data'), 116 | 'style' => 'height: 100px;', 117 | )); 118 | 119 | $form->setValues($model->getData()); 120 | 121 | return parent::_prepareForm(); 122 | } 123 | 124 | 125 | } 126 | -------------------------------------------------------------------------------- /src/app/code/community/Hackathon/DerivedAttributes/Block/Adminhtml/Rule/Grid.php: -------------------------------------------------------------------------------- 1 | <@fschmengler> 7 | * @category Hackathon 8 | * @package Hackathon_DerivedAttributes 9 | */ 10 | 11 | /** 12 | * @package Hackathon_DerivedAttributes 13 | */ 14 | class Hackathon_DerivedAttributes_Block_Adminhtml_Rule_Grid extends Mage_Adminhtml_Block_Widget_Grid 15 | { 16 | public function getRowUrl($item) 17 | { 18 | return $this->getUrl('*/*/edit', array('id' => $item->getId())); 19 | } 20 | 21 | /** 22 | * Get collection object 23 | * @return Hackathon_DerivedAttributes_Model_Resource_Derivedattributes_Rule_Collection 24 | */ 25 | public function getCollection() 26 | { 27 | if (!parent::getCollection()) { 28 | $collection = Mage::getResourceModel('derivedattributes/rule_collection'); 29 | $this->setCollection($collection); 30 | } 31 | 32 | return parent::getCollection(); 33 | } 34 | 35 | /** 36 | * Prepare columns 37 | * @return Hackathon_DerivedAttributes_Block_Adminhtml_Rule_Grid 38 | */ 39 | protected function _prepareColumns() 40 | { 41 | $this->addColumn('rule_id', array( 42 | 'header' => $this->__('ID'), 43 | 'align' =>'right', 44 | 'width' => '50px', 45 | 'index' => 'rule_id', 46 | )); 47 | 48 | $this->addColumn('name', array( 49 | 'header' => $this->__('Rule Name'), 50 | 'align' =>'left', 51 | 'index' => 'name', 52 | )); 53 | 54 | $this->addColumn('is_active', array( 55 | 'header' => $this->__('Status'), 56 | 'align' => 'left', 57 | 'width' => '80px', 58 | 'index' => 'is_active', 59 | 'type' => 'options', 60 | 'options' => array( 61 | 1 => 'Active', 62 | 0 => 'Inactive', 63 | ), 64 | )); 65 | 66 | if (!Mage::app()->isSingleStoreMode()) { 67 | $this->addColumn('rule_store_id', array( 68 | 'header' => $this->__('Store'), 69 | 'align' =>'left', 70 | 'index' => 'store_id', 71 | 'type' => 'options', 72 | 'sortable' => false, 73 | 'options' => Mage::getSingleton('adminhtml/system_store')->getStoreOptionHash(), 74 | 'width' => 200, 75 | )); 76 | } 77 | 78 | $this->addColumn('priority', array( 79 | 'header' => $this->__('Priority'), 80 | 'align' => 'right', 81 | 'index' => 'priority', 82 | 'width' => 100, 83 | )); 84 | return parent::_prepareColumns(); 85 | } 86 | 87 | 88 | } -------------------------------------------------------------------------------- /src/app/code/community/Hackathon/DerivedAttributes/Helper/Data.php: -------------------------------------------------------------------------------- 1 | <@fschmengler> 7 | * @category Hackathon 8 | * @package Hackathon_DerivedAttributes 9 | */ 10 | 11 | use Hackathon\DerivedAttributes\Updater; 12 | use Hackathon\DerivedAttributes\RuleBuilder; 13 | use Hackathon\DerivedAttributes\Attribute; 14 | use Hackathon\DerivedAttributes\StoreSet; 15 | use Hackathon\DerivedAttributes\Service\Manager; 16 | 17 | /** 18 | * Data Helper 19 | * @package Hackathon_DerivedAttributes 20 | */ 21 | class Hackathon_DerivedAttributes_Helper_Data extends Mage_Core_Helper_Abstract 22 | { 23 | /** 24 | * @var Manager 25 | */ 26 | protected $_ruleManager; 27 | 28 | /** 29 | * @return \Hackathon\DerivedAttributes\Service\Manager 30 | */ 31 | public function getServiceManager() 32 | { 33 | if(is_null($this->_ruleManager)){ 34 | $this->_ruleManager = new Manager(); 35 | Mage::dispatchEvent("derivedattribute_new_rulemanager", [ 36 | 'rule_manager' => $this->_ruleManager 37 | ]); 38 | } 39 | return $this->_ruleManager; 40 | } 41 | 42 | /** 43 | * Factory method for Updater 44 | * 45 | * @return Updater 46 | */ 47 | public function getNewUpdater() 48 | { 49 | $ruleRepository = Mage::getModel('derivedattributes/bridge_ruleRepository'); 50 | $ruleLogger = Mage::getModel('derivedattributes/bridge_ruleLogger'); 51 | 52 | return new Updater($ruleRepository, $ruleLogger); 53 | } 54 | 55 | /** 56 | * Factory method for RuleBuilder 57 | * 58 | * @return RuleBuilder 59 | */ 60 | public function getNewRuleBuilder(Attribute $attribute) 61 | { 62 | return new RuleBuilder($this->getServiceManager(), $attribute); 63 | } 64 | 65 | /** 66 | * Factory method for EntityIterator bridge 67 | * 68 | * @param Mage_Eav_Model_Entity_Type $entityTypeInstance 69 | * @return Hackathon_DerivedAttributes_Model_Bridge_EntityIterator 70 | */ 71 | public function getNewEntityIterator(Mage_Eav_Model_Entity_Type $entityTypeInstance, $entityIds) 72 | { 73 | /** @var Mage_Eav_Model_Entity_Collection_Abstract $collection */ 74 | $collection = Mage::getModel($entityTypeInstance->getEntityModel())->getCollection(); 75 | $collection->addFieldToFilter('entity_id', ['in' => $entityIds]); 76 | /* 77 | * I tried using the flat catalog if available but using it from admin area is not intended 78 | * and there are too many restrictions and special cases 79 | */ 80 | $collection->addAttributeToSelect('*'); 81 | 82 | $iterator = new Hackathon_DerivedAttributes_Model_Bridge_EntityIterator($collection); 83 | return $iterator; 84 | } 85 | 86 | /** 87 | * Factory method for Entity bridge 88 | * 89 | * @param Mage_Eav_Model_Entity_Type $entityTypeInstance 90 | * @return Hackathon_DerivedAttributes_Model_Bridge_Entity 91 | */ 92 | public function getNewEntityModel(Mage_Eav_Model_Entity_Type $entityTypeInstance, Mage_Core_Model_Abstract $entity = null) 93 | { 94 | if ($entity === null) { 95 | $entity = Mage::getModel($entityTypeInstance->getEntityModel()); 96 | } 97 | $entityModel = new Hackathon_DerivedAttributes_Model_Bridge_Entity($entityTypeInstance, $entity); 98 | return $entityModel; 99 | } 100 | 101 | /** 102 | * Factory method for StoreSet 103 | * 104 | * @param array $storeIds 105 | * @param bool $expand Set to true if ['0'] should be expanded to all store views 106 | * @return StoreSet 107 | */ 108 | public function createStoreSet(array $storeIds, $expand = false) 109 | { 110 | if ($storeIds === ['0']) { 111 | if (! $expand) { 112 | return StoreSet::all(); 113 | } 114 | $storeIds = array_keys(Mage::app()->getStores(true)); 115 | } 116 | return new StoreSet($storeIds); 117 | } 118 | 119 | /** 120 | * Load entity type instance by code 121 | * 122 | * @param $entityType 123 | * @return Mage_Eav_Model_Entity_Type 124 | * @throws Mage_Core_Exception 125 | */ 126 | public function getEntityTypeInstance($entityType) 127 | { 128 | $entityTypeInstance = Mage::getModel('eav/entity_type')->loadByCode($entityType); 129 | if (!$entityTypeInstance->getId()) { 130 | Mage::throwException(Mage::helper('derivedattributes')->__('Invalid entity type %s.', $entityType)); 131 | return $entityTypeInstance; 132 | } 133 | return $entityTypeInstance; 134 | } 135 | } -------------------------------------------------------------------------------- /src/app/code/community/Hackathon/DerivedAttributes/Helper/Rule/Director.php: -------------------------------------------------------------------------------- 1 | getNewRuleBuilder($this->getAttributeById($ruleData->getData('attribute_id'))); 14 | $builder->setActive((bool) $ruleData->getData('active')) 15 | ->setPriority((int) $ruleData->getData('priority')) 16 | ->setConditionType($ruleData->getData('condition_type')) 17 | ->setConditionData($ruleData->getData('condition_data')) 18 | ->setGeneratorType($ruleData->getData('generator_type')) 19 | ->setGeneratorData($ruleData->getData('generator_data')) 20 | ->setName($ruleData->getData('name')) 21 | ->setDescription($ruleData->getData('description')) 22 | ->setStores($helper->createStoreSet($ruleData->getData('store_id'))); 23 | return $builder->build(); 24 | } 25 | /** 26 | * Returns the Attribute instance 27 | * 28 | * @return Attribute 29 | */ 30 | private function getAttributeById($attributeId){ 31 | 32 | /* @var $magentoAttribute Mage_Eav_Model_Entity_Attribute */ 33 | $magentoAttribute = Mage::getModel("eav/entity_attribute")->load($attributeId); 34 | 35 | $attribute = new Attribute($magentoAttribute->getEntityType()->getEntityTypeCode(), $magentoAttribute->getAttributeCode()); 36 | 37 | return $attribute; 38 | } 39 | 40 | } -------------------------------------------------------------------------------- /src/app/code/community/Hackathon/DerivedAttributes/Model/Bridge/Entity.php: -------------------------------------------------------------------------------- 1 | 4 | */ 5 | 6 | use Hackathon\DerivedAttributes\BridgeInterface\EntityInterface; 7 | use Hackathon\DerivedAttributes\Attribute; 8 | 9 | /** 10 | * Entity implementation of entity-bridge-interface. 11 | */ 12 | class Hackathon_DerivedAttributes_Model_Bridge_Entity implements \Hackathon\DerivedAttributes\BridgeInterface\EntityInterface 13 | { 14 | /** 15 | * @var Mage_Eav_Model_Entity_Type 16 | */ 17 | private $type; 18 | /** 19 | * @var Mage_Core_Model_Abstract 20 | */ 21 | private $entity; 22 | 23 | public function __construct(Mage_Eav_Model_Entity_Type $type, Mage_Core_Model_Abstract $entity) 24 | { 25 | $this->type = $type; 26 | $this->entity = $entity; 27 | } 28 | 29 | /** 30 | * Returns true if entity has this kind of attribute 31 | * 32 | * @param Attribute $attribute 33 | * @return bool 34 | */ 35 | function hasAttribute(Attribute $attribute) 36 | { 37 | return $attribute->getEntityTypeCode() == $this->getEntityTypeCode(); 38 | } 39 | 40 | /** 41 | * @return string 42 | */ 43 | function getEntityTypeCode() 44 | { 45 | return $this->type->getEntityTypeCode(); 46 | } 47 | 48 | 49 | /** 50 | * @return boolean 51 | */ 52 | public function isChanged() 53 | { 54 | return $this->entity->hasDataChanges(); 55 | } 56 | 57 | /** 58 | * @param Attribute $attribute 59 | * @return mixed 60 | */ 61 | public function getAttributeValue(Attribute $attribute) 62 | { 63 | return $this->entity->getData($attribute->getAttributeCode()); 64 | } 65 | 66 | /** 67 | * @param Attribute $attribute 68 | * @return string 69 | */ 70 | public function getLocalizedAttributeValue(Attribute $attribute) 71 | { 72 | $value = $this->entity->getAttributeText($attribute->getAttributeCode()); 73 | if ($value) { 74 | return $value; 75 | } else { 76 | return $this->entity->getData($attribute->getAttributeCode()); 77 | } 78 | } 79 | 80 | /** 81 | * @param Attribute $attribute 82 | * @param mixed $value 83 | * @return void 84 | */ 85 | public function setAttributeValue(Attribute $attribute, $value) 86 | { 87 | $this->entity->setData($attribute->getAttributeCode(), $value); 88 | } 89 | 90 | /** 91 | * Sets raw data from database 92 | * 93 | * @param string[] $data 94 | * @return void 95 | */ 96 | public function setRawData($data) 97 | { 98 | $this->entity->addData($data); 99 | } 100 | 101 | /** 102 | * Save changed attribute values in database 103 | * 104 | * @return void 105 | */ 106 | public function saveAttributes() 107 | { 108 | $resource = $this->entity->getResource(); 109 | $resource->isPartialSave(true); 110 | $resource->save($this->entity); 111 | } 112 | 113 | /** 114 | * Reset to empty instance 115 | * 116 | * @return void 117 | */ 118 | public function clearInstance() 119 | { 120 | $this->entity->clearInstance(); 121 | } 122 | 123 | /** 124 | * Returns collection iterator 125 | * 126 | * @return Hackathon_DerivedAttributes_Bridge_EntityIterator 127 | */ 128 | public function getCollectionIterator() 129 | { 130 | $collection = $this->entity->getCollection(); 131 | $iterator = new Hackathon_DerivedAttributes_Bridge_EntityIterator($collection); 132 | return $iterator; 133 | } 134 | 135 | } 136 | -------------------------------------------------------------------------------- /src/app/code/community/Hackathon/DerivedAttributes/Model/Bridge/EntityIterator.php: -------------------------------------------------------------------------------- 1 | _collection = $collection; 31 | } 32 | 33 | /** 34 | * @param $offset 35 | */ 36 | public function setIterationOffset($offset) 37 | { 38 | $this->_iterationOffset = $offset; 39 | } 40 | 41 | /** 42 | * Returns total size of collection 43 | * 44 | * @return mixed 45 | */ 46 | function getSize() 47 | { 48 | return $this->_collection->getSize(); 49 | } 50 | 51 | function getStore() 52 | { 53 | return $this->_store; 54 | } 55 | 56 | function setStore(Store $store) 57 | { 58 | $this->_store = $store; 59 | } 60 | 61 | public function current() 62 | { 63 | $data = $this->_iterator->current()->getData(); 64 | $data['store_id'] = (string) $this->_store; 65 | return $data; 66 | } 67 | 68 | public function next() 69 | { 70 | $this->_iteration++; 71 | $this->_iterator->next(); 72 | } 73 | 74 | public function key() 75 | { 76 | return $this->_iteration + $this->_iterationOffset; 77 | } 78 | 79 | public function valid() 80 | { 81 | return $this->_iterator->valid(); 82 | } 83 | 84 | public function rewind() 85 | { 86 | $this->_collection->clear() 87 | ->setStoreId((string) $this->_store) 88 | ->load(); 89 | //TODO: load all ids, then chunk 90 | $this->_iteration = 0; 91 | $this->_iterator = $this->_collection->getIterator(); 92 | } 93 | 94 | } -------------------------------------------------------------------------------- /src/app/code/community/Hackathon/DerivedAttributes/Model/Bridge/RuleLogger.php: -------------------------------------------------------------------------------- 1 | _ruleStorage = new SplObjectStorage(); 19 | } 20 | 21 | public function getRuleId(Rule $rule) 22 | { 23 | if ($this->_ruleStorage->contains($rule)) { 24 | return $this->_ruleStorage[$rule]; 25 | } 26 | return null; 27 | } 28 | 29 | public function findById($ruleId) 30 | { 31 | $model = Mage::getModel(Hackathon_DerivedAttributes_Model_Rule::ALIAS)->load($ruleId); 32 | if (! $model->getId()) { 33 | Mage::throwException(Mage::helper('derivedattributes')->__('Rule not found')); 34 | } 35 | $ruleDirector = Mage::helper('derivedattributes/rule_director'); 36 | $rule = $ruleDirector->createRule($model); 37 | $this->_registerRule($ruleId, $rule); 38 | return $rule; 39 | } 40 | 41 | /** 42 | * Returns rule sets with all active rules, grouped by store, ordered by priority 43 | * 44 | * @param StoreSet $stores 45 | * @return \Hackathon\DerivedAttributes\RuleSetsByStore 46 | */ 47 | public function findRuleSetsForStores(StoreSet $stores) 48 | { 49 | $ruleSetsByStore = new RuleSetsByStore(); 50 | foreach ($stores as $store) { 51 | $ruleSetsByStore->addRuleSet($this->findRuleSetForStore($store), $store); 52 | } 53 | return $ruleSetsByStore; 54 | } 55 | 56 | /** 57 | * Returns rule set with all active rules for given store, ordered by priority 58 | * 59 | * @param Store $store 60 | * @return RuleSet 61 | */ 62 | private function findRuleSetForStore(Store $store) 63 | { 64 | $collection = $this->getRuleCollection(); 65 | # ... WHERE store_id IS NULL OR FIND_IN_SET(:storeId, store_id) 66 | $collection->addFieldToFilter('store_id', [ 67 | ['eq' => '0'], 68 | ['finset' => [(string) $store]] 69 | ]); 70 | $collection->addFieldToFilter('active', "1"); 71 | $collection->setOrder('priority', Varien_Data_Collection_Db::SORT_ORDER_ASC); 72 | 73 | $ruleSet = new RuleSet(); 74 | $ruleDirector = Mage::helper('derivedattributes/rule_director'); 75 | foreach($collection as $ruleData) { 76 | $rule = $ruleDirector->createRule($ruleData); 77 | $this->_registerRule($ruleData->getId(), $rule); 78 | $ruleSet->addRule($rule); 79 | } 80 | return $ruleSet; 81 | } 82 | 83 | /** 84 | * @param Rule $newRule 85 | * @return void 86 | */ 87 | public function createRule(Rule $newRule) 88 | { 89 | $model = $this->getRuleModel($newRule); 90 | $model->save(); 91 | $this->_registerRule($model->getId(), $newRule); 92 | } 93 | 94 | /** 95 | * @param Rule $oldRule 96 | * @param Rule $newRule 97 | * @return void 98 | */ 99 | function replaceRule(Rule $oldRule, Rule $newRule) 100 | { 101 | $model = $this->getRuleModel($newRule); 102 | $model->setId($this->getRuleId($oldRule)); 103 | $model->save(); 104 | $this->_unregisterRule($oldRule); 105 | $this->_registerRule($model->getId(), $newRule); 106 | } 107 | 108 | /** 109 | * @param Rule $ruleToBeDeleted 110 | * @return void 111 | */ 112 | public function deleteRule(Rule $ruleToBeDeleted) 113 | { 114 | $this->getRuleModel($ruleToBeDeleted)->delete(); 115 | $this->_unregisterRule($ruleToBeDeleted); 116 | } 117 | 118 | /** 119 | * Registers Rule with ID for database mapping 120 | * 121 | * @param int $ruleId 122 | * @param Rule $rule 123 | * @return $this 124 | */ 125 | protected function _registerRule($ruleId, Rule $rule) 126 | { 127 | $this->_ruleStorage->attach($rule, $ruleId); 128 | return $this; 129 | } 130 | 131 | /** 132 | * Unregisters removed/replace Rule for database mapping 133 | * 134 | * @param Rule $rule 135 | * @return $this 136 | */ 137 | protected function _unregisterRule(Rule $rule) 138 | { 139 | $this->_ruleStorage->detach($rule); 140 | return $this; 141 | } 142 | 143 | /** 144 | * @return Hackathon_DerivedAttributes_Model_Resource_Rule_Collection 145 | */ 146 | private function getRuleCollection() 147 | { 148 | return Mage::getResourceModel('derivedattributes/rule_collection'); 149 | } 150 | 151 | public function getRuleModel(Rule $rule) 152 | { 153 | $manager = Mage::helper('derivedattributes')->getServiceManager(); 154 | /** @var Hackathon_DerivedAttributes_Model_Rule $model */ 155 | $model = Mage::getModel(Hackathon_DerivedAttributes_Model_Rule::ALIAS); 156 | $model->setData(array( 157 | 'name' => $rule->getName(), 158 | 'description' => $rule->getDescription(), 159 | 'active' => $rule->isActive(), 160 | 'priority' => $rule->getPriority(), 161 | 'attribute_id' => self::getAttributeId($rule->getAttribute()), 162 | 'condition_type' => $manager->getConditionType($rule->getCondition()), 163 | 'condition_data' => $rule->getCondition()->getData(), 164 | 'generator_type' => $manager->getGeneratorType($rule->getGenerator()), 165 | 'generator_data' => $rule->getGenerator()->getData(), 166 | 'store_id' => $rule->getStores() === StoreSet::all() ? ['0'] : $rule->getStores()->getStoreCodes() 167 | ) 168 | ); 169 | $model->setId($this->getRuleId($rule)); 170 | return $model; 171 | } 172 | 173 | /** 174 | * Returns Magento attrribute id based on Attribute instance 175 | * 176 | * @return Attribute 177 | */ 178 | private static function getAttributeId(Attribute $attribute) 179 | { 180 | /* @var $magentoAttribute Mage_Eav_Model_Entity_Attribute */ 181 | $magentoAttribute = Mage::getModel("eav/entity_attribute") 182 | ->loadByCode($attribute->getEntityTypeCode(), $attribute->getAttributeCode()); 183 | 184 | return $magentoAttribute->getId(); 185 | } 186 | 187 | } -------------------------------------------------------------------------------- /src/app/code/community/Hackathon/DerivedAttributes/Model/Massupdater.php: -------------------------------------------------------------------------------- 1 | getEntityTypeInstance($entityType); 17 | 18 | $updater = $helper->getNewUpdater(); 19 | $updater->setDryRun($isDryRun); 20 | $updater->massUpdate( 21 | $helper->getNewEntityIterator($entityTypeInstance, $entityIds), 22 | $helper->getNewEntityModel($entityTypeInstance), 23 | $helper->createStoreSet($storeIds, true)); 24 | } 25 | 26 | } -------------------------------------------------------------------------------- /src/app/code/community/Hackathon/DerivedAttributes/Model/Observer.php: -------------------------------------------------------------------------------- 1 | 4 | */ 5 | 6 | use Hackathon\DerivedAttributes\Store; 7 | 8 | /** 9 | * Event-observer for derived attributes. 10 | */ 11 | class Hackathon_DerivedAttributes_Model_Observer 12 | { 13 | /** 14 | * @see event before_model_save 15 | * @param Varien_Object $observer 16 | * @throws Mage_Core_Exception 17 | */ 18 | public function beforeModelSave(Varien_Object $observer) 19 | { 20 | $helper = Mage::helper('derivedattributes'); 21 | /* @var $modelObject Mage_Core_Model_Abstract */ 22 | $modelObject = $observer->getObject(); 23 | $modelResource = $modelObject->getResource(); 24 | 25 | if($modelResource instanceof Mage_Eav_Model_Entity_Abstract){ 26 | /* @var $bridgeObject Hackathon_DerivedAttributes_Model_Bridge_Entity */ 27 | $bridgeObject = $helper->getNewEntityModel($modelResource->getEntityType(), $modelObject); 28 | 29 | $updater = $helper->getNewUpdater(); 30 | $updater->update($bridgeObject, new Store($modelObject->getStoreId())); 31 | } 32 | 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/app/code/community/Hackathon/DerivedAttributes/Model/Resource/Rule.php: -------------------------------------------------------------------------------- 1 | 4 | */ 5 | 6 | class Hackathon_DerivedAttributes_Model_Resource_Rule 7 | extends Mage_Core_Model_Resource_Db_Abstract 8 | { 9 | protected function _construct(){ 10 | $this->_init("derivedattributes/rule", "rule_id"); 11 | } 12 | 13 | protected function _afterLoad(Mage_Core_Model_Abstract $object) 14 | { 15 | $object->setStoreId(explode(',', $object->getStoreId())); 16 | return parent::_afterLoad($object); 17 | } 18 | 19 | protected function _beforeSave(Mage_Core_Model_Abstract $object) 20 | { 21 | $object->setStoreId(join(',', (array)$object->getStoreId())); 22 | return parent::_beforeSave($object); 23 | } 24 | 25 | } -------------------------------------------------------------------------------- /src/app/code/community/Hackathon/DerivedAttributes/Model/Resource/Rule/Collection.php: -------------------------------------------------------------------------------- 1 | 4 | */ 5 | 6 | class Hackathon_DerivedAttributes_Model_Resource_Rule_Collection extends Mage_Core_Model_Resource_Db_Collection_Abstract 7 | { 8 | protected function _construct() 9 | { 10 | $this->_init(Hackathon_DerivedAttributes_Model_Rule::ALIAS); 11 | } 12 | 13 | protected function _afterLoad() 14 | { 15 | foreach ($this->_items as $item) { 16 | $item->setStoreId(explode(',', $item->getStoreId())); 17 | } 18 | return parent::_afterLoad(); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/app/code/community/Hackathon/DerivedAttributes/Model/Rule.php: -------------------------------------------------------------------------------- 1 | 4 | */ 5 | 6 | use Hackathon\DerivedAttributes\Rule; 7 | 8 | /** 9 | * Magento model for rules. 10 | * 11 | * Only used internally by resource model 12 | */ 13 | class Hackathon_DerivedAttributes_Model_Rule extends Mage_Core_Model_Abstract 14 | { 15 | const ALIAS = 'derivedattributes/rule'; 16 | 17 | protected function _construct(){ 18 | $this->_init(self::ALIAS); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/app/code/community/Hackathon/DerivedAttributes/Model/Source/Abstract.php: -------------------------------------------------------------------------------- 1 | _serviceManager = Mage::helper('derivedattributes')->getServiceManager(); 12 | } 13 | 14 | abstract public function getOptions(); 15 | 16 | public function toOptionArray($withEmpty = false) 17 | { 18 | $options = array(); 19 | 20 | foreach ($this->getOptions() as $value => $label) { 21 | $options[] = array( 22 | 'label' => $label, 23 | 'value' => $value 24 | ); 25 | } 26 | 27 | if ($withEmpty) { 28 | array_unshift($options, array('value'=>'', 'label'=>Mage::helper('page')->__('-- Please Select --'))); 29 | } 30 | 31 | return $options; 32 | } 33 | } -------------------------------------------------------------------------------- /src/app/code/community/Hackathon/DerivedAttributes/Model/Source/Attribute.php: -------------------------------------------------------------------------------- 1 | addVisibleFilter(); 15 | $values = array(); 16 | foreach ($attributeCollection as $attribute) { 17 | /** @var Mage_Eav_Model_Attribute $attribute */ 18 | $values[] = array( 19 | 'label' => $attribute->getFrontendLabel() ?: $attribute->getAttributeCode(), 20 | 'value' => $attribute->getId() 21 | ); 22 | } 23 | $options[] = array( 24 | 'label' => 'Product', 25 | 'value' => $values 26 | ); 27 | 28 | /** @var Mage_Customer_Model_Resource_Attribute_Collection $attributeCollection */ 29 | $attributeCollection = Mage::getResourceModel('customer/attribute_collection'); 30 | $attributeCollection->addSystemHiddenFilter(); 31 | $values = array(); 32 | foreach ($attributeCollection as $attribute) { 33 | /** @var Mage_Eav_Model_Attribute $attribute */ 34 | $values[] = array( 35 | 'label' => $attribute->getFrontendLabel() ?: $attribute->getAttributeCode(), 36 | 'value' => $attribute->getId() 37 | ); 38 | } 39 | $options[] = array( 40 | 'label' => 'Customer', 41 | 'value' => $values 42 | ); 43 | 44 | return $options; 45 | } 46 | 47 | } -------------------------------------------------------------------------------- /src/app/code/community/Hackathon/DerivedAttributes/Model/Source/Condition.php: -------------------------------------------------------------------------------- 1 | _serviceManager->getAvailableConditionTypes() as $id => $info) { 9 | $options[$id] = Mage::helper('derivedattributes')->__($info['title']); 10 | } 11 | return $options; 12 | } 13 | } -------------------------------------------------------------------------------- /src/app/code/community/Hackathon/DerivedAttributes/Model/Source/Generator.php: -------------------------------------------------------------------------------- 1 | _serviceManager->getAvailableGeneratorTypes() as $id => $info) { 8 | $options[$id] = Mage::helper('derivedattributes')->__($info['title']); 9 | } 10 | return $options; 11 | } 12 | } -------------------------------------------------------------------------------- /src/app/code/community/Hackathon/DerivedAttributes/Test/Block/Entity.php: -------------------------------------------------------------------------------- 1 | adminSession(); 19 | $this->dispatch('adminhtml/derivedAttributes_entity'); 20 | $this->assertRequestRoute('adminhtml/derivedAttributes_entity/index'); 21 | $this->assertLayoutHandleLoaded('adminhtml_derivedattributes_entity_index'); 22 | $this->assertLayoutBlockRendered('derivedattributes_entity', 'Container should be instantiated'); 23 | $this->assertLayoutBlockRendered('derivedattributes_entity_form', 'Form should be instantiated'); 24 | $this->assertLayoutBlockRendered('derivedattributes_entity_tabs', 'Tabs should be instantiated'); 25 | 26 | $this->assertResponseBodyXpath( 27 | '//select[@id="customerGrid_massaction-select"]/option[@value="apply"]', 28 | 'Customer grid should contain mass action "apply".'); 29 | $this->assertResponseBodyXpath( 30 | '//select[@id="customerGrid_massaction-select"]/option[@value="dryrun"]', 31 | 'Customer grid should contain mass action "dryrun".'); 32 | 33 | $this->assertResponseBodyXpath( 34 | '//select[@id="productDerivedAttributesGrid_massaction-select"]/option[@value="apply"]', 35 | 'Product grid should contain mass action "apply".'); 36 | $this->assertResponseBodyXpath( 37 | '//select[@id="productDerivedAttributesGrid_massaction-select"]/option[@value="dryrun"]', 38 | 'Product grid should contain mass action "dryrun".'); 39 | 40 | $this->assertResponseBodyContains('name="entity_type"', 'Mass action block should contain hidden input with entity type'); 41 | // assertLayoutBlockRendered does not work with blocks created from code (i.e. the grids) 42 | } 43 | } -------------------------------------------------------------------------------- /src/app/code/community/Hackathon/DerivedAttributes/Test/Block/Rule.php: -------------------------------------------------------------------------------- 1 | adminSession(); 19 | $this->dispatch('adminhtml/derivedAttributes_rule'); 20 | $this->assertRequestRoute('adminhtml/derivedAttributes_rule/index'); 21 | $this->assertLayoutHandleLoaded('adminhtml_derivedattributes_rule_index'); 22 | $this->assertLayoutBlockCreated('derivedattributes_rule', 'Grid container should be instantiated'); 23 | $this->assertResponseBodyNotContains('empty-text', 'Grid content should not be empty'); 24 | $this->assertResponseBodyContains('Test Rule 1'); 25 | $this->assertResponseBodyContains('/derivedAttributes_entity/index', '"Apply rules" link should be present'); 26 | } 27 | 28 | /** 29 | * @test 30 | * @singleton adminhtml/session 31 | * @singleton admin/session 32 | * @singleton index/indexer 33 | * @registry current_derived_attribute_rule 34 | */ 35 | public function newFormShouldBeLoaded() 36 | { 37 | $this->adminSession(); 38 | $this->dispatch('adminhtml/derivedAttributes_rule/new'); 39 | $this->assertRequestRoute('adminhtml/derivedAttributes_rule/edit'); 40 | $this->assertLayoutHandleLoaded('adminhtml_derivedattributes_rule_edit'); 41 | $this->assertLayoutBlockCreated('derivedattributes_rule_edit', 'Form should be instantiated'); 42 | $this->assertResponseBodyQueryContains( 43 | 'h3.head-adminhtml-rule', 'New Rule', 44 | 'Title should be "New Rule"'); 45 | $this->assertResponseBodyXpath( 46 | '//select[@name="generator_type"]/option[@value="template"]', 47 | 'Dropdown with generator types should be present'); 48 | $this->assertResponseBodyXpath( 49 | '//select[@name="condition_type"]/option[@value="boolean-attribute"]', 50 | 'Dropdown with condition types should be present'); 51 | $this->assertResponseBodyXpathContains( 52 | '//select[@name="store_id[]"]//option[@value="0"]', 'All Store Views', 53 | 'Select box for store views should contain "All Store Views"'); 54 | 55 | $this->assertResponseBodyXpath( 56 | '//select[@name="attribute_id"]/optgroup[@label="Customer"]', 57 | 'Dropdown with attribute ids should contain customer optgroup'); 58 | $this->assertResponseBodyXpath( 59 | '//select[@name="attribute_id"]/optgroup[@label="Product"]', 60 | 'Dropdown with attribute ids should contain product optgroup'); 61 | $this->assertResponseBodyXpath( 62 | '//select[@name="attribute_id"]//option[@value="72"]', 63 | 'Dropdown with attribute ids should contain product description attribute id'); 64 | $this->assertResponseBodyNotXpath( 65 | '//select[@name="attribute_id"]//option[@value="111"]', 66 | 'Dropdown with attribute ids should not contain product has_options attribute id (invisible)'); 67 | $this->assertResponseBodyXpath( 68 | '//select[@name="attribute_id"]//option[@value="5"]', 69 | 'Dropdown with attribute ids should contain customer first name attribute id'); 70 | $this->assertResponseBodyNotXpath( 71 | '//select[@name="attribute_id"]//option[@value="16"]', 72 | 'Dropdown with attribute ids should not contain customer confirmation attribute id (invisible)'); 73 | } 74 | /** 75 | * @test 76 | * @singleton adminhtml/session 77 | * @singleton admin/session 78 | * @singleton index/indexer 79 | * @registry current_derived_attribute_rule 80 | */ 81 | public function editFormShouldBeLoaded() 82 | { 83 | $ruleId = 1; 84 | $this->adminSession(); 85 | $this->dispatch('adminhtml/derivedAttributes_rule/edit', ['id' => $ruleId]); 86 | $this->assertRequestRoute('adminhtml/derivedAttributes_rule/edit'); 87 | $this->assertLayoutHandleLoaded('adminhtml_derivedattributes_rule_edit'); 88 | $this->assertLayoutBlockCreated('derivedattributes_rule_edit', 'Form should be instantiated'); 89 | $this->assertResponseBodyQueryContains( 90 | 'h3.head-adminhtml-rule', 'Edit Rule \'Test Rule 1\'', 91 | 'Title should be "Edit Rule"'); 92 | $this->assertResponseBodyXpath( 93 | '//input[@name="name"][@value="Test Rule 1"]', 'Name input should be filled'); 94 | $this->assertResponseBodyXpathContains( 95 | '//select[@name="active"]/option[@value="1"][@selected="selected"]', 'Active', 96 | 'Status input should be set to active'); 97 | $this->assertResponseBodyXpathContains( 98 | '//select[@name="store_id[]"]//option[@value="0"][@selected="selected"]', 'All Store Views', 99 | 'Store view input should be set to "All Store Views"'); 100 | $this->assertResponseBodyXpathContains( 101 | '//textarea[@name="description"]', 'This is a template generator rule for the product description', 102 | 'Description input should be filled'); 103 | } 104 | } -------------------------------------------------------------------------------- /src/app/code/community/Hackathon/DerivedAttributes/Test/Case/Controller/Dom.php: -------------------------------------------------------------------------------- 1 | 3.5 3 | $__old_error_reporting = error_reporting(error_reporting() & ~ E_STRICT); 4 | 5 | /** 6 | * Extension of DomQuery constraint with configuration in constructor instead 7 | * of in evaluate() - so that it can be used in EcomDev PHPUnit Controller 8 | * Test Case 9 | */ 10 | class Hackathon_DerivedAttributes_Test_Constraint_DomQuery extends Zend_Test_PHPUnit_Constraint_DomQuery 11 | { 12 | const ASSERT_XPATH = 'assertXpath'; 13 | const ASSERT_XPATH_CONTENT_CONTAINS = 'assertXpathContentContains'; 14 | const ASSERT_XPATH_CONTENT_REGEX = 'assertXpathContentRegex'; 15 | const ASSERT_XPATH_CONTENT_COUNT = 'assertXpathCount'; 16 | const ASSERT_XPATH_CONTENT_COUNT_MIN= 'assertXpathCountMin'; 17 | const ASSERT_XPATH_CONTENT_COUNT_MAX= 'assertXpathCountMax'; 18 | 19 | protected $_defaultType; 20 | 21 | protected $_additionalParameter; 22 | 23 | public function __construct( 24 | $path, $defaultType = self::ASSERT_QUERY, $additionalParameter = null) 25 | { 26 | $this->_defaultType = $defaultType; 27 | $this->_additionalParameter = $additionalParameter; 28 | parent::__construct($path); 29 | } 30 | 31 | /* (non-PHPdoc) 32 | * @see Zend_Test_PHPUnit_Constraint_DomQuery::evaluate() 33 | */ 34 | public function evaluate ($other, $assertType='', $match=false) 35 | { 36 | if ($assertType === '') { 37 | $assertType = $this->_defaultType; 38 | } 39 | if ($this->_additionalParameter !== null) { 40 | return parent::evaluate( 41 | $other, $assertType, $this->_additionalParameter 42 | ); 43 | } 44 | return parent::evaluate($other, $assertType); 45 | } 46 | 47 | /* (non-PHPdoc) 48 | * @see Zend_Test_PHPUnit_Constraint_DomQuery::toString() 49 | */ 50 | public function toString () 51 | { 52 | return sprintf( 53 | 'matches %s(%s%s)', 54 | $this->_assertType, 55 | var_export($this->_path, true), 56 | $this->_additionalParameter === null 57 | ? '' 58 | : ', ' . var_export($this->_additionalParameter, true) 59 | ); 60 | } 61 | 62 | } 63 | 64 | // restore error reporting 65 | error_reporting($__old_error_reporting); 66 | unset($__old_error_reporting); -------------------------------------------------------------------------------- /src/app/code/community/Hackathon/DerivedAttributes/Test/Controller/EntityController.php: -------------------------------------------------------------------------------- 1 | getModelMock('derivedattributes/massupdater', ['update']); 22 | $updaterMock->expects($this->once())->method('update') 23 | ->with($postData['entity_ids'], $postData['entity_type'], $expectedStoreIds, false); 24 | $this->replaceByMock('model', 'derivedattributes/massupdater', $updaterMock); 25 | 26 | $this->adminSession(); 27 | $this->getRequest()->setMethod('POST'); 28 | $this->getRequest()->setPost($postData); 29 | $this->dispatch('adminhtml/derivedAttributes_entity/applyRules'); 30 | $this->assertRequestRoute('adminhtml/derivedAttributes_entity/applyRules'); 31 | $this->assertResponseHttpCode(200); 32 | $this->assertResponseHeaderContains('content-type', 'application/json'); 33 | $this->assertResponseBodyJsonMatch(array('final' => false)); 34 | 35 | $this->getRequest()->setPost(array()); 36 | $this->dispatch('adminhtml/derivedAttributes_entity/applyRules'); 37 | $this->assertRequestRoute('adminhtml/derivedAttributes_entity/applyRules'); 38 | $this->assertResponseHttpCode(200); 39 | $this->assertResponseHeaderContains('content-type', 'application/json'); 40 | $this->assertResponseBodyJsonMatch(array('final' => true)); 41 | } 42 | 43 | public static function dataMassUpdateForm() 44 | { 45 | return [ 46 | [ 47 | 'postData' => [ 48 | 'store_id' => ['1'], 49 | 'entity_ids' => ['1', '2', '3'], 50 | 'entity_type' => 'catalog_product' 51 | ], 52 | 'expectedStoreIds' => ['1'] 53 | ], 54 | [ 55 | 'postData' => [ 56 | 'store_id' => ['1', '2'], 57 | 'entity_ids' => ['1', '2', '3'], 58 | 'entity_type' => 'catalog_product' 59 | ], 60 | 'expectedStoreIds' => ['1', '2'] 61 | ], 62 | [ 63 | 'postData' => [ 64 | 'store_id' => ['0'], 65 | 'entity_ids' => ['1', '2', '3'], 66 | 'entity_type' => 'catalog_product' 67 | ], 68 | 'expectedStoreIds' => ['0'] 69 | ], 70 | ]; 71 | } 72 | /** 73 | * @test 74 | * @loadFixture products.yaml 75 | * @singleton adminhtml/session 76 | * @singleton admin/session 77 | */ 78 | public function invalidEntityType() 79 | { 80 | $this->adminSession(); 81 | $this->getRequest()->setMethod('POST'); 82 | $this->getRequest()->setPost(array( 83 | 'entity_ids' => [1, 2, 3], 84 | 'entity_type' => 'invalid_entity_type' 85 | )); 86 | $this->dispatch('adminhtml/derivedAttributes_entity/applyRules'); 87 | $this->assertRequestRoute('adminhtml/derivedAttributes_entity/applyRules'); 88 | $this->assertResponseHttpCode(200); 89 | $this->assertResponseHeaderContains('content-type', 'application/json'); 90 | $this->assertResponseBodyJsonMatch(array('final' => false)); 91 | 92 | $this->getRequest()->setPost(array()); 93 | $this->dispatch('adminhtml/derivedAttributes_entity/applyRules'); 94 | $this->assertRequestRoute('adminhtml/derivedAttributes_entity/applyRules'); 95 | $this->assertResponseHttpCode(200); 96 | $this->assertResponseHeaderContains('content-type', 'application/json'); 97 | $this->assertResponseBodyJsonMatch(array('final' => true, 'error' => ''), 'Response should contain error'); 98 | } 99 | } -------------------------------------------------------------------------------- /src/app/code/community/Hackathon/DerivedAttributes/Test/Controller/RuleController.php: -------------------------------------------------------------------------------- 1 | adminSession(); 21 | $this->getRequest()->setMethod('POST'); 22 | $this->getRequest()->setPost($postData); 23 | $this->dispatch('adminhtml/derivedAttributes_rule/save'); 24 | $this->assertRequestRoute('adminhtml/derivedAttributes_rule/save'); 25 | $this->assertRedirectTo('adminhtml/derivedAttributes_rule/index'); 26 | 27 | $rule = Mage::getModel('derivedattributes/rule')->load($postData['rule_id']); 28 | $this->assertEquals($postData, $rule->getData(), 'Rule data should be as posted'); 29 | } 30 | 31 | public static function dataSaveExistingRule() 32 | { 33 | return array( 34 | [ 'postData' => [ 35 | 'rule_id' => '1', 36 | 'priority' => '4', 37 | 'active' => '1', 38 | 'name' => 'Edited Test Rule', 39 | 'description' => 'The rule has been edited', 40 | 'attribute_id' => '72', 41 | 'condition_type' => 'always', 42 | 'condition_data' => '', 43 | 'generator_type' => 'template', 44 | 'generator_data' => 'Edited template', 45 | 'store_id' => [ '1', '2'] 46 | ]] 47 | ); 48 | } 49 | 50 | /** 51 | * @test 52 | * @loadFixture products.yaml 53 | * @singleton adminhtml/session 54 | * @singleton admin/session 55 | */ 56 | public function existingRuleShouldBeDeleted() 57 | { 58 | $ruleId = '1'; 59 | 60 | $this->adminSession(); 61 | $this->getRequest()->setParam('id', $ruleId); 62 | $this->dispatch('adminhtml/derivedAttributes_rule/delete'); 63 | $this->assertRequestRoute('adminhtml/derivedAttributes_rule/delete'); 64 | $this->assertRedirectTo('adminhtml/derivedAttributes_rule/index'); 65 | 66 | $rule = Mage::getModel('derivedattributes/rule')->load($ruleId); 67 | $this->assertNull($rule->getId(), 'Rule should not be loaded after delete'); 68 | } 69 | } -------------------------------------------------------------------------------- /src/app/code/community/Hackathon/DerivedAttributes/Test/Model/Bridge/Entity.php: -------------------------------------------------------------------------------- 1 | assertEquals('Test Product 1', $this->getProductEntityBridge(1, 1) 18 | ->getLocalizedAttributeValue($this->getAttributeBridge('name'))); 19 | $this->assertEquals('Test Product 1 in store view', $this->getProductEntityBridge(2, 1) 20 | ->getLocalizedAttributeValue($this->getAttributeBridge('name'))); 21 | 22 | $this->assertEquals('description.', $this->getProductEntityBridge(1, 1) 23 | ->getLocalizedAttributeValue($this->getAttributeBridge('description'))); 24 | $this->assertEquals('description in store view', $this->getProductEntityBridge(2, 1) 25 | ->getLocalizedAttributeValue($this->getAttributeBridge('description'))); 26 | } 27 | 28 | /** 29 | * @param $storeId 30 | * @param $productId 31 | * @return Hackathon_DerivedAttributes_Model_Bridge_Entity 32 | */ 33 | private function getProductEntityBridge($storeId, $productId) 34 | { 35 | /** @var Hackathon_DerivedAttributes_Helper_Data $helper */ 36 | $helper = Mage::helper('derivedattributes'); 37 | $entity = $helper->getNewEntityModel( 38 | $helper->getEntityTypeInstance(Mage_Catalog_Model_Product::ENTITY), 39 | Mage::getModel('catalog/product')->setStoreId($storeId)->load($productId) 40 | ); 41 | return $entity; 42 | } 43 | 44 | /** 45 | * @param $attributeCode 46 | * @return Attribute 47 | */ 48 | private function getAttributeBridge($attributeCode) 49 | { 50 | return new Attribute(Mage_Catalog_Model_Product::ENTITY, $attributeCode); 51 | } 52 | } -------------------------------------------------------------------------------- /src/app/code/community/Hackathon/DerivedAttributes/Test/Model/Bridge/RuleRepository.php: -------------------------------------------------------------------------------- 1 | findRuleSetsForStores($stores); 22 | $expectedRuleSets = $this->expected('stores-%s', join('-', $stores->getStoreCodes()))->getData('rulesets'); 23 | $this->assertEquals(count($expectedRuleSets), count($actualRuleSets), 'Expected number of rule sets'); 24 | $actualRuleSets->rewind(); 25 | while ($actualRuleSets->valid()) { 26 | $this->assertArrayHasKey((string) $actualRuleSets->key(), $expectedRuleSets, 'Expected store code'); 27 | $expectedRuleIds = $expectedRuleSets[(string) $actualRuleSets->key()]; 28 | $actualRules = $actualRuleSets->current()->getRules(); 29 | $this->assertEquals($expectedRuleIds, array_map(function(Rule $rule) use ($ruleRepository) { 30 | return $ruleRepository->getRuleId($rule); 31 | }, $actualRules), 'Expected rules'); 32 | $actualRuleSets->next(); 33 | } 34 | 35 | } 36 | public static function dataStoreSets() 37 | { 38 | $helper = Mage::helper('derivedattributes'); 39 | return array( 40 | [$helper->createStoreSet(['0', '1', '2'])], 41 | [$helper->createStoreSet(['1'])], 42 | [$helper->createStoreSet(['2'])] 43 | ); 44 | } 45 | } -------------------------------------------------------------------------------- /src/app/code/community/Hackathon/DerivedAttributes/Test/Model/Massupdater.php: -------------------------------------------------------------------------------- 1 | getProcessByCode('catalog_product_flat')->reindexAll(); 15 | } 16 | 17 | /** 18 | * @test 19 | * @dataProvider dataProvider 20 | * @dataProviderFile testProductMassUpdateByStoreView 21 | * @loadExpectation attributes 22 | * @helper catalog/product_flat 23 | * @param $entityIds 24 | * @param $storeIds 25 | */ 26 | public function testProductMassUpdateByStoreView($entityIds, $storeIds) 27 | { 28 | $this->setCurrentStore('admin'); 29 | $this->assertTrue(Mage::getStoreConfigFlag('catalog/frontend/flat_catalog_product'), 'Flat index should be enabled.'); 30 | $this->assertFalse(Mage::getResourceModel('catalog/product_collection')->isEnabledFlat(), 'Flat index should not be used'); 31 | 32 | /** @var Hackathon_DerivedAttributes_Model_Massupdater $updater */ 33 | $updater = Mage::getModel('derivedattributes/massupdater'); 34 | $updater->update($entityIds, 'catalog_product', $storeIds, false); 35 | 36 | if ($storeIds === ['0']) { 37 | $storeIds = ['0', '1', '2']; 38 | } 39 | foreach($storeIds as $storeId) { 40 | foreach ($entityIds as $entityId) { 41 | $product = Mage::getModel('catalog/product')->setStoreId($storeId)->load($entityId); 42 | $expectedAttributeValues = $this->expected('product_%d_store_%d', $entityId, $storeId)->getAttributes(); 43 | foreach ($expectedAttributeValues as $attributeCode => $expectedValue) { 44 | $this->assertEquals( 45 | $expectedValue, 46 | $product->getData($attributeCode), 47 | sprintf('Store %d Product %d Attribute %s', $storeId, $entityId, $attributeCode)); 48 | } 49 | } 50 | } 51 | } 52 | 53 | /** 54 | * @test 55 | * @expectedException Mage_Core_Exception 56 | * @expectedExceptionMessageRegExp Invalid entity type foo_bar. 57 | */ 58 | public function testInvalidEntityType() 59 | { 60 | /** @var Hackathon_DerivedAttributes_Model_Massupdater $updater */ 61 | $updater = Mage::getModel('derivedattributes/massupdater'); 62 | $updater->update(['1'], 'foo_bar', ['0'], false); 63 | } 64 | } -------------------------------------------------------------------------------- /src/app/code/community/Hackathon/DerivedAttributes/Test/Model/Massupdater/providers/testProductMassUpdateByStoreView.yaml: -------------------------------------------------------------------------------- 1 | - 2 | entity_ids: [1, 2, 3] 3 | store_ids: ['0'] 4 | - 5 | entity_ids: [1, 2, 3] 6 | store_ids: ['1'] 7 | - 8 | entity_ids: [1, 2, 3] 9 | store_ids: ['1', '2'] 10 | -------------------------------------------------------------------------------- /src/app/code/community/Hackathon/DerivedAttributes/Test/Model/Rule.php: -------------------------------------------------------------------------------- 1 | setStoreId($storeId)->load($productId); 22 | $product->setDataChanges(true)->save(); 23 | } 24 | $this->assertEventDispatchedAtLeast('model_save_before', count($storeIds)); 25 | 26 | foreach ($storeIds as $storeId) { 27 | $product->setStoreId($storeId)->load($productId); 28 | $expectedAttributeValues = $this->expected('product_%d_store_%d', $productId, $storeId)->getAttributes(); 29 | foreach ($expectedAttributeValues as $attributeCode => $expectedValue) { 30 | $this->assertEquals( 31 | $expectedValue, 32 | $product->getData($attributeCode)); 33 | } 34 | } 35 | } 36 | 37 | /** 38 | * @test 39 | * @dataProvider dataProvider 40 | * @dataProviderFile rulesShouldBeAppliedOnModelSave 41 | * @helper catalog/product_flat 42 | * @param $productId 43 | */ 44 | public function rulesShouldBeAppliedOnModelSaveInFrontend($productId) 45 | { 46 | $this->markTestSkipped('model cannot be saved in frontend.'); 47 | $this->setCurrentStore('default'); 48 | $this->assertTrue(Mage::getResourceModel('catalog/product_collection')->isEnabledFlat()); 49 | $this->rulesShouldBeAppliedOnModelSave($productId); 50 | } 51 | 52 | /** 53 | * @test 54 | * @dataProvider dataProvider 55 | * @dataProviderFile rulesShouldBeAppliedOnModelSave 56 | * @helper catalog/product_flat 57 | * @param $productId 58 | */ 59 | public function rulesShouldBeAppliedOnModelSaveInFrontendWithoutFlatIndex($productId) 60 | { 61 | $this->markTestSkipped('model cannot be saved in frontend.'); 62 | $this->setCurrentStore('default'); 63 | $this->app()->getStore()->setConfig('catalog/frontend/flat_catalog_product', '0'); 64 | Mage::unregister('_helper/catalog/product_flat'); 65 | $this->assertFalse(Mage::getResourceModel('catalog/product_collection')->isEnabledFlat()); 66 | $this->rulesShouldBeAppliedOnModelSave($productId); 67 | } 68 | } -------------------------------------------------------------------------------- /src/app/code/community/Hackathon/DerivedAttributes/Test/Model/Rule/providers/rulesShouldBeAppliedOnModelSave.yaml: -------------------------------------------------------------------------------- 1 | - 2 | product_id: 1 -------------------------------------------------------------------------------- /src/app/code/community/Hackathon/DerivedAttributes/Test/expectations/attributes.yaml: -------------------------------------------------------------------------------- 1 | product_1_store_0: 2 | attributes: 3 | description: 'The description contains the short description.' 4 | meta_description: 'meta description.' 5 | meta_keyword: 'meta keyword.' 6 | meta_title: 'meta title.' 7 | product_1_store_1: 8 | attributes: 9 | description: 'The description contains the short description.' 10 | meta_description: 'meta description.' 11 | meta_keyword: 'The meta keywords contain the short description.' 12 | meta_title: 'meta title.' 13 | product_1_store_2: 14 | attributes: 15 | description: 'The description contains the short description in store view' 16 | meta_description: 'The meta description contains the short description in store view' 17 | meta_keyword: 'The meta keywords contain the short description in store view' 18 | meta_title: 'meta title.' 19 | product_2_store_0: 20 | attributes: 21 | description: 'The description contains the short description.' 22 | meta_description: 'meta description.' 23 | meta_keyword: 'meta keyword.' 24 | meta_title: 'meta title.' 25 | product_2_store_1: 26 | attributes: 27 | description: 'The description contains the short description.' 28 | meta_description: 'meta description.' 29 | meta_keyword: 'The meta keywords contain the short description.' 30 | meta_title: 'meta title.' 31 | product_2_store_2: 32 | attributes: 33 | description: 'The description contains the short description in store view' 34 | meta_description: 'The meta description contains the short description in store view' 35 | meta_keyword: 'The meta keywords contain the short description in store view' 36 | meta_title: 'meta title.' 37 | product_3_store_0: 38 | attributes: 39 | description: 'The description contains the short description.' 40 | meta_description: 'meta description.' 41 | meta_keyword: 'meta keyword.' 42 | meta_title: 'meta title.' 43 | product_3_store_1: 44 | attributes: 45 | description: 'The description contains the short description.' 46 | meta_description: 'meta description.' 47 | meta_keyword: 'The meta keywords contain the short description.' 48 | meta_title: 'meta title.' 49 | product_3_store_2: 50 | attributes: 51 | description: 'The description contains the short description in store view' 52 | meta_description: 'The meta description contains the short description in store view' 53 | meta_keyword: 'The meta keywords contain the short description in store view' 54 | meta_title: 'meta title.' 55 | -------------------------------------------------------------------------------- /src/app/code/community/Hackathon/DerivedAttributes/Test/expectations/rulesets.yaml: -------------------------------------------------------------------------------- 1 | stores-0-1-2: 2 | rulesets: 3 | 0: ['4', '1'] 4 | 1: ['4', '3', '1'] 5 | 2: ['4', '3', '2', '1'] 6 | stores-1: 7 | rulesets: 8 | 1: ['4', '3', '1'] 9 | stores-2: 10 | rulesets: 11 | 2: ['4', '3', '2', '1'] 12 | -------------------------------------------------------------------------------- /src/app/code/community/Hackathon/DerivedAttributes/Test/fixtures/products.yaml: -------------------------------------------------------------------------------- 1 | config: 2 | default/catalog/frontend/flat_catalog_product: 1 3 | eav: 4 | catalog_product: 5 | - entity_id: 1 6 | type_id: simple 7 | attribute_set_id: 4 8 | name: Test Product 1 9 | sku: test-1 10 | description: description. 11 | meta_description: meta description. 12 | meta_keyword: meta keyword. 13 | meta_title: meta title. 14 | short_description: short description. 15 | /stores: 16 | second: 17 | name: Test Product 1 in store view 18 | description: description in store view 19 | meta_description: meta description in store view 20 | short_description: short description in store view 21 | - entity_id: 2 22 | type_id: simple 23 | attribute_set_id: 4 24 | name: Test Product 2 25 | sku: test-2 26 | description: description. 27 | meta_description: meta description. 28 | meta_keyword: meta keyword. 29 | meta_title: meta title. 30 | short_description: short description. 31 | /stores: 32 | second: 33 | description: description in store view 34 | meta_description: meta description in store view 35 | short_description: short description in store view 36 | - entity_id: 3 37 | type_id: simple 38 | attribute_set_id: 4 39 | name: Test Product 3 40 | sku: test-3 41 | description: description. 42 | meta_description: meta description. 43 | meta_keyword: meta keyword. 44 | meta_title: meta title. 45 | short_description: short description. 46 | /stores: 47 | second: 48 | description: description in store view 49 | meta_description: meta description in store view 50 | short_description: short description in store view 51 | -------------------------------------------------------------------------------- /src/app/code/community/Hackathon/DerivedAttributes/Test/fixtures/rules.yaml: -------------------------------------------------------------------------------- 1 | tables: 2 | derivedattributes/rule: 3 | - rule_id: 1 4 | name: Test Rule 1 5 | description: This is a template generator rule for the product description 6 | store_id: 0 7 | attribute_id: 72 #product description 8 | generator_type: template 9 | generator_data: "The description contains the #short_description#" 10 | condition_type: always 11 | condition_data: "" 12 | active: 1 13 | priority: 4 14 | - rule_id: 2 15 | name: Test Rule 2 16 | meta_description: This is a template generator rule for the meta product description (store view based) 17 | store_id: 2 18 | attribute_id: 84 #product meta description 19 | generator_type: template 20 | generator_data: "The meta description contains the #short_description#" 21 | condition_type: always 22 | condition_data: "" 23 | active: 1 24 | priority: 3 25 | - rule_id: 3 26 | name: Test Rule 3 27 | meta_description: This is a template generator rule for the meta product keywords (multiple store views) 28 | store_id: "1,2" 29 | attribute_id: 83 #product meta keywords 30 | generator_type: template 31 | generator_data: "The meta keywords contain the #short_description#" 32 | condition_type: always 33 | condition_data: "" 34 | active: 1 35 | priority: 2 36 | - rule_id: 4 37 | name: Test Rule 4 38 | meta_description: This is a rule for a category attribute with conflicting attribute code 39 | store_id: 0 40 | attribute_id: 46 #category meta_title 41 | generator_type: template 42 | generator_data: "I am a category" 43 | condition_type: always 44 | condition_data: "" 45 | active: 1 46 | priority: 1 47 | -------------------------------------------------------------------------------- /src/app/code/community/Hackathon/DerivedAttributes/Test/fixtures/stores.yaml: -------------------------------------------------------------------------------- 1 | scope: 2 | website: # Initialize websites 3 | - website_id: 2 4 | code: second_website 5 | name: Second Website 6 | default_group_id: 2 7 | group: # Initializes store groups 8 | - group_id: 2 9 | website_id: 2 10 | name: Second Store Group 11 | default_store_id: 2 12 | root_category_id: 2 # Default Category 13 | store: # Initializes store views 14 | - store_id: 2 15 | website_id: 2 16 | group_id: 2 17 | code: second 18 | name: Second Store 19 | is_active: 1 -------------------------------------------------------------------------------- /src/app/code/community/Hackathon/DerivedAttributes/controllers/Adminhtml/DerivedAttributes/EntityController.php: -------------------------------------------------------------------------------- 1 | isAllowed('catalog/attributes/derived_attributes'); 8 | } 9 | 10 | protected function _initAction() 11 | { 12 | $this->loadLayout() 13 | ->_setActiveMenu('catalog/attributes/derived_attributes') 14 | ->_addBreadcrumb($this->__('Derived Attributes'),$this->__('Entities')) 15 | ; 16 | return $this; 17 | } 18 | 19 | public function indexAction() 20 | { 21 | $this->_title($this->__('Derived Attributes'))->_title($this->__('Entities')); 22 | $this->_initAction(); 23 | Mage::helper('integernet_gridmassactionpager')->addScript(); 24 | $this->renderLayout(); 25 | } 26 | 27 | /** 28 | * Product grid for AJAX request 29 | */ 30 | public function productGridAction() 31 | { 32 | $this->loadLayout(); 33 | $this->renderLayout(); 34 | } 35 | /** 36 | * Customer grid for AJAX request 37 | */ 38 | public function customerGridAction() 39 | { 40 | $this->loadLayout(); 41 | $this->renderLayout(); 42 | } 43 | 44 | public function applyRulesAction() 45 | { 46 | /** @var IntegerNet_GridMassActionPager_Model_GridMassActionPager $gridMassActionPager */ 47 | $gridMassActionPager = Mage::getModel('integernet_gridmassactionpager/gridMassActionPager'); 48 | 49 | $error = false; 50 | try { 51 | if ($entityIds = (array)$this->getRequest()->getParam('entity_ids')) { 52 | 53 | $gridMassActionPager->init($entityIds, 100); 54 | $gridMassActionPager->getPager() 55 | ->setEntityType($this->getRequest()->getParam('entity_type')) 56 | ->setStoreId($this->getRequest()->getParam('store_id')) 57 | ->setDryRun((bool) $this->getRequest()->getParam('dry_run')); 58 | 59 | } elseif ($pageIds = $gridMassActionPager->getPageIds()) { 60 | 61 | $entityType = $gridMassActionPager->getPager()->getEntityType(); 62 | $isDryRun = $gridMassActionPager->getPager()->getDryRun(); 63 | $storeIds = $gridMassActionPager->getPager()->getStoreId(); 64 | $this->_process($pageIds, $entityType, $storeIds, $isDryRun); 65 | 66 | $gridMassActionPager->next(); 67 | } else { 68 | //TODO (optionally) generate results page, trigger redirect 69 | // - log generated attributes to new table with RuleLogger (entity_type, attribute_id, applied_rule_id, value) 70 | // - override IntegerNet_GridMassActionPager.prototype.process(transport) 71 | // - if (transport.final), redirect 72 | } 73 | } catch (Exception $e) { 74 | $error = $e->getMessage(); 75 | } 76 | 77 | $message = $this->__('Process Entities...
{{from}} to {{to}} of {{of}}'); 78 | 79 | $this->getResponse()->setHeader('Content-Type', 'application/json'); 80 | $status = $gridMassActionPager->getStatus(false, $message); 81 | if ($error) { 82 | $status['error'] = $error; 83 | } 84 | //TODO show error in frontend 85 | $this->getResponse()->setBody(Mage::helper('core')->jsonEncode($status)); 86 | } 87 | protected function _process($pageIds, $entityType, $storeIds, $isDryRun) 88 | { 89 | Mage::getModel('derivedattributes/massupdater')->update($pageIds, $entityType, $storeIds, $isDryRun); 90 | } 91 | } -------------------------------------------------------------------------------- /src/app/code/community/Hackathon/DerivedAttributes/controllers/Adminhtml/DerivedAttributes/RuleController.php: -------------------------------------------------------------------------------- 1 | isAllowed('catalog/attributes/derived_attributes'); 8 | } 9 | 10 | /** 11 | * @var Hackathon_DerivedAttributes_Model_Resource_Rule 12 | */ 13 | protected $_repository; 14 | 15 | protected function _construct() 16 | { 17 | $this->_repository = Mage::getModel('derivedattributes/bridge_ruleRepository'); 18 | } 19 | 20 | protected function _initAction() 21 | { 22 | $this->loadLayout() 23 | ->_setActiveMenu('catalog/attributes/derived_attributes') 24 | ->_addBreadcrumb($this->__('Derived Attributes'),$this->__('Rules')) 25 | ; 26 | return $this; 27 | } 28 | 29 | public function indexAction() 30 | { 31 | $this->_title($this->__('Derived Attributes'))->_title($this->__('Rules')); 32 | 33 | $this->_initAction() 34 | ->renderLayout(); 35 | } 36 | 37 | public function newAction() 38 | { 39 | $this->_forward('edit'); 40 | } 41 | 42 | public function editAction() 43 | { 44 | $id = $this->getRequest()->getParam('id'); 45 | $model = new Varien_Object(); 46 | 47 | if ($id) { 48 | try { 49 | $rule = $this->_repository->findById($id); 50 | $model = $this->_repository->getRuleModel($rule); 51 | } catch (Mage_Core_Exception $e) { 52 | Mage::getSingleton('adminhtml/session')->addError($e->getMessage()); 53 | $this->_redirect('*/*'); 54 | return; 55 | } 56 | } 57 | 58 | $this->_title($model->getId() ? $rule->getName() : $this->__('New Rule')); 59 | 60 | $data = Mage::getSingleton('adminhtml/session')->getPageData(true); 61 | if (!empty($data)) { 62 | $model->addData($data); 63 | } 64 | 65 | Mage::register('current_derived_attribute_rule', $model); 66 | 67 | $this->_initAction()->getLayout()->getBlock('derivedattributes_rule_edit') 68 | ->setData('action', $this->getUrl('*/*/save')); 69 | 70 | $this 71 | ->_addBreadcrumb( 72 | $id ? $this->__('Edit Rule') 73 | : $this->__('New Rule'), 74 | $id ? $this->__('Edit Rule') 75 | : $this->__('New Rule')) 76 | ->renderLayout(); 77 | 78 | } 79 | 80 | public function saveAction() 81 | { 82 | if ($this->getRequest()->getPost()) { 83 | try { 84 | Mage::dispatchEvent( 85 | 'adminhtml_controller_derivedattributes_rule_prepare_save', 86 | array('request' => $this->getRequest())); 87 | $data = $this->getRequest()->getPost(); 88 | // $data = $this->_filterDates($data, array('from_date', 'to_date')); 89 | 90 | // $validateResult = $model->validateData(new Varien_Object($data)); 91 | // if ($validateResult !== true) { 92 | // foreach($validateResult as $errorMessage) { 93 | // $session->addError($errorMessage); 94 | // } 95 | // $session->setPageData($data); 96 | // $this->_redirect('*/*/edit', array('id'=>$model->getId())); 97 | // return; 98 | // } 99 | 100 | 101 | $id = $this->getRequest()->getParam('rule_id'); 102 | if ($id) { 103 | $oldRule = $this->_repository->findById($id); 104 | $model = $this->_repository->getRuleModel($oldRule); 105 | $model->addData($data); 106 | $newRule = $this->_createRuleFromModel($model); 107 | $this->_repository->replaceRule($oldRule, $newRule); 108 | } else { 109 | $newRule = $this->_createRuleFromModel(new Varien_Object($data)); 110 | $this->_repository->createRule($newRule); 111 | } 112 | 113 | $this->_getSession()->addSuccess($this->__('The rule has been saved.')); 114 | $this->_getSession()->setPageData(false); 115 | if ($this->getRequest()->getParam('back')) { 116 | $this->_redirect('*/*/edit', array('id' => $this->_repository->getRuleId($newRule))); 117 | return; 118 | } 119 | $this->_redirect('*/*/'); 120 | return; 121 | } catch (Mage_Core_Exception $e) { 122 | $this->_getSession()->addError($e->getMessage()); 123 | $id = (int)$this->getRequest()->getParam('rule_id'); 124 | if (!empty($id)) { 125 | $this->_redirect('*/*/edit', array('id' => $id)); 126 | } else { 127 | $this->_redirect('*/*/new'); 128 | } 129 | return; 130 | 131 | } catch (Exception $e) { 132 | $this->_getSession()->addError( 133 | $this->__('An error occurred while saving the rule data. Please review the log and try again.')); 134 | Mage::logException($e); 135 | Mage::getSingleton('adminhtml/session')->setPageData($data); 136 | $this->_redirect('*/*/edit', array('id' => $this->getRequest()->getParam('rule_id'))); 137 | return; 138 | } 139 | } 140 | $this->_redirect('*/*/'); 141 | } 142 | 143 | public function deleteAction() 144 | { 145 | if ($id = $this->getRequest()->getParam('id')) { 146 | try { 147 | $ruleToBeDeleted = $this->_repository->findById($id); 148 | $this->_repository->deleteRule($ruleToBeDeleted); 149 | Mage::getSingleton('adminhtml/session')->addSuccess( 150 | $this->__('The rule has been deleted.')); 151 | $this->_redirect('*/*/'); 152 | return; 153 | } catch (Mage_Core_Exception $e) { 154 | $this->_getSession()->addError($e->getMessage()); 155 | } catch (Exception $e) { 156 | $this->_getSession()->addError( 157 | $this->__('An error occurred while deleting the rule. Please review the log and try again.')); 158 | Mage::logException($e); 159 | $this->_redirect('*/*/edit', array('id' => $this->getRequest()->getParam('id'))); 160 | return; 161 | } 162 | } 163 | Mage::getSingleton('adminhtml/session')->addError( 164 | $this->__('Unable to find a rule to delete.')); 165 | $this->_redirect('*/*/'); 166 | } 167 | 168 | public function gridAction() 169 | { 170 | $this->_title($this->__('Derived Attributes'))->_title($this->__('Rules')); 171 | $this->loadLayout()->renderLayout(); 172 | } 173 | 174 | /** 175 | * @param $model 176 | * @return \Hackathon\DerivedAttributes\Rule 177 | */ 178 | protected function _createRuleFromModel($model) 179 | { 180 | $newRule = Mage::helper('derivedattributes/rule_director')->createRule($model); 181 | $this->_getSession()->setPageData($this->_repository->getRuleModel($newRule)->getData()); 182 | return $newRule; 183 | } 184 | 185 | } -------------------------------------------------------------------------------- /src/app/code/community/Hackathon/DerivedAttributes/etc/adminhtml.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Derived Attributes 10 | 100 11 | adminhtml/derivedAttributes_rule/index 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | Derived Attributes 28 | 100 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /src/app/code/community/Hackathon/DerivedAttributes/etc/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 0.1.3 6 | 7 | 8 | 9 | 10 | 11 | Hackathon_DerivedAttributes_Block 12 | 13 | 14 | 15 | 16 | Hackathon_DerivedAttributes_Helper 17 | 18 | 19 | 20 | 21 | Hackathon_DerivedAttributes_Model 22 | derivedattributes_resource 23 | 24 | 25 | Hackathon_DerivedAttributes_Model_Resource 26 | 27 | 28 | derivedattributes_rule
29 |
30 | 31 | derivedattributes_rule_filter
32 |
33 | 34 | derivedattributes_rule_condition
35 |
36 |
37 |
38 |
39 | 40 | 41 | 42 | Hackathon_DerivedAttributes 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | singleton 51 | Hackathon_DerivedAttributes_Model_Observer 52 | beforeModelSave 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 |
61 | 62 | 63 | 64 | 65 | 66 | Hackathon_DerivedAttributes_Adminhtml 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | hackathon_derivedattributes.xml 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 |
89 | -------------------------------------------------------------------------------- /src/app/code/community/Hackathon/DerivedAttributes/sql/derivedattributes_setup/mysql4-install-0.1.0.php: -------------------------------------------------------------------------------- 1 | startSetup(); 7 | 8 | // install table 9 | $createTableStatements = " 10 | 11 | CREATE TABLE `{$installer->getTable('derivedattributes/rule')}` ( 12 | `rule_id` int(11) NOT NULL AUTO_INCREMENT, 13 | `attribute_id` smallint(5) unsigned NOT NULL, 14 | `generator_type` varchar(64) DEFAULT NULL, 15 | `generator_data` tinytext, 16 | `condition_type` varchar(64) DEFAULT NULL, 17 | `condition_data` tinytext, 18 | `store_id` smallint(5) unsigned DEFAULT NULL, 19 | `active` tinyint(4) NOT NULL DEFAULT '1', 20 | `priority` int(11) DEFAULT '0', 21 | PRIMARY KEY (`rule_id`), 22 | KEY `fk_derivedattributes_rule_1_idx` (`attribute_id`), 23 | KEY `fk_derivedattributes_rule_2_idx` (`store_id`), 24 | CONSTRAINT `fk_derivedattributes_rule_2` FOREIGN KEY (`store_id`) REFERENCES `core_store` (`store_id`) ON DELETE NO ACTION ON UPDATE NO ACTION, 25 | CONSTRAINT `fk_derivedattributes_rule_1` FOREIGN KEY (`attribute_id`) REFERENCES `eav_attribute` (`attribute_id`) ON DELETE NO ACTION ON UPDATE NO ACTION 26 | ) ENGINE=InnoDB DEFAULT CHARSET=latin1; 27 | 28 | CREATE TABLE `{$installer->getTable('derivedattributes/rulecondition')}` ( 29 | `rule_condition_id` int(11) NOT NULL AUTO_INCREMENT, 30 | `rule_id` int(11) DEFAULT NULL, 31 | `condition_type` varchar(64) DEFAULT NULL, 32 | `condition_data` tinytext, 33 | `active` tinyint(4) NOT NULL DEFAULT '1', 34 | PRIMARY KEY (`rule_condition_id`), 35 | KEY `fk_derivedattributes_rule_condition_1_idx` (`rule_id`), 36 | CONSTRAINT `fk_derivedattributes_rule_condition_1` FOREIGN KEY (`rule_id`) REFERENCES `derivedattributes_rule` (`rule_id`) ON DELETE NO ACTION ON UPDATE NO ACTION 37 | ) ENGINE=InnoDB DEFAULT CHARSET=latin1; 38 | 39 | CREATE TABLE `{$installer->getTable('derivedattributes/rulefilter')}` ( 40 | `rule_filter_id` int(11) NOT NULL AUTO_INCREMENT, 41 | `rule_id` int(11) DEFAULT NULL, 42 | `filter_type` varchar(64) DEFAULT NULL, 43 | `filter_data` tinytext, 44 | `active` tinyint(4) NOT NULL DEFAULT '1', 45 | `priority` int(11) DEFAULT '0', 46 | PRIMARY KEY (`rule_filter_id`), 47 | KEY `fk_derivedattributes_rule_filter_1_idx` (`rule_id`), 48 | CONSTRAINT `fk_derivedattributes_rule_filter_1` FOREIGN KEY (`rule_id`) REFERENCES `derivedattributes_rule` (`rule_id`) ON DELETE NO ACTION ON UPDATE NO ACTION 49 | ) ENGINE=InnoDB DEFAULT CHARSET=latin1; 50 | 51 | "; 52 | $installer->run($createTableStatements); 53 | 54 | $installer->endSetup(); 55 | -------------------------------------------------------------------------------- /src/app/code/community/Hackathon/DerivedAttributes/sql/derivedattributes_setup/upgrade-0.1.0-0.1.1.php: -------------------------------------------------------------------------------- 1 | startSetup(); 7 | $ruleTable = $this->getTable('derivedattributes/rule'); 8 | 9 | $installer->getConnection()->addColumn($ruleTable, 'name', array( 10 | 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, 11 | 'size' => 255, 12 | 'nullable' => false, 13 | 'comment' => 'Rule name' 14 | )); 15 | $installer->getConnection()->addColumn($ruleTable, 'description', array( 16 | 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, 17 | 'size' => 65536, 18 | 'nullable' => false, 19 | 'default' => '', 20 | 'comment' => 'Rule Description' 21 | )); 22 | 23 | $installer->endSetup(); -------------------------------------------------------------------------------- /src/app/code/community/Hackathon/DerivedAttributes/sql/derivedattributes_setup/upgrade-0.1.0-0.1.2.php: -------------------------------------------------------------------------------- 1 | startSetup(); 7 | $ruleTable = $this->getTable('derivedattributes/rule'); 8 | 9 | $installer->getConnection()->addColumn($ruleTable, 'name', array( 10 | 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, 11 | 'length' => 255, 12 | 'nullable' => false, 13 | 'comment' => 'Rule name' 14 | )); 15 | $installer->getConnection()->addColumn($ruleTable, 'description', array( 16 | 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, 17 | 'length' => 65536, 18 | 'nullable' => false, 19 | 'default' => '', 20 | 'comment' => 'Rule description' 21 | )); 22 | 23 | $installer->endSetup(); -------------------------------------------------------------------------------- /src/app/code/community/Hackathon/DerivedAttributes/sql/derivedattributes_setup/upgrade-0.1.1-0.1.2.php: -------------------------------------------------------------------------------- 1 | startSetup(); 7 | $ruleTable = $this->getTable('derivedattributes/rule'); 8 | 9 | $installer->getConnection()->modifyColumn($ruleTable, 'name', array( 10 | 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, 11 | 'length' => 255, 12 | 'nullable' => false, 13 | 'comment' => 'Rule name' 14 | )); 15 | $installer->getConnection()->addColumn($ruleTable, 'description', array( 16 | 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, 17 | 'length' => 65536, 18 | 'nullable' => false, 19 | 'default' => '', 20 | 'comment' => 'Rule description' 21 | )); 22 | 23 | $installer->endSetup(); -------------------------------------------------------------------------------- /src/app/code/community/Hackathon/DerivedAttributes/sql/derivedattributes_setup/upgrade-0.1.2-0.1.3.php: -------------------------------------------------------------------------------- 1 | startSetup(); 7 | $ruleTable = $this->getTable('derivedattributes/rule'); 8 | 9 | $installer->getConnection()->dropForeignKey($ruleTable, 'fk_derivedattributes_rule_2'); 10 | $installer->getConnection()->modifyColumn($ruleTable, 'store_id', array( 11 | 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, 12 | 'length' => 255, 13 | 'nullable' => false, 14 | 'comment' => 'Rule name' 15 | )); 16 | $installer->getConnection()->resetDdlCache($ruleTable); 17 | 18 | $installer->endSetup(); -------------------------------------------------------------------------------- /src/app/design/adminhtml/base/default/layout/hackathon_derivedattributes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 1 11 | 1 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /src/app/etc/modules/Hackathon_DerivedAttributes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | true 6 | community 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/lib/Hackathon/DerivedAttributes/Attribute.php: -------------------------------------------------------------------------------- 1 | entityTypeCode = (string) $entityTypeCode; 29 | $this->attributeCode = (string) $attributeCode; 30 | } 31 | 32 | /** 33 | * @return string 34 | */ 35 | public function getAttributeCode() 36 | { 37 | return $this->attributeCode; 38 | } 39 | 40 | /** 41 | * @return string 42 | */ 43 | public function getEntityTypeCode() 44 | { 45 | return $this->entityTypeCode; 46 | } 47 | 48 | } -------------------------------------------------------------------------------- /src/lib/Hackathon/DerivedAttributes/BridgeInterface/EntityInterface.php: -------------------------------------------------------------------------------- 1 | attribute = $builder->getAttribute(); 58 | $this->condition = $builder->getCondition(); 59 | $this->generator = $builder->getGenerator(); 60 | $this->priority = $builder->getPriority(); 61 | $this->active = $builder->isActive(); 62 | $this->name = $builder->getName(); 63 | $this->description = $builder->getDescription(); 64 | $this->stores = $builder->getStores(); 65 | } 66 | 67 | /** 68 | * @return int 69 | */ 70 | public function getPriority() 71 | { 72 | return $this->priority; 73 | } 74 | 75 | /** 76 | * @return boolean 77 | */ 78 | public function isActive() 79 | { 80 | return $this->active; 81 | } 82 | 83 | /** 84 | * @return string 85 | */ 86 | public function getName() 87 | { 88 | return $this->name; 89 | } 90 | 91 | /** 92 | * @return string 93 | */ 94 | public function getDescription() 95 | { 96 | return $this->description; 97 | } 98 | 99 | /** 100 | * @return Attribute 101 | */ 102 | public function getAttribute() 103 | { 104 | return $this->attribute; 105 | } 106 | 107 | /** 108 | * @return StoreSet 109 | */ 110 | public function getStores() 111 | { 112 | return $this->stores; 113 | } 114 | 115 | /** 116 | * @return ConditionInterface 117 | */ 118 | public function getCondition() 119 | { 120 | return $this->condition; 121 | } 122 | 123 | /** 124 | * @return GeneratorInterface 125 | */ 126 | public function getGenerator() 127 | { 128 | return $this->generator; 129 | } 130 | 131 | /** 132 | * Applies attribute rule to product. Returns true if rule was applicable 133 | * 134 | * @param EntityInterface $entity 135 | * @return bool 136 | */ 137 | public function applyToEntity(EntityInterface $entity) 138 | { 139 | if ($entity->hasAttribute($this->attribute) && $this->condition->match($entity)) { 140 | $value = $this->generator->generateAttributeValue($entity); 141 | $entity->setAttributeValue($this->getAttribute(), $value); 142 | return true; 143 | } 144 | return false; 145 | } 146 | } -------------------------------------------------------------------------------- /src/lib/Hackathon/DerivedAttributes/RuleBuilder.php: -------------------------------------------------------------------------------- 1 | serviceManager = $serviceManager; 33 | $this->attribute = $attribute; 34 | $this->stores = StoreSet::all(); 35 | } 36 | 37 | /** 38 | * @param boolean $active 39 | * @return $this 40 | */ 41 | public function setActive($active) 42 | { 43 | $this->active = $active; 44 | return $this; 45 | } 46 | /** 47 | * @param $priority 48 | * @return $this 49 | */ 50 | public function setPriority($priority) 51 | { 52 | $this->priority = $priority; 53 | return $this; 54 | } 55 | 56 | /** 57 | * @param string $name 58 | * @return $this 59 | */ 60 | public function setName($name) 61 | { 62 | $this->name = $name; 63 | return $this; 64 | } 65 | 66 | /** 67 | * @param string $description 68 | * @return $this 69 | */ 70 | public function setDescription($description) 71 | { 72 | $this->description = $description; 73 | return $this; 74 | } 75 | 76 | /** 77 | * @param mixed $conditionType 78 | * @return $this 79 | */ 80 | public function setConditionType($conditionType) 81 | { 82 | $this->conditionType = $conditionType; 83 | return $this; 84 | } 85 | 86 | /** 87 | * @param mixed $conditionData 88 | * @return $this 89 | */ 90 | public function setConditionData($conditionData) 91 | { 92 | $this->conditionData = $conditionData; 93 | return $this; 94 | } 95 | 96 | /** 97 | * @param mixed $generatorType 98 | * @return $this 99 | */ 100 | public function setGeneratorType($generatorType) 101 | { 102 | $this->generatorType = $generatorType; 103 | return $this; 104 | } 105 | 106 | /** 107 | * @param mixed $generatorData 108 | * @return $this 109 | */ 110 | public function setGeneratorData($generatorData) 111 | { 112 | $this->generatorData = $generatorData; 113 | return $this; 114 | } 115 | 116 | /** 117 | * @param GeneratorInterface $generator 118 | * @return $this 119 | */ 120 | public function setGenerator(GeneratorInterface $generator) 121 | { 122 | $this->generator = $generator; 123 | return $this; 124 | } 125 | /** 126 | * @param ConditionInterface $condition 127 | * @return $this 128 | */ 129 | public function setCondition(ConditionInterface $condition) 130 | { 131 | $this->condition = $condition; 132 | return $this; 133 | } 134 | 135 | /** 136 | * @return $this 137 | */ 138 | private function buildCondition() 139 | { 140 | if ($this->condition === null) { 141 | $this->setCondition($this->serviceManager->getCondition($this->conditionType, $this->conditionData)); 142 | } 143 | return $this; 144 | } 145 | 146 | /** 147 | * @return $this 148 | */ 149 | private function buildGenerator() 150 | { 151 | if ($this->generator === null) { 152 | $this->setGenerator($this->serviceManager->getGenerator($this->generatorType, $this->generatorData)); 153 | } 154 | return $this; 155 | } 156 | /** 157 | * @param Attribute $attribute 158 | * @return $this 159 | */ 160 | public function setAttribute(Attribute $attribute) 161 | { 162 | $this->attribute = $attribute; 163 | return $this; 164 | } 165 | 166 | public function setStores(StoreSet $stores) 167 | { 168 | $this->stores = $stores; 169 | } 170 | 171 | /** 172 | * @return boolean 173 | */ 174 | public function isActive() 175 | { 176 | return $this->active; 177 | } 178 | 179 | /** 180 | * @return int 181 | */ 182 | public function getPriority() 183 | { 184 | return $this->priority; 185 | } 186 | 187 | /** 188 | * @return string 189 | */ 190 | public function getName() 191 | { 192 | return $this->name; 193 | } 194 | 195 | /** 196 | * @return string 197 | */ 198 | public function getDescription() 199 | { 200 | return $this->description; 201 | } 202 | 203 | /** 204 | * @return mixed 205 | */ 206 | public function getCondition() 207 | { 208 | return $this->condition; 209 | } 210 | 211 | /** 212 | * @return mixed 213 | */ 214 | public function getGenerator() 215 | { 216 | return $this->generator; 217 | } 218 | 219 | /** 220 | * @return Attribute 221 | */ 222 | public function getAttribute() 223 | { 224 | return $this->attribute; 225 | } 226 | 227 | /** 228 | * @return StoreSet 229 | */ 230 | public function getStores() 231 | { 232 | return $this->stores; 233 | } 234 | 235 | /** 236 | * @return Rule 237 | */ 238 | public function build() 239 | { 240 | $this->buildCondition(); 241 | $this->buildGenerator(); 242 | $rule = new Rule($this); 243 | return $rule; 244 | } 245 | } -------------------------------------------------------------------------------- /src/lib/Hackathon/DerivedAttributes/RuleSet.php: -------------------------------------------------------------------------------- 1 | rules[] = $rule; 31 | } 32 | 33 | public function getRules() 34 | { 35 | return $this->rules; 36 | } 37 | 38 | /** 39 | * @param EntityInterface $entity 40 | * @return void 41 | */ 42 | public function applyToEntity(EntityInterface $entity, RuleLoggerInterface $logger) 43 | { 44 | $affectedAttributes = array(); 45 | foreach ($this->rules as $rule) { 46 | $code = $rule->getAttribute()->getAttributeCode(); 47 | if (isset($affectedAttributes[$code])) { 48 | break; 49 | } 50 | if ($rule->applyToEntity($entity)) { 51 | $logger->logAppliedRule($rule, $entity->getAttributeValue($rule->getAttribute())); 52 | $affectedAttributes[$code] = true; 53 | } 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /src/lib/Hackathon/DerivedAttributes/RuleSetsByStore.php: -------------------------------------------------------------------------------- 1 | ruleSets[(string) $store] = $ruleSet; 25 | $this->stores[(string) $store] = $store; 26 | } 27 | 28 | /** 29 | * @param Store $store 30 | * @return RuleSet 31 | */ 32 | public function getRuleSet(Store $store) 33 | { 34 | if (! isset($this->ruleSets[(string) $store])) { 35 | throw new \OutOfBoundsException("No rule set found for store {$store}"); 36 | } 37 | return $this->ruleSets[(string) $store]; 38 | } 39 | 40 | public function current() 41 | { 42 | return current($this->ruleSets); 43 | } 44 | 45 | public function next() 46 | { 47 | next($this->ruleSets); 48 | next($this->stores); 49 | } 50 | 51 | public function key() 52 | { 53 | return current($this->stores); 54 | } 55 | 56 | public function valid() 57 | { 58 | return current($this->ruleSets) !== false; 59 | } 60 | 61 | public function rewind() 62 | { 63 | reset($this->ruleSets); 64 | reset($this->stores); 65 | } 66 | 67 | public function count() 68 | { 69 | return count($this->ruleSets); 70 | } 71 | 72 | } -------------------------------------------------------------------------------- /src/lib/Hackathon/DerivedAttributes/Service/Condition/AlwaysCondition.php: -------------------------------------------------------------------------------- 1 | data = $data; 23 | return $this; 24 | } 25 | 26 | /** 27 | * @internal used to test instantiation 28 | * @return string 29 | */ 30 | public function getData() 31 | { 32 | return $this->data; 33 | } 34 | /** 35 | * @param EntityInterface $entity 36 | * @return boolean 37 | */ 38 | public function match(EntityInterface $entity) 39 | { 40 | return true; 41 | } 42 | 43 | /** 44 | * @return string 45 | */ 46 | public function getTitle() 47 | { 48 | return self::TITLE; 49 | } 50 | 51 | /** 52 | * @return string 53 | */ 54 | public function getDescription() 55 | { 56 | return self::DESCRIPTION; 57 | } 58 | 59 | } -------------------------------------------------------------------------------- /src/lib/Hackathon/DerivedAttributes/Service/Condition/BooleanAttributeCondition.php: -------------------------------------------------------------------------------- 1 | data = $data; 24 | return $this; 25 | } 26 | 27 | /** 28 | * @internal used to test instantiation 29 | * @return string 30 | */ 31 | public function getData() 32 | { 33 | return $this->data; 34 | } 35 | /** 36 | * @param EntityInterface $entity 37 | * @return boolean 38 | */ 39 | public function match(EntityInterface $entity) 40 | { 41 | return (bool) $entity->getAttributeValue(new Attribute($entity->getEntityTypeCode(), $this->data)); 42 | } 43 | 44 | /** 45 | * @return string 46 | */ 47 | public function getTitle() 48 | { 49 | return self::TITLE; 50 | } 51 | 52 | /** 53 | * @return string 54 | */ 55 | public function getDescription() 56 | { 57 | return self::DESCRIPTION; 58 | } 59 | 60 | } -------------------------------------------------------------------------------- /src/lib/Hackathon/DerivedAttributes/Service/Generator/TemplateGenerator.php: -------------------------------------------------------------------------------- 1 | data = $data; 25 | return $this; 26 | } 27 | 28 | /** 29 | * @internal used to test instantiation 30 | * @return string 31 | */ 32 | public function getData() 33 | { 34 | return $this->data; 35 | } 36 | 37 | /** 38 | * @param EntityInterface $entity 39 | * @return mixed 40 | */ 41 | public function generateAttributeValue(EntityInterface $entity) 42 | { 43 | return $this->_parseData($entity); 44 | } 45 | 46 | /** 47 | * @return string 48 | */ 49 | public function getTitle() 50 | { 51 | return self::TITLE; 52 | } 53 | 54 | /** 55 | * @return string 56 | */ 57 | public function getDescription() 58 | { 59 | return self::DESCRIPTION; 60 | } 61 | 62 | /** 63 | * Return default value based on configured template 64 | * 65 | * @return string 66 | */ 67 | private function _parseData(EntityInterface $entity) 68 | { 69 | $this->entity = $entity; 70 | $valueTemplate = $this->data; 71 | $value = preg_replace_callback('~#([\w\-]+)#~', function ($matches) use ($entity) { 72 | /* 73 | * getData() for multiselect attribute should be a comma separated string, 74 | * but something converts it to an array and we need to revert that to make 75 | * the frontend model work: 76 | */ 77 | $attr = new Attribute($entity->getEntityTypeCode(), $matches[1]); 78 | if (is_array($this->entity->getLocalizedAttributeValue($attr))) { 79 | $this->entity->getLocalizedAttributeValue($attr, 80 | join(',', $this->entity->getLocalizedAttributeValue($attr))); 81 | } 82 | $_value = $this->entity->getLocalizedAttributeValue($attr); 83 | return $_value; 84 | }, $valueTemplate); 85 | return $value; 86 | } 87 | 88 | } -------------------------------------------------------------------------------- /src/lib/Hackathon/DerivedAttributes/Service/Manager.php: -------------------------------------------------------------------------------- 1 | resetGeneratorTypes(); 19 | $this->resetConditionTypes(); 20 | } 21 | 22 | public function resetGeneratorTypes(){ 23 | $this->generatorTypes = [ 24 | 'template' => TemplateGenerator::__CLASS 25 | ]; 26 | } 27 | 28 | public function resetConditionTypes(){ 29 | $this->conditionTypes = [ 30 | 'boolean-attribute' => BooleanAttributeCondition::__CLASS, 31 | 'always' => AlwaysCondition::__CLASS 32 | ]; 33 | } 34 | 35 | public function getGeneratorTypes(){ 36 | return $this->generatorTypes; 37 | } 38 | 39 | public function addGeneratorType($id, $class){ 40 | if(is_object($class)){ 41 | $class = get_class($class); 42 | } 43 | $this->generatorTypes[(string)$id] = $class; 44 | } 45 | 46 | public function getConditionTypes(){ 47 | return $this->conditionTypes; 48 | } 49 | 50 | public function addConditionType($id, $class){ 51 | if(is_object($class)){ 52 | $class = get_class($class); 53 | } 54 | $this->conditionTypes[(string)$id] = $class; 55 | } 56 | 57 | /** 58 | * Return meta information of available generators 59 | * 60 | * @return array as [ $identifier => [ "title" => $title, "description" => $description ] 61 | */ 62 | public function getAvailableGeneratorTypes() 63 | { 64 | $result = array(); 65 | foreach ($this->generatorTypes as $id => $class) { 66 | if (is_subclass_of($class, GeneratorInterface::__INTERFACE)) { 67 | $generator = new $class; 68 | $result[$id] = [ 69 | 'title' => $generator->getTitle(), 70 | 'description' => $generator->getDescription() 71 | ]; 72 | } 73 | } 74 | return $result; 75 | } 76 | 77 | /** 78 | * Return meta information of available generators 79 | * 80 | * @return array as [ $identifier => [ "title" => $title, "description" => $description ] 81 | */ 82 | public function getAvailableConditionTypes() 83 | { 84 | $result = array(); 85 | foreach ($this->conditionTypes as $id => $class) { 86 | if (is_subclass_of($class, ConditionInterface::__INTERFACE)) { 87 | $condition = new $class; 88 | $result[$id] = ['title' => $condition->getTitle(), 'description' => $condition->getDescription()]; 89 | } 90 | } 91 | return $result; 92 | } 93 | 94 | /** 95 | * Factory method: instantiate condition based on bridge interface 96 | * 97 | * @param $conditionType 98 | * @param $conditionData 99 | * @return ConditionInterface 100 | */ 101 | public function getCondition($conditionType, $conditionData) 102 | { 103 | if (!isset($this->conditionTypes[$conditionType])) { 104 | throw new \InvalidArgumentException(sprintf('Unknown condition type "%s".', $conditionType)); 105 | } 106 | /** @var ConditionInterface $condition */ 107 | $condition = new $this->conditionTypes[$conditionType]; 108 | $condition->configure($conditionData); 109 | return $condition; 110 | } 111 | 112 | /** 113 | * Factory method: instantiate generator based on bridge interface 114 | * 115 | * @param $generatorType 116 | * @param $generatorData 117 | * @return GeneratorInterface 118 | */ 119 | public function getGenerator($generatorType, $generatorData) 120 | { 121 | if (!isset($this->generatorTypes[$generatorType])) { 122 | throw new \InvalidArgumentException(sprintf('Unknown generator type "%s".', $generatorType)); 123 | } 124 | /** @var GeneratorInterface $generator */ 125 | $generator = new $this->generatorTypes[$generatorType]; 126 | $generator->configure($generatorData); 127 | return $generator; 128 | } 129 | 130 | /** 131 | * Determine condition type identifier based on condition instance 132 | * 133 | * @param ConditionInterface $condition 134 | * @return string 135 | * @throws \OutOfBoundsException 136 | */ 137 | public function getConditionType(ConditionInterface $condition) 138 | { 139 | $result = array_search(get_class($condition), $this->conditionTypes); 140 | if ($result === false) { 141 | throw new \OutOfBoundsException('Condition type not registered.'); 142 | } 143 | return $result; 144 | } 145 | 146 | /** 147 | * Determine generator type identifier based on generator instance 148 | * 149 | * @param GeneratorInterface $generator 150 | * @return string 151 | * @throws \OutOfBoundsException 152 | */ 153 | public function getGeneratorType(GeneratorInterface $generator) 154 | { 155 | $result = array_search(get_class($generator), $this->generatorTypes); 156 | if ($result === false) { 157 | throw new \OutOfBoundsException('Generator type not registered.'); 158 | } 159 | return $result; 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /src/lib/Hackathon/DerivedAttributes/ServiceInterface/ConditionInterface.php: -------------------------------------------------------------------------------- 1 | storeCode = (string) $storeCode; 18 | } 19 | public function __toString() 20 | { 21 | return $this->storeCode; 22 | } 23 | } -------------------------------------------------------------------------------- /src/lib/Hackathon/DerivedAttributes/StoreSet.php: -------------------------------------------------------------------------------- 1 | stores[(string) $storeCode] = new Store($storeCode); 29 | } 30 | } 31 | 32 | /** 33 | * Returns special instance representing all stores 34 | * 35 | * @return StoreSet 36 | */ 37 | public static function all() 38 | { 39 | static $all; 40 | if (! $all) { 41 | $all = new self([]); 42 | } 43 | return $all; 44 | } 45 | 46 | /** 47 | * @return string[] 48 | */ 49 | public function getStoreCodes() 50 | { 51 | return array_keys($this->stores); 52 | } 53 | 54 | /** 55 | * (PHP 5 >= 5.0.0)
56 | * Retrieve an external iterator 57 | * @link http://php.net/manual/en/iteratoraggregate.getiterator.php 58 | * @return Traversable An instance of an object implementing Iterator or 59 | * Traversable 60 | */ 61 | public function getIterator() 62 | { 63 | return new ArrayIterator($this->stores); 64 | } 65 | 66 | /** 67 | * (PHP 5 >= 5.1.0)
68 | * Count elements of an object 69 | * @link http://php.net/manual/en/countable.count.php 70 | * @return int The custom count as an integer. 71 | *

72 | *

73 | * The return value is cast to an integer. 74 | */ 75 | public function count() 76 | { 77 | return count($this->stores); 78 | } 79 | 80 | 81 | } -------------------------------------------------------------------------------- /src/lib/Hackathon/DerivedAttributes/Updater.php: -------------------------------------------------------------------------------- 1 | ruleRepository = $ruleRepository; 37 | $this->ruleLogger = $ruleLogger; 38 | } 39 | 40 | /** 41 | * Define if massUpdate() should run without actually saving data 42 | * 43 | * @param bool $isDryRun 44 | */ 45 | public function setDryRun($isDryRun) 46 | { 47 | $this->isDryRun = $isDryRun; 48 | } 49 | 50 | /** 51 | * Update entity based on loaded rule set 52 | * 53 | * @param EntityInterface $entity 54 | * @param Store $store 55 | */ 56 | public function update(EntityInterface $entity, Store $store) 57 | { 58 | $ruleSets = $this->getRuleSets(new StoreSet([$store])); 59 | $ruleSets->getRuleSet($store)->applyToEntity($entity, $this->ruleLogger); 60 | } 61 | 62 | /** 63 | * Update entity collection based on loaded rule set 64 | * 65 | * @param EntityIteratorInterface $iterator 66 | * @param EntityInterface $entityModel 67 | */ 68 | public function massUpdate(EntityIteratorInterface $iterator, EntityInterface $entityModel, StoreSet $stores) 69 | { 70 | $this->entityModel = $entityModel; 71 | $this->outputStart(); 72 | $this->getRuleSets($stores); 73 | foreach ($stores as $store) { 74 | $iterator->setStore($store); 75 | foreach ($iterator as $entityData) { 76 | $this->updateCurrentRow($entityData, $store); 77 | $this->outputIteratorStatus($iterator); 78 | } 79 | } 80 | $this->outputDone(); 81 | $this->entityModel = null; 82 | } 83 | 84 | /** 85 | * Update current row of entity collection 86 | * 87 | * @param array $data 88 | * @param Store $store 89 | * @internal param EntityIteratorInterface $iterator 90 | */ 91 | private function updateCurrentRow(array $data, Store $store) 92 | { 93 | $this->entityModel->setRawData($data); 94 | $this->update($this->entityModel, $store); 95 | if ($this->entityModel->isChanged() && ! $this->isDryRun) { 96 | $this->entityModel->saveAttributes(); 97 | } 98 | $this->entityModel->clearInstance(); 99 | } 100 | private function outputStart() 101 | { 102 | //TODO implement output/log 103 | } 104 | /** 105 | * @param EntityIteratorInterface $iterator 106 | */ 107 | private function outputIteratorStatus(EntityIteratorInterface $iterator) 108 | { 109 | //TODO implement output/log 110 | } 111 | private function outputDone() 112 | { 113 | //TODO implement output/log 114 | } 115 | 116 | /** 117 | * @return RuleSetsByStore 118 | */ 119 | private function getRuleSets(StoreSet $stores) 120 | { 121 | if ($this->ruleSets === null) { 122 | $this->ruleSets = $this->ruleRepository->findRuleSetsForStores($stores); 123 | } 124 | return $this->ruleSets; 125 | } 126 | } --------------------------------------------------------------------------------