├── .gitignore ├── .travis.yml ├── Api ├── ComponentInterface.php ├── ComponentListInterface.php ├── ComponentProcessorInterface.php ├── ConfigInterface.php ├── ConfiguratorAdapterInterface.php ├── FileComponentInterface.php └── LoggerInterface.php ├── CONTRIBUTING.md ├── Component ├── AdminRoles.php ├── AdminUsers.php ├── ApiIntegrations.php ├── AttributeSets.php ├── Attributes.php ├── Blocks.php ├── CatalogPriceRules.php ├── CatalogPriceRules │ └── CatalogPriceRulesProcessor.php ├── Categories.php ├── ComponentList.php ├── Config.php ├── CustomerAttributes.php ├── CustomerGroups.php ├── Customers.php ├── Media.php ├── OrderStatuses.php ├── Pages.php ├── Processor │ └── SqlSplitProcessor.php ├── Product │ ├── AttributeOption.php │ ├── Image.php │ └── Validator.php ├── ProductLinks.php ├── Products.php ├── ReviewRating.php ├── Rewrite.php ├── Rewrites.php ├── Sequence.php ├── ShippingTableRates.php ├── Sql.php ├── TaxRates.php ├── TaxRules.php ├── TieredPrices.php ├── Websites.php └── Widgets.php ├── Console └── Command │ ├── ListCommand.php │ └── RunCommand.php ├── Exception ├── ComponentException.php └── ConfiguratorAdapterException.php ├── LICENSE ├── Model ├── Logging.php └── Processor.php ├── README.md ├── Samples ├── Components │ ├── AdminRoles │ │ ├── List_of_all_avaliable_ResourceIds.txt │ │ ├── adminroles.yaml │ │ └── apiroles.yaml │ ├── AdminUsers │ │ ├── adminusers.yaml │ │ └── apiusers.yaml │ ├── ApiIntegrations │ │ └── apiintegrations.yaml │ ├── Attributes │ │ ├── attribute_sets.yaml │ │ └── attributes.yaml │ ├── Blocks │ │ ├── allstores.html │ │ ├── blocks.yaml │ │ ├── uk.html │ │ └── us.html │ ├── Categories │ │ └── categories.yaml │ ├── Configuration │ │ ├── base-website-config.yaml │ │ └── global.yaml │ ├── CustomerAttributes │ │ └── customer_attributes.yaml │ ├── CustomerGroups │ │ └── customergroups.yaml │ ├── Customers │ │ └── customers.csv │ ├── Media │ │ └── media.yaml │ ├── OrderStatuses │ │ └── orderstatuses.yaml │ ├── Pages │ │ ├── allstores.html │ │ ├── pages.yaml │ │ ├── uk.html │ │ └── us.html │ ├── ProductLinks │ │ ├── cross-sells.yaml │ │ ├── related.yaml │ │ └── up-sells.yaml │ ├── Products │ │ ├── configurable.csv │ │ └── simple.csv │ ├── ReviewRating │ │ └── reviewrating.yaml │ ├── Rewrites │ │ └── rewrites.csv │ ├── Sequence │ │ └── sequence.yaml │ ├── ShippingTableRates │ │ └── shippingtablerates.yaml │ ├── Sql │ │ ├── sitemap.sql │ │ └── sql.yaml │ ├── TaxRates │ │ ├── multi_example_taxrates.csv │ │ └── taxrates.csv │ ├── TaxRules │ │ ├── multi_example_taxrules.csv │ │ └── taxrules.csv │ ├── TieredPrices │ │ └── prices.csv │ ├── Widgets │ │ └── widgets.yaml │ ├── catalog_price_rules.yaml │ └── websites.yaml └── master.yaml ├── Test ├── Integration │ ├── Component │ │ └── CatalogPriceRulesTest.php │ ├── RewritesTest.php │ ├── ShippingTablesRatesTest.php │ ├── run-configurator.sh │ └── setup-magento.sh └── Unit │ ├── Component │ ├── CatalogPriceRules │ │ └── CatalogPriceRulesTest.php │ ├── CatalogPriceRulesTest.php │ ├── ConfigTest.php │ ├── CustomersTest.php │ ├── Product │ │ ├── AttributeOptionTest.php │ │ └── ImageTest.php │ ├── ProductsTest.php │ └── ReviewRatingTest.php │ ├── Processor │ └── SqlSplitProcessorTest.php │ ├── ProcessorTest.php │ └── _files │ └── sql │ └── test.sql ├── composer.json ├── docs ├── assets │ ├── css │ │ ├── font-awesome.min.css │ │ ├── ie8.css │ │ ├── ie9.css │ │ ├── images │ │ │ └── overlay.png │ │ └── main.css │ ├── fonts │ │ ├── FontAwesome.otf │ │ ├── fontawesome-webfont.eot │ │ ├── fontawesome-webfont.svg │ │ ├── fontawesome-webfont.ttf │ │ ├── fontawesome-webfont.woff │ │ └── fontawesome-webfont.woff2 │ ├── js │ │ ├── ie │ │ │ ├── PIE.htc │ │ │ ├── html5shiv.js │ │ │ └── respond.min.js │ │ ├── jquery.min.js │ │ ├── jquery.scrollex.min.js │ │ ├── jquery.scrolly.min.js │ │ ├── main.js │ │ ├── skel.min.js │ │ └── util.js │ └── sass │ │ ├── base │ │ ├── _page.scss │ │ └── _typography.scss │ │ ├── components │ │ ├── _box.scss │ │ ├── _button.scss │ │ ├── _features.scss │ │ ├── _form.scss │ │ ├── _icon.scss │ │ ├── _image.scss │ │ ├── _list.scss │ │ ├── _section.scss │ │ ├── _spotlight.scss │ │ ├── _statistics.scss │ │ └── _table.scss │ │ ├── ie8.scss │ │ ├── ie9.scss │ │ ├── layout │ │ ├── _footer.scss │ │ ├── _header.scss │ │ ├── _main.scss │ │ ├── _nav.scss │ │ └── _wrapper.scss │ │ ├── libs │ │ ├── _functions.scss │ │ ├── _mixins.scss │ │ ├── _skel.scss │ │ └── _vars.scss │ │ └── main.scss ├── images │ └── logo.svg └── index.html ├── etc ├── di.xml └── module.xml ├── phpunit_config.xml └── registration.php /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: trusty 2 | language: php 3 | php: 4 | - 7.3 5 | services: 6 | - mysql 7 | - elasticsearch 8 | sudo: required 9 | env: 10 | matrix: 11 | - TEST_SUITE=unit 12 | - TEST_SUITE=phpcs 13 | - TEST_SUITE=configurator 14 | MAGE_VERSION=2.4.1 15 | 16 | before_install: 17 | - phpenv config-rm xdebug.ini || true 18 | - composer self-update --1 19 | - echo "{\"http-basic\":{\"repo.magento.com\":{\"username\":\"${MAGENTO_USERNAME}\",\"password\":\"${MAGENTO_PASSWORD}\"}}}" > auth.json 20 | - sh -c "if [ '$TEST_SUITE' = 'phpcs' ]; then composer require magento/framework:^103.0.1; fi" 21 | - sh -c "if [ '$TEST_SUITE' = 'unit' ]; then composer require magento/framework:^103.0.1; fi" 22 | - sh -c "if [ '$TEST_SUITE' = 'unit' ]; then composer require magento/module-catalog; fi" 23 | - sh -c "if [ '$TEST_SUITE' = 'unit' ]; then composer require magento/zendframework1; fi" 24 | install: 25 | - sh -c "if [ '$TEST_SUITE' = 'phpcs' ]; then composer install --prefer-dist; fi" 26 | - sh -c "if [ '$TEST_SUITE' = 'unit' ]; then composer install --prefer-dist; fi" 27 | before_script: 28 | - sh -c "if [ '$TEST_SUITE' = 'configurator' ]; then ./Test/Integration/setup-magento.sh $MAGE_VERSION; fi" 29 | script: 30 | - sh -c "if [ '$TEST_SUITE' = 'phpcs' ]; then php vendor/bin/phpcs --standard=vendor/magento/magento-coding-standard/Magento2/ruleset.xml Model/ Console/ Test/ Component/ Api/ Exception/; fi" 31 | - sh -c "if [ '$TEST_SUITE' = 'phpcs' ]; then php vendor/bin/phpmd Model/,Console/,Test/,Component/,Api/,Exception/ text cleancode,codesize,controversial,design,naming,unusedcode; fi" 32 | - sh -c "if [ '$TEST_SUITE' = 'phpcs' ]; then php vendor/bin/phpcpd Model/ Console/ Test/ Component/ Api/ Exception/; fi" 33 | - sh -c "if [ '$TEST_SUITE' = 'unit' ]; then php vendor/bin/phpunit --coverage-clover build/logs/clover.xml Test/Unit/; fi" 34 | - sh -c "if [ '$TEST_SUITE' = 'configurator' ]; then ./Test/Integration/run-configurator.sh; fi" 35 | notifications: 36 | slack: 37 | rooms: 38 | secure: BWl/riMVP1ANjN0GyVdgBslI+eK5sz7g3s2fQtyyq7SPgWjrz5XpU0a5TcSQWa12wseY1mS09ln0UUMDYvpXucBhIqnB4LMYFHJyeKSbp232L+mQ6INM7xgBkwzsAtdQ8UqFSPWPbpUW+7Ah2DLEo2RSSfbvZIHD4ylEUJ0xDjOUzZicLdUbPSLO1NZV9JfHYHAHnsudRskoPVQ53dSSy3j5i9g9iDMTQAjzDAWRW8fEFWxfnyuggMA4Quwi+jUxOH38L6BsmZfje6K54dfBTcmTHHTxdinbOHiem9jSN/dDxp6EXSLTCxX4+qtd8ix/KRUTXj0BJbamO/HgzgYhSBjTwsJjwvnjCwnhNgsL08e8nxIEOmXrMxddMUJK9Qh8Rt5DzGTxSs2B+GRfuiFTAuwjI166k3wkxDnYrltxmvl4L8icoCsd7rVTNnwWNvPBdKkOB6tE1lGu98hIcGZ1lbPE14+oRFATpnanDiGffFgdxEbyDeQBRc4aqZYgFd2lR5SRRdy1p6ysQAJNDRGp/nIqzctLrrBYeGOcVk0WOpYmzUTVMIWiYrI36l4Hv+OJ+x+Aei7GJfBozVRu+/BPWx3RUNvt67c2flc/OPFhzX75yc+zJUsAy2yWN/qW6wJmzEc/wU2IIFL/EKIQl7dD02pdXXBxk0d9thy1GWK4exk= 39 | -------------------------------------------------------------------------------- /Api/ComponentInterface.php: -------------------------------------------------------------------------------- 1 | 5 | * @copyright 2017 CtiDigital 6 | */ 7 | 8 | namespace CtiDigital\Configurator\Api; 9 | 10 | /** 11 | * Interface ComponentProcessorInterface 12 | */ 13 | interface ComponentProcessorInterface 14 | { 15 | /** 16 | * @param array $data 17 | * 18 | * @return $this 19 | */ 20 | public function setData(array $data); 21 | 22 | /** 23 | * @param array $config 24 | * 25 | * @return $this 26 | */ 27 | public function setConfig(array $config); 28 | 29 | /** 30 | * Configure rules 31 | * 32 | * @return void 33 | */ 34 | public function process(); 35 | } 36 | -------------------------------------------------------------------------------- /Api/ConfigInterface.php: -------------------------------------------------------------------------------- 1 | " --components="" 47 | ``` -------------------------------------------------------------------------------- /Component/AdminRoles.php: -------------------------------------------------------------------------------- 1 | roleFactory = $roleFactory; 52 | $this->rulesFactory = $rulesFactory; 53 | $this->log = $log; 54 | } 55 | 56 | /** 57 | * @param $data 58 | */ 59 | public function execute($data = null) 60 | { 61 | if (isset($data['adminroles'])) { 62 | foreach ($data['adminroles'] as $role) { 63 | try { 64 | if (isset($role['name'])) { 65 | $this->createAdminRole($role['name'], $role['resources']); 66 | } 67 | } catch (ComponentException $e) { 68 | $this->log->logError($e->getMessage()); 69 | } 70 | } 71 | } 72 | } 73 | 74 | /** 75 | * Create Admin user roles, or update them if they exist 76 | * 77 | * @param string $roleName 78 | * @param array $resources 79 | */ 80 | private function createAdminRole($roleName, $resources) 81 | { 82 | $role = $this->roleFactory->create(); 83 | $roleCount = $role->getCollection()->addFieldToFilter('role_name', $roleName)->getSize(); 84 | 85 | // Create or get existing user 86 | if ($roleCount > 0) { 87 | $this->log->logInfo( 88 | sprintf('Admin Role "%s" creation skipped: Already exists in database', $roleName) 89 | ); 90 | 91 | //Get exisiting Role 92 | $role = $role->getCollection()->addFieldToFilter('role_name', $roleName)->getFirstItem(); 93 | $this->setResourceIds($role, $resources); 94 | 95 | return; 96 | } 97 | 98 | $this->log->logInfo( 99 | sprintf('Admin Role "%s" being created', $roleName) 100 | ); 101 | 102 | $role->setRoleName($roleName) 103 | ->setParentId(0) 104 | ->setRoleType(RoleGroup::ROLE_TYPE) 105 | ->setUserType(UserContextInterface::USER_TYPE_ADMIN) 106 | ->setSortOrder(0) 107 | ->save(); 108 | 109 | $this->setResourceIds($role, $resources); 110 | } 111 | 112 | /** 113 | * Set ResourceIDs the Admin Role will have access to 114 | * 115 | * @param role 116 | * @param array|null $resources 117 | */ 118 | private function setResourceIds($role, ?array $resources) 119 | { 120 | $roleName = $role->getRoleName(); 121 | 122 | if ($resources !== null) { 123 | $this->log->logInfo( 124 | sprintf('Admin Role "%s" resources updating', $roleName) 125 | ); 126 | 127 | $this->rulesFactory->create()->setRoleId($role->getId())->setResources($resources)->saveRel(); 128 | return; 129 | } 130 | 131 | $this->log->logError( 132 | sprintf('Admin Role "%s" Resources are empty, please check your yaml file', $roleName) 133 | ); 134 | } 135 | 136 | /** 137 | * @return string 138 | */ 139 | public function getAlias() 140 | { 141 | return $this->alias; 142 | } 143 | 144 | /** 145 | * @return string 146 | */ 147 | public function getDescription() 148 | { 149 | return $this->description; 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /Component/CatalogPriceRules.php: -------------------------------------------------------------------------------- 1 | 5 | * @copyright 2017 CtiDigital 6 | */ 7 | 8 | namespace CtiDigital\Configurator\Component; 9 | 10 | use CtiDigital\Configurator\Api\ComponentInterface; 11 | use CtiDigital\Configurator\Api\LoggerInterface; 12 | use CtiDigital\Configurator\Component\CatalogPriceRules\CatalogPriceRulesProcessor; 13 | use Magento\CatalogRule\Api\Data\RuleInterfaceFactory; 14 | 15 | class CatalogPriceRules implements ComponentInterface 16 | { 17 | /** 18 | * @var string 19 | */ 20 | protected $alias = 'catalog_price_rules'; 21 | 22 | /** 23 | * @var string 24 | */ 25 | protected $name = 'Catalog Price Rules'; 26 | 27 | /** 28 | * @var string 29 | */ 30 | protected $description = 'Component to manage Catalog Price Rules'; 31 | 32 | /** 33 | * @var CatalogPriceRulesProcessor 34 | */ 35 | private $processor; 36 | 37 | /** 38 | * @var LoggerInterface 39 | */ 40 | private $log; 41 | 42 | /** 43 | * CatalogPriceRules constructor. 44 | * 45 | * @param LoggerInterface $log 46 | * @param CatalogPriceRulesProcessor $processor 47 | */ 48 | public function __construct( 49 | CatalogPriceRulesProcessor $processor, 50 | LoggerInterface $log 51 | ) { 52 | $this->processor = $processor; 53 | $this->log = $log; 54 | } 55 | 56 | /** 57 | * This method should be used to process the data and populate the Magento Database. 58 | * 59 | * @param $data 60 | * 61 | * @return void 62 | */ 63 | public function execute($data = null) 64 | { 65 | $rules = $data['rules'] ?: []; 66 | $config = $data['config'] ?: []; 67 | 68 | $this->processor->setData($rules) 69 | ->setConfig($config) 70 | ->process(); 71 | } 72 | 73 | /** 74 | * @return string 75 | */ 76 | public function getAlias() 77 | { 78 | return $this->alias; 79 | } 80 | 81 | /** 82 | * @return string 83 | */ 84 | public function getDescription() 85 | { 86 | return $this->description; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /Component/ComponentList.php: -------------------------------------------------------------------------------- 1 | components = $components; 18 | } 19 | 20 | /** 21 | * @inheritDoc 22 | */ 23 | public function getComponent($componentAlias) 24 | { 25 | if (array_key_exists($componentAlias, $this->components) === true) { 26 | return $this->components[$componentAlias]; 27 | } 28 | return false; 29 | } 30 | 31 | /** 32 | * @inheritDoc 33 | */ 34 | public function getAllComponents() 35 | { 36 | return $this->components; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Component/CustomerAttributes.php: -------------------------------------------------------------------------------- 1 | 'is_visible', 34 | 'position' => 'sort_order', 35 | 'system' => 'is_system' 36 | ]; 37 | 38 | /** 39 | * @var CustomerSetupFactory 40 | */ 41 | protected $customerSetup; 42 | 43 | /** 44 | * @var Attribute 45 | */ 46 | protected $attributeResource; 47 | 48 | /** 49 | * @var LoggerInterface 50 | */ 51 | private $log; 52 | 53 | /** 54 | * @var array 55 | */ 56 | protected $defaultForms = [ 57 | 'values' => [ 58 | 'customer_account_create', 59 | 'customer_account_edit', 60 | 'adminhtml_checkout', 61 | 'adminhtml_customer' 62 | ] 63 | ]; 64 | 65 | /** 66 | * CustomerAttributes constructor. 67 | * @param EavSetup $eavSetup 68 | * @param AttributeRepository $attributeRepository 69 | * @param CustomerSetupFactory $customerSetupFactory 70 | * @param Attribute $attributeResource 71 | * @param LoggerInterface $log 72 | */ 73 | public function __construct( 74 | EavSetup $eavSetup, 75 | AttributeRepository $attributeRepository, 76 | CustomerSetupFactory $customerSetupFactory, 77 | Attribute $attributeResource, 78 | LoggerInterface $log, 79 | \Magento\Eav\Model\ResourceModel\Entity\Attribute\Option\CollectionFactory $attrOptionCollectionFactory, 80 | \Magento\Eav\Model\Config $eavConfig 81 | ) { 82 | parent::__construct($eavSetup, $attributeRepository, $log, $attrOptionCollectionFactory, $eavConfig); 83 | $this->attributeConfigMap = array_merge($this->attributeConfigMap, $this->customerConfigMap); 84 | $this->customerSetup = $customerSetupFactory; 85 | $this->attributeResource = $attributeResource; 86 | $this->log = $log; 87 | } 88 | 89 | /** 90 | * @param array $attributeConfigurationData 91 | */ 92 | public function execute($attributeConfigurationData = null) 93 | { 94 | try { 95 | foreach ($attributeConfigurationData['customer_attributes'] as $attributeCode => $attributeConfiguration) { 96 | $this->processAttribute($attributeCode, $attributeConfiguration); 97 | $this->addAdditionalValues($attributeCode, $attributeConfiguration); 98 | } 99 | } catch (ComponentException $e) { 100 | $this->log->logError($e->getMessage()); 101 | } 102 | } 103 | 104 | /** 105 | * Adds necessary additional values to the attribute. Without these, values can't be saved 106 | * to the attribute and it won't appear in any forms. 107 | * 108 | * @param $attributeCode 109 | * @param $attributeConfiguration 110 | */ 111 | protected function addAdditionalValues($attributeCode, $attributeConfiguration) 112 | { 113 | if ($this->attributeExists) { 114 | return; 115 | } 116 | if (!isset($attributeConfiguration['used_in_forms']) || 117 | !isset($attributeConfiguration['used_in_forms']['values'])) { 118 | $attributeConfiguration['used_in_forms'] = $this->defaultForms; 119 | } 120 | 121 | /** @var CustomerSetup $customerSetup */ 122 | $customerSetup = $this->customerSetup->create(); 123 | try { 124 | $attribute = $customerSetup->getEavConfig() 125 | ->getAttribute($this->entityTypeId, $attributeCode) 126 | ->addData([ 127 | 'attribute_set_id' => self::DEFAULT_ATTRIBUTE_SET_ID, 128 | 'attribute_group_id' => self::DEFAULT_ATTRIBUTE_GROUP_ID, 129 | 'used_in_forms' => $attributeConfiguration['used_in_forms']['values'] 130 | ]); 131 | $this->attributeResource->save($attribute); 132 | } catch (LocalizedException $e) { 133 | $this->log->logError(sprintf( 134 | 'Error applying additional values to %s: %s', 135 | $attributeCode, 136 | $e->getMessage() 137 | )); 138 | } catch (\Exception $e) { 139 | $this->log->logError(sprintf( 140 | 'Error saving additional values for %s: %s', 141 | $attributeCode, 142 | $e->getMessage() 143 | )); 144 | } 145 | } 146 | 147 | /** 148 | * @return string 149 | */ 150 | public function getAlias() 151 | { 152 | return $this->alias; 153 | } 154 | 155 | /** 156 | * @return string 157 | */ 158 | public function getDescription() 159 | { 160 | return $this->description; 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /Component/CustomerGroups.php: -------------------------------------------------------------------------------- 1 | groupFactory = $groupFactory; 47 | $this->classModelFactory = $classModelFactory; 48 | $this->log = $log; 49 | } 50 | 51 | /** 52 | * @param $data 53 | */ 54 | public function execute($data = null) 55 | { 56 | foreach ($data['customergroups'] as $taxClass) { 57 | $taxClassName = $taxClass['taxclass']; 58 | $taxClassId = $this->getTaxClassIdFromName($taxClassName); 59 | 60 | if ($taxClassId) { 61 | foreach ($taxClass['groups'] as $group) { 62 | try { 63 | $this->validateGroupName($group); 64 | $this->createCustomerGroup($group['name'], $taxClassId); 65 | } catch (ComponentException $e) { 66 | $this->log->logError($e->getMessage()); 67 | } 68 | } 69 | } 70 | } 71 | } 72 | 73 | /** 74 | * Create Customer Groups from YAML file 75 | * 76 | * @param string $groupName 77 | * @param int $taxClassId 78 | */ 79 | private function createCustomerGroup($groupName, $taxClassId) 80 | { 81 | $customerGroup = $this->groupFactory->create(); 82 | $groupCount = $customerGroup->getCollection()->addFieldToFilter('customer_group_code', $groupName)->getSize(); 83 | 84 | if ($groupCount > 0) { 85 | $this->log->logInfo( 86 | sprintf('Customer Group "%s" already exists, creation skipped', $groupName) 87 | ); 88 | 89 | return; 90 | } 91 | 92 | $customerGroup 93 | ->setCustomerGroupCode($groupName) 94 | ->setTaxClassId($taxClassId) 95 | ->save(); 96 | 97 | $this->log->logInfo( 98 | sprintf('Customer Group "%s" created', $groupName) 99 | ); 100 | } 101 | 102 | /** 103 | * perform customer group name validation 104 | * 105 | * @param array $group 106 | * @return null 107 | * @throw ComponentException 108 | */ 109 | private function validateGroupName(array $group) 110 | { 111 | if (!isset($group['name'])) { 112 | throw new ComponentException(__('The customer group name is mandatory')); 113 | } 114 | 115 | if (strlen($group['name'])>32) { 116 | throw new ComponentException( 117 | __('The customer group name "%1" is too long (maximum length is 32 characters)', $group['name']) 118 | ); 119 | } 120 | } 121 | 122 | /** 123 | * Return tax class id when given name 124 | * 125 | * @param string $taxClassName 126 | * @return int|null 127 | */ 128 | private function getTaxClassIdFromName($taxClassName) 129 | { 130 | $taxClassModel = $this->classModelFactory->create(); 131 | $taxClass = $taxClassModel->getCollection()->addFieldToFilter('class_name', $taxClassName)->getFirstItem(); 132 | $taxclassId = $taxClass->getId(); 133 | 134 | if (!$taxclassId) { 135 | $this->log->logError( 136 | sprintf('There is no Tax class with the name "%s" in this database', $taxClassName) 137 | ); 138 | 139 | return null; 140 | } 141 | 142 | return $taxclassId; 143 | } 144 | 145 | /** 146 | * @return string 147 | */ 148 | public function getAlias() 149 | { 150 | return $this->alias; 151 | } 152 | 153 | /** 154 | * @return string 155 | */ 156 | public function getDescription() 157 | { 158 | return $this->description; 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /Component/Media.php: -------------------------------------------------------------------------------- 1 | directoryList = $directoryList; 33 | $this->log = $log; 34 | } 35 | 36 | /** 37 | * @param $data 38 | */ 39 | public function execute($data = null) 40 | { 41 | try { 42 | // Load root media path 43 | $mediaPath = $this->directoryList->getPath(DirectoryList::MEDIA); 44 | 45 | // Loop through top level nodes 46 | foreach ($data as $name => $childNode) { 47 | // Create a child folder or file item 48 | $this->createChildFolderFileItem($mediaPath, $name, $childNode); 49 | } 50 | } catch (ComponentException $e) { 51 | $this->log->logError($e->getMessage()); 52 | } 53 | } 54 | 55 | private function createChildFolderFileItem($currentPath, $name, $node, $nest = 0) 56 | { 57 | try { 58 | // Update the current path to the new path 59 | $newPath = $currentPath . DIRECTORY_SEPARATOR . $name; 60 | 61 | // Check if a folder exists and create if required 62 | $this->checkAndCreateFolder($newPath, $name, $nest); 63 | 64 | // If the node does not have a numeric index 65 | if (!is_numeric($name)) { 66 | $nest++; 67 | 68 | // Loop through the child node 69 | foreach ($node as $childName => $childNode) { 70 | // Create a child folder 71 | $this->createChildFolderFileItem($newPath, $childName, $childNode, $nest); 72 | } 73 | 74 | return; 75 | } 76 | 77 | if (!isset($node['name'])) { 78 | throw new ComponentException(sprintf('No name set for a child item in %s', $currentPath)); 79 | } 80 | 81 | if (!isset($node['location'])) { 82 | throw new ComponentException(sprintf('No location set for a child item in %s', $currentPath)); 83 | } 84 | 85 | $newPath = $currentPath . DIRECTORY_SEPARATOR . $node['name']; 86 | 87 | // phpcs:ignore Magento2.Functions.DiscouragedFunction 88 | if (file_exists($newPath)) { 89 | $this->log->logComment(sprintf('File already exists: %s', $newPath), $nest); 90 | return; 91 | } 92 | 93 | // Download the file and place it in the price place 94 | $this->downloadAndSetFile($newPath, $node, $nest); 95 | } catch (ComponentException $e) { 96 | $this->log->logError($e->getMessage(), $nest); 97 | } 98 | } 99 | 100 | /** 101 | * @param $newPath 102 | * @param $name 103 | * @param $nest 104 | */ 105 | private function checkAndCreateFolder($newPath, $name, $nest) 106 | { 107 | // Check if the file/folder exists 108 | // phpcs:ignore Magento2.Functions.DiscouragedFunction 109 | if (!file_exists($newPath)) { 110 | // If the node does not have a numeric index 111 | if (!is_numeric($name)) { 112 | // Then it is a directory so create it 113 | // phpcs:ignore Magento2.Functions.DiscouragedFunction 114 | mkdir($newPath, $this::FULL_ACCESS, true); 115 | $this->log->logInfo(sprintf('Created new media directory %s', $name), $nest); 116 | } 117 | 118 | return; 119 | } 120 | 121 | // If the node does not have a numeric index 122 | if (!is_numeric($name)) { 123 | $this->log->logComment(sprintf('Directory Exists %s', $name), $nest); 124 | } 125 | } 126 | 127 | /** 128 | * @param $path 129 | * @param $node 130 | * @param $nest 131 | */ 132 | private function downloadAndSetFile($path, $node, $nest) 133 | { 134 | $this->log->logInfo(sprintf('Downloading contents of file from %s', $node['location']), $nest); 135 | // phpcs:ignore Magento2.Functions.DiscouragedFunction 136 | $fileContents = file_get_contents($node['location']); 137 | // phpcs:ignore Magento2.Functions.DiscouragedFunction 138 | file_put_contents($path, $fileContents); 139 | $this->log->logInfo(sprintf('Created new file: %s', $path), $nest); 140 | } 141 | 142 | /** 143 | * @return string 144 | */ 145 | public function getAlias() 146 | { 147 | return $this->alias; 148 | } 149 | 150 | /** 151 | * @return string 152 | */ 153 | public function getDescription() 154 | { 155 | return $this->description; 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /Component/OrderStatuses.php: -------------------------------------------------------------------------------- 1 | createOrderStatuses($statusSet); 67 | } catch (ComponentException $e) { 68 | $this->log->logError($e->getMessage()); 69 | } 70 | } 71 | } 72 | } 73 | 74 | /** 75 | * @param $statusSet 76 | * @throws AlreadyExistsException 77 | */ 78 | public function createOrderStatuses($statusSet) 79 | { 80 | foreach ($statusSet['statuses'] as $statusData) { 81 | /** @var StatusResource $statusResource */ 82 | $statusResource = $this->statusResource->create(); 83 | /** @var Status $status */ 84 | $status = $this->statusFactory->create(); 85 | $status->setData([ 86 | 'status' => $statusData['code'], 87 | 'label' => $statusData['name'], 88 | ]); 89 | 90 | try { 91 | $statusResource->save($status); 92 | } catch (ComponentException $e) { 93 | $this->log->logError($e->getMessage()); 94 | } 95 | 96 | $status->assignState($statusSet['state'], false, true); 97 | 98 | $this->log->logInfo( 99 | sprintf('Order status %s created', $statusData['name']) 100 | ); 101 | } 102 | } 103 | 104 | public function getAlias(): string 105 | { 106 | return $this->alias; 107 | } 108 | 109 | public function getDescription(): string 110 | { 111 | return $this->description; 112 | } 113 | 114 | } 115 | -------------------------------------------------------------------------------- /Component/Processor/SqlSplitProcessor.php: -------------------------------------------------------------------------------- 1 | 5 | * @copyright 2017 CtiDigital 6 | */ 7 | 8 | namespace CtiDigital\Configurator\Component\Processor; 9 | 10 | use CtiDigital\Configurator\Api\LoggerInterface; 11 | use Magento\Framework\App\ResourceConnection; 12 | use Magento\Framework\DB\Adapter\AdapterInterface; 13 | 14 | class SqlSplitProcessor 15 | { 16 | /** 17 | * @var LoggerInterface 18 | */ 19 | private $log; 20 | 21 | /** 22 | * @var ResourceConnection 23 | */ 24 | private $resource; 25 | 26 | /** 27 | * @var AdapterInterface 28 | */ 29 | private $connection; 30 | /** 31 | * @param LoggerInterface $log 32 | * @param ResourceConnection $resource 33 | */ 34 | public function __construct( 35 | LoggerInterface $log, 36 | ResourceConnection $resource 37 | ) { 38 | $this->log = $log; 39 | $this->resource = $resource; 40 | $this->connection = $resource->getConnection(); 41 | } 42 | 43 | /** 44 | * @param string $name 45 | * @param string $filePath 46 | * 47 | * return void 48 | */ 49 | public function process($name, $filePath) 50 | { 51 | $this->log->logInfo("- Processing file '$name'"); 52 | 53 | $queries = $this->extractQueriesFromFile($filePath); 54 | 55 | $totalSqlCnt = count($queries); 56 | $cnt = 1; 57 | 58 | if ($totalSqlCnt === 0) { 59 | $this->log->logInfo('No queries has been found in file.'); 60 | 61 | return; 62 | } 63 | 64 | $this->connection->beginTransaction(); 65 | 66 | try { 67 | foreach ($queries as $query) { 68 | $this->log->logComment($query, 1); 69 | $this->connection->query($query); 70 | $this->log->logInfo("[{$cnt}/$totalSqlCnt] queries executed.", 1); 71 | $cnt++; 72 | } 73 | 74 | $this->connection->commit(); 75 | } catch (\Exception $ex) { 76 | $this->log->logError($ex->getMessage()); 77 | $this->connection->rollBack(); 78 | } 79 | } 80 | 81 | /** 82 | * Split file content string into separate queries, allowing for 83 | * multi-line queries using preg_match 84 | * 85 | * @param string $filePath 86 | * @param string $delimiter 87 | * 88 | * @return array 89 | */ 90 | private function extractQueriesFromFile($filePath, $delimiter = ';') 91 | { 92 | $obBaseLevel = ob_get_level(); 93 | $queries = []; 94 | // phpcs:ignore Magento2.Functions.DiscouragedFunction 95 | $file = fopen($filePath, 'r'); 96 | if (is_resource($file) === true) { 97 | $query = []; 98 | while (feof($file) === false) { 99 | // phpcs:ignore Magento2.Functions.DiscouragedFunction 100 | $query[] = fgets($file); 101 | 102 | if (preg_match('~' . preg_quote($delimiter, '~') . '\s*$~iS', end($query)) === 1) { 103 | $query = trim(implode('', $query)); 104 | 105 | $queries[] = $query; 106 | 107 | while (ob_get_level() > $obBaseLevel) { 108 | ob_end_flush(); 109 | } 110 | flush(); 111 | } 112 | 113 | if (is_string($query) === true) { 114 | $query = []; 115 | } 116 | } 117 | } 118 | // phpcs:ignore Magento2.Functions.DiscouragedFunction 119 | fclose($file); 120 | return $queries; 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /Component/Rewrite.php: -------------------------------------------------------------------------------- 1 | requestPath = $requestPath; 25 | $this->targetPath = $targetPath; 26 | $this->redirectType = $redirectType; 27 | $this->storeId = $storeId; 28 | $this->description = $description; 29 | } 30 | 31 | /** 32 | * @return string 33 | */ 34 | public function getRequestPath() 35 | { 36 | return $this->requestPath; 37 | } 38 | 39 | /** 40 | * @param string $requestPath 41 | */ 42 | public function setRequestPath($requestPath) 43 | { 44 | $this->requestPath = $requestPath; 45 | } 46 | 47 | /** 48 | * @return string 49 | */ 50 | public function getTargetPath() 51 | { 52 | return $this->targetPath; 53 | } 54 | 55 | /** 56 | * @param string $targetPath 57 | */ 58 | public function setTargetPath($targetPath) 59 | { 60 | $this->targetPath = $targetPath; 61 | } 62 | 63 | /** 64 | * @return string 65 | */ 66 | public function getRedirectType() 67 | { 68 | return $this->redirectType; 69 | } 70 | 71 | /** 72 | * @param string $redirectType 73 | */ 74 | public function setRedirectType($redirectType) 75 | { 76 | $this->redirectType = $redirectType; 77 | } 78 | 79 | /** 80 | * @return string 81 | */ 82 | public function getStoreId() 83 | { 84 | return $this->storeId; 85 | } 86 | 87 | /** 88 | * @param string $storeId 89 | */ 90 | public function setStoreId($storeId) 91 | { 92 | $this->storeId = $storeId; 93 | } 94 | 95 | /** 96 | * @return string 97 | */ 98 | public function getDescription() 99 | { 100 | return $this->description; 101 | } 102 | 103 | /** 104 | * @param string $description 105 | */ 106 | public function setDescription($description) 107 | { 108 | $this->description = $description; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /Component/Sequence.php: -------------------------------------------------------------------------------- 1 | sequenceBuilder = $sequenceBuilder; 48 | $this->entityPool = $entityPool; 49 | $this->sequenceConfig = $sequenceConfig; 50 | $this->storeRepository = $repository; 51 | $this->logger = $logger; 52 | } 53 | 54 | public function execute($data) 55 | { 56 | if (!isset($data['stores'])) { 57 | throw new ComponentException("No stores found."); 58 | } 59 | 60 | foreach ($data['stores'] as $code => $overrides) { 61 | try { 62 | $this->logger->logInfo(__("Starting creating sequence tables for %1", $code)); 63 | $store = $this->storeRepository->get($code); 64 | $this->newSequenceTable($store, $overrides); 65 | $this->logger->logInfo(__("Finished creating sequence tables for %1", $code)); 66 | // todo handle existing sequence tables 67 | } catch (\Exception $exception) { 68 | $this->logger->logError($exception->getMessage()); 69 | } 70 | } 71 | } 72 | 73 | public function getAlias() 74 | { 75 | return $this->alias; 76 | } 77 | 78 | public function getDescription() 79 | { 80 | return $this->description; 81 | } 82 | 83 | protected function newSequenceTable($store, $overrides) 84 | { 85 | $configKeys = ['suffix', 'startValue', 'step', 'warningValue', 'maxValue']; 86 | $configValues = []; 87 | foreach ($configKeys as $key) { 88 | $configValues[$key] = $this->sequenceConfig->get($key); 89 | if (isset($overrides[$key])) { 90 | $configValues[$key] = $overrides[$key]; 91 | } 92 | } 93 | 94 | // Prefix Value 95 | $configValues['prefix'] = $store->getId(); 96 | if (isset($overrides['prefix'])) { 97 | $configValues['prefix'] = $overrides['prefix']; 98 | } 99 | 100 | foreach ($this->entityPool->getEntities() as $entityType) { 101 | try { 102 | $this->logger->logComment(__( 103 | 'Store: %1 '. 104 | 'Prefix: %2, '. 105 | 'Suffix: %3, '. 106 | 'Start Value: %4, '. 107 | 'Step: %5, '. 108 | 'Warning Value: %6, '. 109 | 'Max Value: %7, '. 110 | 'Entity Type: %8', 111 | $store->getCode(), 112 | $configValues['prefix'], 113 | $configValues['suffix'], 114 | $configValues['startValue'], 115 | $configValues['step'], 116 | $configValues['warningValue'], 117 | $configValues['maxValue'], 118 | $entityType 119 | ), 1); 120 | $this->sequenceBuilder->setPrefix($configValues['prefix']) 121 | ->setSuffix($configValues['suffix']) 122 | ->setStartValue($configValues['startValue']) 123 | ->setStoreId($store->getId()) 124 | ->setStep($configValues['step']) 125 | ->setWarningValue($configValues['warningValue']) 126 | ->setMaxValue($configValues['maxValue']) 127 | ->setEntityType($entityType) 128 | ->create(); 129 | $this->logger->logInfo(__("Sequence table created for %1", $entityType), 1); 130 | } catch (\Exception $exception) { 131 | $this->logger->logError($exception->getMessage()); 132 | } 133 | } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /Component/ShippingTableRates.php: -------------------------------------------------------------------------------- 1 | tablerateFactory = $tablerateFactory; 53 | $this->websiteFactory = $websiteFactory; 54 | $this->regionFactory = $regionFactory; 55 | $this->log = $log; 56 | } 57 | 58 | /** 59 | * This method should be used to process the data and populate the Magento Database. 60 | * 61 | * @param array $data 62 | * @return void 63 | */ 64 | public function execute($data = null) 65 | { 66 | /** @var Tablerate $tablerateModel */ 67 | $tablerateModel = $this->tablerateFactory->create(); 68 | 69 | $shippingRateCount = 1; 70 | foreach ($data as $website => $shippingRates) { 71 | 72 | /** @var Website $websiteModel */ 73 | $websiteModel = $this->websiteFactory->create(); 74 | $websiteModel->load($website, 'code'); 75 | $websiteId = $websiteModel->getId(); 76 | 77 | if (!$websiteId) { 78 | $this->log->logError(sprintf("No website exists for code '%s'. Skipping.", $website)); 79 | return; 80 | } 81 | 82 | foreach ($shippingRates as $shippingRate) { 83 | $this->createNewShippingTableRate( 84 | $shippingRate, 85 | $websiteId, 86 | $shippingRateCount, 87 | $website, 88 | $tablerateModel 89 | ); 90 | $shippingRateCount++; 91 | } 92 | } 93 | } 94 | 95 | /** 96 | * @param $shippingRate 97 | * @param $websiteId 98 | * @param $shippingRateCount 99 | * @param $website 100 | * @param Tablerate $tablerateModel 101 | */ 102 | private function createNewShippingTableRate( 103 | $shippingRate, 104 | $websiteId, 105 | $shippingRateCount, 106 | $website, 107 | Tablerate $tablerateModel 108 | ) { 109 | $columns = [ 110 | 'website_id', 111 | 'dest_region_id', 112 | 'dest_country_id', 113 | 'dest_zip', 114 | 'condition_name', 115 | 'condition_value', 116 | 'price', 117 | 'cost' 118 | ]; 119 | 120 | /** @var Region */ 121 | $regionModel = $this->regionFactory->create(); 122 | $regionModel = $regionModel->loadByCode($shippingRate['dest_region_code'], $shippingRate['dest_country_id']); 123 | $regionId = $regionModel->getId(); 124 | if ($regionId === null) { 125 | $regionId = 0; 126 | } 127 | 128 | $this->removeYamlKeysFromDatabaseInsert($shippingRate); 129 | 130 | $shippingRate = array_merge( 131 | [ 132 | 'website_id' => $websiteId, 133 | 'dest_region_id' => $regionId 134 | ], 135 | $shippingRate 136 | ); 137 | 138 | $this->log->logInfo( 139 | sprintf( 140 | "Shipping rate #%s for website %s being created", 141 | $shippingRateCount, 142 | $website 143 | ) 144 | ); 145 | $tablerateModel->getConnection() 146 | ->insertOnDuplicate($tablerateModel->getMainTable(), [$shippingRate], $columns); 147 | } 148 | /** 149 | * @param array $shippingRate 150 | */ 151 | private function removeYamlKeysFromDatabaseInsert(array &$shippingRate) 152 | { 153 | unset($shippingRate['dest_region_code']); 154 | unset($shippingRate['website_code']); 155 | } 156 | 157 | /** 158 | * @return string 159 | */ 160 | public function getAlias() 161 | { 162 | return $this->alias; 163 | } 164 | 165 | /** 166 | * @return string 167 | */ 168 | public function getDescription() 169 | { 170 | return $this->description; 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /Component/Sql.php: -------------------------------------------------------------------------------- 1 | 5 | * @copyright 2017 CtiDigital 6 | */ 7 | 8 | namespace CtiDigital\Configurator\Component; 9 | 10 | use CtiDigital\Configurator\Api\ComponentInterface; 11 | use CtiDigital\Configurator\Api\LoggerInterface; 12 | use CtiDigital\Configurator\Component\Processor\SqlSplitProcessor; 13 | 14 | /** 15 | * Class Sql - Runs raw SQL queries - generally a fallback for when a configurator component is not available. 16 | */ 17 | class Sql implements ComponentInterface 18 | { 19 | /** 20 | * @var string 21 | */ 22 | protected $alias = 'sql'; 23 | 24 | /** 25 | * @var string 26 | */ 27 | protected $name = 'Custom Sql'; 28 | 29 | /** 30 | * @var string 31 | */ 32 | protected $description = 'Component for an execution of custom queries'; 33 | 34 | /** 35 | * @var SqlSplitProcessor 36 | */ 37 | private $processor; 38 | 39 | /** 40 | * @var LoggerInterface 41 | */ 42 | private $log; 43 | 44 | /** 45 | * Sql constructor. 46 | * @param SqlSplitProcessor $processor 47 | * @param LoggerInterface $log 48 | */ 49 | public function __construct( 50 | SqlSplitProcessor $processor, 51 | LoggerInterface $log 52 | ) { 53 | $this->processor = $processor; 54 | $this->log = $log; 55 | } 56 | 57 | /** 58 | * This method should be used to process the data and populate the Magento Database. 59 | * 60 | * @param mixed $data 61 | * 62 | * @return void 63 | */ 64 | public function execute($data = null) 65 | { 66 | if (!isset($data['sql'])) { 67 | return; 68 | } 69 | 70 | $this->log->logInfo('Beginning of custom queries configuration:'); 71 | foreach ($data['sql'] as $name => $sqlFile) { 72 | $path = BP . '/' . $sqlFile; 73 | // phpcs:ignore Magento2.Functions.DiscouragedFunction 74 | if (false === file_exists($path)) { 75 | $this->log->logError("{$path} does not exist. Skipping."); 76 | continue; 77 | } 78 | $this->processor->process($name, $path); 79 | } 80 | } 81 | 82 | /** 83 | * @return string 84 | */ 85 | public function getAlias() 86 | { 87 | return $this->alias; 88 | } 89 | 90 | /** 91 | * @return string 92 | */ 93 | public function getDescription() 94 | { 95 | return $this->description; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /Component/TaxRates.php: -------------------------------------------------------------------------------- 1 | csvImportHandler = $csvImportHandler; 37 | $this->log = $log; 38 | } 39 | 40 | /** 41 | * @param null $data 42 | * @throws LocalizedException 43 | */ 44 | public function execute($data = null) 45 | { 46 | try { 47 | // Sort data into order importExport requires 48 | $sortedData = $this->getSortedData($data); 49 | 50 | // Generate sorted csv file 51 | $tmpFile = $this->getTmpFile($sortedData); 52 | 53 | // Pass the temporary file name to the import handler 54 | $this->csvImportHandler->importFromCsvFile(['tmp_name' => $tmpFile]); 55 | 56 | // Remove the temporary file 57 | unlink($tmpFile); 58 | 59 | // We don't know how many were successfully imported 60 | // so we can't log the number of records imported, but we can log that the import was successful 61 | // we could diff the count of the state before and after but it would be expensive 62 | $this->log->logInfo( 63 | sprintf('Tax rates finished importing, check the rates in the admin panel.') 64 | ); 65 | } catch (ComponentException $e) { 66 | $this->log->logError($e->getMessage()); 67 | } 68 | } 69 | 70 | /** 71 | * @return string 72 | */ 73 | public function getAlias() 74 | { 75 | return $this->alias; 76 | } 77 | 78 | /** 79 | * @return string 80 | */ 81 | public function getDescription() 82 | { 83 | return $this->description; 84 | } 85 | 86 | /** 87 | * @param array $data 88 | * @return array 89 | */ 90 | protected function getSortedData(array $data): array 91 | { 92 | $sortedData = []; 93 | 94 | foreach ($data as $index => $rate) { 95 | if ($index === 0) { 96 | $sortedData[] = $rate; 97 | continue; // Skip the header row 98 | } 99 | 100 | $relativeData = array_combine($data[0], $rate); 101 | 102 | // Reorder the data to match the expected format 103 | // Why this is a requirement is not clear 104 | $rateData = [ 105 | $relativeData['code'], 106 | $relativeData['tax_country_id'], 107 | $relativeData['tax_region_id'], 108 | $relativeData['tax_postcode'], 109 | $relativeData['rate'], 110 | $relativeData['zip_is_range'], 111 | $relativeData['zip_from'], 112 | $relativeData['zip_to'] 113 | ]; 114 | $sortedData[] = $rateData; 115 | } 116 | return $sortedData; 117 | } 118 | 119 | /** 120 | * @param array $sortedData 121 | * @return string 122 | */ 123 | protected function getTmpFile(array $sortedData): string 124 | { 125 | // Define a temporary file name 126 | $tmpFile = sys_get_temp_dir() . '/tax_rates_' . uniqid() . '.csv'; 127 | 128 | // Write the CSV data to the temporary file 129 | $fileHandle = fopen($tmpFile, 'w'); 130 | foreach ($sortedData as $line) { 131 | fputcsv($fileHandle, $line, escape: ''); 132 | } 133 | // close stream 134 | fclose($fileHandle); 135 | 136 | // Return the path to the temporary file 137 | return $tmpFile; 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /Component/TieredPrices.php: -------------------------------------------------------------------------------- 1 | importerFactory = $importerFactory; 65 | $this->attributeOption = $attributeOption; 66 | $this->log = $log; 67 | } 68 | 69 | /** 70 | * @param null $data 71 | * 72 | * @SuppressWarnings(PHPMD.CyclomaticComplexity) 73 | * @SuppressWarnings(PHPMD.NPathComplexity) 74 | */ 75 | public function execute($data = null) 76 | { 77 | // Get the first row of the CSV file for the attribute columns. 78 | if (!isset($data[0])) { 79 | throw new ComponentException( 80 | sprintf('The row data is not valid.') 81 | ); 82 | } 83 | $attributeKeys = $this->getAttributesFromCsv($data); 84 | $this->skuColumn = $this->getSkuColumnIndex($attributeKeys); 85 | $totalColumnCount = count($attributeKeys); 86 | unset($data[0]); 87 | 88 | $pricesArray = []; 89 | 90 | foreach ($data as $tieredPrice) { 91 | if (count($tieredPrice) !== $totalColumnCount) { 92 | $this->skippedPrices[] = $tieredPrice[$this->skuColumn]; 93 | continue; 94 | } 95 | $priceArray = []; 96 | foreach ($attributeKeys as $column => $code) { 97 | $priceArray[$code] = $tieredPrice[$column]; 98 | $this->attributeOption->processAttributeValues($code, $priceArray[$code]); 99 | } 100 | $pricesArray[] = $priceArray; 101 | $this->successPrices[] = $tieredPrice[$this->skuColumn]; 102 | } 103 | 104 | if (count($this->skippedPrices) > 0) { 105 | $this->log->logInfo( 106 | sprintf( 107 | 'The following tiered prices were skipped as they do not have the required columns: ' 108 | .PHP_EOL.'%s', 109 | implode(PHP_EOL, $this->skippedPrices) 110 | ) 111 | ); 112 | } 113 | 114 | $this->log->logInfo(sprintf('Attempting to import %s rows', count($this->successPrices))); 115 | try { 116 | $import = $this->importerFactory->create(); 117 | $import->setEntityCode('advanced_pricing'); 118 | $import->setMultipleValueSeparator(self::SEPARATOR); 119 | $import->processImport($pricesArray); 120 | } catch (\Exception $e) { 121 | $this->log->logError($e->getMessage()); 122 | } 123 | $this->log->logInfo($import->getLogTrace()); 124 | $this->log->logError($import->getErrorMessages()); 125 | } 126 | 127 | /** 128 | * Gets the first row of the CSV file as these should be the attribute keys 129 | * 130 | * @param null $data 131 | * @return array 132 | */ 133 | public function getAttributesFromCsv($data = null) 134 | { 135 | $attributes = []; 136 | foreach ($data[0] as $attributeCode) { 137 | $attributes[] = $attributeCode; 138 | } 139 | return $attributes; 140 | } 141 | 142 | /** 143 | * Get the column index of the SKU 144 | * 145 | * @param $headers 146 | * 147 | * @return mixed 148 | */ 149 | public function getSkuColumnIndex($headers) 150 | { 151 | return array_search(self::SKU_COLUMN_HEADING, $headers); 152 | } 153 | 154 | /** 155 | * @return string 156 | */ 157 | public function getAlias() 158 | { 159 | return $this->alias; 160 | } 161 | 162 | /** 163 | * @return string 164 | */ 165 | public function getDescription() 166 | { 167 | return $this->description; 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /Console/Command/ListCommand.php: -------------------------------------------------------------------------------- 1 | setName('configurator:list'); 29 | $this->setDescription('List configurator components'); 30 | } 31 | 32 | /** 33 | * @param InputInterface $input 34 | * @param OutputInterface $output 35 | * @return int 36 | * @SuppressWarnings(PHPMD.UnusedFormalParameter) 37 | */ 38 | protected function execute(InputInterface $input, OutputInterface $output): int 39 | { 40 | try { 41 | $count = 1; 42 | foreach ($this->componentList->getAllComponents() as $component) { 43 | $comment = str_pad($count.') ', 4) 44 | . str_pad($component->getAlias(), 20) 45 | . ' - ' 46 | . $component->getDescription(); 47 | 48 | $output->writeln('' . $comment . ''); 49 | $count++; 50 | } 51 | 52 | return self::SUCCESS; 53 | } catch (ConfiguratorAdapterException $e) { 54 | $output->writeln('' . $e->getMessage() . ''); 55 | 56 | return self::FAILURE; 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Console/Command/RunCommand.php: -------------------------------------------------------------------------------- 1 | setName('configurator:run') 55 | ->setDescription('Run configurator components') 56 | ->setDefinition( 57 | new InputDefinition([ 58 | $environmentOption, 59 | $componentOption, 60 | $ignoreMissingFiles 61 | ]) 62 | ); 63 | } 64 | 65 | /** 66 | * @param InputInterface $input 67 | * @param OutputInterface $output 68 | * @return int 69 | * @SuppressWarnings(PHPMD) 70 | */ 71 | protected function execute(InputInterface $input, OutputInterface $output): int 72 | { 73 | try { 74 | if ($output->getVerbosity() > OutputInterface::VERBOSITY_NORMAL) { 75 | $output->writeln('Starting Configurator'); 76 | } 77 | 78 | $environment = $input->getOption('env'); 79 | $components = $input->getOption('component'); 80 | 81 | if ($input->getOption('ignore-missing-files') !== false) { 82 | $this->processor->setIgnoreMissingFiles(true); 83 | } 84 | 85 | $logLevel = OutputInterface::VERBOSITY_NORMAL; 86 | $verbose = $input->getOption('verbose'); 87 | 88 | if ($environment == null) { 89 | throw new ConfiguratorAdapterException('Please specify an environment using --env=""'); 90 | } 91 | 92 | if ($verbose) { 93 | $logLevel = OutputInterface::VERBOSITY_VERBOSE; 94 | } 95 | 96 | $this->processor->setEnvironment($environment); 97 | 98 | foreach ($components as $component) { 99 | $this->processor->addComponent($component); 100 | } 101 | 102 | $this->processor->getLogger()->setLogLevel($logLevel); 103 | $this->processor->run(); 104 | 105 | if ($output->getVerbosity() > OutputInterface::VERBOSITY_NORMAL) { 106 | $output->writeln('Finished Configurator'); 107 | } 108 | 109 | return self::SUCCESS; 110 | 111 | } catch (ConfiguratorAdapterException $e) { 112 | $output->writeln('' . $e->getMessage() . ''); 113 | 114 | return self::INVALID; 115 | } catch (\Exception $e) { 116 | $output->writeln('' . $e->getMessage() . ''); 117 | 118 | return self::FAILURE; 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /Exception/ComponentException.php: -------------------------------------------------------------------------------- 1 | output = $output; 18 | $this->level = $level; 19 | } 20 | 21 | public function setLogLevel($level = OutputInterface::VERBOSITY_NORMAL) 22 | { 23 | $this->level = $level; 24 | return $this; 25 | } 26 | 27 | public function getLogLevel() 28 | { 29 | return $this->level; 30 | } 31 | 32 | public function log($message, $level, $nest = 0) 33 | { 34 | $prepend = ''; 35 | for ($i = 0; $i < $nest; $i++) { 36 | $prepend .= "| "; 37 | } 38 | if (is_array($message)) { 39 | $message = 'Log array: ' . print_r($message, 1); 40 | } 41 | $this->output->writeln($prepend . '<' . $level . '>' . $message . ''); 42 | } 43 | 44 | public function logError($message, $nest = 0) 45 | { 46 | $this->log($message, $this::LEVEL_ERROR, $nest); 47 | } 48 | 49 | public function logQuestion($message, $nest = 0) 50 | { 51 | $this->log($message, $this::LEVEL_QUESTION, $nest); 52 | } 53 | 54 | public function logComment($message, $nest = 0) 55 | { 56 | if ($this->level > OutputInterface::VERBOSITY_NORMAL) { 57 | $this->log($message, $this::LEVEL_COMMENT, $nest); 58 | } 59 | } 60 | 61 | public function logInfo($message, $nest = 0) 62 | { 63 | if ($this->level > OutputInterface::VERBOSITY_QUIET) { 64 | $this->log($message, $this::LEVEL_INFO, $nest); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Samples/Components/AdminRoles/adminroles.yaml: -------------------------------------------------------------------------------- 1 | adminroles: 2 | - name: "Sales Users" 3 | resources: 4 | - 'Magento_Backend::dashboard' 5 | - 'Magento_Backend::admin' 6 | - 'Magento_Sales::sales' 7 | - 'Magento_Sales::create' 8 | - 'Magento_Sales::actions_view' 9 | - 'Magento_Sales::actions_edit' 10 | - 'Magento_Sales::cancel' 11 | - 'Magento_Sales::hold' 12 | - 'Magento_Sales::unhold' 13 | - name: "Tax Manager" 14 | resources: 15 | - 'Magento_Backend::dashboard' 16 | - 'Magento_Sales::sales' 17 | - 'Magento_Sales::sales_operation' 18 | - 'Magento_Sales::sales_order' 19 | - 'Magento_Sales::actions' 20 | - 'Magento_Sales::create' 21 | - 'Magento_Sales::actions_view' 22 | - 'Magento_Sales::email' 23 | - 'Magento_Sales::reorder' 24 | - name: "Admin" 25 | resources: -------------------------------------------------------------------------------- /Samples/Components/AdminRoles/apiroles.yaml: -------------------------------------------------------------------------------- 1 | adminroles: 2 | - name: "API Sales Users" 3 | resources: 4 | - 'Magento_Backend::dashboard' 5 | - 'Magento_Backend::admin' 6 | - 'Magento_Sales::sales' 7 | - 'Magento_Sales::create' 8 | - 'Magento_Sales::actions_view' 9 | - 'Magento_Sales::actions_edit' 10 | - 'Magento_Sales::cancel' 11 | - 'Magento_Sales::hold' 12 | - 'Magento_Sales::unhold' 13 | - name: "API Tax Manager" 14 | resources: 15 | - 'Magento_Backend::dashboard' 16 | - 'Magento_Sales::sales' 17 | - 'Magento_Sales::sales_operation' 18 | - 'Magento_Sales::sales_order' 19 | - 'Magento_Sales::actions' 20 | - 'Magento_Sales::create' 21 | - 'Magento_Sales::actions_view' 22 | - 'Magento_Sales::email' 23 | - 'Magento_Sales::reorder' 24 | - name: "Admin" 25 | resources: -------------------------------------------------------------------------------- /Samples/Components/AdminUsers/adminusers.yaml: -------------------------------------------------------------------------------- 1 | adminusers: 2 | - rolename: Sales Users 3 | users: 4 | - username: storeadmin 5 | firstname: store 6 | secondname: admin 7 | email: storeadmin@email.com 8 | password: fakepassword00 9 | - rolename: Tax Manager 10 | users: 11 | - username: taxmanager 12 | firstname: tax 13 | secondname: manager 14 | email: taxmanager@email.com 15 | password: Fakepassword01 -------------------------------------------------------------------------------- /Samples/Components/AdminUsers/apiusers.yaml: -------------------------------------------------------------------------------- 1 | adminusers: 2 | - rolename: API Sales Users 3 | users: 4 | - username: storeadmin 5 | firstname: store 6 | secondname: admin 7 | email: apistoreadmin@email.com 8 | password: fakepassword00 9 | - rolename: API Product Manager 10 | users: 11 | - username: productmanager 12 | firstname: product 13 | secondname: manager 14 | email: apiproductmanager@email.com 15 | password: Fakepassword01 -------------------------------------------------------------------------------- /Samples/Components/ApiIntegrations/apiintegrations.yaml: -------------------------------------------------------------------------------- 1 | apiintegrations: 2 | - name: API Product Management 3 | email: apiprodmanagement@email.com 4 | setuptype: 1 5 | callbackurl: 6 | identityurl: 7 | resources: 8 | - 'Magento_Backend::admin' 9 | - 'Magento_Catalog::catalog' 10 | - 'Magento_Catalog::catalog_inventory' 11 | - 'Magento_Catalog::products' 12 | - 'Magento_Catalog::categories' 13 | - 'Magento_Catalog::attributes_attributes' 14 | - 'Magento_Catalog::update_attributes' 15 | - 'Magento_Catalog::sets' 16 | - name: API Sales Management 17 | email: apisalesmanagement@email.com 18 | setuptype: 0 19 | callbackurl: 20 | identityurl: 21 | resources: 22 | - 'Magento_Backend::admin' 23 | - 'Magento_Sales::sales' 24 | - 'Magento_Sales::create' 25 | - 'Magento_Sales::actions_view' 26 | - 'Magento_Sales::actions_edit' 27 | - 'Magento_Sales::cancel' 28 | - 'Magento_Sales::hold' 29 | - 'Magento_Sales::unhold' -------------------------------------------------------------------------------- /Samples/Components/Attributes/attribute_sets.yaml: -------------------------------------------------------------------------------- 1 | attribute_sets: 2 | - 3 | name: Default 4 | inherit: Default 5 | groups: 6 | - 7 | name: General 8 | code: general 9 | attributes: 10 | - color 11 | - 12 | name: Shirts 13 | inherit: Default 14 | groups: 15 | - 16 | name: General 17 | code: general 18 | attributes: 19 | - colour 20 | - color 21 | - 22 | name: Prices 23 | code: prices 24 | attributes: 25 | - rrp 26 | - test_attr 27 | - 28 | name: Example Attribute Set 2 29 | inherit: Default 30 | groups: 31 | - 32 | name: Prices 33 | attributes: 34 | - rrp 35 | - 36 | name: Matthew Attribute Set 37 | inherit: Default 38 | groups: 39 | - 40 | name: Prices 41 | attributes: 42 | - rrp 43 | - 44 | name: Matthew Attribute Set 2 45 | groups: 46 | - 47 | name: Prices 48 | attributes: 49 | - rrp 50 | 51 | -------------------------------------------------------------------------------- /Samples/Components/Attributes/attributes.yaml: -------------------------------------------------------------------------------- 1 | attributes: 2 | test_attr: 3 | global: 1 4 | label: Test Attribute 5 | type: text 6 | input: select 7 | visible_on_front: 1 8 | filterable: 1 9 | searchable: 1 10 | visible_in_advanced_search: 0 11 | product_types: 12 | - simple 13 | option: 14 | values: 15 | - Red 16 | - Green 17 | - Yellow 18 | - Blue 19 | - Purple 20 | - Pink 21 | - Orange 22 | - Brown 23 | rrp: 24 | frontend_label: Recommended Retail Price 25 | frontend_input: price 26 | used_in_product_listing: 1 27 | product_types: 28 | - bundle 29 | - simple 30 | colour: 31 | frontend_label: Colour 32 | frontend_input: text 33 | used_in_product_listing: 1 34 | product_types: 35 | - bundle 36 | - simple 37 | colour_with_swatches: 38 | global: 1 39 | label: Colour 40 | type: int 41 | input: swatch_visual 42 | visible_on_front: 1 43 | filterable: 1 44 | filterable_in_search: 1 45 | required: 0 46 | searchable: 1 47 | visible_in_advanced_search: 1 48 | used_for_promo_rules: 0 49 | used_in_product_listing: 1 50 | position: 20 51 | product_types: 52 | - simple 53 | - configurable 54 | option: 55 | values: 56 | 'Aqua blue': '#A1DAD7' 57 | 'Aqua marine': '#7FFFD4' 58 | 'Black': '#000000' 59 | 'Blue': '#0000FF' 60 | 'Bottle green': '#093624' 61 | 'Bright blue': '#2EFFFA' 62 | 'Brown': '#964B00' 63 | 'Cerise pink': '#DA3287' 64 | 'Cinnabar': '#E34234' 65 | 'Dark brown': '#5F1811' 66 | 'Dark green': '#105B10' 67 | 'Dark grey': '#484747' 68 | 'Dark purple': '#3A0156' 69 | 'Denim blue': '#1560BD' 70 | 'Forest green': '#228B22' 71 | 'French navy': '#005280' 72 | 'Grape': '#381A51' 73 | 'Green': '#00FF00' 74 | 'Grey': '#808080' 75 | 'Indigo denim': '#3752B3' 76 | 'Khaki': '#F0E68C' 77 | 'Lemon yellow': '#FDE910' 78 | 'Light blue': '#D6E6FF' 79 | 'Light green': '#D6FFD8' 80 | 'Light grey': '#DEDEDE' 81 | 'Lime green': '#BFFF00' 82 | 'Mustard yellow': '#FFDB58' 83 | 'Navy blue': '#000080' 84 | 'Orange': '#FF681F' 85 | 'Pale Green': '#72836E' 86 | 'Pink': '#FFC0CB' 87 | 'Purple': '#660099' 88 | 'Red': '#FF0000' 89 | 'Sky blue': '#76D7EA' 90 | 'Slate grey': '#708090' 91 | 'Tomato red': '#FF3100' 92 | 'Turquoise': '#30D5C8' 93 | 'White': '#FFFFFF' 94 | 'Yellow': '#FFFF00' -------------------------------------------------------------------------------- /Samples/Components/Blocks/allstores.html: -------------------------------------------------------------------------------- 1 |

This block belongs to all stores.

-------------------------------------------------------------------------------- /Samples/Components/Blocks/blocks.yaml: -------------------------------------------------------------------------------- 1 | all_stores_identifier: 2 | block: 3 | - 4 | source: ../configurator/Blocks/allstores.html 5 | title: All stores 6 | is_active: 1 7 | certain_stores_identifier: 8 | block: 9 | - 10 | source: ../configurator/Blocks/uk.html 11 | title: UK Block 12 | is_active: 1 13 | stores: 14 | - default 15 | - 16 | source: ../configurator/Blocks/us.html 17 | title: US Block 18 | is_active: 1 19 | stores: 20 | - usa_en_us -------------------------------------------------------------------------------- /Samples/Components/Blocks/uk.html: -------------------------------------------------------------------------------- 1 |

UK Block

-------------------------------------------------------------------------------- /Samples/Components/Blocks/us.html: -------------------------------------------------------------------------------- 1 |

US Block

-------------------------------------------------------------------------------- /Samples/Components/Categories/categories.yaml: -------------------------------------------------------------------------------- 1 | categories: 2 | - 3 | store_group: Main Website Store 4 | categories: 5 | - 6 | name: Category 1 7 | description: "A description" 8 | url_key: "category-1" 9 | cms_block: "All stores" # cms block title 10 | categories: 11 | - 12 | name: "Sub Category 1" 13 | description: "Sub Category 1 description" 14 | url_key: "sub-category-1" 15 | - 16 | name: "Sub Category 2" 17 | description: "Sub Category 2 description" 18 | url_key: "sub-category-2" 19 | - 20 | name: "Sub Category 3" 21 | description: "Sub Category 3 description" 22 | url_key: "sub-category-3" 23 | - 24 | name: Category 2 25 | description: "A description" 26 | url_key: "category-2" 27 | - 28 | name: Category 3 29 | description: "A description" 30 | url_key: "category-3" 31 | categories: 32 | - 33 | name: "Sub Category 4" 34 | description: "Sub Category 4 description" 35 | url_key: "sub-category-4" 36 | categories: 37 | - 38 | name: "Sub Category 5" 39 | description: "Sub Category 5 description" 40 | url_key: "sub-category-5" 41 | - 42 | name: "Sub Category 6" 43 | description: "Sub Category 6 description" 44 | url_key: "sub-category-6" 45 | categories: 46 | - 47 | name: "Sub Category 7" 48 | description: "Sub Category 7 description" 49 | url_key: "sub-category-7" 50 | - 51 | name: "Sub Category 8" 52 | description: "Sub Category 8 description" 53 | url_key: "sub-category-8" -------------------------------------------------------------------------------- /Samples/Components/Configuration/base-website-config.yaml: -------------------------------------------------------------------------------- 1 | websites: 2 | base: 3 | - 4 | path: general/country/default 5 | value: GB 6 | stores: 7 | default: 8 | - 9 | path: general/locale/code 10 | value: en_GB -------------------------------------------------------------------------------- /Samples/Components/Configuration/global.yaml: -------------------------------------------------------------------------------- 1 | global: 2 | - 3 | path: general/country/default 4 | value: US 5 | - 6 | path: carriers/tablerate/active 7 | value: 1 8 | - 9 | path: carriers/tablerate/condition_name 10 | value: package_value -------------------------------------------------------------------------------- /Samples/Components/CustomerAttributes/customer_attributes.yaml: -------------------------------------------------------------------------------- 1 | customer_attributes: 2 | text_attribute: 3 | label: Text attribute 4 | input: text 5 | visible: 1 6 | position: 10 7 | used_in_forms: customer_account_create,customer_account_edit 8 | dropdown_attribute: 9 | label: Dropdown attribute 10 | input: select 11 | visible: 1 12 | position: 20 13 | used_in_forms: customer_account_create,customer_account_edit 14 | option: 15 | values: 16 | - Option 1 17 | - Option 2 18 | - Option 3 19 | - etc 20 | boolean_attribute: 21 | label: Boolean attribute 22 | input: boolean 23 | visible: 1 24 | position: 30 25 | used_in_forms: checkout_register,customer_account_create,customer_account_edit,adminhtml_checkout 26 | -------------------------------------------------------------------------------- /Samples/Components/CustomerGroups/customergroups.yaml: -------------------------------------------------------------------------------- 1 | customergroups: 2 | - taxclass: Retail Customer 3 | groups: 4 | - name: VIP 5 | - name: Subscriber -------------------------------------------------------------------------------- /Samples/Components/Customers/customers.csv: -------------------------------------------------------------------------------- 1 | email,_website,_store,firstname,gender,group_id,lastname,middlename,prefix,_address_city,_address_company,_address_country_id,_address_fax,_address_firstname,_address_lastname,_address_middlename,_address_postcode,_address_prefix,_address_region,_address_street,_address_suffix,_address_telephone,_address_vat_id,_address_default_billing_,_address_default_shipping_ 2 | test@test.com,base,admin,Test,,1,Test,,,Test,,GB,,Address 1 First Name,Address 2 Last Name,,123,,,Test,,123456789,,1, 3 | ,,,,,,,,,Another Test,,GB,,Address 2 First Name,Address 2 Last Name,,12345,,,Another Test,,23423,,,0 -------------------------------------------------------------------------------- /Samples/Components/Media/media.yaml: -------------------------------------------------------------------------------- 1 | wysiwyg: 2 | - 3 | name: placeholder.gif 4 | location: http://placehold.it/350x150 5 | - 6 | name: placeholder2.gif 7 | location: http://placehold.it/350x550 8 | other_folder: 9 | another_sub_folder: 10 | - 11 | name: placeholder1.gif 12 | location: http://placehold.it/350x150 13 | - 14 | name: placeholder2.gif 15 | location: http://placehold.it/350x550 -------------------------------------------------------------------------------- /Samples/Components/OrderStatuses/orderstatuses.yaml: -------------------------------------------------------------------------------- 1 | order_statuses: 2 | - state: processing 3 | statuses: 4 | - code: processing_custom1 5 | name: Processing Custom 1 6 | - code: processing_custom2 7 | name: Processing Custom 2 8 | - state: new 9 | statuses: 10 | - code: new_custom1 11 | name: New Custom 1 -------------------------------------------------------------------------------- /Samples/Components/Pages/allstores.html: -------------------------------------------------------------------------------- 1 |

This page belongs to all stores.

-------------------------------------------------------------------------------- /Samples/Components/Pages/pages.yaml: -------------------------------------------------------------------------------- 1 | all_stores_identifier: 2 | page: 3 | - 4 | source: ../configurator/Pages/allstores.html 5 | title: All stores Update 2 6 | meta_title: 7 | meta_keywords: 8 | meta_description: 9 | content_heading: 10 | content: 11 | sort_order: 12 | layout_update_xml: 13 | custom_theme: 14 | custom_root_template: 15 | custom_layout_update_xml: 16 | custom_theme_from: 17 | custom_theme_to: 18 | page_layout: 1column 19 | is_active: 1 20 | stores: 21 | - usa_en_us 22 | certain_stores_identifier: 23 | page: 24 | - 25 | source: ../configurator/Pages/uk.html 26 | title: UK Page Update 2 27 | is_active: 1 28 | page_layout: 1column 29 | stores: 30 | - default 31 | - 32 | source: ../configurator/Pages/us.html 33 | title: US Page Update 2 34 | page_layout: 1column 35 | is_active: 1 36 | stores: 37 | - usa_en_us 38 | inline_content_page: 39 | page: 40 | - 41 | content: Page Content 42 | title: Inline Content Page 43 | -------------------------------------------------------------------------------- /Samples/Components/Pages/uk.html: -------------------------------------------------------------------------------- 1 |

UK Page

-------------------------------------------------------------------------------- /Samples/Components/Pages/us.html: -------------------------------------------------------------------------------- 1 |

US Page

-------------------------------------------------------------------------------- /Samples/Components/ProductLinks/cross-sells.yaml: -------------------------------------------------------------------------------- 1 | cross_sell: 2 | configurable_product_2: 3 | - simple-product 4 | - configurable_product_1 -------------------------------------------------------------------------------- /Samples/Components/ProductLinks/related.yaml: -------------------------------------------------------------------------------- 1 | relation: 2 | simple-product: 3 | - configurable_product_1 4 | - configurable_product_2 -------------------------------------------------------------------------------- /Samples/Components/ProductLinks/up-sells.yaml: -------------------------------------------------------------------------------- 1 | up_sell: 2 | configurable_product_1: 3 | - simple-product 4 | - configurable_product_2 -------------------------------------------------------------------------------- /Samples/Components/Products/configurable.csv: -------------------------------------------------------------------------------- 1 | attribute_set_code,product_websites,product_type,sku,name,short_description,description,price,url_key,visibility,meta_title,meta_keywords,meta_description,associated_products,configurable_attributes,image,small_image,thumbnail 2 | "Default","base","configurable",configurable_product,"Configurable","Configurable short description","Configurable full description","9.99","configurable","catalog, search","Configurable Product Title","Configurable Product meta keywords","Configurable product description","configurable_product_1,configurable_product_2","color","http://placehold.it/1000x1000","http://placehold.it/1000x1000","http://placehold.it/1000x1000" -------------------------------------------------------------------------------- /Samples/Components/Products/simple.csv: -------------------------------------------------------------------------------- 1 | attribute_set_code,product_websites,product_type,sku,name,short_description,description,price,url_key,visibility,meta_title,meta_keywords,meta_description,color,image,small_image,thumbnail 2 | Default,base,simple,simple-product,Simple Product,This is the short description for the simple product.,This is the longer description for the simple product.,9.99,simple-product,"catalog, search",Product 1,A few keywords,Product 1 description,,http://placehold.it/1000x1000,http://placehold.it/1000x1000,http://placehold.it/1000x1000 3 | Default,base,simple,configurable_product_1,Configurable Product 1,This is the short description for configurable_produc_1.,This is the longer description for configurable_product_1.,9.99,,not visible individually,Product 1,A few keywords,Configurable Product 1 description,Red,http://placehold.it/1000x1000,http://placehold.it/1000x1000,http://placehold.it/1000x1000 4 | Default,base,simple,configurable_product_2,Configurable Product 2,This is the short description for the configurable_product_2.,This is the longer description for configurable_product_2,9.99,,not visible individually,Product 2,A few keywords,Configurable Product 2 description,Black,http://placehold.it/1000x1000,http://placehold.it/1000x1000,http://placehold.it/1000x1000 -------------------------------------------------------------------------------- /Samples/Components/ReviewRating/reviewrating.yaml: -------------------------------------------------------------------------------- 1 | review_rating: 2 | Quality: 3 | is_active: 1 4 | position: 0 5 | stores: 6 | - default 7 | Value: 8 | is_active: 1 9 | position: 1 10 | stores: 11 | - default 12 | Price: 13 | is_active: 1 14 | position: 2 15 | stores: 16 | - default -------------------------------------------------------------------------------- /Samples/Components/Rewrites/rewrites.csv: -------------------------------------------------------------------------------- 1 | requestPath,targetPath,redirectType,storeId,description 2 | aaa,aab,302,1,Redirect One 3 | bbb,bbc,302,1,Redirect Two 4 | aac,aad,302,1,Redirect Three 5 | -------------------------------------------------------------------------------- /Samples/Components/Sequence/sequence.yaml: -------------------------------------------------------------------------------- 1 | stores: 2 | default: 3 | prefix: PREFIX_ 4 | startValue: 5000 5 | usa_en_us: 6 | prefix: USA_ 7 | startValue: 1000 -------------------------------------------------------------------------------- /Samples/Components/ShippingTableRates/shippingtablerates.yaml: -------------------------------------------------------------------------------- 1 | base: 2 | - 3 | dest_country_id: DE 4 | dest_region_code: BER 5 | dest_zip: 10405 6 | condition_name: package_value 7 | condition_value: 1.99 8 | price: 5.99 9 | cost: 1.99 10 | - 11 | dest_country_id: GB 12 | dest_region_code: "*" 13 | dest_zip: "*" 14 | condition_name: package_value 15 | condition_value: 0 16 | price: 3.99 17 | cost: 1 18 | usa: 19 | - 20 | dest_country_id: US 21 | dest_region_code: "*" 22 | dest_zip: "*" 23 | condition_name: package_value 24 | condition_value: 1.99 25 | price: 4.99 26 | cost: 1 -------------------------------------------------------------------------------- /Samples/Components/Sql/sitemap.sql: -------------------------------------------------------------------------------- 1 | DELETE FROM `sitemap`; 2 | INSERT INTO `sitemap` (`sitemap_id`, `sitemap_type`, `sitemap_filename`, `sitemap_path`, `sitemap_time`, `store_id`) VALUES (1, NULL, 'sitemap.xml', '/', '2015-08-21 15:13:24', 1); 3 | -------------------------------------------------------------------------------- /Samples/Components/Sql/sql.yaml: -------------------------------------------------------------------------------- 1 | sql: 2 | sitemap: ../configurator/Sql/sitemap.sql 3 | -------------------------------------------------------------------------------- /Samples/Components/TaxRates/multi_example_taxrates.csv: -------------------------------------------------------------------------------- 1 | code,tax_country_id,tax_region_id,rate,tax_postcode,zip_is_range,zip_from,zip_to 2 | VAT Standard Rate,GB,*,*,20,,, 3 | VAT Zero Rate,GB,*,*,0,,, 4 | Czech Republic,CZ,*,*,20,,, 5 | Austria,AT,*,*,20,,, 6 | Belgium,BE,*,*,20,,, 7 | -------------------------------------------------------------------------------- /Samples/Components/TaxRates/taxrates.csv: -------------------------------------------------------------------------------- 1 | code,tax_country_id,tax_region_id,rate,tax_postcode,zip_is_range,zip_from,zip_to 2 | "US-CA-*-Rate 1","US","CA","*","8.2500","","","" 3 | "US-NY-*-Rate 1","US","NY","*","8.3750","","","" 4 | -------------------------------------------------------------------------------- /Samples/Components/TaxRules/multi_example_taxrules.csv: -------------------------------------------------------------------------------- 1 | code,tax_rate_ids,customer_tax_class_ids,product_tax_class_ids,priority,calculate_subtotal,position 2 | VAT Standard,"VAT Standard Rate,Czech Republic,Austria,Belgium",Retail Customer,VAT Standard,1,0,1 3 | VAT Zero,VAT Zero Rate,Retail Customer,VAT Zero,0,0,0 -------------------------------------------------------------------------------- /Samples/Components/TaxRules/taxrules.csv: -------------------------------------------------------------------------------- 1 | code,tax_rate_ids,customer_tax_class_ids,product_tax_class_ids,priority,calculate_subtotal,position 2 | Tax Rule One,US-CA-*-Rate 1,Retail Customer,Taxable Goods,0,0,1 3 | Tax Rate Two,US-NY-*-Rate 1,Retail Customer,Taxable Goods,1,1,2 4 | -------------------------------------------------------------------------------- /Samples/Components/TieredPrices/prices.csv: -------------------------------------------------------------------------------- 1 | sku,tier_price_website,tier_price_customer_group,tier_price_qty,tier_price,tier_price_value_type 2 | SKU1,All Websites [GBP],ALL GROUPS,10,0.85,Fixed 3 | SKU1,All Websites [GBP],ALL GROUPS,50,0.79,Fixed 4 | SKU1,All Websites [GBP],ALL GROUPS,10,0.79,Fixed 5 | SKU2,All Websites [GBP],ALL GROUPS,24,2.99,Fixed 6 | SKU2,All Websites [GBP],ALL GROUPS,24,3.25,Fixed 7 | SKU2,All Websites [GBP],ALL GROUPS,24,3.25,Fixed -------------------------------------------------------------------------------- /Samples/Components/Widgets/widgets.yaml: -------------------------------------------------------------------------------- 1 | - 2 | instance_type: Magento\Cms\Block\Widget\Page\Link 3 | title: Test Link 4 | theme: Magento/blank 5 | stores: 6 | - default 7 | - usa_en_us 8 | parameters: 9 | anchor_text: Anchor Test 10 | title: Anchor Title 11 | page_id: 4 12 | - 13 | instance_type: Magento\Cms\Block\Widget\Block 14 | title: Test Block 15 | theme: Magento/blank 16 | stores: 17 | - default 18 | parameters: 19 | block_id: 13 -------------------------------------------------------------------------------- /Samples/Components/catalog_price_rules.yaml: -------------------------------------------------------------------------------- 1 | config: 2 | apply_all: true 3 | rules: 4 | rule1: 5 | name: Test Rule 6 | description: Some crafty description 7 | is_active: 1 8 | sort_order: 100 9 | website_ids: 10 | - 1 11 | customer_group_ids: 12 | - 1 13 | - 2 14 | from_date: 15 | ### Date format dd/mm/yyyy 16 | to_date: 11/10/2021 17 | conditions_serialized: '{"type":"Magento\\CatalogRule\\Model\\Rule\\Condition\\Combine","attribute":null,"operator":null,"value":"1","is_value_processed":null,"aggregator":"all","conditions":[{"type":"Magento\\CatalogRule\\Model\\Rule\\Condition\\Product","attribute":"sku","operator":"==","value":"12asd","is_value_processed":false}]}' 18 | actions_serialized: '{"type":"Magento\\CatalogRule\\Model\\Rule\\Action\\Collection","attribute":null,"operator":"=","value":null}' 19 | ### Values: [by_fixed|by_percent|to_percent|to_fixed] 20 | simple_action: by_fixed 21 | ### Range: 0-100 22 | discount_amount: 20 23 | stop_rules_processing: 0 24 | another_rule: 25 | name: Another rule 26 | description: I am so descriptive! 27 | is_active: 0 28 | sort_order: 20 29 | website_ids: 30 | - 1 31 | customer_group_ids: 32 | - 0 33 | from_date: 11/2/2018 34 | to_date: 11/10/2021 35 | conditions_serialized: '{"type":"Magento\\CatalogRule\\Model\\Rule\\Condition\\Combine","attribute":null,"operator":null,"value":"1","is_value_processed":null,"aggregator":"all","conditions":[{"type":"Magento\\CatalogRule\\Model\\Rule\\Condition\\Product","attribute":"sku","operator":"==","value":"12asd","is_value_processed":false}]}' 36 | actions_serialized: '{"type":"Magento\\CatalogRule\\Model\\Rule\\Action\\Collection","attribute":null,"operator":"=","value":null}' 37 | simple_action: by_percent 38 | discount_amount: 100 39 | stop_rules_processing: 1 40 | 41 | -------------------------------------------------------------------------------- /Samples/Components/websites.yaml: -------------------------------------------------------------------------------- 1 | websites: 2 | base: 3 | name: Configurator's Website UK 4 | store_groups: 5 | - 6 | group_id: 1 7 | name: Main Website Store 8 | root_category_id: 2 9 | default_store: default 10 | store_views: 11 | default: 12 | name: Configurator's Store View 13 | is_active: 1 14 | usa: 15 | name: Configurator's Website US 16 | store_groups: 17 | - 18 | name: Configurator's Website Store US 19 | code: usa_en 20 | root_category_id: 2 21 | default_store: usa_en_us 22 | store_views: 23 | usa_en_us: 24 | name: Configurator's USA Store View 25 | is_active: 1 26 | -------------------------------------------------------------------------------- /Samples/master.yaml: -------------------------------------------------------------------------------- 1 | websites: 2 | enabled: 1 3 | method: code 4 | sources: 5 | - ../configurator/websites.yaml 6 | env: 7 | local: 8 | log: info 9 | qa: 10 | log: notice 11 | stage: 12 | log: warning 13 | live: 14 | log: error 15 | config: 16 | enabled: 1 17 | method: code 18 | sources: 19 | - ../configurator/Configuration/global.yaml 20 | - ../configurator/Configuration/base-website-config.yaml 21 | sequence: 22 | enabled: 1 23 | method: code 24 | sources: 25 | - ../configurator/Sequence/sequence.yaml 26 | attributes: 27 | enabled: 1 28 | method: code 29 | sources: 30 | - ../configurator/Attributes/attributes.yaml 31 | attribute_sets: 32 | enabled: 1 33 | method: code 34 | sources: 35 | - ../configurator/Attributes/attribute_sets.yaml 36 | categories: 37 | enabled: 1 38 | method: code 39 | sources: 40 | - ../configurator/Categories/categories.yaml 41 | env: 42 | local: 43 | mode: maintain 44 | log: debug 45 | products: 46 | enabled: 1 47 | method: code 48 | sources: 49 | - ../configurator/Products/simple.csv 50 | - ../configurator/Products/configurable.csv 51 | env: 52 | local: 53 | mode: maintain 54 | log: debug 55 | blocks: 56 | enabled: 1 57 | method: code 58 | sources: 59 | - ../configurator/Blocks/blocks.yaml 60 | env: 61 | local: 62 | mode: maintain 63 | log: debug 64 | apiintegrations: 65 | enabled: 1 66 | method: code 67 | sources: 68 | - ../configurator/ApiIntegrations/apiintegrations.yaml 69 | taxrates: 70 | enabled: 1 71 | method: code 72 | sources: 73 | - ../configurator/TaxRates/taxrates.csv 74 | env: 75 | local: 76 | mode: maintain 77 | log: debug 78 | taxrules: 79 | enabled: 1 80 | method: code 81 | sources: 82 | - ../configurator/TaxRules/taxrules.csv 83 | env: 84 | local: 85 | mode: maintain 86 | log: debug 87 | pages: 88 | enabled: 1 89 | method: code 90 | sources: 91 | - ../configurator/Pages/pages.yaml 92 | env: 93 | local: 94 | mode: maintain 95 | log: debug 96 | widgets: 97 | enabled: 1 98 | method: code 99 | sources: 100 | - ../configurator/Widgets/widgets.yaml 101 | env: 102 | local: 103 | mode: maintain 104 | log: debug 105 | customergroups: 106 | enabled: 1 107 | method: code 108 | sources: 109 | - ../configurator/CustomerGroups/customergroups.yaml 110 | env: 111 | local: 112 | mode: maintain 113 | log: debug 114 | adminroles: 115 | enabled: 1 116 | method: code 117 | sources: 118 | - ../configurator/AdminRoles/adminroles.yaml 119 | - ../configurator/AdminRoles/apiroles.yaml 120 | env: 121 | local: 122 | mode: maintain 123 | log: debug 124 | adminusers: 125 | enabled: 1 126 | method: code 127 | sources: 128 | - ../configurator/AdminUsers/adminusers.yaml 129 | - ../configurator/AdminUsers/apiusers.yaml 130 | env: 131 | local: 132 | mode: maintain 133 | log: debug 134 | media: 135 | enabled: 1 136 | method: code 137 | sources: 138 | - ../configurator/Media/media.yaml 139 | rewrites: 140 | enabled: 1 141 | method: code 142 | sources: 143 | - ../configurator/Rewrites/rewrites.csv 144 | review_rating: 145 | enabled: 1 146 | method: code 147 | sources: 148 | - ../configurator/ReviewRating/reviewrating.yaml 149 | product_links: 150 | enabled: 1 151 | method: code 152 | sources: 153 | - ../configurator/ProductLinks/related.yaml 154 | - ../configurator/ProductLinks/cross-sells.yaml 155 | - ../configurator/ProductLinks/up-sells.yaml 156 | customer_attributes: 157 | enabled: 1 158 | method: code 159 | sources: 160 | - ../configurator/CustomerAttributes/customer_attributes.yaml 161 | customers: 162 | enabled: 1 163 | method: code 164 | sources: 165 | - ../configurator/Customers/customers.csv 166 | sql: 167 | enabled: 1 168 | method: code 169 | sources: 170 | - ../configurator/Sql/sql.yaml 171 | catalog_price_rules: 172 | enabled: 1 173 | method: code 174 | sources: 175 | - ../configurator/catalog_price_rules.yaml 176 | shippingtablerates: 177 | enabled: 1 178 | method: code 179 | sources: 180 | - ../configurator/ShippingTableRates/shippingtablerates.yaml 181 | order_statuses: 182 | enabled: 1 183 | method: code 184 | sources: 185 | - ../configurator/OrderStatuses/orderstatuses.yaml 186 | -------------------------------------------------------------------------------- /Test/Integration/Component/CatalogPriceRulesTest.php: -------------------------------------------------------------------------------- 1 | 5 | * @copyright 2017 CtiDigital 6 | */ 7 | 8 | namespace CtiDigital\Configurator\Test\Integration\Component; 9 | 10 | use CtiDigital\Configurator\Console\Command\RunCommand; 11 | use Magento\CatalogRule\Model\ResourceModel\Rule\Collection; 12 | use Magento\CatalogRule\Model\RuleFactory; 13 | use Magento\Framework\ObjectManagerInterface; 14 | use Magento\TestFramework\Helper\Bootstrap; 15 | use PHPUnit\Framework\TestCase; 16 | use Symfony\Component\Console\Tester\CommandTester; 17 | 18 | /** 19 | * Class CatalogPriceRulesTest 20 | * @codingStandardsIgnoreStart 21 | * @SuppressWarnings(PHPMD) 22 | */ 23 | class CatalogPriceRulesTest extends TestCase 24 | { 25 | /** 26 | * @var RuleFactory 27 | */ 28 | private $ruleFactory; 29 | 30 | /** 31 | * @var Collection 32 | */ 33 | private $collection; 34 | 35 | /** 36 | * @var CommandTester 37 | */ 38 | private $cmdTester; 39 | 40 | /** 41 | * @var RunCommand 42 | */ 43 | private $command; 44 | 45 | /** 46 | * @var ObjectManagerInterface 47 | */ 48 | private $objectManager; 49 | 50 | public function setUp(): void 51 | { 52 | $this->objectManager = Bootstrap::getObjectManager(); 53 | $this->collection = $this->objectManager->create(Collection::class); 54 | $this->ruleFactory = $this->objectManager->create(RuleFactory::class); 55 | } 56 | 57 | /** 58 | * @magentoDbIsolation enabled 59 | */ 60 | public function testRequestedRulesAreCreated() 61 | { 62 | $this->executeCommand(); 63 | 64 | $expectedRulesCount = 2; 65 | 66 | $this->assertEquals($expectedRulesCount, $this->collection->count()); 67 | } 68 | 69 | private function executeCommand() 70 | { 71 | $this->command = $this->objectManager->create(RunCommand::class); 72 | $this->command->addOption('verbose', '-v'); 73 | $this->cmdTester = new CommandTester($this->command); 74 | $this->cmdTester->execute(['--component' => ['catalog_price_rules'], '--env' => 'local']); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /Test/Integration/RewritesTest.php: -------------------------------------------------------------------------------- 1 | expectedRewrites = $this->getExpectedRewrites(); 35 | 36 | $this->rewritesCsvPath = sprintf("%s/../../Samples/Components/Rewrites/rewrites.csv", __DIR__); 37 | $this->rewritesComponent = Bootstrap::getObjectManager() 38 | ->get('CtiDigital\Configurator\Model\Component\Rewrites'); 39 | 40 | /** 41 | * @var UrlRewriteFactory 42 | */ 43 | $urlRewriteFactory = Bootstrap::getObjectManager() 44 | ->get('Magento\UrlRewrite\Model\UrlRewriteFactory'); 45 | 46 | $this->urlRewriteModel = $urlRewriteFactory->create(); 47 | 48 | $file = new File(); 49 | $this->rewritesData = new Csv($file); 50 | $this->rewritesData = $this->rewritesData->getData($this->rewritesCsvPath); 51 | } 52 | 53 | public function testShouldCreateNewRewritesFromCsv() 54 | { 55 | // given a CSV file containing rewrites (created in setUp) 56 | 57 | // when we run the Rewrites component 58 | $this->rewritesComponent->processData($this->rewritesData); 59 | 60 | // then it should create rewrites in the database 61 | $this->assertThatExpectedRewritesExist($this->expectedRewrites); 62 | } 63 | 64 | private function assertThatExpectedRewritesExist(array $rewrites) 65 | { 66 | foreach ($rewrites as $rewrite) { 67 | $this->assertThatExpectedRewriteExists($rewrite); 68 | } 69 | } 70 | 71 | private function assertThatExpectedRewriteExists(Rewrite $expectedRewrite) 72 | { 73 | $actualRewrite = $this->urlRewriteModel->getCollection() 74 | ->addFieldToFilter("request_path", $expectedRewrite->getRequestPath()) 75 | ->getFirstItem() 76 | ->getData(); 77 | 78 | $this->assertEquals($actualRewrite['request_path'], $expectedRewrite->getRequestPath()); 79 | $this->assertEquals($actualRewrite['target_path'], $expectedRewrite->getTargetPath()); 80 | $this->assertEquals($actualRewrite['redirect_type'], $expectedRewrite->getRedirectType()); 81 | $this->assertEquals($actualRewrite['store_id'], $expectedRewrite->getStoreId()); 82 | $this->assertEquals($actualRewrite['description'], $expectedRewrite->getDescription()); 83 | } 84 | 85 | public function getExpectedRewrites() 86 | { 87 | return [ 88 | new Rewrite( 89 | "aaa", 90 | "aab", 91 | "302", 92 | "1", 93 | "Redirect One" 94 | ), 95 | new Rewrite( 96 | "bbb", 97 | "bbc", 98 | "302", 99 | "1", 100 | "Redirect Two" 101 | ), 102 | new Rewrite( 103 | "aac", 104 | "aad", 105 | "302", 106 | "1", 107 | "Redirect Three" 108 | ) 109 | ]; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /Test/Integration/run-configurator.sh: -------------------------------------------------------------------------------- 1 | cd magento2 2 | 3 | echo List Configurator Command Test... 4 | php bin/magento configurator:list 5 | 6 | echo Run Configurator Command Test... 7 | php bin/magento configurator:run --env local 8 | 9 | echo Run Configurator Command Test in verbose... 10 | php bin/magento configurator:run --env local -v -------------------------------------------------------------------------------- /Test/Integration/setup-magento.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo Setting up Magento 4 | 5 | echo Disabling xdebug for performance 6 | echo '' > ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/xdebug.ini 7 | 8 | echo Creating configurator database 9 | mysql -e 'CREATE DATABASE IF NOT EXISTS configurator;' 10 | 11 | echo Install Magento 12 | git clone https://github.com/magento/magento2 13 | cd magento2 14 | 15 | git checkout tags/$1 -b $1 16 | 17 | # Update symfony/service-contracts as recommended in https://github.com/magento/magento2/issues/24937 18 | composer update symfony/service-contracts 19 | composer install 20 | 21 | if [ -z "${TRAVIS_TAG}" ]; then 22 | echo Require configurator branch: ${TRAVIS_BRANCH} commit: ${TRAVIS_COMMIT} 23 | composer require ctidigital/magento2-configurator dev-${TRAVIS_BRANCH}\#${TRAVIS_COMMIT} 24 | else 25 | echo Require configurator release ${TRAVIS_TAG:1} 26 | composer require ctidigital/magento2-configurator ${TRAVIS_TAG:1} 27 | fi 28 | 29 | php bin/magento setup:install --cleanup-database --admin-email "test@test.com" --admin-firstname "CTI" --admin-lastname "Test" --admin-password "password123" --admin-user "admin" --backend-frontname admin --base-url "http://configurator.dev" --db-host 127.0.0.1 --db-name configurator --db-user root --session-save files --use-rewrites 1 --use-secure 0 -vvv 30 | 31 | echo Go to app etc folder 32 | cd app/etc 33 | 34 | echo Copy master.yaml folder 35 | cp ../../../Samples/master.yaml master.yaml 36 | 37 | pwd 38 | ls -alh 39 | cat master.yaml 40 | 41 | cd ../.. 42 | php bin/magento cache:flush 43 | php bin/magento module:status 44 | cd .. 45 | 46 | mv Samples/Components configurator -------------------------------------------------------------------------------- /Test/Unit/Component/CatalogPriceRulesTest.php: -------------------------------------------------------------------------------- 1 | 5 | * @copyright 2017 CtiDigital 6 | */ 7 | 8 | namespace CtiDigital\Configurator\Test\Unit\Component; 9 | 10 | use CtiDigital\Configurator\Api\ComponentProcessorInterface; 11 | use CtiDigital\Configurator\Component\CatalogPriceRules; 12 | use CtiDigital\Configurator\Component\CatalogPriceRules\CatalogPriceRulesProcessor; 13 | use CtiDigital\Configurator\Api\LoggerInterface; 14 | 15 | /** 16 | * Class CatalogPriceRulesTest 17 | * @codingStandardsIgnoreStart 18 | * @SuppressWarnings(PHPMD) 19 | */ 20 | class CatalogPriceRulesTest extends \PHPUnit\Framework\TestCase 21 | { 22 | /** 23 | * @var CatalogPriceRules 24 | */ 25 | private $catalogPriceRules; 26 | 27 | /** 28 | * @var LoggerInterface|\PHPUnit_Framework_MockObject_MockObject 29 | */ 30 | private $log; 31 | 32 | /** 33 | * @var ComponentProcessorInterface|\PHPUnit_Framework_MockObject_MockObject 34 | */ 35 | private $mockComponentProcessor; 36 | 37 | protected function setUp(): void 38 | { 39 | $this->mockComponentProcessor = $this->getMockBuilder(CatalogPriceRulesProcessor::class) 40 | ->disableOriginalConstructor() 41 | ->setMethods(['setData', 'setConfig', 'process']) 42 | ->getMock(); 43 | 44 | $this->log = $this->getMockBuilder(LoggerInterface::class) 45 | ->disableOriginalConstructor() 46 | ->getMock(); 47 | 48 | $this->catalogPriceRules = new CatalogPriceRules( 49 | $this->mockComponentProcessor, 50 | $this->log 51 | ); 52 | } 53 | 54 | public function testProcessDataExecution() 55 | { 56 | $this->mockComponentProcessor->expects($this->once()) 57 | ->method('setData') 58 | ->willReturn($this->mockComponentProcessor); 59 | 60 | $this->mockComponentProcessor->expects($this->once()) 61 | ->method('setConfig') 62 | ->willReturn($this->mockComponentProcessor); 63 | 64 | $this->mockComponentProcessor->expects($this->once()) 65 | ->method('process'); 66 | 67 | $this->catalogPriceRules->execute(); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Test/Unit/Component/ConfigTest.php: -------------------------------------------------------------------------------- 1 | configResource = $this->getMockBuilder(ConfigResource::class) 70 | ->disableOriginalConstructor() 71 | ->getMock(); 72 | $this->scopeConfig = $this->getMockBuilder(ScopeConfig::class) 73 | ->disableOriginalConstructor() 74 | ->getMock(); 75 | $this->initialConfig = $this->getMockBuilder(ScopeConfig\Initial::class) 76 | ->disableOriginalConstructor() 77 | ->getMock(); 78 | $this->collection = $this->getMockBuilder(Collection::class) 79 | ->disableOriginalConstructor() 80 | ->getMock(); 81 | $this->collectionFactory = $this->getMockBuilder(CollectionFactory::class) 82 | ->disableOriginalConstructor() 83 | ->setMethods(['create']) 84 | ->getMock(); 85 | $this->collectionFactory->expects($this->any())->method('create')->willReturn($this->collection); 86 | $this->encryptorInterface = $this->getMockBuilder(EncryptorInterface::class) 87 | ->disableOriginalConstructor() 88 | ->getMock(); 89 | $this->websiteFactory = $this->getMockBuilder(WebsiteFactory::class) 90 | ->disableOriginalConstructor() 91 | ->getMock(); 92 | $this->storeFactory = $this->getMockBuilder(StoreFactory::class) 93 | ->disableOriginalConstructor() 94 | ->getMock(); 95 | $this->log = $this->getMockBuilder(LoggerInterface::class) 96 | ->disableOriginalConstructor() 97 | ->getMock(); 98 | $this->config = new Config( 99 | $this->configResource, 100 | $this->scopeConfig, 101 | $this->initialConfig, 102 | $this->collectionFactory, 103 | $this->encryptorInterface, 104 | $this->websiteFactory, 105 | $this->storeFactory, 106 | $this->log 107 | ); 108 | } 109 | 110 | /** 111 | * Test check if the path is the configuration path without an ID 112 | */ 113 | public function testIsConfigTheme() 114 | { 115 | $this->assertTrue($this->config->isConfigTheme(Config::PATH_THEME_ID, 'theme')); 116 | } 117 | 118 | /** 119 | * Test ignoring a path that has an ID for the config path 120 | */ 121 | public function testIsConfigThemeWithAnId() 122 | { 123 | $this->assertTrue($this->config->isConfigTheme(Config::PATH_THEME_ID, '1')); 124 | } 125 | 126 | /** 127 | * Test ignoring non-config theme path 128 | */ 129 | public function testNotConfigTheme() 130 | { 131 | $this->assertFalse($this->config->isConfigTheme('a/path', '1')); 132 | } 133 | 134 | /** 135 | * Test getting theme by the path 136 | */ 137 | public function testGetThemeById() 138 | { 139 | $mockThemeModel = $this->getMockBuilder('Magento\Theme\Model\Theme') 140 | ->disableOriginalConstructor() 141 | ->setMethods(['getThemeId']) 142 | ->getMock(); 143 | 144 | $mockThemeModel->expects($this->once()) 145 | ->method('getThemeId') 146 | ->willReturn(3); 147 | 148 | $this->collection->expects($this->once()) 149 | ->method('getThemeByFullPath') 150 | ->with('frontend/test/theme') 151 | ->willReturn($mockThemeModel); 152 | 153 | $this->assertEquals(3, $this->config->getThemeIdByPath('frontend/test/theme')); 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /Test/Unit/Component/Product/ImageTest.php: -------------------------------------------------------------------------------- 1 | fileSystem = $this->getMockBuilder(Filesystem::class) 50 | ->disableOriginalConstructor() 51 | ->getMock(); 52 | 53 | $this->config = $this->getMockBuilder(Config::class) 54 | ->disableOriginalConstructor() 55 | ->getMock(); 56 | 57 | $this->httpMock = $this->getMockBuilder(ZendClient::class) 58 | ->disableOriginalConstructor() 59 | ->setMethods(['setUri', 'request', 'getBody']) 60 | ->getMock(); 61 | 62 | $this->httpFactoryMock = $this->getMockBuilder(ZendClientFactory::class) 63 | ->disableOriginalConstructor() 64 | ->setMethods(['create']) 65 | ->getMock(); 66 | 67 | $this->httpFactoryMock->expects($this->any()) 68 | ->method('create') 69 | ->willReturn($this->httpMock); 70 | 71 | $this->log = $this->getMockBuilder(LoggerInterface::class) 72 | ->disableOriginalConstructor() 73 | ->getMock(); 74 | 75 | $this->image = new Image( 76 | $this->fileSystem, 77 | $this->config, 78 | $this->httpFactoryMock, 79 | $this->log 80 | ); 81 | } 82 | 83 | public function testIsValueUrl() 84 | { 85 | $testUrl = "http://test.com/media/item.png"; 86 | $testFilename = 'item.png'; 87 | $this->assertNotFalse($this->image->isValueUrl($testUrl)); 88 | $this->assertFalse($this->image->isValueUrl($testFilename)); 89 | } 90 | 91 | public function testDownloadFile() 92 | { 93 | $this->httpMock->expects($this->any())->method('setUri')->willReturnSelf(); 94 | $this->httpMock->expects($this->any())->method('request')->willReturnSelf(); 95 | $this->httpMock->expects($this->any())->method('getBody')->willReturn('testbinarycontent'); 96 | $this->assertEquals('testbinarycontent', $this->image->downloadFile('http://test.com/media/item.png')); 97 | } 98 | 99 | public function testGetFileName() 100 | { 101 | $testUrl = "http://test.com/media/item.png"; 102 | $this->assertEquals('item.png', $this->image->getFileName($testUrl)); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /Test/Unit/Component/ReviewRatingTest.php: -------------------------------------------------------------------------------- 1 | ratingFactory = $this->getMockBuilder(RatingFactory::class) 46 | ->disableOriginalConstructor() 47 | ->setMethods(['create']) 48 | ->getMock(); 49 | $this->storeRepository = $this->getMockBuilder(StoreRepositoryInterface::class) 50 | ->disableOriginalConstructor() 51 | ->getMock(); 52 | $this->optionFactory = $this->getMockBuilder(OptionFactory::class) 53 | ->disableOriginalConstructor() 54 | ->getMock(); 55 | $this->entityFactory = $this->getMockBuilder(EntityFactory::class) 56 | ->disableOriginalConstructor() 57 | ->getMock(); 58 | $this->log = $this->getMockBuilder(LoggerInterface::class) 59 | ->disableOriginalConstructor() 60 | ->getMock(); 61 | $this->reviewRating = new ReviewRating( 62 | $this->ratingFactory, 63 | $this->storeRepository, 64 | $this->optionFactory, 65 | $this->entityFactory, 66 | $this->log 67 | ); 68 | } 69 | 70 | /** 71 | * Test get the review ratings from the data 72 | */ 73 | public function testGetReviewRatings() 74 | { 75 | $data = [ 76 | 'review_rating' => [ 77 | 'Quality' => [], 78 | 'Value' => [], 79 | 'Price' => [] 80 | ] 81 | ]; 82 | 83 | $expectedData = [ 84 | 'Quality' => [], 85 | 'Value' => [], 86 | 'Price' => [] 87 | ]; 88 | 89 | $this->assertEquals($expectedData, $this->reviewRating->getReviewRatings($data)); 90 | } 91 | 92 | /** 93 | * Test get the review rating model using the code 94 | */ 95 | public function testGetReviewRating() 96 | { 97 | $mockRating = $this->getMockBuilder(\Magento\Review\Model\Rating::class) 98 | ->disableOriginalConstructor() 99 | ->setMethods(['load', 'getId']) 100 | ->getMock(); 101 | 102 | $mockRating->expects($this->once()) 103 | ->method('load') 104 | ->willReturnSelf(); 105 | 106 | $mockRating->expects($this->once()) 107 | ->method('getId') 108 | ->willReturn(1); 109 | 110 | $this->ratingFactory->expects($this->once()) 111 | ->method('create') 112 | ->willReturn($mockRating); 113 | $rating = $this->reviewRating->getReviewRating('value'); 114 | $this->assertEquals(1, $rating->getId()); 115 | } 116 | 117 | /** 118 | * Test get a review rating that doesn't exist 119 | */ 120 | public function testGetNewRating() 121 | { 122 | $mockRating = $this->getMockBuilder(\Magento\Review\Model\Rating::class) 123 | ->disableOriginalConstructor() 124 | ->setMethods(['load', 'getId']) 125 | ->getMock(); 126 | 127 | $mockRating->expects($this->once()) 128 | ->method('load') 129 | ->willReturnSelf(); 130 | 131 | $mockRating->expects($this->once()) 132 | ->method('getId') 133 | ->willReturn(null); 134 | 135 | $this->ratingFactory->expects($this->once()) 136 | ->method('create') 137 | ->willReturn($mockRating); 138 | 139 | $rating = $this->reviewRating->getReviewRating('price'); 140 | $this->assertNull($rating->getId()); 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /Test/Unit/Processor/SqlSplitProcessorTest.php: -------------------------------------------------------------------------------- 1 | 5 | * @copyright 2017 CtiDigital 6 | */ 7 | 8 | namespace CtiDigital\Configurator\Test\Unit\Processor; 9 | 10 | use CtiDigital\Configurator\Api\LoggerInterface; 11 | use CtiDigital\Configurator\Component\Processor\SqlSplitProcessor; 12 | use CtiDigital\Configurator\Model\Logging; 13 | use Exception; 14 | use Magento\Framework\App\ResourceConnection; 15 | use Magento\Framework\DB\Adapter\AdapterInterface; 16 | use Magento\Framework\DB\Adapter\Pdo\Mysql; 17 | use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; 18 | use PHPUnit\Framework\TestCase; 19 | use PHPUnit_Framework_MockObject_MockObject; 20 | 21 | /** 22 | * Class SqlSplitProcessorTest 23 | * @codingStandardsIgnoreStart 24 | */ 25 | class SqlSplitProcessorTest extends TestCase 26 | { 27 | const TEST_SQL_PATH = '/Unit/_files/sql/test.sql'; 28 | 29 | /** 30 | * @var LoggerInterface|PHPUnit_Framework_MockObject_MockObject 31 | */ 32 | protected $mockLogger; 33 | 34 | /** 35 | * @var ResourceConnection|PHPUnit_Framework_MockObject_MockObject 36 | */ 37 | protected $mockResource; 38 | 39 | /** 40 | * @var AdapterInterface|PHPUnit_Framework_MockObject_MockObject 41 | */ 42 | protected $mockConnection; 43 | 44 | /** 45 | * @var SqlSplitProcessor 46 | */ 47 | protected $processor; 48 | 49 | /** 50 | * @var ObjectManager 51 | */ 52 | private $objectManager; 53 | 54 | public function setUp(): void 55 | { 56 | $this->objectManager = new ObjectManager($this); 57 | $this->mockLogger = $this->getMockBuilder(Logging::class) 58 | ->disableOriginalConstructor() 59 | ->setMethods(['logInfo', 'logError']) 60 | ->getMock(); 61 | $this->mockResource = $this->getMockBuilder(ResourceConnection::class) 62 | ->disableOriginalConstructor() 63 | ->setMethods(['getConnection']) 64 | ->getMock(); 65 | 66 | $this->mockConnection = $this->getMockBuilder(Mysql::class) 67 | ->disableOriginalConstructor() 68 | ->setMethods(['beginTransaction', 'query', 'rollBack', 'commit']) 69 | ->getMock(); 70 | 71 | $this->mockResource->expects($this->once()) 72 | ->method('getConnection') 73 | ->willReturn($this->mockConnection); 74 | 75 | $this->processor = $this->objectManager->getObject(SqlSplitProcessor::class, [ 76 | 'log' => $this->mockLogger, 77 | 'resource' => $this->mockResource, 78 | ]); 79 | } 80 | 81 | public function sqlDataProvider() 82 | { 83 | return [ 84 | ['sitemap', "SELECT * FROM sitemap;\nANOTHER QUERY;", 2], 85 | ['another', 'DELETE from store;', 1], 86 | ['empty', '', 0], 87 | ]; 88 | } 89 | 90 | public function testExceptionHandling() 91 | { 92 | $this->markTestSkipped(); 93 | $name = 'name1'; 94 | $exMsg = 'exception message'; 95 | 96 | $this->mockConnection 97 | ->expects($this->any()) 98 | ->method('query') 99 | ->willThrowException(new Exception($exMsg)); 100 | 101 | $this->mockLogger 102 | ->expects($this->at(1)) 103 | ->method('logError') 104 | ->with($exMsg); 105 | 106 | $this->mockConnection 107 | ->expects($this->once()) 108 | ->method('rollBack'); 109 | 110 | $this->processor->process($name, dirname(dirname(__DIR__)) . self::TEST_SQL_PATH); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /Test/Unit/ProcessorTest.php: -------------------------------------------------------------------------------- 1 | getMockBuilder(ConsoleOutputInterface::class) 39 | ->disableOriginalConstructor() 40 | ->getMock(); 41 | $scopeInterface = $this->getMockBuilder(ScopeInterface::class) 42 | ->disableOriginalConstructor() 43 | ->getMock(); 44 | $this->componentList = $this->getMockBuilder(ComponentListInterface::class) 45 | ->disableOriginalConstructor() 46 | ->getMock(); 47 | $this->state = $this->getMockBuilder(State::class) 48 | ->disableOriginalConstructor() 49 | ->setConstructorArgs([$scopeInterface]) 50 | ->getMock(); 51 | $this->loggerInterface = $this->getMockBuilder(LoggerInterface::class) 52 | ->disableOriginalConstructor() 53 | ->setConstructorArgs([$consoleOutput]) 54 | ->getMock(); 55 | 56 | $this->processor = new Processor( 57 | $this->componentList, 58 | $this->state, 59 | $this->loggerInterface 60 | ); 61 | } 62 | 63 | public function testICanSetAnEnvironment() 64 | { 65 | $environment = 'stage'; 66 | $this->processor->setEnvironment($environment); 67 | $this->assertEquals($environment, $this->processor->getEnvironment()); 68 | } 69 | 70 | public function testICanAddASingleComponent() 71 | { 72 | $component = 'websites'; 73 | $this->processor->addComponent($component); 74 | $this->assertArrayHasKey($component, $this->processor->getComponents()); 75 | } 76 | 77 | public function testICanAddMultipleComponents() 78 | { 79 | $components = ['website', 'config']; 80 | foreach ($components as $component) { 81 | $this->processor->addComponent($component); 82 | } 83 | $this->assertCount(2, $this->processor->getComponents()); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /Test/Unit/_files/sql/test.sql: -------------------------------------------------------------------------------- 1 | SELECT * FROM unknown; -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ctidigital/magento2-configurator", 3 | "description": "Keep magento persistently configured using files", 4 | "type": "magento2-module", 5 | "repositories": [ 6 | { 7 | "type": "composer", 8 | "url": "https://repo.magento.com/" 9 | } 10 | ], 11 | "require": { 12 | "symfony/yaml": ">=4.0", 13 | "firegento/fastsimpleimport": "2.0.1", 14 | "ext-exif": "*" 15 | }, 16 | "require-dev": { 17 | "phpmd/phpmd": "^2.12.0", 18 | "squizlabs/php_codesniffer": "~3.6.0", 19 | "sebastian/phpcpd": "^6.0.3", 20 | "phpunit/phpunit": "~9.5.20", 21 | "magento/magento-coding-standard": "*" 22 | }, 23 | "version": "4.0.0", 24 | "autoload": { 25 | "files": [ 26 | "registration.php" 27 | ], 28 | "psr-4": { 29 | "CtiDigital\\Configurator\\": "" 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /docs/assets/css/ie8.css: -------------------------------------------------------------------------------- 1 | /* 2 | Stellar by HTML5 UP 3 | html5up.net | @ajlkn 4 | Free for personal and commercial use under the CCA 3.0 license (html5up.net/license) 5 | */ 6 | 7 | /* Type */ 8 | 9 | body { 10 | color: #ffffff; 11 | } 12 | 13 | /* Button */ 14 | 15 | .button { 16 | position: relative; 17 | border: solid 1px !important; 18 | } 19 | 20 | .button.special { 21 | border: none !important; 22 | } 23 | 24 | /* Icon */ 25 | 26 | .icon.major { 27 | -ms-behavior: url("assets/js/ie/PIE.htc"); 28 | } 29 | 30 | .icon.major:before { 31 | font-size: 4em; 32 | border: 0; 33 | } 34 | 35 | /* Spotlight */ 36 | 37 | .spotlight .image { 38 | position: relative; 39 | -ms-behavior: url("assets/js/ie/PIE.htc"); 40 | } 41 | 42 | .spotlight .image img { 43 | position: relative; 44 | -ms-behavior: url("assets/js/ie/PIE.htc"); 45 | } 46 | 47 | /* Features */ 48 | 49 | .features li { 50 | width: 29%; 51 | } 52 | 53 | /* Footer */ 54 | 55 | #footer > * { 56 | width: 50%; 57 | margin-left: 0; 58 | } 59 | 60 | /* Main */ 61 | 62 | #main > .main > .image.main:first-child { 63 | margin-top: 0; 64 | margin-left: 0; 65 | width: 100%; 66 | } -------------------------------------------------------------------------------- /docs/assets/css/ie9.css: -------------------------------------------------------------------------------- 1 | /* 2 | Stellar by HTML5 UP 3 | html5up.net | @ajlkn 4 | Free for personal and commercial use under the CCA 3.0 license (html5up.net/license) 5 | */ 6 | 7 | /* Spotlight */ 8 | 9 | .spotlight:after { 10 | content: ''; 11 | display: block; 12 | clear: both; 13 | } 14 | 15 | .spotlight .content { 16 | float: left; 17 | width: 60%; 18 | } 19 | 20 | .spotlight .image { 21 | float: left; 22 | } 23 | 24 | /* Features */ 25 | 26 | .features:after { 27 | content: ''; 28 | display: block; 29 | clear: both; 30 | } 31 | 32 | .features li { 33 | float: left; 34 | } 35 | 36 | /* Statistics */ 37 | 38 | .statistics { 39 | display: inline-block; 40 | width: auto; 41 | } 42 | 43 | .statistics:after { 44 | content: ''; 45 | display: block; 46 | clear: both; 47 | } 48 | 49 | .statistics li { 50 | float: left; 51 | padding: 1.5em 2.5em; 52 | } 53 | 54 | /* Footer */ 55 | 56 | #footer > * { 57 | float: left; 58 | } 59 | 60 | #footer .copyright { 61 | clear: both; 62 | } -------------------------------------------------------------------------------- /docs/assets/css/images/overlay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ctidigital/magento2-configurator/69a3a832871289ba2e3468cba7749f7774c0c243/docs/assets/css/images/overlay.png -------------------------------------------------------------------------------- /docs/assets/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ctidigital/magento2-configurator/69a3a832871289ba2e3468cba7749f7774c0c243/docs/assets/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /docs/assets/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ctidigital/magento2-configurator/69a3a832871289ba2e3468cba7749f7774c0c243/docs/assets/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /docs/assets/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ctidigital/magento2-configurator/69a3a832871289ba2e3468cba7749f7774c0c243/docs/assets/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /docs/assets/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ctidigital/magento2-configurator/69a3a832871289ba2e3468cba7749f7774c0c243/docs/assets/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /docs/assets/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ctidigital/magento2-configurator/69a3a832871289ba2e3468cba7749f7774c0c243/docs/assets/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /docs/assets/js/ie/html5shiv.js: -------------------------------------------------------------------------------- 1 | /* 2 | HTML5 Shiv v3.6.2 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed 3 | */ 4 | (function(l,f){function m(){var a=e.elements;return"string"==typeof a?a.split(" "):a}function i(a){var b=n[a[o]];b||(b={},h++,a[o]=h,n[h]=b);return b}function p(a,b,c){b||(b=f);if(g)return b.createElement(a);c||(c=i(b));b=c.cache[a]?c.cache[a].cloneNode():r.test(a)?(c.cache[a]=c.createElem(a)).cloneNode():c.createElem(a);return b.canHaveChildren&&!s.test(a)?c.frag.appendChild(b):b}function t(a,b){if(!b.cache)b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag(); 5 | a.createElement=function(c){return!e.shivMethods?b.createElem(c):p(c,a,b)};a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+m().join().replace(/\w+/g,function(a){b.createElem(a);b.frag.createElement(a);return'c("'+a+'")'})+");return n}")(e,b.frag)}function q(a){a||(a=f);var b=i(a);if(e.shivCSS&&!j&&!b.hasCSS){var c,d=a;c=d.createElement("p");d=d.getElementsByTagName("head")[0]||d.documentElement;c.innerHTML="x"; 6 | c=d.insertBefore(c.lastChild,d.firstChild);b.hasCSS=!!c}g||t(a,b);return a}var k=l.html5||{},s=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,r=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,j,o="_html5shiv",h=0,n={},g;(function(){try{var a=f.createElement("a");a.innerHTML="";j="hidden"in a;var b;if(!(b=1==a.childNodes.length)){f.createElement("a");var c=f.createDocumentFragment();b="undefined"==typeof c.cloneNode|| 7 | "undefined"==typeof c.createDocumentFragment||"undefined"==typeof c.createElement}g=b}catch(d){g=j=!0}})();var e={elements:k.elements||"abbr article aside audio bdi canvas data datalist details figcaption figure footer header hgroup main mark meter nav output progress section summary time video",version:"3.6.2",shivCSS:!1!==k.shivCSS,supportsUnknownElements:g,shivMethods:!1!==k.shivMethods,type:"default",shivDocument:q,createElement:p,createDocumentFragment:function(a,b){a||(a=f);if(g)return a.createDocumentFragment(); 8 | for(var b=b||i(a),c=b.frag.cloneNode(),d=0,e=m(),h=e.length;d #mq-test-1 { width: 42px; }',c.insertBefore(e,d),b=42===f.offsetWidth,c.removeChild(e),{matches:b,media:a}}}(a.document)}(this),function(a){"use strict";function b(){v(!0)}var c={};a.respond=c,c.update=function(){};var d=[],e=function(){var b=!1;try{b=new a.XMLHttpRequest}catch(c){b=new a.ActiveXObject("Microsoft.XMLHTTP")}return function(){return b}}(),f=function(a,b){var c=e();c&&(c.open("GET",a,!0),c.onreadystatechange=function(){4!==c.readyState||200!==c.status&&304!==c.status||b(c.responseText)},4!==c.readyState&&c.send(null))},g=function(a){return a.replace(c.regex.minmaxwh,"").match(c.regex.other)};if(c.ajax=f,c.queue=d,c.unsupportedmq=g,c.regex={media:/@media[^\{]+\{([^\{\}]*\{[^\}\{]*\})+/gi,keyframes:/@(?:\-(?:o|moz|webkit)\-)?keyframes[^\{]+\{(?:[^\{\}]*\{[^\}\{]*\})+[^\}]*\}/gi,comments:/\/\*[^*]*\*+([^/][^*]*\*+)*\//gi,urls:/(url\()['"]?([^\/\)'"][^:\)'"]+)['"]?(\))/g,findStyles:/@media *([^\{]+)\{([\S\s]+?)$/,only:/(only\s+)?([a-zA-Z]+)\s?/,minw:/\(\s*min\-width\s*:\s*(\s*[0-9\.]+)(px|em)\s*\)/,maxw:/\(\s*max\-width\s*:\s*(\s*[0-9\.]+)(px|em)\s*\)/,minmaxwh:/\(\s*m(in|ax)\-(height|width)\s*:\s*(\s*[0-9\.]+)(px|em)\s*\)/gi,other:/\([^\)]*\)/g},c.mediaQueriesSupported=a.matchMedia&&null!==a.matchMedia("only all")&&a.matchMedia("only all").matches,!c.mediaQueriesSupported){var h,i,j,k=a.document,l=k.documentElement,m=[],n=[],o=[],p={},q=30,r=k.getElementsByTagName("head")[0]||l,s=k.getElementsByTagName("base")[0],t=r.getElementsByTagName("link"),u=function(){var a,b=k.createElement("div"),c=k.body,d=l.style.fontSize,e=c&&c.style.fontSize,f=!1;return b.style.cssText="position:absolute;font-size:1em;width:1em",c||(c=f=k.createElement("body"),c.style.background="none"),l.style.fontSize="100%",c.style.fontSize="100%",c.appendChild(b),f&&l.insertBefore(c,l.firstChild),a=b.offsetWidth,f?l.removeChild(c):c.removeChild(b),l.style.fontSize=d,e&&(c.style.fontSize=e),a=j=parseFloat(a)},v=function(b){var c="clientWidth",d=l[c],e="CSS1Compat"===k.compatMode&&d||k.body[c]||d,f={},g=t[t.length-1],p=(new Date).getTime();if(b&&h&&q>p-h)return a.clearTimeout(i),i=a.setTimeout(v,q),void 0;h=p;for(var s in m)if(m.hasOwnProperty(s)){var w=m[s],x=w.minw,y=w.maxw,z=null===x,A=null===y,B="em";x&&(x=parseFloat(x)*(x.indexOf(B)>-1?j||u():1)),y&&(y=parseFloat(y)*(y.indexOf(B)>-1?j||u():1)),w.hasquery&&(z&&A||!(z||e>=x)||!(A||y>=e))||(f[w.media]||(f[w.media]=[]),f[w.media].push(n[w.rules]))}for(var C in o)o.hasOwnProperty(C)&&o[C]&&o[C].parentNode===r&&r.removeChild(o[C]);o.length=0;for(var D in f)if(f.hasOwnProperty(D)){var E=k.createElement("style"),F=f[D].join("\n");E.type="text/css",E.media=D,r.insertBefore(E,g.nextSibling),E.styleSheet?E.styleSheet.cssText=F:E.appendChild(k.createTextNode(F)),o.push(E)}},w=function(a,b,d){var e=a.replace(c.regex.comments,"").replace(c.regex.keyframes,"").match(c.regex.media),f=e&&e.length||0;b=b.substring(0,b.lastIndexOf("/"));var h=function(a){return a.replace(c.regex.urls,"$1"+b+"$2$3")},i=!f&&d;b.length&&(b+="/"),i&&(f=1);for(var j=0;f>j;j++){var k,l,o,p;i?(k=d,n.push(h(a))):(k=e[j].match(c.regex.findStyles)&&RegExp.$1,n.push(RegExp.$2&&h(RegExp.$2))),o=k.split(","),p=o.length;for(var q=0;p>q;q++)l=o[q],g(l)||m.push({media:l.split("(")[0].match(c.regex.only)&&RegExp.$2||"all",rules:n.length-1,hasquery:l.indexOf("(")>-1,minw:l.match(c.regex.minw)&&parseFloat(RegExp.$1)+(RegExp.$2||""),maxw:l.match(c.regex.maxw)&&parseFloat(RegExp.$1)+(RegExp.$2||"")})}v()},x=function(){if(d.length){var b=d.shift();f(b.href,function(c){w(c,b.href,b.media),p[b.href]=!0,a.setTimeout(function(){x()},0)})}},y=function(){for(var b=0;b1){for(var r=0;r=i&&o>=t};break;case"bottom":h=function(t,e,n,i,o){return n>=i&&o>=n};break;case"middle":h=function(t,e,n,i,o){return e>=i&&o>=e};break;case"top-only":h=function(t,e,n,i,o){return i>=t&&n>=i};break;case"bottom-only":h=function(t,e,n,i,o){return n>=o&&o>=t};break;default:case"default":h=function(t,e,n,i,o){return n>=i&&o>=t}}return c=function(t){var i,o,l,s,r,a,u=this.state,h=!1,c=this.$element.offset();i=n.height(),o=t+i/2,l=t+i,s=this.$element.outerHeight(),r=c.top+e(this.options.top,s,i),a=c.top+s-e(this.options.bottom,s,i),h=this.test(t,o,l,r,a),h!=u&&(this.state=h,h?this.options.enter&&this.options.enter.apply(this.element):this.options.leave&&this.options.leave.apply(this.element)),this.options.scroll&&this.options.scroll.apply(this.element,[(o-r)/(a-r)])},p={id:a,options:u,test:h,handler:c,state:null,element:this,$element:s,timeoutId:null},o[a]=p,s.data("_scrollexId",p.id),p.options.initialize&&p.options.initialize.apply(this),s},jQuery.fn.unscrollex=function(){var e=t(this);if(0==this.length)return e;if(this.length>1){for(var n=0;n1){for(o=0;o 0) { 48 | 49 | // Shrink effect. 50 | $main 51 | .scrollex({ 52 | mode: 'top', 53 | enter: function() { 54 | $nav.addClass('alt'); 55 | }, 56 | leave: function() { 57 | $nav.removeClass('alt'); 58 | }, 59 | }); 60 | 61 | // Links. 62 | var $nav_a = $nav.find('a'); 63 | 64 | $nav_a 65 | .scrolly({ 66 | speed: 1000, 67 | offset: function() { return $nav.height(); } 68 | }) 69 | .on('click', function() { 70 | 71 | var $this = $(this); 72 | 73 | // External link? Bail. 74 | if ($this.attr('href').charAt(0) != '#') 75 | return; 76 | 77 | // Deactivate all links. 78 | $nav_a 79 | .removeClass('active') 80 | .removeClass('active-locked'); 81 | 82 | // Activate link *and* lock it (so Scrollex doesn't try to activate other links as we're scrolling to this one's section). 83 | $this 84 | .addClass('active') 85 | .addClass('active-locked'); 86 | 87 | }) 88 | .each(function() { 89 | 90 | var $this = $(this), 91 | id = $this.attr('href'), 92 | $section = $(id); 93 | 94 | // No section for this link? Bail. 95 | if ($section.length < 1) 96 | return; 97 | 98 | // Scrollex. 99 | $section.scrollex({ 100 | mode: 'middle', 101 | initialize: function() { 102 | 103 | // Deactivate section. 104 | if (skel.canUse('transition')) 105 | $section.addClass('inactive'); 106 | 107 | }, 108 | enter: function() { 109 | 110 | // Activate section. 111 | $section.removeClass('inactive'); 112 | 113 | // No locked links? Deactivate all links and activate this section's one. 114 | if ($nav_a.filter('.active-locked').length == 0) { 115 | 116 | $nav_a.removeClass('active'); 117 | $this.addClass('active'); 118 | 119 | } 120 | 121 | // Otherwise, if this section's link is the one that's locked, unlock it. 122 | else if ($this.hasClass('active-locked')) 123 | $this.removeClass('active-locked'); 124 | 125 | } 126 | }); 127 | 128 | }); 129 | 130 | } 131 | 132 | // Scrolly. 133 | $('.scrolly').scrolly({ 134 | speed: 1000 135 | }); 136 | 137 | }); 138 | 139 | })(jQuery); -------------------------------------------------------------------------------- /docs/assets/sass/base/_page.scss: -------------------------------------------------------------------------------- 1 | /// 2 | /// Stellar by HTML5 UP 3 | /// html5up.net | @ajlkn 4 | /// Free for personal and commercial use under the CCA 3.0 license (html5up.net/license) 5 | /// 6 | 7 | /* Basic */ 8 | 9 | // MSIE: Required for IEMobile. 10 | @-ms-viewport { 11 | width: device-width; 12 | } 13 | 14 | // MSIE: Prevents scrollbar from overlapping content. 15 | body { 16 | -ms-overflow-style: scrollbar; 17 | } 18 | 19 | // Ensures page width is always >=320px. 20 | @include breakpoint(xsmall) { 21 | html, body { 22 | min-width: 320px; 23 | } 24 | } 25 | 26 | body { 27 | background-color: _palette(bg); 28 | @include vendor('background-image', ( 29 | 'url("images/overlay.png")', 30 | 'linear-gradient(45deg, #{_palette(bg1)} 15%, #{_palette(bg2) 85%})', 31 | )); 32 | 33 | // Prevents animation/transition "flicker" on page load. 34 | // Automatically added/removed by js/main.js. 35 | &.is-loading { 36 | *, *:before, *:after { 37 | @include vendor('animation', 'none !important'); 38 | @include vendor('transition', 'none !important'); 39 | } 40 | } 41 | 42 | } -------------------------------------------------------------------------------- /docs/assets/sass/base/_typography.scss: -------------------------------------------------------------------------------- 1 | /// 2 | /// Stellar by HTML5 UP 3 | /// html5up.net | @ajlkn 4 | /// Free for personal and commercial use under the CCA 3.0 license (html5up.net/license) 5 | /// 6 | 7 | /* Type */ 8 | 9 | body { 10 | background-color: _palette(bg); 11 | color: _palette(fg); 12 | } 13 | 14 | body, input, select, textarea { 15 | font-family: _font(family); 16 | font-size: 17pt; 17 | font-weight: _font(weight); 18 | line-height: 1.65; 19 | 20 | @include breakpoint(xlarge) { 21 | font-size: 14pt; 22 | } 23 | 24 | @include breakpoint(large) { 25 | font-size: 12pt; 26 | } 27 | 28 | @include breakpoint(xxsmall) { 29 | font-size: 11pt; 30 | } 31 | } 32 | 33 | a { 34 | @include vendor('transition', ( 35 | 'color #{_duration(transition)} ease', 36 | 'border-bottom #{_duration(transition)} ease' 37 | )); 38 | text-decoration: none; 39 | border-bottom: dotted 1px; 40 | color: inherit; 41 | 42 | &:hover { 43 | border-bottom-color: transparent; 44 | } 45 | } 46 | 47 | strong, b { 48 | font-weight: _font(weight-bold); 49 | } 50 | 51 | em, i { 52 | font-style: italic; 53 | } 54 | 55 | p { 56 | margin: 0 0 _size(element-margin) 0; 57 | 58 | &.content { 59 | -moz-columns: 20em 2; 60 | -webkit-columns: 20em 2; 61 | -ms-columns: 20em 2; 62 | columns: 20em 2; 63 | -moz-column-gap: _size(element-margin); 64 | -webkit-column-gap: _size(element-margin); 65 | -ms-column-gap: _size(element-margin); 66 | column-gap: _size(element-margin); 67 | text-align: justify; 68 | } 69 | } 70 | 71 | h1, h2, h3, h4, h5, h6 { 72 | font-weight: _font(weight); 73 | line-height: 1.5; 74 | margin: 0 0 (_size(element-margin) * 0.35) 0; 75 | letter-spacing: _font(letter-spacing); 76 | 77 | a { 78 | color: inherit; 79 | text-decoration: none; 80 | } 81 | } 82 | 83 | h1 { 84 | font-size: 2.5em; 85 | line-height: 1.2; 86 | } 87 | 88 | h2 { 89 | font-size: 1.5em; 90 | } 91 | 92 | h3 { 93 | font-size: 1.25em; 94 | } 95 | 96 | h4 { 97 | font-size: 1.1em; 98 | } 99 | 100 | h5 { 101 | font-size: 0.9em; 102 | } 103 | 104 | h6 { 105 | font-size: 0.7em; 106 | } 107 | 108 | @include breakpoint(small) { 109 | h1 { 110 | font-size: 2em; 111 | } 112 | } 113 | 114 | sub { 115 | font-size: 0.8em; 116 | position: relative; 117 | top: 0.5em; 118 | } 119 | 120 | sup { 121 | font-size: 0.8em; 122 | position: relative; 123 | top: -0.5em; 124 | } 125 | 126 | blockquote { 127 | border-left: solid 4px; 128 | font-style: italic; 129 | margin: 0 0 _size(element-margin) 0; 130 | padding: (_size(element-margin) / 4) 0 (_size(element-margin) / 4) _size(element-margin); 131 | } 132 | 133 | code { 134 | border-radius: _size(border-radius); 135 | border: solid 1px; 136 | font-family: _font(family-fixed); 137 | font-size: 0.9em; 138 | margin: 0 0.25em; 139 | padding: 0.25em 0.65em; 140 | } 141 | 142 | pre { 143 | -webkit-overflow-scrolling: touch; 144 | font-family: _font(family-fixed); 145 | font-size: 0.9em; 146 | margin: 0 0 _size(element-margin) 0; 147 | 148 | code { 149 | display: block; 150 | line-height: 1.75; 151 | padding: 1em 1.5em; 152 | overflow-x: auto; 153 | } 154 | } 155 | 156 | hr { 157 | border: 0; 158 | border-bottom: solid 1px; 159 | margin: _size(element-margin) 0; 160 | 161 | &.major { 162 | margin: (_size(element-margin) * 1.5) 0; 163 | } 164 | } 165 | 166 | .align-left { 167 | text-align: left; 168 | } 169 | 170 | .align-center { 171 | text-align: center; 172 | } 173 | 174 | .align-right { 175 | text-align: right; 176 | } 177 | 178 | @mixin color-typography($p: null) { 179 | @if $p != null { 180 | background-color: _palette($p, bg); 181 | color: _palette($p, fg); 182 | } 183 | 184 | input, select, textarea { 185 | color: _palette($p, fg-bold); 186 | } 187 | 188 | a { 189 | &:hover { 190 | color: _palette($p, fg-bold); 191 | } 192 | } 193 | 194 | strong, b { 195 | color: _palette($p, fg-bold); 196 | } 197 | 198 | h1, h2, h3, h4, h5, h6 { 199 | color: _palette($p, fg-bold); 200 | } 201 | 202 | blockquote { 203 | border-left-color: _palette($p, border); 204 | } 205 | 206 | code { 207 | background: _palette($p, border-bg); 208 | border-color: _palette($p, border); 209 | } 210 | 211 | hr { 212 | border-bottom-color: _palette($p, border); 213 | } 214 | } 215 | 216 | @include color-typography; -------------------------------------------------------------------------------- /docs/assets/sass/components/_box.scss: -------------------------------------------------------------------------------- 1 | /// 2 | /// Stellar by HTML5 UP 3 | /// html5up.net | @ajlkn 4 | /// Free for personal and commercial use under the CCA 3.0 license (html5up.net/license) 5 | /// 6 | 7 | /* Box */ 8 | 9 | .box { 10 | border-radius: _size(border-radius); 11 | border: solid _size(border-width); 12 | margin-bottom: _size(element-margin); 13 | padding: 1.5em; 14 | 15 | > :last-child, 16 | > :last-child > :last-child, 17 | > :last-child > :last-child > :last-child { 18 | margin-bottom: 0; 19 | } 20 | 21 | &.alt { 22 | border: 0; 23 | border-radius: 0; 24 | padding: 0; 25 | } 26 | } 27 | 28 | @mixin color-box($p: null) { 29 | .box { 30 | border-color: _palette($p, border); 31 | } 32 | } 33 | 34 | @include color-box; -------------------------------------------------------------------------------- /docs/assets/sass/components/_button.scss: -------------------------------------------------------------------------------- 1 | /// 2 | /// Stellar by HTML5 UP 3 | /// html5up.net | @ajlkn 4 | /// Free for personal and commercial use under the CCA 3.0 license (html5up.net/license) 5 | /// 6 | 7 | /* Button */ 8 | 9 | input[type="submit"], 10 | input[type="reset"], 11 | input[type="button"], 12 | button, 13 | .button { 14 | @include vendor('appearance', 'none'); 15 | @include vendor('transition', ( 16 | 'background-color #{_duration(transition)} ease-in-out', 17 | 'color #{_duration(transition)} ease-in-out' 18 | )); 19 | border-radius: _size(border-radius); 20 | border: 0; 21 | cursor: pointer; 22 | display: inline-block; 23 | font-weight: _font(weight); 24 | height: 2.75em; 25 | line-height: 2.75em; 26 | min-width: 9.25em; 27 | padding: 0 1.5em; 28 | text-align: center; 29 | text-decoration: none; 30 | white-space: nowrap; 31 | 32 | &.icon { 33 | padding-left: 1.35em; 34 | 35 | &:before { 36 | margin-right: 0.5em; 37 | } 38 | } 39 | 40 | &.fit { 41 | display: block; 42 | margin: 0 0 (_size(element-margin) * 0.5) 0; 43 | width: 100%; 44 | } 45 | 46 | &.small { 47 | font-size: 0.8em; 48 | } 49 | 50 | &.big { 51 | font-size: 1.35em; 52 | } 53 | 54 | &.disabled, 55 | &:disabled { 56 | @include vendor('pointer-events', 'none'); 57 | opacity: 0.25; 58 | } 59 | 60 | @include breakpoint(small) { 61 | min-width: 0; 62 | } 63 | } 64 | 65 | @mixin color-button($p: null) { 66 | input[type="submit"], 67 | input[type="reset"], 68 | input[type="button"], 69 | button, 70 | .button { 71 | background-color: transparent; 72 | box-shadow: inset 0 0 0 1px _palette($p, border); 73 | color: _palette($p, fg-bold) !important; 74 | 75 | &:hover { 76 | background-color: _palette($p, border-bg); 77 | } 78 | 79 | &:active { 80 | background-color: _palette($p, border2-bg); 81 | } 82 | 83 | &.icon { 84 | &:before { 85 | color: _palette($p, fg-light); 86 | } 87 | } 88 | 89 | &.special { 90 | background-color: _palette(accent); 91 | color: _palette(invert, bg) !important; 92 | box-shadow: none; 93 | 94 | &:hover { 95 | background-color: lighten(_palette(accent), 3); 96 | } 97 | 98 | &:active { 99 | background-color: darken(_palette(accent), 3); 100 | } 101 | 102 | &.icon { 103 | &:before { 104 | color: _palette(invert, bg) !important; 105 | } 106 | } 107 | } 108 | } 109 | } 110 | 111 | @include color-button; -------------------------------------------------------------------------------- /docs/assets/sass/components/_features.scss: -------------------------------------------------------------------------------- 1 | /// 2 | /// Stellar by HTML5 UP 3 | /// html5up.net | @ajlkn 4 | /// Free for personal and commercial use under the CCA 3.0 license (html5up.net/license) 5 | /// 6 | 7 | /* Features */ 8 | 9 | .features { 10 | @include vendor('display', 'flex'); 11 | @include vendor('flex-wrap', 'wrap'); 12 | @include vendor('justify-content', 'center'); 13 | width: calc(100% + #{_size(element-margin)}); 14 | margin: 0 0 (_size(element-margin) * 1.5) (_size(element-margin) * -1); 15 | padding: 0; 16 | list-style: none; 17 | 18 | li { 19 | width: calc(#{(100% / 3)} - #{_size(element-margin)}); 20 | margin-left: _size(element-margin); 21 | margin-top: (_size(element-margin) * 1.5); 22 | padding: 0; 23 | 24 | &:nth-child(1), 25 | &:nth-child(2), 26 | &:nth-child(3) { 27 | margin-top: 0; 28 | } 29 | 30 | > :last-child { 31 | margin-bottom: 0; 32 | } 33 | } 34 | 35 | @include breakpoint(medium) { 36 | li { 37 | width: calc(#{(100% / 2)} - #{_size(element-margin)}); 38 | 39 | &:nth-child(3) { 40 | margin-top: (_size(element-margin) * 1.5); 41 | } 42 | } 43 | } 44 | 45 | @include breakpoint(small) { 46 | width: 100%; 47 | margin: 0 0 _size(element-margin) 0; 48 | 49 | li { 50 | width: 100%; 51 | margin-left: 0; 52 | margin-top: _size(element-margin); 53 | 54 | &:nth-child(2), 55 | &:nth-child(3) { 56 | margin-top: _size(element-margin); 57 | } 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /docs/assets/sass/components/_form.scss: -------------------------------------------------------------------------------- 1 | /// 2 | /// Stellar by HTML5 UP 3 | /// html5up.net | @ajlkn 4 | /// Free for personal and commercial use under the CCA 3.0 license (html5up.net/license) 5 | /// 6 | 7 | /* Form */ 8 | 9 | form { 10 | margin: 0 0 _size(element-margin) 0; 11 | } 12 | 13 | label { 14 | display: block; 15 | font-size: 0.9em; 16 | font-weight: _font(weight-bold); 17 | margin: 0 0 (_size(element-margin) * 0.5) 0; 18 | } 19 | 20 | input[type="text"], 21 | input[type="password"], 22 | input[type="email"], 23 | select, 24 | textarea { 25 | @include vendor('appearance', 'none'); 26 | border-radius: _size(border-radius); 27 | border: solid 1px; 28 | color: inherit; 29 | display: block; 30 | outline: 0; 31 | padding: 0 1em; 32 | text-decoration: none; 33 | width: 100%; 34 | 35 | &:invalid { 36 | box-shadow: none; 37 | } 38 | } 39 | 40 | .select-wrapper { 41 | @include icon; 42 | display: block; 43 | position: relative; 44 | 45 | &:before { 46 | content: '\f078'; 47 | display: block; 48 | height: _size(element-height); 49 | line-height: _size(element-height); 50 | pointer-events: none; 51 | position: absolute; 52 | right: 0; 53 | text-align: center; 54 | top: 0; 55 | width: _size(element-height); 56 | } 57 | 58 | select::-ms-expand { 59 | display: none; 60 | } 61 | } 62 | 63 | input[type="text"], 64 | input[type="password"], 65 | input[type="email"], 66 | select { 67 | height: _size(element-height); 68 | } 69 | 70 | textarea { 71 | padding: 0.75em 1em; 72 | } 73 | 74 | input[type="checkbox"], 75 | input[type="radio"], { 76 | @include vendor('appearance', 'none'); 77 | display: block; 78 | float: left; 79 | margin-right: -2em; 80 | opacity: 0; 81 | width: 1em; 82 | z-index: -1; 83 | 84 | & + label { 85 | @include icon; 86 | cursor: pointer; 87 | display: inline-block; 88 | font-size: 1em; 89 | font-weight: _font(weight); 90 | padding-left: (_size(element-height) * 0.6) + 0.75em; 91 | padding-right: 0.75em; 92 | position: relative; 93 | 94 | &:before { 95 | border-radius: _size(border-radius); 96 | border: solid 1px; 97 | content: ''; 98 | display: inline-block; 99 | height: (_size(element-height) * 0.6); 100 | left: 0; 101 | line-height: (_size(element-height) * 0.575); 102 | position: absolute; 103 | text-align: center; 104 | top: 0; 105 | width: (_size(element-height) * 0.6); 106 | } 107 | } 108 | 109 | &:checked + label { 110 | &:before { 111 | content: '\f00c'; 112 | } 113 | } 114 | } 115 | 116 | input[type="checkbox"] { 117 | & + label { 118 | &:before { 119 | border-radius: _size(border-radius); 120 | } 121 | } 122 | } 123 | 124 | input[type="radio"] { 125 | & + label { 126 | &:before { 127 | border-radius: 100%; 128 | } 129 | } 130 | } 131 | 132 | ::-webkit-input-placeholder { 133 | opacity: 1.0; 134 | } 135 | 136 | :-moz-placeholder { 137 | opacity: 1.0; 138 | } 139 | 140 | ::-moz-placeholder { 141 | opacity: 1.0; 142 | } 143 | 144 | :-ms-input-placeholder { 145 | opacity: 1.0; 146 | } 147 | 148 | .formerize-placeholder { 149 | opacity: 1.0; 150 | } 151 | 152 | @mixin color-form($p: null) { 153 | label { 154 | color: _palette($p, fg-bold); 155 | } 156 | 157 | input[type="text"], 158 | input[type="password"], 159 | input[type="email"], 160 | select, 161 | textarea { 162 | background: _palette($p, border-bg); 163 | border-color: _palette($p, border); 164 | 165 | &:focus { 166 | border-color: _palette(accent); 167 | box-shadow: 0 0 0 1px _palette(accent); 168 | } 169 | } 170 | 171 | .select-wrapper { 172 | &:before { 173 | color: _palette($p, border); 174 | } 175 | } 176 | 177 | input[type="checkbox"], 178 | input[type="radio"], { 179 | & + label { 180 | color: _palette($p, fg); 181 | 182 | &:before { 183 | background: _palette($p, border-bg); 184 | border-color: _palette($p, border); 185 | } 186 | } 187 | 188 | &:checked + label { 189 | &:before { 190 | background-color: _palette($p, fg-bold); 191 | border-color: _palette($p, fg-bold); 192 | color: _palette($p, bg); 193 | } 194 | } 195 | 196 | &:focus + label { 197 | &:before { 198 | border-color: _palette(accent); 199 | box-shadow: 0 0 0 1px _palette(accent); 200 | } 201 | } 202 | } 203 | 204 | ::-webkit-input-placeholder { 205 | color: _palette($p, fg-light) !important; 206 | } 207 | 208 | :-moz-placeholder { 209 | color: _palette($p, fg-light) !important; 210 | } 211 | 212 | ::-moz-placeholder { 213 | color: _palette($p, fg-light) !important; 214 | } 215 | 216 | :-ms-input-placeholder { 217 | color: _palette($p, fg-light) !important; 218 | } 219 | 220 | .formerize-placeholder { 221 | color: _palette($p, fg-light) !important; 222 | } 223 | } 224 | 225 | @include color-form; -------------------------------------------------------------------------------- /docs/assets/sass/components/_icon.scss: -------------------------------------------------------------------------------- 1 | /// 2 | /// Stellar by HTML5 UP 3 | /// html5up.net | @ajlkn 4 | /// Free for personal and commercial use under the CCA 3.0 license (html5up.net/license) 5 | /// 6 | 7 | /* Icon */ 8 | 9 | .icon { 10 | @include icon; 11 | @include vendor('transition', ( 12 | 'background-color #{_duration(transition)} ease-in-out', 13 | 'color #{_duration(transition)} ease-in-out' 14 | )); 15 | border-bottom: none; 16 | position: relative; 17 | 18 | > .label { 19 | display: none; 20 | } 21 | 22 | &.major { 23 | border: solid 1px; 24 | display: inline-block; 25 | border-radius: 100%; 26 | padding: 0.65em; 27 | margin: 0 0 _size(element-margin) 0; 28 | cursor: default; 29 | 30 | &:before { 31 | display: inline-block; 32 | font-size: 6.25rem; 33 | width: 2.25em; 34 | height: 2.25em; 35 | line-height: 2.2em; 36 | border-radius: 100%; 37 | border: solid 1px; 38 | text-align: center; 39 | } 40 | } 41 | 42 | &.alt { 43 | display: inline-block; 44 | border: solid 1px; 45 | border-radius: 100%; 46 | 47 | &:before { 48 | display: block; 49 | font-size: 1.25em; 50 | width: 2em; 51 | height: 2em; 52 | text-align: center; 53 | line-height: 2em; 54 | } 55 | } 56 | 57 | &.style1 { 58 | color: _palette(accent1); 59 | } 60 | 61 | &.style2 { 62 | color: _palette(accent2); 63 | } 64 | 65 | &.style3 { 66 | color: _palette(accent3); 67 | } 68 | 69 | &.style4 { 70 | color: _palette(accent4); 71 | } 72 | 73 | &.style5 { 74 | color: _palette(accent5); 75 | } 76 | 77 | @include breakpoint(xlarge) { 78 | &.major { 79 | &:before { 80 | font-size: 5.5rem; 81 | } 82 | } 83 | } 84 | 85 | @include breakpoint(large) { 86 | &.major { 87 | &:before { 88 | font-size: 4.75rem; 89 | } 90 | } 91 | } 92 | 93 | @include breakpoint(small) { 94 | &.major { 95 | margin: 0 0 (_size(element-margin) * 0.75) 0; 96 | padding: 0.35em; 97 | 98 | &:before { 99 | font-size: 3.5rem; 100 | } 101 | } 102 | } 103 | } 104 | 105 | @mixin color-icon($p: null) { 106 | .icon { 107 | &.major { 108 | border-color: _palette($p, border); 109 | 110 | &:before { 111 | border-color: _palette($p, border); 112 | } 113 | } 114 | 115 | &.alt { 116 | border-color: _palette($p, border); 117 | color: _palette($p, fg-bold); 118 | 119 | &:hover { 120 | background-color: _palette($p, border-bg); 121 | } 122 | 123 | &:active { 124 | background-color: _palette($p, border2-bg); 125 | } 126 | } 127 | } 128 | } 129 | 130 | @include color-icon; -------------------------------------------------------------------------------- /docs/assets/sass/components/_image.scss: -------------------------------------------------------------------------------- 1 | /// 2 | /// Stellar by HTML5 UP 3 | /// html5up.net | @ajlkn 4 | /// Free for personal and commercial use under the CCA 3.0 license (html5up.net/license) 5 | /// 6 | 7 | /* Image */ 8 | 9 | .image { 10 | border-radius: _size(border-radius); 11 | border: 0; 12 | display: inline-block; 13 | position: relative; 14 | 15 | img { 16 | border-radius: _size(border-radius); 17 | display: block; 18 | } 19 | 20 | &.left, 21 | &.right { 22 | max-width: 40%; 23 | 24 | img { 25 | width: 100%; 26 | } 27 | } 28 | 29 | &.left { 30 | float: left; 31 | margin: 0 1.5em 1em 0; 32 | top: 0.25em; 33 | } 34 | 35 | &.right { 36 | float: right; 37 | margin: 0 0 1em 1.5em; 38 | top: 0.25em; 39 | } 40 | 41 | &.fit { 42 | display: block; 43 | margin: 0 0 _size(element-margin) 0; 44 | width: 100%; 45 | 46 | img { 47 | width: 100%; 48 | } 49 | } 50 | 51 | &.main { 52 | display: block; 53 | margin: 0 0 (_size(element-margin) * 1.5) 0; 54 | width: 100%; 55 | 56 | img { 57 | width: 100%; 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /docs/assets/sass/components/_list.scss: -------------------------------------------------------------------------------- 1 | /// 2 | /// Stellar by HTML5 UP 3 | /// html5up.net | @ajlkn 4 | /// Free for personal and commercial use under the CCA 3.0 license (html5up.net/license) 5 | /// 6 | 7 | /* List */ 8 | 9 | ol { 10 | list-style: decimal; 11 | margin: 0 0 _size(element-margin) 0; 12 | padding-left: 1.25em; 13 | 14 | li { 15 | padding-left: 0.25em; 16 | } 17 | } 18 | 19 | ul { 20 | list-style: disc; 21 | margin: 0 0 _size(element-margin) 0; 22 | padding-left: 1em; 23 | 24 | li { 25 | padding-left: 0.5em; 26 | } 27 | 28 | &.alt { 29 | list-style: none; 30 | padding-left: 0; 31 | 32 | li { 33 | border-top: solid 1px; 34 | padding: 0.5em 0; 35 | 36 | &:first-child { 37 | border-top: 0; 38 | padding-top: 0; 39 | } 40 | } 41 | } 42 | 43 | &.icons { 44 | cursor: default; 45 | list-style: none; 46 | padding-left: 0; 47 | 48 | li { 49 | display: inline-block; 50 | padding: 0 0.65em 0 0; 51 | 52 | &:last-child { 53 | padding-right: 0 !important; 54 | } 55 | } 56 | } 57 | 58 | &.actions { 59 | cursor: default; 60 | list-style: none; 61 | padding-left: 0; 62 | 63 | li { 64 | display: inline-block; 65 | padding: 0 (_size(element-margin) * 0.5) 0 0; 66 | vertical-align: middle; 67 | 68 | &:last-child { 69 | padding-right: 0; 70 | } 71 | } 72 | 73 | &.small { 74 | li { 75 | padding: 0 (_size(element-margin) * 0.25) 0 0; 76 | } 77 | } 78 | 79 | &.vertical { 80 | li { 81 | display: block; 82 | padding: (_size(element-margin) * 0.5) 0 0 0; 83 | 84 | &:first-child { 85 | padding-top: 0; 86 | } 87 | 88 | > * { 89 | margin-bottom: 0; 90 | } 91 | } 92 | 93 | &.small { 94 | li { 95 | padding: (_size(element-margin) * 0.25) 0 0 0; 96 | 97 | &:first-child { 98 | padding-top: 0; 99 | } 100 | } 101 | } 102 | } 103 | 104 | &.fit { 105 | display: table; 106 | margin-left: (_size(element-margin) * -0.5); 107 | padding: 0; 108 | table-layout: fixed; 109 | width: calc(100% + #{(_size(element-margin) * 0.5)}); 110 | 111 | li { 112 | display: table-cell; 113 | padding: 0 0 0 (_size(element-margin) * 0.5); 114 | 115 | > * { 116 | margin-bottom: 0; 117 | } 118 | } 119 | 120 | &.small { 121 | margin-left: (_size(element-margin) * -0.25); 122 | width: calc(100% + #{(_size(element-margin) * 0.25)}); 123 | 124 | li { 125 | padding: 0 0 0 (_size(element-margin) * 0.25); 126 | } 127 | } 128 | } 129 | 130 | @include breakpoint(xsmall) { 131 | margin: 0 0 _size(element-margin) 0; 132 | 133 | li { 134 | padding: (_size(element-margin) * 0.5) 0 0 0; 135 | display: block; 136 | text-align: center; 137 | width: 100%; 138 | 139 | &:first-child { 140 | padding-top: 0; 141 | } 142 | 143 | > * { 144 | width: 100%; 145 | margin: 0 !important; 146 | } 147 | } 148 | 149 | &.small { 150 | li { 151 | padding: (_size(element-margin) * 0.25) 0 0 0; 152 | 153 | &:first-child { 154 | padding-top: 0; 155 | } 156 | } 157 | } 158 | } 159 | } 160 | } 161 | 162 | dl { 163 | margin: 0 0 _size(element-margin) 0; 164 | 165 | dt { 166 | display: block; 167 | font-weight: _font(weight-bold); 168 | margin: 0 0 (_size(element-margin) * 0.5) 0; 169 | } 170 | 171 | dd { 172 | margin-left: _size(element-margin); 173 | } 174 | 175 | &.alt { 176 | dt { 177 | display: block; 178 | width: 3em; 179 | margin: 0; 180 | clear: left; 181 | float: left; 182 | } 183 | 184 | dd { 185 | margin: 0 0 0.85em 5.5em; 186 | } 187 | 188 | &:after { 189 | content: ''; 190 | display: block; 191 | clear: both; 192 | } 193 | } 194 | } 195 | 196 | @mixin color-list($p: null) { 197 | ul { 198 | &.alt { 199 | li { 200 | border-top-color: _palette($p, border); 201 | } 202 | } 203 | } 204 | 205 | dl { 206 | dt { 207 | color: _palette($p, fg-bold); 208 | } 209 | } 210 | } 211 | 212 | @include color-list; -------------------------------------------------------------------------------- /docs/assets/sass/components/_section.scss: -------------------------------------------------------------------------------- 1 | /// 2 | /// Stellar by HTML5 UP 3 | /// html5up.net | @ajlkn 4 | /// Free for personal and commercial use under the CCA 3.0 license (html5up.net/license) 5 | /// 6 | 7 | /* Section/Article */ 8 | 9 | section, article { 10 | &.special { 11 | text-align: center; 12 | } 13 | } 14 | 15 | header { 16 | &.major { 17 | margin-bottom: (_size(element-margin) * 1.5); 18 | 19 | h2 { 20 | font-size: 2em; 21 | 22 | &:after { 23 | display: block; 24 | content: ''; 25 | width: 3.25em; 26 | height: 2px; 27 | margin: (_size(element-margin) * 0.35) 0 (_size(element-margin) * 0.5) 0; 28 | border-radius: 2px; 29 | 30 | section.special &, article.special & { 31 | margin-left: auto; 32 | margin-right: auto; 33 | } 34 | } 35 | } 36 | 37 | p { 38 | font-size: 1.25em; 39 | letter-spacing: _font(letter-spacing); 40 | } 41 | 42 | &.special { 43 | text-align: center; 44 | 45 | h2 { 46 | &:after { 47 | margin-left: auto; 48 | margin-right: auto; 49 | } 50 | } 51 | } 52 | } 53 | } 54 | 55 | footer { 56 | &.major { 57 | margin-top: (_size(element-margin) * 1.5); 58 | } 59 | } 60 | 61 | @include breakpoint(small) { 62 | header { 63 | &.major { 64 | margin-bottom: 0; 65 | 66 | h2 { 67 | font-size: 1.5em; 68 | } 69 | 70 | p { 71 | font-size: 1em; 72 | letter-spacing: 0; 73 | 74 | br { 75 | display: none; 76 | } 77 | } 78 | } 79 | } 80 | 81 | footer { 82 | &.major { 83 | margin-top: 0; 84 | } 85 | } 86 | } 87 | 88 | @mixin color-section($p: null) { 89 | header { 90 | &.major { 91 | h2 { 92 | &:after { 93 | background-color: _palette($p, border); 94 | 95 | @if $p == 'invert' { 96 | @include vendor('background-image', 'linear-gradient(90deg, #{_palette(accent1)}, #{_palette(accent3)}, #{_palette(accent5)})'); 97 | } 98 | } 99 | } 100 | } 101 | } 102 | } 103 | 104 | @include color-section; -------------------------------------------------------------------------------- /docs/assets/sass/components/_spotlight.scss: -------------------------------------------------------------------------------- 1 | /// 2 | /// Stellar by HTML5 UP 3 | /// html5up.net | @ajlkn 4 | /// Free for personal and commercial use under the CCA 3.0 license (html5up.net/license) 5 | /// 6 | 7 | /* Spotlight */ 8 | 9 | .spotlight { 10 | @include vendor('display', 'flex'); 11 | @include vendor('align-items', 'center'); 12 | margin: 0 0 _size(element-margin) 0; 13 | 14 | .content { 15 | @include vendor('flex', '1'); 16 | 17 | > :last-child { 18 | margin-bottom: 0; 19 | } 20 | 21 | header { 22 | &.major { 23 | margin: 0 0 _size(element-margin) 0; 24 | } 25 | } 26 | } 27 | 28 | .image { 29 | display: inline-block; 30 | margin-left: 4em; 31 | padding: 0.65em; 32 | border-radius: 100%; 33 | border: solid 1px; 34 | 35 | img { 36 | display: block; 37 | border-radius: 100%; 38 | width: 16em; 39 | } 40 | } 41 | 42 | @include breakpoint(medium) { 43 | @include vendor('flex-direction', 'column-reverse'); 44 | text-align: center; 45 | 46 | .content { 47 | @include vendor('flex', '0 1 auto'); 48 | width: 100%; 49 | 50 | header { 51 | &.major { 52 | h2 { 53 | &:after { 54 | margin-left: auto; 55 | margin-right: auto; 56 | } 57 | } 58 | } 59 | } 60 | } 61 | 62 | .image { 63 | @include vendor('flex', '0 1 auto'); 64 | margin-left: 0; 65 | margin-bottom: _size(element-margin); 66 | } 67 | } 68 | 69 | @include breakpoint(small) { 70 | .image { 71 | padding: 0.35em; 72 | 73 | img { 74 | width: 12em; 75 | } 76 | } 77 | } 78 | } 79 | 80 | @mixin color-spotlight($p: null) { 81 | .spotlight { 82 | .image { 83 | border-color: _palette($p, border); 84 | } 85 | } 86 | } 87 | 88 | @include color-spotlight; -------------------------------------------------------------------------------- /docs/assets/sass/components/_statistics.scss: -------------------------------------------------------------------------------- 1 | /// 2 | /// Stellar by HTML5 UP 3 | /// html5up.net | @ajlkn 4 | /// Free for personal and commercial use under the CCA 3.0 license (html5up.net/license) 5 | /// 6 | 7 | /* Statistics */ 8 | 9 | .statistics { 10 | @include vendor('display', 'flex'); 11 | width: 100%; 12 | margin: 0 0 (_size(element-margin) * 1.5) 0; 13 | padding: 0; 14 | list-style: none; 15 | cursor: default; 16 | 17 | li { 18 | @include vendor('flex', '1'); 19 | padding: 1.5em; 20 | color: _palette(fg-bold); 21 | text-align: center; 22 | 23 | &.style1 { 24 | background-color: _palette(accent1); 25 | } 26 | 27 | &.style2 { 28 | background-color: _palette(accent2); 29 | } 30 | 31 | &.style3 { 32 | background-color: _palette(accent3); 33 | } 34 | 35 | &.style4 { 36 | background-color: _palette(accent4); 37 | } 38 | 39 | &.style5 { 40 | background-color: _palette(accent5); 41 | } 42 | 43 | strong, b { 44 | display: block; 45 | font-size: 2em; 46 | line-height: 1.1; 47 | color: inherit !important; 48 | font-weight: _font(weight); 49 | letter-spacing: _font(letter-spacing); 50 | } 51 | 52 | &:first-child { 53 | border-top-left-radius: _size(border-radius); 54 | border-bottom-left-radius: _size(border-radius); 55 | } 56 | 57 | &:last-child { 58 | border-top-right-radius: _size(border-radius); 59 | border-bottom-right-radius: _size(border-radius); 60 | } 61 | 62 | .icon { 63 | display: inline-block; 64 | 65 | &:before { 66 | font-size: 2.75rem; 67 | line-height: 1.3; 68 | } 69 | } 70 | } 71 | 72 | @include breakpoint(medium) { 73 | li { 74 | strong, b { 75 | font-size: 1.5em; 76 | } 77 | } 78 | } 79 | 80 | @include breakpoint(small) { 81 | display: block; 82 | width: 20em; 83 | max-width: 100%; 84 | margin: 0 auto _size(element-margin) auto; 85 | 86 | li { 87 | &:first-child { 88 | border-bottom-left-radius: 0; 89 | border-top-right-radius: _size(border-radius); 90 | } 91 | 92 | &:last-child { 93 | border-top-right-radius: 0; 94 | border-bottom-left-radius: _size(border-radius); 95 | } 96 | 97 | .icon { 98 | &:before { 99 | font-size: 3.75rem; 100 | } 101 | } 102 | 103 | strong, b { 104 | font-size: 2.5em; 105 | } 106 | } 107 | } 108 | } -------------------------------------------------------------------------------- /docs/assets/sass/components/_table.scss: -------------------------------------------------------------------------------- 1 | /// 2 | /// Stellar by HTML5 UP 3 | /// html5up.net | @ajlkn 4 | /// Free for personal and commercial use under the CCA 3.0 license (html5up.net/license) 5 | /// 6 | 7 | /* Table */ 8 | 9 | .table-wrapper { 10 | -webkit-overflow-scrolling: touch; 11 | overflow-x: auto; 12 | } 13 | 14 | table { 15 | margin: 0 0 _size(element-margin) 0; 16 | width: 100%; 17 | 18 | tbody { 19 | tr { 20 | border: solid 1px; 21 | border-left: 0; 22 | border-right: 0; 23 | } 24 | } 25 | 26 | td { 27 | padding: 0.75em 0.75em; 28 | } 29 | 30 | th { 31 | font-size: 0.9em; 32 | font-weight: _font(weight-bold); 33 | padding: 0 0.75em 0.75em 0.75em; 34 | text-align: left; 35 | } 36 | 37 | thead { 38 | border-bottom: solid 2px; 39 | } 40 | 41 | tfoot { 42 | border-top: solid 2px; 43 | } 44 | 45 | &.alt { 46 | border-collapse: separate; 47 | 48 | tbody { 49 | tr { 50 | td { 51 | border: solid 1px; 52 | border-left-width: 0; 53 | border-top-width: 0; 54 | 55 | &:first-child { 56 | border-left-width: 1px; 57 | } 58 | } 59 | 60 | &:first-child { 61 | td { 62 | border-top-width: 1px; 63 | } 64 | } 65 | } 66 | } 67 | 68 | thead { 69 | border-bottom: 0; 70 | } 71 | 72 | tfoot { 73 | border-top: 0; 74 | } 75 | } 76 | } 77 | 78 | @mixin color-table($p: null) { 79 | table { 80 | tbody { 81 | tr { 82 | border-color: _palette($p, border); 83 | 84 | &:nth-child(2n + 1) { 85 | background-color: _palette($p, border-bg); 86 | } 87 | } 88 | } 89 | 90 | th { 91 | color: _palette($p, fg-bold); 92 | } 93 | 94 | thead { 95 | border-bottom-color: _palette($p, border); 96 | } 97 | 98 | tfoot { 99 | border-top-color: _palette($p, border); 100 | } 101 | 102 | &.alt { 103 | tbody { 104 | tr { 105 | td { 106 | border-color: _palette($p, border); 107 | } 108 | } 109 | } 110 | } 111 | } 112 | } 113 | 114 | @include color-table; -------------------------------------------------------------------------------- /docs/assets/sass/ie8.scss: -------------------------------------------------------------------------------- 1 | @import 'libs/vars'; 2 | @import 'libs/functions'; 3 | @import 'libs/mixins'; 4 | @import 'libs/skel'; 5 | 6 | /* 7 | Stellar by HTML5 UP 8 | html5up.net | @ajlkn 9 | Free for personal and commercial use under the CCA 3.0 license (html5up.net/license) 10 | */ 11 | 12 | /* Type */ 13 | 14 | body { 15 | color: _palette(fg-bold); 16 | } 17 | 18 | /* Button */ 19 | 20 | .button { 21 | position: relative; 22 | border: solid 1px !important; 23 | 24 | &.special { 25 | border: none !important; 26 | } 27 | } 28 | 29 | /* Icon */ 30 | 31 | .icon { 32 | &.major { 33 | -ms-behavior: url('assets/js/ie/PIE.htc'); 34 | 35 | &:before { 36 | font-size: 4em; 37 | border: 0; 38 | } 39 | } 40 | } 41 | 42 | /* Spotlight */ 43 | 44 | .spotlight { 45 | .image { 46 | position: relative; 47 | -ms-behavior: url('assets/js/ie/PIE.htc'); 48 | 49 | img { 50 | position: relative; 51 | -ms-behavior: url('assets/js/ie/PIE.htc'); 52 | } 53 | } 54 | } 55 | 56 | /* Features */ 57 | 58 | .features { 59 | li { 60 | width: 29%; 61 | } 62 | } 63 | 64 | /* Footer */ 65 | 66 | #footer { 67 | > * { 68 | width: 50%; 69 | margin-left: 0; 70 | } 71 | } 72 | 73 | /* Main */ 74 | 75 | #main { 76 | > .main { 77 | > .image.main:first-child { 78 | margin-top: 0; 79 | margin-left: 0; 80 | width: 100%; 81 | } 82 | } 83 | } -------------------------------------------------------------------------------- /docs/assets/sass/ie9.scss: -------------------------------------------------------------------------------- 1 | @import 'libs/vars'; 2 | @import 'libs/functions'; 3 | @import 'libs/mixins'; 4 | @import 'libs/skel'; 5 | 6 | /* 7 | Stellar by HTML5 UP 8 | html5up.net | @ajlkn 9 | Free for personal and commercial use under the CCA 3.0 license (html5up.net/license) 10 | */ 11 | 12 | /* Spotlight */ 13 | 14 | .spotlight { 15 | &:after { 16 | content: ''; 17 | display: block; 18 | clear: both; 19 | } 20 | 21 | .content { 22 | float: left; 23 | width: 60%; 24 | } 25 | 26 | .image { 27 | float: left; 28 | } 29 | } 30 | 31 | /* Features */ 32 | 33 | .features { 34 | &:after { 35 | content: ''; 36 | display: block; 37 | clear: both; 38 | } 39 | 40 | li { 41 | float: left; 42 | } 43 | } 44 | 45 | /* Statistics */ 46 | 47 | .statistics { 48 | display: inline-block; 49 | width: auto; 50 | 51 | &:after { 52 | content: ''; 53 | display: block; 54 | clear: both; 55 | } 56 | 57 | li { 58 | float: left; 59 | padding: 1.5em 2.5em; 60 | } 61 | } 62 | 63 | /* Footer */ 64 | 65 | #footer { 66 | > * { 67 | float: left; 68 | } 69 | 70 | .copyright { 71 | clear: both; 72 | } 73 | } -------------------------------------------------------------------------------- /docs/assets/sass/layout/_footer.scss: -------------------------------------------------------------------------------- 1 | /// 2 | /// Stellar by HTML5 UP 3 | /// html5up.net | @ajlkn 4 | /// Free for personal and commercial use under the CCA 3.0 license (html5up.net/license) 5 | /// 6 | 7 | /* Footer */ 8 | 9 | #footer { 10 | @include vendor('display', 'flex'); 11 | @include vendor('flex-wrap', 'wrap'); 12 | @include padding(5em, 5em); 13 | width: calc(100% + #{_size(element-margin)}); 14 | margin: 0 0 (_size(element-margin) * 1.5) (_size(element-margin) * -1); 15 | 16 | > * { 17 | width: calc(50% - #{_size(element-margin)}); 18 | margin-left: _size(element-margin); 19 | } 20 | 21 | .copyright { 22 | width: 100%; 23 | margin: (_size(element-margin) * 1.25) 0 _size(element-margin) 0; 24 | font-size: 0.8em; 25 | text-align: center; 26 | } 27 | 28 | @include breakpoint(large) { 29 | @include padding(4em, 4em); 30 | } 31 | 32 | @include breakpoint(medium) { 33 | @include padding(4em, 3em); 34 | display: block; 35 | margin: 0 0 (_size(element-margin) * 1.5) 0; 36 | width: 100%; 37 | 38 | > * { 39 | width: 100%; 40 | margin-left: 0; 41 | margin-bottom: (_size(element-margin) * 1.5); 42 | } 43 | 44 | .copyright { 45 | text-align: left; 46 | } 47 | } 48 | 49 | @include breakpoint(small) { 50 | @include padding(3em, 2em); 51 | } 52 | 53 | @include breakpoint(xsmall) { 54 | @include padding(3em, 1.5em); 55 | } 56 | 57 | @include breakpoint(xsmall) { 58 | @include padding(2.5em, 1em); 59 | } 60 | } -------------------------------------------------------------------------------- /docs/assets/sass/layout/_header.scss: -------------------------------------------------------------------------------- 1 | /// 2 | /// Stellar by HTML5 UP 3 | /// html5up.net | @ajlkn 4 | /// Free for personal and commercial use under the CCA 3.0 license (html5up.net/license) 5 | /// 6 | 7 | /* Header */ 8 | 9 | #header { 10 | @include padding(5em, 5em, (0, 0, -2em, 0)); 11 | text-align: center; 12 | 13 | h1 { 14 | margin: 0 0 (_size(element-margin) * 0.125) 0; 15 | } 16 | 17 | p { 18 | font-size: 1.25em; 19 | letter-spacing: _font(letter-spacing); 20 | } 21 | 22 | &.alt { 23 | @include padding(6em, 5em, (1em, 0, 0, 0)); 24 | 25 | h1 { 26 | font-size: 3.25em; 27 | } 28 | 29 | > * { 30 | @include vendor('transition', 'opacity 3s ease'); 31 | @include vendor('transition-delay', '0.5s'); 32 | opacity: 1; 33 | } 34 | 35 | .logo { 36 | @include vendor('transition', ( 37 | 'opacity 1.25s ease', 38 | 'transform 0.5s ease' 39 | )); 40 | @include vendor('transition-delay', '0s'); 41 | display: block; 42 | margin: 0 0 (_size(element-margin) * 0.75) 0; 43 | 44 | img { 45 | display: block; 46 | margin: 0 auto; 47 | max-width: 75%; 48 | } 49 | } 50 | } 51 | 52 | @include breakpoint(large) { 53 | @include padding(4em, 4em, (0, 0, -2em, 0)); 54 | 55 | &.alt { 56 | @include padding(5em, 4em, (1em, 0, 0, 0)); 57 | } 58 | } 59 | 60 | @include breakpoint(medium) { 61 | @include padding(4em, 3em, (0, 0, -2em, 0)); 62 | 63 | &.alt { 64 | @include padding(4em, 3em, (1em, 0, 0, 0)); 65 | } 66 | } 67 | 68 | @include breakpoint(small) { 69 | @include padding(3em, 2em, (0, 0, -1em, 0)); 70 | 71 | p { 72 | font-size: 1em; 73 | letter-spacing: 0; 74 | 75 | br { 76 | display: none; 77 | } 78 | } 79 | 80 | &.alt { 81 | @include padding(3em, 2em, (1em, 0, 0, 0)); 82 | 83 | h1 { 84 | font-size: 2.5em; 85 | } 86 | } 87 | } 88 | 89 | @include breakpoint(xsmall) { 90 | @include padding(3em, 1.5em, (0, 0, -1em, 0)); 91 | 92 | &.alt { 93 | @include padding(3em, 1.5em, (1em, 0, 0, 0)); 94 | } 95 | } 96 | 97 | @include breakpoint(xxsmall) { 98 | @include padding(2.5em, 1em, (0, 0, -1em, 0)); 99 | 100 | &.alt { 101 | @include padding(2.5em, 1em, (1em, 0, 0, 0)); 102 | } 103 | } 104 | 105 | body.is-loading & { 106 | &.alt { 107 | > * { 108 | opacity: 0; 109 | } 110 | 111 | .logo { 112 | @include vendor('transform', 'scale(0.8) rotate(-30deg)'); 113 | } 114 | } 115 | } 116 | } -------------------------------------------------------------------------------- /docs/assets/sass/layout/_main.scss: -------------------------------------------------------------------------------- 1 | /// 2 | /// Stellar by HTML5 UP 3 | /// html5up.net | @ajlkn 4 | /// Free for personal and commercial use under the CCA 3.0 license (html5up.net/license) 5 | /// 6 | 7 | /* Main */ 8 | 9 | #main { 10 | @include color(invert); 11 | border-radius: _size(border-radius-main); 12 | 13 | > .main { 14 | @include padding(5em, 5em); 15 | border-top: solid 1px _palette(invert, border); 16 | 17 | &:first-child { 18 | border-top: 0; 19 | } 20 | 21 | > .image.main:first-child { 22 | margin: -5em 0 5em -5em; 23 | width: calc(100% + 10em); 24 | border-top-right-radius: _size(border-radius-main); 25 | border-top-left-radius: _size(border-radius-main); 26 | border-bottom-right-radius: 0; 27 | border-bottom-left-radius: 0; 28 | 29 | img { 30 | border-top-right-radius: _size(border-radius-main); 31 | border-top-left-radius: _size(border-radius-main); 32 | border-bottom-right-radius: 0; 33 | border-bottom-left-radius: 0; 34 | } 35 | } 36 | } 37 | 38 | @include breakpoint(large) { 39 | > .main { 40 | @include padding(4em, 4em); 41 | 42 | > .image.main:first-child { 43 | margin: -4em 0 4em -4em; 44 | width: calc(100% + 8em); 45 | } 46 | } 47 | } 48 | 49 | @include breakpoint(medium) { 50 | > .main { 51 | @include padding(4em, 3em); 52 | 53 | > .image.main:first-child { 54 | margin: -4em 0 4em -3em; 55 | width: calc(100% + 6em); 56 | } 57 | } 58 | } 59 | 60 | @include breakpoint(small) { 61 | > .main { 62 | @include padding(3em, 2em); 63 | 64 | > .image.main:first-child { 65 | margin: -3em 0 2em -2em; 66 | width: calc(100% + 4em); 67 | } 68 | } 69 | } 70 | 71 | @include breakpoint(xsmall) { 72 | > .main { 73 | @include padding(3em, 1.5em); 74 | 75 | > .image.main:first-child { 76 | margin: -3em 0 1.5em -1.5em; 77 | width: calc(100% + 3em); 78 | } 79 | } 80 | } 81 | 82 | @include breakpoint(xxsmall) { 83 | border-radius: 0; 84 | 85 | > .main { 86 | @include padding(2.5em, 1em); 87 | 88 | > .image.main:first-child { 89 | margin: -2.5em 0 1.5em -1em; 90 | width: calc(100% + 2em); 91 | border-radius: 0; 92 | 93 | img { 94 | border-radius: 0; 95 | } 96 | } 97 | } 98 | } 99 | } -------------------------------------------------------------------------------- /docs/assets/sass/layout/_nav.scss: -------------------------------------------------------------------------------- 1 | /// 2 | /// Stellar by HTML5 UP 3 | /// html5up.net | @ajlkn 4 | /// Free for personal and commercial use under the CCA 3.0 license (html5up.net/license) 5 | /// 6 | 7 | /* Nav */ 8 | 9 | #nav { 10 | @include vendor('transition', ( 11 | 'background-color #{_duration(transition)} ease', 12 | 'border-top-left-radius #{_duration(transition)} ease', 13 | 'border-top-right-radius #{_duration(transition)} ease', 14 | 'padding #{_duration(transition)} ease', 15 | )); 16 | @include color-typography(invert); 17 | position: absolute; 18 | width: _size(inner); 19 | max-width: calc(100% - #{_size(element-margin) * 2}); 20 | padding: 1em; 21 | background-color: _palette(invert, bg-alt); 22 | border-top-left-radius: _size(border-radius-main); 23 | border-top-right-radius: _size(border-radius-main); 24 | cursor: default; 25 | text-align: center; 26 | 27 | & + #main { 28 | padding-top: 4.25em; 29 | } 30 | 31 | ul { 32 | margin: 0; 33 | padding: 0; 34 | list-style: none; 35 | 36 | li { 37 | @include vendor('transition', ( 38 | 'margin #{_duration(transition)} ease' 39 | )); 40 | display: inline-block; 41 | margin: 0 0.35em; 42 | padding: 0; 43 | vertical-align: middle; 44 | 45 | a { 46 | @include vendor('transition', ( 47 | 'font-size #{_duration(transition)} ease' 48 | )); 49 | display: inline-block; 50 | height: 2.25em; 51 | line-height: 2.25em; 52 | padding: 0 1.25em; 53 | border: 0; 54 | border-radius: _size(border-radius); 55 | box-shadow: inset 0 0 0 1px transparent; 56 | 57 | &:hover { 58 | background-color: _palette(invert, border-bg); 59 | } 60 | 61 | &.active { 62 | background-color: _palette(invert, bg); 63 | box-shadow: none; 64 | } 65 | } 66 | } 67 | } 68 | 69 | &.alt { 70 | position: fixed; 71 | top: 0; 72 | padding: 0.5em 1em; 73 | background-color: transparentize(_palette(invert, bg-alt), 0.05); 74 | border-top-left-radius: 0; 75 | border-top-right-radius: 0; 76 | z-index: _misc(z-index-base); 77 | 78 | ul { 79 | li { 80 | margin: 0 0.175em; 81 | 82 | a { 83 | font-size: 0.9em; 84 | } 85 | } 86 | } 87 | } 88 | 89 | @include breakpoint(small) { 90 | display: none; 91 | 92 | & + #main { 93 | padding-top: 0; 94 | } 95 | } 96 | } -------------------------------------------------------------------------------- /docs/assets/sass/layout/_wrapper.scss: -------------------------------------------------------------------------------- 1 | /// 2 | /// Stellar by HTML5 UP 3 | /// html5up.net | @ajlkn 4 | /// Free for personal and commercial use under the CCA 3.0 license (html5up.net/license) 5 | /// 6 | 7 | /* Wrapper */ 8 | 9 | #wrapper { 10 | width: _size(inner); 11 | max-width: calc(100% - 4em); 12 | margin: 0 auto; 13 | 14 | @include breakpoint(xsmall) { 15 | max-width: calc(100% - 2em); 16 | } 17 | 18 | @include breakpoint(xxsmall) { 19 | max-width: 100%; 20 | } 21 | } -------------------------------------------------------------------------------- /docs/assets/sass/libs/_functions.scss: -------------------------------------------------------------------------------- 1 | /// Gets a duration value. 2 | /// @param {string} $keys Key(s). 3 | /// @return {string} Value. 4 | @function _duration($keys...) { 5 | @return val($duration, $keys...); 6 | } 7 | 8 | /// Gets a font value. 9 | /// @param {string} $keys Key(s). 10 | /// @return {string} Value. 11 | @function _font($keys...) { 12 | @return val($font, $keys...); 13 | } 14 | 15 | /// Gets a misc value. 16 | /// @param {string} $keys Key(s). 17 | /// @return {string} Value. 18 | @function _misc($keys...) { 19 | @return val($misc, $keys...); 20 | } 21 | 22 | /// Gets a palette value. 23 | /// @param {string} $keys Key(s). 24 | /// @return {string} Value. 25 | @function _palette($keys...) { 26 | @return val($palette, $keys...); 27 | } 28 | 29 | /// Gets a size value. 30 | /// @param {string} $keys Key(s). 31 | /// @return {string} Value. 32 | @function _size($keys...) { 33 | @return val($size, $keys...); 34 | } -------------------------------------------------------------------------------- /docs/assets/sass/libs/_mixins.scss: -------------------------------------------------------------------------------- 1 | /// Makes an element's :before pseudoelement a FontAwesome icon. 2 | /// @param {string} $content Optional content value to use. 3 | /// @param {string} $where Optional pseudoelement to target (before or after). 4 | @mixin icon($content: false, $where: before) { 5 | 6 | text-decoration: none; 7 | 8 | &:#{$where} { 9 | 10 | @if $content { 11 | content: $content; 12 | } 13 | 14 | -moz-osx-font-smoothing: grayscale; 15 | -webkit-font-smoothing: antialiased; 16 | font-family: FontAwesome; 17 | font-style: normal; 18 | font-weight: normal; 19 | text-transform: none !important; 20 | 21 | } 22 | 23 | } 24 | 25 | /// Applies padding to an element, taking the current element-margin value into account. 26 | /// @param {mixed} $tb Top/bottom padding. 27 | /// @param {mixed} $lr Left/right padding. 28 | /// @param {list} $pad Optional extra padding (in the following order top, right, bottom, left) 29 | /// @param {bool} $important If true, adds !important. 30 | @mixin padding($tb, $lr, $pad: (0,0,0,0), $important: null) { 31 | 32 | @if $important { 33 | $important: '!important'; 34 | } 35 | 36 | padding: ($tb + nth($pad,1)) ($lr + nth($pad,2)) max(0.1em, $tb - _size(element-margin) + nth($pad,3)) ($lr + nth($pad,4)) #{$important}; 37 | 38 | } 39 | 40 | /// Encodes a SVG data URL so IE doesn't choke (via codepen.io/jakob-e/pen/YXXBrp). 41 | /// @param {string} $svg SVG data URL. 42 | /// @return {string} Encoded SVG data URL. 43 | @function svg-url($svg) { 44 | 45 | $svg: str-replace($svg, '"', '\''); 46 | $svg: str-replace($svg, '<', '%3C'); 47 | $svg: str-replace($svg, '>', '%3E'); 48 | $svg: str-replace($svg, '&', '%26'); 49 | $svg: str-replace($svg, '#', '%23'); 50 | $svg: str-replace($svg, '{', '%7B'); 51 | $svg: str-replace($svg, '}', '%7D'); 52 | $svg: str-replace($svg, ';', '%3B'); 53 | 54 | @return url("data:image/svg+xml;charset=utf8,#{$svg}"); 55 | 56 | } -------------------------------------------------------------------------------- /docs/assets/sass/libs/_vars.scss: -------------------------------------------------------------------------------- 1 | // Misc. 2 | $misc: ( 3 | z-index-base: 10000 4 | ); 5 | 6 | // Duration. 7 | $duration: ( 8 | transition: 0.2s 9 | ); 10 | 11 | // Size. 12 | $size: ( 13 | border-radius: 8px, 14 | border-radius-main: 0.25em, 15 | element-height: 2.75em, 16 | element-margin: 2em, 17 | inner: 64em 18 | ); 19 | 20 | // Font. 21 | $font: ( 22 | family: ('Source Sans Pro', Helvetica, sans-serif), 23 | family-fixed: ('Courier New', monospace), 24 | weight: 300, 25 | weight-bold: 400, 26 | letter-spacing: -0.025em 27 | ); 28 | 29 | // Palette. 30 | $palette: ( 31 | bg: #935d8c, 32 | fg: rgba(255,255,255,0.65), 33 | fg-bold: #ffffff, 34 | fg-light: rgba(255,255,255,0.5), 35 | border: rgba(255,255,255,0.35), 36 | border-bg: rgba(255,255,255,0.075), 37 | border2: rgba(255,255,255,0.75), 38 | border2-bg: rgba(255,255,255,0.2), 39 | 40 | invert: ( 41 | bg: #ffffff, 42 | bg-alt: #f7f7f7, 43 | fg: #636363, 44 | fg-bold: #636363, 45 | fg-light: rgba(99,99,99,0.25), 46 | border: #dddddd, 47 | border-bg: rgba(222,222,222,0.25), 48 | border2: #dddddd, 49 | border2-bg: rgba(222,222,222,0.5), 50 | ), 51 | 52 | accent: #8cc9f0, 53 | accent1: #efa8b0, 54 | accent2: #c79cc8, 55 | accent3: #a89cc8, 56 | accent4: #9bb2e1, 57 | accent5: #8cc9f0, 58 | bg1: #76a1e3, 59 | bg2: #93674d 60 | ); 61 | -------------------------------------------------------------------------------- /docs/assets/sass/main.scss: -------------------------------------------------------------------------------- 1 | @import 'libs/vars'; 2 | @import 'libs/functions'; 3 | @import 'libs/mixins'; 4 | @import 'libs/skel'; 5 | @import 'font-awesome.min.css'; 6 | @import 'https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400'; 7 | 8 | @include skel-breakpoints(( 9 | xlarge: '(max-width: 1680px)', 10 | large: '(max-width: 1280px)', 11 | medium: '(max-width: 980px)', 12 | small: '(max-width: 736px)', 13 | xsmall: '(max-width: 480px)', 14 | xxsmall: '(max-width: 360px)' 15 | )); 16 | 17 | @include skel-layout(( 18 | reset: 'full', 19 | boxModel: 'border', 20 | grid: ( gutters: 1.5em ), 21 | breakpoints: ( 22 | small: ( 23 | grid: ( gutters: 1em ), 24 | ) 25 | ) 26 | )); 27 | 28 | @mixin color($p) { 29 | @include color-typography($p); 30 | @include color-box($p); 31 | @include color-button($p); 32 | @include color-form($p); 33 | @include color-icon($p); 34 | @include color-list($p); 35 | @include color-section($p); 36 | @include color-table($p); 37 | @include color-spotlight($p); 38 | } 39 | 40 | // Base. 41 | 42 | @import 'base/page'; 43 | @import 'base/typography'; 44 | 45 | // Component. 46 | 47 | @import 'components/box'; 48 | @import 'components/button'; 49 | @import 'components/form'; 50 | @import 'components/icon'; 51 | @import 'components/image'; 52 | @import 'components/list'; 53 | @import 'components/section'; 54 | @import 'components/table'; 55 | @import 'components/features'; 56 | @import 'components/statistics'; 57 | @import 'components/spotlight'; 58 | 59 | // Layout. 60 | 61 | @import 'layout/header'; 62 | @import 'layout/nav'; 63 | @import 'layout/main'; 64 | @import 'layout/footer'; 65 | @import 'layout/wrapper'; -------------------------------------------------------------------------------- /docs/images/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 53 | 55 | 56 | 57 | 60 | 61 | 62 | 63 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 78 | 80 | 81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /etc/di.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 7 | 8 | 9 | 11 | 13 | 14 | 15 | 16 | 17 | CtiDigital\Configurator\Console\Command\ListCommand 18 | 19 | CtiDigital\Configurator\Console\Command\RunCommand 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | CtiDigital\Configurator\Component\Websites 29 | CtiDigital\Configurator\Component\Config 30 | CtiDigital\Configurator\Component\Sequence 31 | CtiDigital\Configurator\Component\Attributes 32 | CtiDigital\Configurator\Component\AttributeSets 33 | CtiDigital\Configurator\Component\AdminRoles 34 | CtiDigital\Configurator\Component\AdminUsers 35 | CtiDigital\Configurator\Component\CustomerGroups 36 | CtiDigital\Configurator\Component\Categories 37 | CtiDigital\Configurator\Component\TaxRates 38 | CtiDigital\Configurator\Component\TaxRules 39 | CtiDigital\Configurator\Component\Products 40 | CtiDigital\Configurator\Component\Blocks 41 | CtiDigital\Configurator\Component\Pages 42 | CtiDigital\Configurator\Component\ApiIntegrations 43 | CtiDigital\Configurator\Component\Widgets 44 | CtiDigital\Configurator\Component\Media 45 | CtiDigital\Configurator\Component\Rewrites 46 | CtiDigital\Configurator\Component\ReviewRating 47 | CtiDigital\Configurator\Component\ProductLinks 48 | CtiDigital\Configurator\Component\Customers 49 | 50 | CtiDigital\Configurator\Component\CatalogPriceRules\Proxy 51 | 52 | CtiDigital\Configurator\Component\Sql 53 | CtiDigital\Configurator\Component\ShippingTableRates 54 | 55 | 56 | CtiDigital\Configurator\Component\CustomerAttributes 57 | 58 | CtiDigital\Configurator\Component\TieredPrices 59 | 60 | 61 | 62 | 63 | 64 | 65 | Magento\Framework\Module\Setup\Context 66 | 67 | 68 | 69 | 70 | 71 | CtiDigital\Configurator\Setup\Module\DataSetup 72 | 73 | 74 | 75 | 76 | 77 | 78 | CtiDigital\Configurator\Component\CatalogPriceRules\CatalogPriceRulesProcessor 79 | 80 | 81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /etc/module.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /phpunit_config.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | ../../../vendor/ctidigital/magento2-configurator/Test/Unit 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | ../../../vendor/ctidigital/magento2-configurator 19 | 20 | ../../../app/code/*/*/Test 21 | ../../../lib/internal/*/*/Test 22 | ../../../lib/internal/*/*/*/Test 23 | ../../../setup/src/*/*/Test 24 | ../../../update/app/code/*/*/Test 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /registration.php: -------------------------------------------------------------------------------- 1 |