├── .gitignore ├── Basecom └── Bundle │ └── RulesEngineBundle │ ├── BasecomRulesEngine.php │ ├── Controller │ ├── OverwriteRuleController.php │ └── RuleController.php │ ├── DTO │ ├── Action.php │ ├── Condition.php │ └── RuleDefinition.php │ ├── DependencyInjection │ └── BasecomRulesEngineExtension.php │ ├── Engine │ └── ProductRuleBuilder.php │ ├── Form │ ├── DataTransformer │ │ └── AttributeCodeToAttributeTransformer.php │ └── Type │ │ ├── ActionType.php │ │ ├── ConditionType.php │ │ └── RuleDefinitionType.php │ ├── README.md │ └── Resources │ ├── config │ ├── acl.yml │ ├── assets.yml │ ├── controllers.yml │ ├── datagrid │ │ └── rule.yml │ ├── engine.yml │ ├── form_extensions │ │ └── rules_index.yml │ ├── form_types.yml │ ├── requirejs.yml │ └── routing │ │ └── rules.yml │ ├── public │ ├── css │ │ └── rules.less │ ├── js │ │ └── edit.js │ ├── less │ │ └── index.less │ └── logo.png │ ├── translations │ ├── jsmessages.de.yml │ ├── jsmessages.en.yml │ ├── messages.de.yml │ └── messages.en.yml │ └── views │ ├── OverwriteRule │ └── index.html.twig │ ├── Rule │ ├── _actions.html.twig │ └── edit.html.twig │ ├── form_themes.html.twig │ └── macros.html.twig ├── LICENSE ├── README.md └── composer.json /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | vendor/ 3 | composer.lock 4 | 5 | -------------------------------------------------------------------------------- /Basecom/Bundle/RulesEngineBundle/BasecomRulesEngine.php: -------------------------------------------------------------------------------- 1 | 22 | * @author Jordan Kniest 23 | */ 24 | class OverwriteRuleController extends BaseRuleController 25 | { 26 | /** 27 | * List all rules. 28 | * 29 | * @Template 30 | * 31 | * @AclAncestor("pimee_catalog_rule_rule_view_permissions") 32 | * 33 | * @return array 34 | */ 35 | public function indexAction(): array 36 | { 37 | return []; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Basecom/Bundle/RulesEngineBundle/Controller/RuleController.php: -------------------------------------------------------------------------------- 1 | 31 | * @author Jordan Kniest 32 | */ 33 | class RuleController extends AbstractController 34 | { 35 | /** 36 | * @var RouterInterface 37 | */ 38 | protected $router; 39 | 40 | /** 41 | * @var RuleDefinitionSaver 42 | */ 43 | protected $ruleDefinitionSaver; 44 | 45 | /** 46 | * @var ValidatorInterface 47 | */ 48 | protected $validator; 49 | 50 | /** 51 | * @var DenormalizerInterface 52 | */ 53 | protected $denormalizer; 54 | 55 | /** 56 | * @var FormFactory 57 | */ 58 | protected $formFactory; 59 | 60 | /** 61 | * @var RuleDefinitionRepository 62 | */ 63 | protected $ruleDefinitionRepository; 64 | 65 | /** 66 | * @var AttributeRepositoryInterface 67 | */ 68 | protected $attributeRepository; 69 | 70 | /** 71 | * @var ProductRuleBuilder 72 | */ 73 | protected $productRuleBuilder; 74 | 75 | /** 76 | * Constructor of RuleController. 77 | * 78 | * @param RouterInterface $router 79 | * @param RuleDefinitionSaver $ruleDefinitionSaver 80 | * @param ValidatorInterface $validator 81 | * @param DenormalizerInterface $denormalizer 82 | * @param FormFactory $formFactory 83 | * @param RuleDefinitionRepository $ruleDefinitionRepository 84 | * @param AttributeRepositoryInterface $attributeRepository 85 | * @param ProductRuleBuilder $productRuleBuilder 86 | */ 87 | public function __construct( 88 | RouterInterface $router, 89 | RuleDefinitionSaver $ruleDefinitionSaver, 90 | ValidatorInterface $validator, 91 | DenormalizerInterface $denormalizer, 92 | FormFactory $formFactory, 93 | RuleDefinitionRepository $ruleDefinitionRepository, 94 | AttributeRepositoryInterface $attributeRepository, 95 | ProductRuleBuilder $productRuleBuilder 96 | ) { 97 | $this->router = $router; 98 | $this->ruleDefinitionSaver = $ruleDefinitionSaver; 99 | $this->validator = $validator; 100 | $this->denormalizer = $denormalizer; 101 | $this->formFactory = $formFactory; 102 | $this->ruleDefinitionRepository = $ruleDefinitionRepository; 103 | $this->attributeRepository = $attributeRepository; 104 | $this->productRuleBuilder = $productRuleBuilder; 105 | } 106 | 107 | /** 108 | * Create rule. 109 | * 110 | * @AclAncestor("basecom_rule_create") 111 | * 112 | * @param Request $request 113 | * 114 | * @return Response 115 | */ 116 | public function createAction(Request $request): Response 117 | { 118 | $ruleDefinition = new RuleDefinition(); 119 | 120 | return $this->editAction($request, $ruleDefinition); 121 | } 122 | 123 | /** 124 | * Edit rule. 125 | * 126 | * @AclAncestor("basecom_rule_edit") 127 | * 128 | * @param Request $request 129 | * @param RuleDefinition $ruleDefinition 130 | * 131 | * @return Response 132 | */ 133 | public function editAction(Request $request, RuleDefinition $ruleDefinition): Response 134 | { 135 | $ruleDefinitionData = RuleDefinitionDTO::fromEntity($ruleDefinition); 136 | $form = $this->formFactory->create(RuleDefinitionType::class, $ruleDefinitionData); 137 | $form->handleRequest($request); 138 | 139 | /** @var Attribute[] $attributes */ 140 | $attributes = $this->attributeRepository->findAll(); 141 | $attributesData = []; 142 | foreach ($attributes as $attribute) { 143 | $attributesData[$attribute->getCode()] = [ 144 | 'is_localizable' => $attribute->isLocalizable(), 145 | 'is_scopable' => $attribute->isScopable(), 146 | 'is_metric' => $attribute->getMetricFamily() ? true : false, 147 | 'is_price' => AttributeTypes::PRICE_COLLECTION === $attribute->getType() ? true : false, 148 | ]; 149 | } 150 | 151 | if (!$form->isSubmitted() || !$form->isValid()) { 152 | return $this->render('BasecomRulesEngine:Rule:edit.html.twig', [ 153 | 'form' => $form->createView(), 154 | 'attributesData' => $attributesData, 155 | 'ruleDefinition' => $ruleDefinition, 156 | ]); 157 | } 158 | 159 | $ruleDefinition = $ruleDefinitionData->getEntity(); 160 | $violations = $this->validator->validate($ruleDefinition); 161 | 162 | foreach ($violations as $violation) { 163 | /** @var ConstraintViolation $violation */ 164 | $violationString = sprintf('%s: %s', $violation->getPropertyPath(), $violation->getMessage()); 165 | $form->addError(new FormError($violationString)); 166 | } 167 | 168 | $ruleViolations = []; 169 | try { 170 | $rule = $this->productRuleBuilder->getRuleByRuleDefinition($ruleDefinition); 171 | $ruleViolations = $this->productRuleBuilder->validateRule($rule); 172 | } catch (\LogicException $e) { 173 | $form->addError(new FormError($e->getMessage())); 174 | } 175 | 176 | foreach ($ruleViolations as $ruleViolation) { 177 | /* @var ConstraintViolationInterface $ruleViolation */ 178 | $form->addError(new FormError($ruleViolation->getMessage())); 179 | } 180 | 181 | if (!$form->isValid()) { 182 | $this->addFlash('error', 'basecom.flash.rule.error.save'); 183 | 184 | return $this->render('BasecomRulesEngine:Rule:edit.html.twig', [ 185 | 'form' => $form->createView(), 186 | 'attributesData' => $attributesData, 187 | 'ruleDefinition' => $ruleDefinition, 188 | ]); 189 | } 190 | 191 | $this->ruleDefinitionSaver->save($ruleDefinition); 192 | $ruleDefinition = $this->ruleDefinitionRepository->findOneByIdentifier($ruleDefinition->getCode()); 193 | $this->addFlash('success', 'basecom.flash.rule.saved'); 194 | 195 | return new RedirectResponse( 196 | $this->router->generate('basecom_rule_edit', ['id' => $ruleDefinition->getId()]) 197 | ); 198 | } 199 | 200 | /** 201 | * Remove rule. 202 | * 203 | * @AclAncestor("basecom_rule_remove") 204 | * 205 | * @param Request $request 206 | * 207 | * @return RedirectResponse|Response 208 | */ 209 | public function removeAction(Request $request): Response 210 | { 211 | if ($request->isXmlHttpRequest()) { 212 | return new Response('', 204); 213 | } 214 | 215 | return new RedirectResponse($this->router->generate('pimee_catalog_rule_rule_index')); 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /Basecom/Bundle/RulesEngineBundle/DTO/Action.php: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | class Action 9 | { 10 | /** 11 | * @var string 12 | */ 13 | public $type; 14 | 15 | /** 16 | * @var string 17 | */ 18 | public $field; 19 | 20 | /** 21 | * @var string 22 | */ 23 | public $value; 24 | 25 | /** 26 | * @var [] 27 | */ 28 | public $items; 29 | 30 | /** 31 | * @var string 32 | */ 33 | public $locale; 34 | 35 | /** 36 | * @var string 37 | */ 38 | public $scope; 39 | 40 | /** 41 | * @var string 42 | */ 43 | public $fromField; 44 | 45 | /** 46 | * @var string 47 | */ 48 | public $fromLocale; 49 | 50 | /** 51 | * @var string 52 | */ 53 | public $fromScope; 54 | 55 | /** 56 | * @var string 57 | */ 58 | public $toField; 59 | 60 | /** 61 | * @var string 62 | */ 63 | public $toLocale; 64 | 65 | /** 66 | * @var string 67 | */ 68 | public $unit; 69 | 70 | /** 71 | * @var string 72 | */ 73 | public $toScope; 74 | 75 | /** 76 | * @return array 77 | */ 78 | public function getData(): array 79 | { 80 | $data = ['type' => $this->type]; 81 | 82 | switch ($this->type) { 83 | case 'copy': 84 | $data['from_field'] = $this->fromField; 85 | $data['to_field'] = $this->toField; 86 | if ('' !== $this->fromLocale) { 87 | $data['from_locale'] = $this->fromLocale; 88 | } 89 | if ('' !== $this->toLocale) { 90 | $data['to_locale'] = $this->toLocale; 91 | } 92 | if ('' !== $this->fromScope) { 93 | $data['from_scope'] = $this->fromScope; 94 | } 95 | if ('' !== $this->toScope) { 96 | $data['to_scope'] = $this->toScope; 97 | } 98 | break; 99 | case 'add': 100 | $data['field'] = $this->field; 101 | $data['items'] = $this->items; 102 | if ('' !== $this->locale) { 103 | $data['locale'] = $this->locale; 104 | } 105 | if ('' !== $this->scope) { 106 | $data['scope'] = $this->scope; 107 | } 108 | break; 109 | case 'set': 110 | $data['field'] = $this->field; 111 | $data['value'] = $this->value; 112 | if ('' !== $this->locale) { 113 | $data['locale'] = $this->locale; 114 | } 115 | if ('' !== $this->scope) { 116 | $data['scope'] = $this->scope; 117 | } 118 | break; 119 | case 'remove': 120 | $data['field'] = $this->field; 121 | $data['items'] = $this->items; 122 | if ('' !== $this->locale) { 123 | $data['locale'] = $this->locale; 124 | } 125 | if ('' !== $this->scope) { 126 | $data['scope'] = $this->scope; 127 | } 128 | break; 129 | } 130 | 131 | return $data; 132 | } 133 | 134 | /** 135 | * @param array $data 136 | * 137 | * @return Action 138 | */ 139 | public static function fromData(array $data): self 140 | { 141 | $action = new self(); 142 | $action->type = $data['type']; 143 | if (array_key_exists('field', $data)) { 144 | $action->field = $data['field']; 145 | } 146 | if (array_key_exists('value', $data)) { 147 | $action->value = $data['value']; 148 | } 149 | if (array_key_exists('items', $data)) { 150 | $action->items = $data['items']; 151 | } 152 | if (array_key_exists('locale', $data)) { 153 | $action->locale = $data['locale']; 154 | } 155 | if (array_key_exists('scope', $data)) { 156 | $action->scope = $data['scope']; 157 | } 158 | if (array_key_exists('from_field', $data)) { 159 | $action->fromField = $data['from_field']; 160 | } 161 | if (array_key_exists('from_locale', $data)) { 162 | $action->fromLocale = $data['from_locale']; 163 | } 164 | if (array_key_exists('from_scope', $data)) { 165 | $action->fromScope = $data['from_scope']; 166 | } 167 | if (array_key_exists('to_field', $data)) { 168 | $action->toField = $data['to_field']; 169 | } 170 | if (array_key_exists('to_locale', $data)) { 171 | $action->toLocale = $data['to_locale']; 172 | } 173 | if (array_key_exists('to_scope', $data)) { 174 | $action->toScope = $data['to_scope']; 175 | } 176 | 177 | return $action; 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /Basecom/Bundle/RulesEngineBundle/DTO/Condition.php: -------------------------------------------------------------------------------- 1 | 10 | * @author Jordan Kniest 11 | */ 12 | class Condition 13 | { 14 | const OPERATORS = [ 15 | 'starts_with' => 'STARTS WITH', 16 | 'ends_with' => 'ENDS WITH', 17 | 'contains' => 'CONTAINS', 18 | 'does_not_contain' => 'DOES NOT CONTAIN', 19 | 'equal' => '=', 20 | 'not_equal' => '!=', 21 | 'empty' => 'EMPTY', 22 | 'not_empty' => 'NOT EMPTY', 23 | 'between' => 'BETWEEN', 24 | 'not_between' => 'NOT BETWEEN', 25 | 'in' => 'IN', 26 | 'not_in' => 'NOT IN', 27 | 'unclassified' => 'UNCLASSIFIED', 28 | 'in_or_unclassified' => 'IN OR UNCLASSIFIED', 29 | 'in_children' => 'IN CHILDREN', 30 | 'not_in_children' => 'NOT IN CHILDREN', 31 | 'greater' => '>', 32 | 'greater_or_equal' => '>=', 33 | 'smaller' => '<', 34 | 'smaller_or_equal' => '<=', 35 | 'at least complete' => 'AT_LEAST_COMPLETE', 36 | 'at least incomplete'=> 'AT_LEAST_INCOMPLETE', 37 | ]; 38 | 39 | const MULTIPLE_VALUE_OPERATORS = [ 40 | 'between' => 'BETWEEN', 41 | 'not_between' => 'NOT BETWEEN', 42 | 'in' => 'IN', 43 | 'not_in' => 'NOT IN', 44 | 'in_or_unclassified' => 'IN OR UNCLASSIFIED', 45 | 'in_children' => 'IN CHILDREN', 46 | ]; 47 | 48 | /** 49 | * @var Attribute|string 50 | */ 51 | public $field; 52 | 53 | /** 54 | * @var string 55 | */ 56 | public $operator; 57 | 58 | /** 59 | * @var string[] 60 | */ 61 | public $values; 62 | 63 | /** 64 | * @var string 65 | */ 66 | public $locale; 67 | 68 | /** 69 | * @var string 70 | */ 71 | public $scope; 72 | 73 | /** 74 | * @var string 75 | */ 76 | public $unit; 77 | 78 | /** 79 | * @var string 80 | */ 81 | public $currency; 82 | 83 | /** 84 | * @return array 85 | */ 86 | public function getData(): array 87 | { 88 | $data['field'] = $this->field instanceof Attribute ? $this->field->getCode() : $this->field; 89 | 90 | if (array_key_exists($this->operator, self::OPERATORS)) { 91 | $data['operator'] = self::OPERATORS[$this->operator]; 92 | } 93 | 94 | if ('' !== $this->scope && (!$this->field instanceof Attribute || $this->field->isScopable())) { 95 | $data['scope'] = $this->scope; 96 | } 97 | if ('' !== $this->locale && (!$this->field instanceof Attribute || $this->field->isLocalizable())) { 98 | $data['locale'] = $this->locale; 99 | } 100 | 101 | $data['value'] = reset($this->values); 102 | 103 | if ('enabled' === $this->field || $this->field instanceof Attribute && AttributeTypes::BOOLEAN === $this->field->getAttributeType()) { 104 | $data['value'] = (bool) $data['value']; 105 | } 106 | 107 | if ('' !== $this->currency && $this->field instanceof Attribute && AttributeTypes::PRICE_COLLECTION === $this->field->getType()) { 108 | $data['value'] = []; 109 | $data['value']['currency'] = $this->currency; 110 | $data['value']['amount'] = reset($this->values); 111 | } 112 | 113 | if ($this->field instanceof Attribute) { 114 | if (null !== $this->field->getMetricFamily() && 0 < strlen($this->field->getMetricFamily())) { 115 | $data['value'] = []; 116 | $data['value']['amount'] = reset($this->values); 117 | $data['value']['unit'] = $this->unit; 118 | } 119 | } elseif (null !== $this->unit && 0 < strlen($this->unit) && 1 === count($this->values)) { 120 | $data['value'] = []; 121 | $data['value']['amount'] = reset($this->values); 122 | $data['value']['unit'] = $this->unit; 123 | } 124 | 125 | if (array_key_exists($this->operator, self::MULTIPLE_VALUE_OPERATORS)) { 126 | $data['value'] = array_values($this->values); 127 | } 128 | 129 | return $data; 130 | } 131 | 132 | /** 133 | * @param array $data 134 | * 135 | * @return Condition 136 | */ 137 | public static function fromData(array $data): self 138 | { 139 | $condition = new self(); 140 | $condition->field = $data['field']; 141 | 142 | if (in_array($data['operator'], self::OPERATORS, true)) { 143 | $condition->operator = array_keys(self::OPERATORS, $data['operator'], true)[0]; 144 | } 145 | 146 | if (array_key_exists('value', $data)) { 147 | if (is_array($data['value'])) { 148 | if (array_key_exists('unit', $data['value'])) { 149 | $condition->values[] = $data['value']['amount']; 150 | $condition->unit = $data['value']['unit']; 151 | } elseif (array_key_exists('currency', $data['value'])) { 152 | $condition->values[] = $data['value']['amount']; 153 | $condition->currency = $data['value']['currency']; 154 | } else { 155 | $condition->values = $data['value']; 156 | } 157 | } else { 158 | $condition->values[] = $data['value']; 159 | } 160 | } 161 | 162 | if (array_key_exists('locale', $data)) { 163 | $condition->locale = $data['locale']; 164 | } 165 | if (array_key_exists('scope', $data)) { 166 | $condition->scope = $data['scope']; 167 | } 168 | 169 | return $condition; 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /Basecom/Bundle/RulesEngineBundle/DTO/RuleDefinition.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class RuleDefinition 11 | { 12 | /** 13 | * @var string 14 | */ 15 | public $code; 16 | 17 | /** 18 | * @var int 19 | */ 20 | public $priority; 21 | 22 | /** 23 | * @var string 24 | */ 25 | public $type = 'product'; 26 | 27 | /** 28 | * @var Action[] 29 | */ 30 | public $actions = []; 31 | 32 | /** 33 | * @var Condition[] 34 | */ 35 | public $conditions = []; 36 | 37 | /** 38 | * @var BaseRuleDefinition 39 | */ 40 | private $entity; 41 | 42 | /** 43 | * @param bool $createNew 44 | * 45 | * @return BaseRuleDefinition 46 | */ 47 | public function getEntity(bool $createNew = false): BaseRuleDefinition 48 | { 49 | if ($createNew || null === $this->entity) { 50 | return new BaseRuleDefinition(); 51 | } 52 | 53 | $this->entity->setCode($this->code); 54 | $this->entity->setPriority($this->priority); 55 | $this->entity->setType($this->type); 56 | 57 | $conditionsData = []; 58 | foreach ($this->conditions as $condition) { 59 | $conditionsData[] = $condition->getData(); 60 | } 61 | 62 | $actionsData = []; 63 | foreach ($this->actions as $action) { 64 | $actionsData[] = $action->getData(); 65 | } 66 | 67 | $content = [ 68 | 'conditions' => $conditionsData, 69 | 'actions' => $actionsData, 70 | ]; 71 | 72 | $this->entity->setContent($content); 73 | 74 | return $this->entity; 75 | } 76 | 77 | /** 78 | * @param BaseRuleDefinition $entity 79 | * 80 | * @return RuleDefinition 81 | */ 82 | public static function fromEntity(BaseRuleDefinition $entity): self 83 | { 84 | $definition = new self(); 85 | $definition->entity = $entity; 86 | $definition->code = $entity->getCode(); 87 | $definition->priority = $entity->getPriority(); 88 | $definition->type = $entity->getType(); 89 | $content = $entity->getContent(); 90 | 91 | if (is_array($content)) { 92 | if (array_key_exists('actions', $content) && is_array($content['actions'])) { 93 | foreach ($content['actions'] as $actionData) { 94 | $definition->actions[] = Action::fromData($actionData); 95 | } 96 | } 97 | 98 | if (array_key_exists('conditions', $content) && is_array($content['conditions'])) { 99 | foreach ($content['conditions'] as $conditionData) { 100 | $definition->conditions[] = Condition::fromData($conditionData); 101 | } 102 | } 103 | } 104 | if (0 === count($definition->conditions)) { 105 | $definition->conditions[] = new Condition(); 106 | } 107 | if (0 === count($definition->actions)) { 108 | $definition->actions[] = new Action(); 109 | } 110 | 111 | return $definition; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /Basecom/Bundle/RulesEngineBundle/DependencyInjection/BasecomRulesEngineExtension.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | class BasecomRulesEngineExtension extends Extension 16 | { 17 | /** 18 | * Loads a specific configuration. 19 | * 20 | * @param array $config An array of configuration values 21 | * @param ContainerBuilder $container A ContainerBuilder instance 22 | * 23 | * @throws \InvalidArgumentException When provided tag is not defined in this extension 24 | * 25 | * @api 26 | */ 27 | public function load(array $config, ContainerBuilder $container) 28 | { 29 | $loader = new YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); 30 | 31 | $loader->load('controllers.yml'); 32 | $loader->load('engine.yml'); 33 | $loader->load('form_types.yml'); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Basecom/Bundle/RulesEngineBundle/Engine/ProductRuleBuilder.php: -------------------------------------------------------------------------------- 1 | 17 | * @author Jordan Kniest 18 | * @author Christopher Steinke 19 | */ 20 | class ProductRuleBuilder extends BaseProductRuleBuilder 21 | { 22 | /** 23 | * @var ValidatorInterface 24 | */ 25 | protected $validator; 26 | 27 | /** 28 | * ProductRuleBuilder constructor. 29 | * @param DenormalizerInterface $chainedDenormalizer 30 | * @param EventDispatcherInterface $eventDispatcher 31 | * @param $ruleClass 32 | * @param ValidatorInterface $validator 33 | */ 34 | public function __construct( 35 | DenormalizerInterface $chainedDenormalizer, 36 | EventDispatcherInterface $eventDispatcher, 37 | $ruleClass, 38 | ValidatorInterface $validator 39 | ) { 40 | parent::__construct($chainedDenormalizer, $eventDispatcher, $ruleClass); 41 | $this->validator = $validator; 42 | } 43 | 44 | /** 45 | * @param RuleInterface $rule 46 | * 47 | * @return ConstraintViolationListInterface 48 | */ 49 | public function validateRule(RuleInterface $rule): ConstraintViolationListInterface 50 | { 51 | return $this->validator->validate($rule); 52 | } 53 | 54 | /** 55 | * @param RuleDefinitionInterface $definition 56 | * 57 | * @return RuleInterface 58 | */ 59 | public function getRuleByRuleDefinition(RuleDefinitionInterface $definition): RuleInterface 60 | { 61 | /** @var RuleInterface $rule */ 62 | $rule = new $this->ruleClass($definition); 63 | 64 | try { 65 | $content = $this->chainedDenormalizer->denormalize( 66 | $definition->getContent(), 67 | $this->ruleClass, 68 | 'rule_content' 69 | ); 70 | } catch (\LogicException $e) { 71 | throw new BuilderException( 72 | sprintf('Impossible to build the rule "%s". %s', $definition->getCode(), $e->getMessage()) 73 | ); 74 | } 75 | 76 | $rule->setConditions($content['conditions']); 77 | $rule->setActions($content['actions']); 78 | 79 | return $rule; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /Basecom/Bundle/RulesEngineBundle/Form/DataTransformer/AttributeCodeToAttributeTransformer.php: -------------------------------------------------------------------------------- 1 | 13 | * @author Jordan Kniest 14 | */ 15 | class AttributeCodeToAttributeTransformer implements DataTransformerInterface 16 | { 17 | /** 18 | * @var AttributeRepositoryInterface 19 | */ 20 | private $attributeRepository; 21 | 22 | /** 23 | * Constructor of CodeToAttributeTransformer. 24 | * 25 | * @param AttributeRepositoryInterface $attributeRepository 26 | */ 27 | public function __construct(AttributeRepositoryInterface $attributeRepository) 28 | { 29 | $this->attributeRepository = $attributeRepository; 30 | } 31 | 32 | /** 33 | * Transforms an object (Attribute) to a string (Attribute-Code). 34 | * 35 | * @param Attribute|null $attribute 36 | * 37 | * @return string 38 | */ 39 | public function transform($attribute): string 40 | { 41 | if (is_string($attribute)) { 42 | return $attribute; 43 | } 44 | 45 | return null === $attribute ? '' : $attribute->getCode(); 46 | } 47 | 48 | /** 49 | * Transforms a string (number) to an object (issue). 50 | * 51 | * @param string $attributeCode 52 | * 53 | * @throws TransformationFailedException if object (Attribute) is not found 54 | * 55 | * @return Attribute|string|null 56 | */ 57 | public function reverseTransform($attributeCode) 58 | { 59 | if (!$attributeCode) { 60 | return null; 61 | } 62 | 63 | if ($attributeCode instanceof Attribute) { 64 | return $attributeCode; 65 | } 66 | 67 | if (in_array($attributeCode, ConditionType::getAdditionalFieldCodes(), true)) { 68 | return $attributeCode; 69 | } 70 | 71 | /** @var Attribute|null $attribute */ 72 | $attribute = $this->attributeRepository->findOneBy(['code' => $attributeCode]); 73 | 74 | if (null === $attribute) { 75 | // causes a validation error 76 | // this message is not shown to the user 77 | // see the invalid_message option 78 | throw new TransformationFailedException(sprintf( 79 | 'An Attribute with the Attribute-Code "%s" does not exist!', 80 | $attributeCode 81 | )); 82 | } 83 | 84 | return $attribute; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /Basecom/Bundle/RulesEngineBundle/Form/Type/ActionType.php: -------------------------------------------------------------------------------- 1 | 19 | * @author Jordan Kniest 20 | */ 21 | class ActionType extends AbstractType 22 | { 23 | /** 24 | * @var LocaleRepositoryInterface 25 | */ 26 | protected $localeRepository; 27 | 28 | /** 29 | * @var ChannelRepositoryInterface 30 | */ 31 | protected $channelRepository; 32 | 33 | /** 34 | * @var AttributeRepositoryInterface 35 | */ 36 | protected $attributeRepository; 37 | 38 | /** 39 | * @var array 40 | */ 41 | protected $operators; 42 | 43 | /** 44 | * Constructor of ActionType. 45 | * 46 | * @param LocaleRepositoryInterface $localeRepository 47 | * @param ChannelRepositoryInterface $channelRepository 48 | * @param AttributeRepositoryInterface $attributeRepository 49 | * @param array $operators 50 | */ 51 | public function __construct( 52 | LocaleRepositoryInterface $localeRepository, 53 | ChannelRepositoryInterface $channelRepository, 54 | AttributeRepositoryInterface $attributeRepository, 55 | array $operators 56 | ) { 57 | $this->localeRepository = $localeRepository; 58 | $this->channelRepository = $channelRepository; 59 | $this->attributeRepository = $attributeRepository; 60 | $this->operators = $operators; 61 | } 62 | 63 | /** 64 | * {@inheritdoc} 65 | */ 66 | public function buildForm(FormBuilderInterface $builder, array $options) 67 | { 68 | $this->addFieldType($builder); 69 | $this->addField($builder); 70 | $this->addFromFieldField($builder); 71 | $this->addFromFieldLocale($builder); 72 | $this->addFromFieldScope($builder); 73 | $this->addToFieldField($builder); 74 | $this->addToFieldLocale($builder); 75 | $this->addToFieldScope($builder); 76 | $this->addFieldLocale($builder); 77 | $this->addFieldScope($builder); 78 | $this->addFieldValue($builder); 79 | $this->addFieldUnit($builder); 80 | $this->addFieldItems($builder); 81 | } 82 | 83 | /** 84 | * Add field id to form builder. 85 | * 86 | * @param FormBuilderInterface $builder 87 | */ 88 | protected function addField(FormBuilderInterface $builder) 89 | { 90 | $builder->add('field', ChoiceType::class, [ 91 | 'choices' => $this->getAttributesAsArray($this->attributeRepository->findAll()), 92 | 'required' => false, 93 | 'multiple' => false, 94 | 'select2' => true, 95 | 'attr' => [ 96 | 'class' => 'action-field', 97 | ], 98 | ]); 99 | } 100 | 101 | /** 102 | * Add field id to form builder. 103 | * 104 | * @param FormBuilderInterface $builder 105 | */ 106 | protected function addFieldValue(FormBuilderInterface $builder) 107 | { 108 | $builder->add('value', TextType::class, [ 109 | 'required' => false, 110 | 'attr' => [ 111 | 'class' => 'action-value', 112 | ], 113 | ]); 114 | } 115 | 116 | /** 117 | * Add field id to form builder. 118 | * 119 | * @param FormBuilderInterface $builder 120 | */ 121 | protected function addFromFieldField(FormBuilderInterface $builder) 122 | { 123 | $builder->add('fromField', ChoiceType::class, [ 124 | 'choices' => $this->getAttributesAsArray($this->attributeRepository->findAll()), 125 | 'required' => false, 126 | 'multiple' => false, 127 | 'select2' => true, 128 | 'attr' => [ 129 | 'class' => 'action-from-field', 130 | ], 131 | ]); 132 | } 133 | 134 | /** 135 | * Add field locale to form builder. 136 | * 137 | * @param FormBuilderInterface $builder 138 | */ 139 | protected function addFromFieldLocale(FormBuilderInterface $builder) 140 | { 141 | $builder->add('fromLocale', ChoiceType::class, [ 142 | 'choices' => $this->getValuesAsArray($this->localeRepository->getActivatedLocaleCodes()), 143 | 'required' => false, 144 | 'multiple' => false, 145 | 'select2' => true, 146 | 'attr' => [ 147 | 'class' => 'action-from-field-locale', 148 | ], 149 | ]); 150 | } 151 | 152 | /** 153 | * Add field locale to form builder. 154 | * 155 | * @param FormBuilderInterface $builder 156 | */ 157 | protected function addFromFieldScope(FormBuilderInterface $builder) 158 | { 159 | $builder->add('fromScope', ChoiceType::class, [ 160 | 'choices' => $this->getValuesAsArray($this->channelRepository->getChannelCodes()), 161 | 'required' => false, 162 | 'multiple' => false, 163 | 'select2' => true, 164 | 'attr' => [ 165 | 'class' => 'action-from-field-scope', 166 | ], 167 | ]); 168 | } 169 | 170 | /** 171 | * Add field id to form builder. 172 | * 173 | * @param FormBuilderInterface $builder 174 | */ 175 | protected function addToFieldField(FormBuilderInterface $builder) 176 | { 177 | $builder->add('toField', ChoiceType::class, [ 178 | 'choices' => $this->getAttributesAsArray($this->attributeRepository->findAll()), 179 | 'required' => false, 180 | 'multiple' => false, 181 | 'select2' => true, 182 | 'attr' => [ 183 | 'class' => 'action-to-field', 184 | ], 185 | ]); 186 | } 187 | 188 | /** 189 | * Add field locale to form builder. 190 | * 191 | * @param FormBuilderInterface $builder 192 | */ 193 | protected function addToFieldLocale(FormBuilderInterface $builder) 194 | { 195 | $builder->add('toLocale', ChoiceType::class, [ 196 | 'choices' => $this->getValuesAsArray($this->localeRepository->getActivatedLocaleCodes()), 197 | 'required' => false, 198 | 'multiple' => false, 199 | 'select2' => true, 200 | 'attr' => [ 201 | 'class' => 'action-to-field-locale', 202 | ], 203 | ]); 204 | } 205 | 206 | /** 207 | * Add field toScope to form builder. 208 | * 209 | * @param FormBuilderInterface $builder 210 | */ 211 | protected function addToFieldScope(FormBuilderInterface $builder) 212 | { 213 | $builder->add('toScope', ChoiceType::class, [ 214 | 'choices' => $this->getValuesAsArray($this->channelRepository->getChannelCodes()), 215 | 'required' => false, 216 | 'multiple' => false, 217 | 'select2' => true, 218 | 'attr' => [ 219 | 'class' => 'action-to-field-scope', 220 | ], 221 | ]); 222 | } 223 | 224 | /** 225 | * Add field locale to form builder. 226 | * 227 | * @param FormBuilderInterface $builder 228 | */ 229 | protected function addFieldLocale(FormBuilderInterface $builder) 230 | { 231 | $builder->add('locale', ChoiceType::class, [ 232 | 'choices' => $this->getValuesAsArray($this->localeRepository->getActivatedLocaleCodes()), 233 | 'required' => false, 234 | 'multiple' => false, 235 | 'select2' => true, 236 | 'attr' => [ 237 | 'class' => 'action-field-locale', 238 | ], 239 | ]); 240 | } 241 | 242 | /** 243 | * Add field scope to form builder. 244 | * 245 | * @param FormBuilderInterface $builder 246 | */ 247 | protected function addFieldScope(FormBuilderInterface $builder) 248 | { 249 | $builder->add('scope', ChoiceType::class, [ 250 | 'choices' => $this->getValuesAsArray($this->channelRepository->getChannelCodes()), 251 | 'required' => false, 252 | 'multiple' => false, 253 | 'select2' => true, 254 | 'attr' => [ 255 | 'class' => 'action-field-scope', 256 | ], 257 | ]); 258 | } 259 | 260 | /** 261 | * Add field type to form builder. 262 | * 263 | * @param FormBuilderInterface $builder 264 | */ 265 | protected function addFieldType(FormBuilderInterface $builder) 266 | { 267 | $builder->add('type', ChoiceType::class, [ 268 | 'choices' => $this->getValuesAsArray($this->operators), 269 | 'required' => true, 270 | 'multiple' => false, 271 | 'select2' => true, 272 | 'attr' => [ 273 | 'class' => 'action-type-select', 274 | ], 275 | ]); 276 | } 277 | 278 | /** 279 | * Add field data to form builder. 280 | * 281 | * @param FormBuilderInterface $builder 282 | */ 283 | protected function addFieldData(FormBuilderInterface $builder) 284 | { 285 | $builder->add('valueData', TextType::class, [ 286 | 'label' => 'Value', 287 | 'required' => false, 288 | 'attr' => [ 289 | 'class' => 'action-field-data', 290 | ], 291 | ]); 292 | } 293 | 294 | /** 295 | * Add field unit to form builder. 296 | * 297 | * @param FormBuilderInterface $builder 298 | */ 299 | protected function addFieldUnit(FormBuilderInterface $builder) 300 | { 301 | $builder->add('unit', TextType::class, [ 302 | 'required' => false, 303 | 'attr' => [ 304 | 'class' => 'action-field-unit', 305 | ], 306 | ]); 307 | } 308 | 309 | /** 310 | * Add field value to form builder. 311 | * 312 | * @param FormBuilderInterface $builder 313 | */ 314 | protected function addFieldItems(FormBuilderInterface $builder) 315 | { 316 | $builder->add( 317 | 'items', 318 | CollectionType::class, 319 | [ 320 | 'entry_type' => TextType::class, 321 | 'allow_add' => true, 322 | 'allow_delete' => true, 323 | 'by_reference' => false, 324 | 'required' => false, 325 | 'label' => 'Value', 326 | 'attr' => [ 327 | 'class' => 'action-field-values-container', 328 | ], 329 | 'entry_options' => [ 330 | 'attr' => [ 331 | 'class' => 'action-field-values-value', 332 | ], 333 | 'required' => false, 334 | 'label' => false, 335 | ], 336 | ] 337 | ); 338 | } 339 | 340 | /** 341 | * @param $array 342 | * 343 | * @return array 344 | */ 345 | protected function getValuesAsArray($array): array 346 | { 347 | $newArray = []; 348 | foreach ($array as $key => $value) { 349 | $newArray[$value] = $value; 350 | } 351 | 352 | return $newArray; 353 | } 354 | 355 | /** 356 | * @param Attribute[] $attributes 357 | * 358 | * @return array 359 | */ 360 | protected function getAttributesAsArray($attributes): array 361 | { 362 | $newArray = []; 363 | 364 | /** @var Attribute $attribute */ 365 | foreach ($attributes as $attribute) { 366 | $newArray[$attribute->getCode()] = $attribute->getCode(); 367 | } 368 | $newArray['group'] = 'group'; 369 | $newArray['categories'] = 'categories'; 370 | $newArray['family'] = 'family'; 371 | 372 | return $newArray; 373 | } 374 | 375 | /** 376 | * {@inheritdoc} 377 | */ 378 | public function configureOptions(OptionsResolver $resolver) 379 | { 380 | $resolver->setDefaults( 381 | [ 382 | 'data_class' => Action::class, 383 | ] 384 | ); 385 | } 386 | 387 | /** 388 | * {@inheritdoc} 389 | */ 390 | public function getName(): string 391 | { 392 | return 'basecom_rule_action'; 393 | } 394 | } 395 | -------------------------------------------------------------------------------- /Basecom/Bundle/RulesEngineBundle/Form/Type/ConditionType.php: -------------------------------------------------------------------------------- 1 | 22 | */ 23 | class ConditionType extends AbstractType 24 | { 25 | /** 26 | * @var string 27 | */ 28 | protected $dataClass; 29 | 30 | /** 31 | * @var LocaleRepositoryInterface 32 | */ 33 | protected $localeRepository; 34 | 35 | /** 36 | * @var ChannelRepositoryInterface 37 | */ 38 | protected $channelRepository; 39 | 40 | /** 41 | * @var AttributeRepositoryInterface 42 | */ 43 | protected $attributeRepository; 44 | 45 | /** 46 | * @var MeasureManager 47 | */ 48 | protected $measureManager; 49 | 50 | /** 51 | * @var CurrencyRepositoryInterface 52 | */ 53 | private $currencyRepository; 54 | 55 | /** 56 | * Constructor of ConditionType. 57 | * 58 | * @param string $dataClass 59 | * @param LocaleRepositoryInterface $localeRepository 60 | * @param ChannelRepositoryInterface $channelRepository 61 | * @param AttributeRepositoryInterface $attributeRepository 62 | * @param MeasureManager $measureManager 63 | * @param CurrencyRepositoryInterface $currencyRepository 64 | */ 65 | public function __construct( 66 | string $dataClass, 67 | LocaleRepositoryInterface $localeRepository, 68 | ChannelRepositoryInterface $channelRepository, 69 | AttributeRepositoryInterface $attributeRepository, 70 | MeasureManager $measureManager, 71 | CurrencyRepositoryInterface $currencyRepository 72 | ) { 73 | $this->dataClass = $dataClass; 74 | $this->localeRepository = $localeRepository; 75 | $this->channelRepository = $channelRepository; 76 | $this->attributeRepository = $attributeRepository; 77 | $this->measureManager = $measureManager; 78 | $this->currencyRepository = $currencyRepository; 79 | } 80 | 81 | /** 82 | * {@inheritdoc} 83 | */ 84 | public function buildForm(FormBuilderInterface $builder, array $options) 85 | { 86 | $this->addFieldOperator($builder); 87 | $this->addFieldField($builder); 88 | $this->addFieldLocale($builder); 89 | $this->addFieldScope($builder); 90 | $this->addFieldValue($builder); 91 | $this->addFieldUnit($builder); 92 | $this->addFieldCurrency($builder); 93 | } 94 | 95 | /** 96 | * Add field field to form builder. 97 | * 98 | * @param FormBuilderInterface $builder 99 | */ 100 | protected function addFieldField(FormBuilderInterface $builder) 101 | { 102 | $builder->add('field', ChoiceType::class, [ 103 | 'choices' => $this->getFieldCodes(), 104 | 'required' => false, 105 | 'multiple' => false, 106 | 'select2' => true, 107 | 'attr' => [ 108 | 'class' => 'condition-field', 109 | ], 110 | ]); 111 | 112 | $transformer = new AttributeCodeToAttributeTransformer($this->attributeRepository); 113 | $builder->get('field')->addModelTransformer($transformer); 114 | } 115 | 116 | /** 117 | * Add field locale to form builder. 118 | * 119 | * @param FormBuilderInterface $builder 120 | */ 121 | protected function addFieldLocale(FormBuilderInterface $builder) 122 | { 123 | $builder->add('locale', ChoiceType::class, [ 124 | 'choices' => $this->getValuesAsArray($this->localeRepository->getActivatedLocaleCodes()), 125 | 'required' => false, 126 | 'multiple' => false, 127 | 'select2' => true, 128 | 'attr' => [ 129 | 'class' => 'condition-field-locale', 130 | ], 131 | ]); 132 | } 133 | 134 | /** 135 | * Add field scope to form builder. 136 | * 137 | * @param FormBuilderInterface $builder 138 | */ 139 | protected function addFieldScope(FormBuilderInterface $builder) 140 | { 141 | $builder->add('scope', ChoiceType::class, [ 142 | 'choices' => $this->getValuesAsArray($this->channelRepository->getChannelCodes()), 143 | 'required' => false, 144 | 'multiple' => false, 145 | 'select2' => true, 146 | 'attr' => [ 147 | 'class' => 'condition-field-scope', 148 | ], 149 | ]); 150 | } 151 | 152 | /** 153 | * Add field operator to form builder. 154 | * 155 | * @param FormBuilderInterface $builder 156 | */ 157 | protected function addFieldOperator(FormBuilderInterface $builder) 158 | { 159 | $builder->add('operator', ChoiceType::class, [ 160 | 'choices' => array_keys(Condition::OPERATORS), 161 | 'choice_label' => function (string $value, string $key, string $index) { 162 | return sprintf('pimee_catalog_rule.condition.operators.%s', Condition::OPERATORS[$value]); 163 | }, 164 | 'required' => true, 165 | 'multiple' => false, 166 | 'select2' => true, 167 | 'attr' => [ 168 | 'class' => 'condition-type-select', 169 | ], 170 | ]); 171 | } 172 | 173 | /** 174 | * Add field value to form builder. 175 | * 176 | * @param FormBuilderInterface $builder 177 | */ 178 | protected function addFieldValue(FormBuilderInterface $builder) 179 | { 180 | $builder->add( 181 | 'values', 182 | CollectionType::class, 183 | [ 184 | 'entry_type' => TextType::class, 185 | 'allow_add' => true, 186 | 'allow_delete' => true, 187 | 'by_reference' => false, 188 | 'required' => false, 189 | 'label' => 'Value', 190 | 'attr' => [ 191 | 'class' => 'condition-field-values-container', 192 | ], 193 | 'entry_options' => [ 194 | 'attr' => [ 195 | 'class' => 'condition-field-values-value', 196 | ], 197 | 'required' => false, 198 | 'label' => false, 199 | ], 200 | ] 201 | ); 202 | } 203 | 204 | /** 205 | * Add field unit to form builder. 206 | * 207 | * @param FormBuilderInterface $builder 208 | */ 209 | protected function addFieldUnit(FormBuilderInterface $builder) 210 | { 211 | $attributes = $this->attributeRepository->findAll(); 212 | $metricFamilies = []; 213 | 214 | foreach ($attributes as $attribute) { 215 | /** @var Attribute $attribute */ 216 | $metricFamily = $attribute->getMetricFamily(); 217 | 218 | if (null === $metricFamily || 0 >= strlen($metricFamily)) { 219 | continue; 220 | } 221 | 222 | if (!in_array($metricFamily, $metricFamilies, true)) { 223 | $metricFamilies[] = $metricFamily; 224 | } 225 | } 226 | 227 | $units = []; 228 | foreach ($metricFamilies as $metricFamily) { 229 | $familyUnits = $this->measureManager->getUnitSymbolsForFamily($metricFamily); 230 | 231 | foreach ($familyUnits as $key => $unitSymbol) { 232 | $units[$key] = $key; 233 | } 234 | } 235 | 236 | $builder->add('unit', ChoiceType::class, [ 237 | 'choices' => $units, 238 | 'required' => false, 239 | 'attr' => [ 240 | 'class' => 'condition-field-unit', 241 | ], 242 | ]); 243 | } 244 | 245 | /** 246 | * @param FormBuilderInterface $builder 247 | */ 248 | protected function addFieldCurrency(FormBuilderInterface $builder) 249 | { 250 | $builder->add('currency', ChoiceType::class, [ 251 | 'choices' => $this->getValuesAsArray($this->currencyRepository->getActivatedCurrencyCodes()), 252 | 'required' => false, 253 | 'multiple' => false, 254 | 'select2' => true, 255 | 'attr' => [ 256 | 'class' => 'condition-field-currency', 257 | ], 258 | ]); 259 | } 260 | 261 | /** 262 | * Get all possible entries for field value. 263 | * 264 | * @return array 265 | */ 266 | protected function getFieldCodes(): array 267 | { 268 | $attributes = $this->attributeRepository->findAll(); 269 | $fieldCodes = []; 270 | 271 | /** @var Attribute $attribute */ 272 | foreach ($attributes as $attribute) { 273 | $fieldCodes[$attribute->getCode()] = $attribute->getCode(); 274 | } 275 | 276 | return array_merge($fieldCodes, self::getAdditionalFieldCodes()); 277 | } 278 | 279 | /** 280 | * @return array 281 | */ 282 | public static function getAdditionalFieldCodes(): array 283 | { 284 | return [ 285 | 'enabled' => 'enabled', 286 | 'completeness' => 'completeness', 287 | 'updated' => 'updated', 288 | 'created' => 'created', 289 | 'groups' => 'groups', 290 | 'family' => 'family', 291 | 'categories' => 'categories', 292 | ]; 293 | } 294 | 295 | /** 296 | * {@inheritdoc} 297 | */ 298 | public function configureOptions(OptionsResolver $resolver) 299 | { 300 | $resolver->setDefaults( 301 | [ 302 | 'data_class' => Condition::class, 303 | ] 304 | ); 305 | } 306 | 307 | /** 308 | * @param $array 309 | * 310 | * @return array 311 | */ 312 | protected function getValuesAsArray(array $array): array 313 | { 314 | $newArray = []; 315 | foreach ($array as $key => $value) { 316 | $newArray[$value] = $value; 317 | } 318 | 319 | return $newArray; 320 | } 321 | 322 | /** 323 | * {@inheritdoc} 324 | */ 325 | public function getName(): string 326 | { 327 | return 'basecom_rule_condition'; 328 | } 329 | } 330 | -------------------------------------------------------------------------------- /Basecom/Bundle/RulesEngineBundle/Form/Type/RuleDefinitionType.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | class RuleDefinitionType extends AbstractType 18 | { 19 | /** 20 | * {@inheritdoc} 21 | */ 22 | public function buildForm(FormBuilderInterface $builder, array $options) 23 | { 24 | $this->addDefinition($builder) 25 | ->addConditions($builder) 26 | ->addActions($builder); 27 | } 28 | 29 | /** 30 | * @param FormBuilderInterface $builder 31 | * 32 | * @return $this 33 | */ 34 | protected function addDefinition(FormBuilderInterface $builder): self 35 | { 36 | $builder 37 | ->add('code', TextType::class, [ 38 | 'required' => true, 39 | ]) 40 | ->add('priority', IntegerType::class, [ 41 | 'required' => true, 42 | ]) 43 | ->add('type', HiddenType::class, [ 44 | 'data' => 'product', 45 | ]); 46 | 47 | return $this; 48 | } 49 | 50 | /** 51 | * @param FormBuilderInterface $builder 52 | * 53 | * @return $this 54 | */ 55 | protected function addActions(FormBuilderInterface $builder): self 56 | { 57 | $builder->add( 58 | 'actions', 59 | CollectionType::class, 60 | [ 61 | 'entry_type' => ActionType::class, 62 | 'allow_add' => true, 63 | 'allow_delete' => true, 64 | 'attr' => [ 65 | 'class' => 'rule-action-container', 66 | ], 67 | 'entry_options' => [ 68 | 'attr' => [ 69 | 'class' => 'rule-action', 70 | ], 71 | 'label' => 'Action', 72 | ], 73 | ] 74 | ); 75 | 76 | return $this; 77 | } 78 | 79 | /** 80 | * @param FormBuilderInterface $builder 81 | * 82 | * @return $this 83 | */ 84 | protected function addConditions(FormBuilderInterface $builder): self 85 | { 86 | $builder->add( 87 | 'conditions', 88 | CollectionType::class, 89 | [ 90 | 'entry_type' => ConditionType::class, 91 | 'allow_add' => true, 92 | 'allow_delete' => true, 93 | 'attr' => [ 94 | 'class' => 'rule-condition-container', 95 | ], 96 | 'entry_options' => [ 97 | 'attr' => [ 98 | 'class' => 'rule-condition', 99 | ], 100 | 'label' => 'Condition', 101 | ], 102 | ] 103 | ); 104 | 105 | return $this; 106 | } 107 | 108 | /** 109 | * {@inheritdoc} 110 | */ 111 | public function configureOptions(OptionsResolver $resolver) 112 | { 113 | $resolver->setDefaults( 114 | [ 115 | 'data_class' => RuleDefinitionDTO::class, 116 | ] 117 | ); 118 | } 119 | 120 | /** 121 | * {@inheritdoc} 122 | */ 123 | public function getName(): string 124 | { 125 | return 'basecom_rule'; 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /Basecom/Bundle/RulesEngineBundle/README.md: -------------------------------------------------------------------------------- 1 | # Akeneo rules engine graphical interface 2 | ## Requirements 3 | 4 | Akeneo PIM Enterprise Edition 3.0.~ 5 | 6 | ## Installation 7 | Enable the bundle in the `app/AppKernel.php` file in the `registerBundles()` method: 8 | 9 | ```php 10 | $bundles = [ 11 | // ... 12 | new \Basecom\Bundle\RulesEngineBundle\BasecomRulesEngine(), 13 | ] 14 | ``` 15 | Enable the route in the 'app/config/routing.yml' file 16 | 17 | ```php 18 | basecom_rules_routing: 19 | resource: "@BasecomRulesEngine/Resources/config/routing/rules.yml" 20 | ``` 21 | 22 | Clear you cache: 23 | 24 | ```bash 25 | php bin/console cache:clear --env=prod 26 | rm -rf var/cache/* ./web/bundles/* ./web/css/* ./web/js/* 27 | bin/console --env=prod pim:installer:assets 28 | bin/console --env=prod cache:warmup 29 | ``` 30 | 31 | 32 | ## Documentation 33 | 34 | - OverwriteRuleController.php overwrites the standard Akeneo RuleController to extend the view with a edit button in the rule overview. 35 | 36 | - Operator Between and not Between is disabled 37 | 38 | https://docs.akeneo.com/master/cookbook/rule/general_information_on_rule_format.html#enrichment-rule-structure 39 | 40 | ### Available Operators Conditions List 41 | - STARTS WITH 42 | - ENDS WITH 43 | - CONTAINS 44 | - DOES NOT CONTAIN 45 | - EMPTY 46 | - NOT EMPTY 47 | - EQUAL ( = ) 48 | - NOT EQUAL ( != ) 49 | - IN 50 | - NOT IN 51 | - UNCLASSIFIED 52 | - IN OR UNCLASSIFIED 53 | - IN CHILDREN 54 | - NOT IN CHILDREN 55 | - GREATER ( > ) 56 | - GREATER OR EQUAL ( >= ) 57 | - SMALLER ( < ) 58 | - SMALLER OR EQUAL ( <= ) 59 | 60 | #### Operator 61 | STARTS WITH 62 | 63 | ##### Requirements 64 | - Attribute (no Simple or Multliselect) 65 | - Locale (optional) 66 | - Scope (optional) 67 | - Value 68 | 69 | #### Operator 70 | ENDS WITH 71 | 72 | ##### Requirements 73 | - Attribute (no Simple or Multliselect) 74 | - Locale (optional) 75 | - Scope (optional) 76 | - Value 77 | 78 | #### Operator 79 | CONTAINS 80 | 81 | ##### Requirements 82 | - Attribute (no Simple or Multliselect) 83 | - Locale (optional) 84 | - Scope (optional) 85 | - Value 86 | 87 | #### Operator 88 | DOES NOT CONTAIN 89 | 90 | ##### Requirements 91 | - Attribute (no Simple or Multliselect) 92 | - Locale (optional) 93 | - Scope (optional) 94 | - Value 95 | 96 | #### Operator 97 | EMPTY 98 | 99 | ##### Requirements 100 | - Attribute, Family (family.code), Groups (groups.code) 101 | - Locale (optional) 102 | - Scope (optional) 103 | 104 | #### Operator 105 | NOT EMPTY 106 | 107 | ##### Requirements 108 | - Attribute, Family (family.code), Groups (groups.code) 109 | - Locale (optional) 110 | - Scope (optional) 111 | 112 | #### Operator 113 | EQUAL ( = ) 114 | 115 | ##### Requirements 116 | - Attribute, created, updated, enabled, completeness 117 | - Value (dates format: yyyy-mm-dd) (enabled and yes/no format = true or false) 118 | - Locale (optional) 119 | - Scope (optional) 120 | - Unit (optional, only if a metric Attribute is selected) 121 | 122 | #### Operator 123 | NOT EQUAL ( != ) 124 | 125 | ##### Requirements 126 | - Attribute (Number or Metric), created, updated, enabled, completeness 127 | - Value (created, updated dates format: yyyy-mm-dd)(enabled format = true or false) 128 | - Locale (optional) 129 | - Scope (optional) 130 | - Unit (optional, only if a metric Attribute is selected) 131 | 132 | #### Operator 133 | IN 134 | 135 | ##### Requirements 136 | - Simple, Multiselect Attribute, Category (categories.code), Family (family.code), Groups (groups.code) 137 | - Locale (optional) 138 | - Scope (optional) 139 | - One or more value 140 | 141 | 142 | #### Operator 143 | NOT IN 144 | 145 | ##### Requirements 146 | - Simple, Multiselect Attribute, Category (categories.code), Family (family.code), Groups (groups.code) 147 | - Locale (optional) 148 | - Scope (optional) 149 | - One or more value 150 | 151 | #### Operator 152 | UNCLASSIFIED 153 | 154 | ##### Requirements 155 | Only available on Categories 156 | - Field = categories.code 157 | - No Attributes have to be selected 158 | 159 | #### Operator 160 | IN OR UNCLASSIFIED 161 | 162 | ##### Requirements 163 | Only available on Categories 164 | - Field = categories.code 165 | - Category code 166 | 167 | #### Operator 168 | IN CHILDREN 169 | 170 | ##### Requirements 171 | Only available on Categories 172 | - Field = categories.code 173 | - Category code 174 | 175 | #### Operator 176 | NOT IN CHILDREN 177 | 178 | ##### Requirements 179 | Only available on Categories 180 | - Field = categories.code 181 | - Category code 182 | 183 | #### Operator 184 | GREATER ( > ) 185 | 186 | ##### Requirements 187 | - Number, Price, Metric, Date Attribute, completeness 188 | - Value (dates format: yyyy-mm-dd) 189 | - Locale (optional) 190 | - Scope (optional) 191 | - Unit (optional, only if a metric Attribute is selected) 192 | 193 | #### Operator 194 | GREATER OR EQUAL ( >= ) 195 | 196 | ##### Requirements 197 | - Number, Price, Metric, Date Attribute 198 | - Value (dates format: yyyy-mm-dd) 199 | - Locale (optional) 200 | - Scope (optional) 201 | - Unit (optional, only if a metric Attribute is selected) 202 | 203 | #### Operator 204 | SMALLER ( < ) 205 | 206 | ##### Requirements 207 | - Number, Price, Metric, Date Attribute, completeness 208 | - Value (dates format: yyyy-mm-dd) 209 | - Locale (optional) 210 | - Scope (optional) 211 | - Unit (optional, only if a metric Attribute is selected) 212 | 213 | #### Operator 214 | SMALLER OR EQUAL ( <= ) 215 | 216 | ##### Requirements 217 | - Number, Price, Metric, Date Attribute 218 | - Value (dates format: yyyy-mm-dd) 219 | - Locale (optional) 220 | - Scope (optional) 221 | - Unit (optional, only if a metric Attribute is selected) 222 | 223 | 224 | ### Available Operators Actions List 225 | - add 226 | - set 227 | - copy 228 | - remove 229 | 230 | #### Operator 231 | add 232 | 233 | ##### Requirements 234 | - field: attribute code. 235 | - locale: local code for which value is assigned (optional). 236 | - scope: channel code for which value is assigned (optional). 237 | - values: attribute values to add. 238 | 239 | #### Operator 240 | set 241 | 242 | ##### Requirements 243 | - field: attribute code. 244 | - locale (optional) 245 | - scope (optional) 246 | - value: attribute value. 247 | 248 | #### Operator 249 | copy 250 | 251 | ##### Requirements 252 | - from_field: code of the attribute to be copied. 253 | - from_locale: locale code of the value to be copied (optional). 254 | - from_scope: channel code of the value to be copied (optional). 255 | - to_field: attribute code the value will be copied into. 256 | - to_locale: locale code the value will be copied into (optional). 257 | - to_scope: channel code the value will be copied into (optional). 258 | 259 | #### Operator 260 | remove 261 | 262 | ##### Requirements 263 | - field: attribute code. 264 | - locale: local code for which value is assigned (optional). 265 | - scope: channel code for which value is assigned (optional). 266 | - values: attribute values to remove. -------------------------------------------------------------------------------- /Basecom/Bundle/RulesEngineBundle/Resources/config/acl.yml: -------------------------------------------------------------------------------- 1 | basecom_rule_remove: 2 | type: action 3 | label: basecom.rules_engine.acl.remove_permissions 4 | group_name: pimee_catalog_rule.acl_group.rule 5 | 6 | basecom_rule_edit: 7 | type: action 8 | label: basecom.rules_engine.acl.edit_permissions 9 | group_name: pimee_catalog_rule.acl_group.rule 10 | 11 | basecom_rule_create: 12 | type: action 13 | label: basecom.rules_engine.acl.create_permissions 14 | group_name: pimee_catalog_rule.acl_group.rule 15 | -------------------------------------------------------------------------------- /Basecom/Bundle/RulesEngineBundle/Resources/config/assets.yml: -------------------------------------------------------------------------------- 1 | css: 2 | basecom_ui: 3 | - bundles/basecomrulesengine/css/rules.less 4 | -------------------------------------------------------------------------------- /Basecom/Bundle/RulesEngineBundle/Resources/config/controllers.yml: -------------------------------------------------------------------------------- 1 | #overwrite the standard akeneo controller to extend the datagrid view 2 | parameters: 3 | basecom_rule_engine.controller.rule.class: 'Basecom\Bundle\RulesEngineBundle\Controller\RuleController' 4 | pimee_catalog_rule.controller.rule.class: 'Basecom\Bundle\RulesEngineBundle\Controller\OverwriteRuleController' 5 | 6 | services: 7 | basecom.controller.rule: 8 | public: true 9 | class: '%basecom_rule_engine.controller.rule.class%' 10 | arguments: 11 | - '@router' 12 | - '@akeneo_rule_engine.saver.rule_definition' 13 | - '@validator' 14 | - '@pimee_catalog_rule.denormalizer.product_rule.chained' 15 | - '@form.factory' 16 | - '@akeneo_rule_engine.repository.rule_definition' 17 | - '@pim_catalog.repository.attribute' 18 | - '@pimee_catalog_rule.builder.product' 19 | -------------------------------------------------------------------------------- /Basecom/Bundle/RulesEngineBundle/Resources/config/datagrid/rule.yml: -------------------------------------------------------------------------------- 1 | datagrid: 2 | rule-grid: 3 | options: 4 | manageFilters: false 5 | entityHint: rule 6 | source: 7 | acl_resource: pimee_catalog_rule_rule_view_permissions 8 | repository_method: createDatagridQueryBuilder 9 | type: pim_datasource_rule 10 | entity: '%akeneo_rule_engine.model.rule_definition.class%' 11 | columns: 12 | code: 13 | label: pimee_catalog_rule.datagrid.rule-grid.column.code 14 | conditions: 15 | label: pimee_catalog_rule.datagrid.rule-grid.column.conditions 16 | type: twig 17 | template: AkeneoPimRuleEngineBundle:Rule:_conditions.html.twig 18 | frontend_type: html 19 | data_name: content 20 | actions: 21 | label: pimee_catalog_rule.datagrid.rule-grid.column.actions 22 | type: twig 23 | template: BasecomRulesEngine:Rule:_actions.html.twig 24 | frontend_type: html 25 | data_name: content 26 | impactedSubjectCount: 27 | label: pimee_catalog_rule.datagrid.rule-grid.column.impacted_product_count.label 28 | type: twig 29 | template: AkeneoPimRuleEngineBundle:Rule:_impacted_product_count.html.twig 30 | frontend_type: html 31 | properties: 32 | id: ~ 33 | execute_link: 34 | type: url 35 | route: pimee_catalog_rule_rule_execute 36 | params: 37 | - code 38 | edit_link: 39 | type: url 40 | route: basecom_rule_edit 41 | params: 42 | - id 43 | delete_link: 44 | type: url 45 | route: pimee_catalog_rule_rule_delete 46 | params: 47 | - id 48 | actions: 49 | execute: 50 | launcherOptions: 51 | className: AknIconButton AknIconButton--small AknIconButton--play 52 | type: ajax 53 | label: pimee_catalog_rule.datagrid.rule-grid.actions.execute 54 | link: execute_link 55 | acl_resource: pimee_catalog_rule_rule_execute_permissions 56 | confirmation: true 57 | messages: 58 | confirm_title: pimee_catalog_rule.datagrid.rule-grid.actions.execute.confirm_title 59 | confirm_content: pimee_catalog_rule.datagrid.rule-grid.actions.execute.confirm_content 60 | confirm_ok: pim_common.ok 61 | edit: 62 | launcherOptions: 63 | className: AknIconButton AknIconButton--small AknIconButton--edit 64 | type: navigate 65 | label: basecom.rule_engine.edit 66 | link: edit_link 67 | acl_resource: basecom_rule_edit 68 | delete: 69 | launcherOptions: 70 | className: AknIconButton AknIconButton--small AknIconButton--trash 71 | type: delete 72 | label: pimee_catalog_rule.datagrid.rule-grid.actions.delete 73 | link: delete_link 74 | acl_resource: basecom_rule_remove 75 | filters: 76 | columns: 77 | code: 78 | type: search 79 | data_name: r.code 80 | sorters: 81 | columns: 82 | code: 83 | data_name: r.code 84 | impactedSubjectCount: 85 | data_name: r.impactedSubjectCount 86 | default: 87 | code: '%oro_datagrid.extension.orm_sorter.class%::DIRECTION_ASC' 88 | mass_actions: 89 | impacted_product_count: 90 | type: ajax 91 | acl_resource: pimee_catalog_rule_rule_impacted_product_count_permissions 92 | handler: rule_impacted_product_count 93 | label: pimee_catalog_rule.datagrid.rule-grid.mass_edit_action.impacted_product_count 94 | route: pimee_catalog_rule_rule_mass_impacted_product_count 95 | className: 'AknButton AknButton--action AknButtonList-item' 96 | messages: 97 | confirm_title: pimee_catalog_rule.datagrid.rule-grid.mass_edit_action.confirm_title 98 | confirm_content: pimee_catalog_rule.datagrid.rule-grid.mass_edit_action.confirm_content 99 | confirm_ok: pim_common.ok 100 | execute: 101 | type: ajax 102 | acl_resource: pimee_catalog_rule_rule_execute_permissions 103 | label: pimee_catalog_rule.datagrid.rule-grid.mass_edit_action.execute 104 | handler: mass_execute_rule 105 | className: 'AknButton AknButton--action AknButtonList-item' 106 | messages: 107 | confirm_title: pimee_catalog_rule.datagrid.rule-grid.mass_action.execute.confirm_title 108 | confirm_content: pimee_catalog_rule.datagrid.rule-grid.mass_action.execute.confirm_content 109 | confirm_ok: pim_common.ok 110 | success: pimee_catalog_rule.datagrid.rule-grid.mass_action.execute.success 111 | error: pimee_catalog_rule.datagrid.rule-grid.mass_action.execute.error 112 | empty_selection: pimee_catalog_rule.datagrid.rule-grid.mass_action.execute.empty_selection 113 | delete: 114 | type: delete 115 | entity_name: rule 116 | acl_resource: pimee_catalog_rule_rule_delete_permissions 117 | handler: mass_delete_rule 118 | label: pimee_catalog_rule.datagrid.rule-grid.mass_edit_action.delete 119 | className: 'AknButton AknButton--important AknButtonList-item' 120 | messages: 121 | confirm_title: pim_common.confirm_deletion 122 | confirm_content: pimee_catalog_rule.datagrid.rule-grid.mass_action.delete.confirm_content 123 | confirm_ok: pim_common.ok 124 | success: pimee_catalog_rule.datagrid.rule-grid.mass_action.delete.success 125 | error: pimee_catalog_rule.datagrid.rule-grid.mass_action.delete.error 126 | empty_selection: pimee_catalog_rule.datagrid.rule-grid.mass_action.delete.empty_selection 127 | -------------------------------------------------------------------------------- /Basecom/Bundle/RulesEngineBundle/Resources/config/engine.yml: -------------------------------------------------------------------------------- 1 | parameters: 2 | pimee_catalog_rule.builder.product.class: 'Basecom\Bundle\RulesEngineBundle\Engine\ProductRuleBuilder' 3 | 4 | services: 5 | pimee_catalog_rule.builder.product: 6 | class: '%pimee_catalog_rule.builder.product.class%' 7 | arguments: 8 | - '@pimee_catalog_rule.denormalizer.product_rule.chained' 9 | - '@event_dispatcher' 10 | - '%akeneo_rule_engine.model.rule.class%' 11 | - '@validator' -------------------------------------------------------------------------------- /Basecom/Bundle/RulesEngineBundle/Resources/config/form_extensions/rules_index.yml: -------------------------------------------------------------------------------- 1 | extensions: 2 | pim-rule-index-create-button: 3 | module: pim/common/redirect 4 | parent: pim-rule-index 5 | targetZone: buttons 6 | position: 50 7 | aclResourceId: basecom_rule_create 8 | config: 9 | label: basecom.rules_engine.menu_entry 10 | route: basecom_rule_create 11 | buttonClass: AknButton AknButton--apply create-new-rule 12 | -------------------------------------------------------------------------------- /Basecom/Bundle/RulesEngineBundle/Resources/config/form_types.yml: -------------------------------------------------------------------------------- 1 | parameters: 2 | basecom.form.type.conditon.class: Basecom\Bundle\RulesEngineBundle\Form\Type\ConditionType 3 | basecom.form.type.rule.definition.class: Basecom\Bundle\RulesEngineBundle\Form\Type\RuleDefinitionType 4 | basecom.form.type.action.class: Basecom\Bundle\RulesEngineBundle\Form\Type\ActionType 5 | 6 | services: 7 | basecom.form.type.rule: 8 | class: '%basecom.form.type.rule.definition.class%' 9 | arguments: 10 | - '%akeneo_rule_engine.model.rule_definition.class%' 11 | tags: 12 | - { name: form.type, alias: basecom_rule } 13 | 14 | basecom.form.type.condition: 15 | class: '%basecom.form.type.conditon.class%' 16 | arguments: 17 | - '%pimee_catalog_rule.model.product_condition.class%' 18 | - '@pim_catalog.repository.locale' 19 | - '@pim_catalog.repository.channel' 20 | - '@pim_catalog.repository.attribute' 21 | - '@akeneo_measure.manager' 22 | - '@pim_catalog.repository.currency' 23 | tags: 24 | - { name: form.type, alias: basecom_rule_condition } 25 | 26 | basecom.form.type.action: 27 | class: '%basecom.form.type.action.class%' 28 | arguments: 29 | - '@pim_catalog.repository.locale' 30 | - '@pim_catalog.repository.channel' 31 | - '@pim_catalog.repository.attribute' 32 | - ['add', 'set', 'copy', 'remove'] 33 | tags: 34 | - { name: form.type, alias: basecom_rule_action } 35 | -------------------------------------------------------------------------------- /Basecom/Bundle/RulesEngineBundle/Resources/config/requirejs.yml: -------------------------------------------------------------------------------- 1 | config: 2 | config: 3 | pim/controller-registry: 4 | defaultController: 5 | module: pim/controller/template 6 | controllers: 7 | basecom_rule_edit: 8 | module: pim/controller/form 9 | basecom_rule_create: 10 | module: pim/controller/form 11 | paths: 12 | bsc-rules/editview: basecomrulesengine/js/edit 13 | -------------------------------------------------------------------------------- /Basecom/Bundle/RulesEngineBundle/Resources/config/routing/rules.yml: -------------------------------------------------------------------------------- 1 | basecom_rule_create: 2 | path: /configuration/rules/create 3 | defaults: { _controller: basecom.controller.rule:createAction } 4 | 5 | basecom_rule_edit: 6 | path: /configuration/rules/edit/{id} 7 | defaults: { _controller: basecom.controller.rule:editAction } 8 | requirements: 9 | id: \d+ 10 | -------------------------------------------------------------------------------- /Basecom/Bundle/RulesEngineBundle/Resources/public/css/rules.less: -------------------------------------------------------------------------------- 1 | @logo-background: #f8f8f9; 2 | @border-white-light: #e6e8ec; 3 | @border-white-medium: #ddd; 4 | 5 | .AknFlash.AknFlash-FormError { 6 | display: block; 7 | width: auto; 8 | min-height: 0; 9 | left: auto; 10 | opacity: 1; 11 | z-index: auto; 12 | } 13 | 14 | .entity-edit-form { 15 | .create-new-rule { 16 | margin-right: 20px; 17 | } 18 | } 19 | .AknFieldContainer-label { 20 | display: unset; 21 | } 22 | .AknFieldContainer-inputContainer { 23 | width: 100%; 24 | } 25 | .basecom-rule { 26 | .btn.remove { 27 | cursor: pointer; 28 | } 29 | 30 | .AknColumnConfigurator { 31 | padding: 0; 32 | margin-right: -24px; 33 | .AknColumnConfigurator-column { 34 | border: none; 35 | } 36 | } 37 | .rule-action, .rule-condition, .rule-action-container, .rule-condition-container { 38 | width: 100%; 39 | .btn-delete { 40 | position: absolute; 41 | top: 0; 42 | right: 0; 43 | } 44 | } 45 | .AknFieldContainer-header { 46 | margin-bottom: 0; 47 | margin-top: 15px; 48 | } 49 | .action-field-values-value, .condition-field-values-value { 50 | position: relative; 51 | .AknFieldContainer-inputContainer { 52 | width: 100%; 53 | } 54 | .AknFieldContainer-header { 55 | margin-top: 0; 56 | margin-bottom: 10px; 57 | } 58 | } 59 | .action-field-values-container, .condition-field-values-container { 60 | width: 100%; 61 | } 62 | .remove-value { 63 | position: absolute; 64 | right: 0; 65 | top: 4px; 66 | cursor: pointer; 67 | } 68 | #create-add-another-action, #create-add-another-condition { 69 | float: right; 70 | margin-right: 30px; 71 | } 72 | .AknFieldContainer { 73 | &.condition-field-unit, 74 | &.condition-field-currency { 75 | width: 100%; 76 | } 77 | } 78 | } 79 | 80 | .basecom-rule { 81 | .AknTitleContainer-userMenu { 82 | margin-right: 10px; 83 | } 84 | .AknButtonList { 85 | .AknButton { 86 | margin-left: 10px; 87 | } 88 | #basecom-logo { 89 | float: left; 90 | border: none; 91 | background-image: url('/bundles/basecomrulesengine/logo.png'); 92 | background-size: contain; 93 | background-color: transparent; 94 | background-repeat: no-repeat; 95 | font-size: 0; 96 | width: 200px; 97 | pointer-events: none; 98 | } 99 | } 100 | 101 | .rule-engine-error { 102 | margin: 10px auto; 103 | width: 50%; 104 | min-width: 700px; 105 | } 106 | #create-add-another-action { 107 | margin-left: 8px; 108 | } 109 | .tabsection-content { 110 | float: left; 111 | width: 100%; 112 | border: 1px solid @border-white-light; 113 | } 114 | #rule_definition { 115 | .AknFieldContainer.rule-condition-container { 116 | float: left; 117 | margin: 20px 50px 0 0; 118 | padding-top: 0; 119 | width: 100%; 120 | > label { 121 | margin-top: 5px; 122 | font-size: 30px; 123 | } 124 | > .controls { 125 | > .rule-condition-container { 126 | > .AknFieldContainer.rule-condition { 127 | min-height: 500px; 128 | padding: 5px; 129 | border: 1px solid @border-white-light; 130 | } 131 | } 132 | } 133 | .rule-condition { 134 | position: relative; 135 | .rule-condition { 136 | .AknFieldContainer { 137 | margin: 0; 138 | .controls { 139 | .icons-container { 140 | display: none; 141 | } 142 | } 143 | } 144 | } 145 | .condition-field-values-container { 146 | width: 100%; 147 | .condition-field-values-value { 148 | .AknFieldContainer-header { 149 | display: none; 150 | } 151 | } 152 | .remove-value.btn { 153 | display: none; 154 | } 155 | .add-value.btn { 156 | display: none; 157 | margin-top: 10px; 158 | cursor: pointer; 159 | } 160 | .condition-field-values-value { 161 | margin-bottom: 0; 162 | .btn { 163 | height: 32px; 164 | line-height: 32px; 165 | } 166 | .controls { 167 | max-width: 458px; 168 | height: 33px; 169 | float: left; 170 | } 171 | } 172 | } 173 | &.rule-condition-type-between, &.rule-condition-type-not_between { 174 | .condition-field-values-value { 175 | margin-top: 5px; 176 | } 177 | } 178 | .remove { 179 | position: absolute; 180 | top: 5px; 181 | right: 30px; 182 | } 183 | .condition-field-scope { 184 | display: none; 185 | } 186 | .condition-field-locale { 187 | display: none; 188 | } 189 | .condition-field-unit { 190 | display: none; 191 | } 192 | .condition-field-currency { 193 | display: none; 194 | } 195 | &.is_metric { 196 | .condition-field-unit { 197 | display: inline-block; 198 | } 199 | } 200 | &.is_price { 201 | .condition-field-currency { 202 | display: inline-block; 203 | } 204 | } 205 | &.is_localizable { 206 | .condition-field-locale { 207 | display: inline-block; 208 | width: 100%; 209 | .select2-container { 210 | border: none; 211 | .select2-choice { 212 | border: 1px solid @border-white-medium; 213 | } 214 | } 215 | } 216 | } 217 | &.is_scopable { 218 | .condition-field-scope { 219 | display: inline-block; 220 | width: 100%; 221 | .select2-container { 222 | border: none; 223 | .select2-choice { 224 | border: 1px solid @border-white-medium; 225 | } 226 | } 227 | } 228 | } 229 | &.rule-condition-type-in, &.rule-condition-type-not_in { 230 | .condition-field-values-container { 231 | .condition-field-values-value { 232 | .AknFieldContainer-inputContainer { 233 | padding-right: 30px; 234 | } 235 | .AknFieldContainer-header { 236 | display: block; 237 | } 238 | .remove-value.btn { 239 | display: inline-block; 240 | } 241 | } 242 | .add-value.btn { 243 | display: inline-block; 244 | } 245 | } 246 | } 247 | &.rule-condition-type-empty { 248 | .condition-field-values-container { 249 | display: none; 250 | } 251 | } 252 | &.rule-condition-type-not_empty { 253 | .condition-field-values-container { 254 | display: none; 255 | } 256 | } 257 | } 258 | } 259 | .AknFieldContainer.rule-action-container { 260 | margin-top: 20px; 261 | padding-top: 0; 262 | width: 100%; 263 | > .controls { 264 | > .rule-action-container { 265 | > .AknFieldContainer.rule-action { 266 | min-height: 500px; 267 | padding: 5px; 268 | border: 1px solid @border-white-light; 269 | } 270 | } 271 | } 272 | > label { 273 | margin-top: 5px; 274 | font-size: 30px; 275 | } 276 | .rule-action { 277 | position: relative; 278 | .rule-action { 279 | .action-field-values-container { 280 | display: none; 281 | .remove-value.btn { 282 | display: none; 283 | } 284 | .add-value.btn { 285 | display: none; 286 | } 287 | .action-field-values-value { 288 | margin-bottom: 0; 289 | .btn { 290 | height: 32px; 291 | line-height: 32px; 292 | } 293 | .controls { 294 | max-width: 458px; 295 | height: 33px; 296 | float: left; 297 | } 298 | } 299 | } 300 | .AknFieldContainer { 301 | margin: 0; 302 | .controls { 303 | .icons-container { 304 | display: none; 305 | } 306 | } 307 | } 308 | } 309 | .remove { 310 | position: absolute; 311 | top: 5px; 312 | right: 30px; 313 | } 314 | .action-field-locale { 315 | display: none; 316 | } 317 | .action-field-scope { 318 | display: none; 319 | } 320 | .action-field-unit { 321 | display: none; 322 | } 323 | .action-to-field-scope, .action-to-field-locale, .action-from-field-scope, .action-from-field-locale { 324 | display: none; 325 | } 326 | .action-field-scope { 327 | display: none; 328 | } 329 | &.is_metric { 330 | .action-field-unit { 331 | display: inline-block; 332 | width: 100%; 333 | } 334 | } 335 | &.is_localizable { 336 | .action-field-locale { 337 | display: inline-block; 338 | width: 100%; 339 | .select2-container { 340 | border: none; 341 | .select2-choice { 342 | border: 1px solid @border-white-medium; 343 | } 344 | } 345 | } 346 | } 347 | &.is_scopable { 348 | .action-field-scope { 349 | display: inline-block; 350 | width: 100%; 351 | .select2-container { 352 | border: none; 353 | .select2-choice { 354 | border: 1px solid @border-white-medium; 355 | } 356 | } 357 | } 358 | } 359 | &.rule-action-type-add, 360 | .rule-action-type-remove { 361 | .action-from-field, 362 | .action-from-field-locale, 363 | .action-from-field-scope, 364 | .action-to-field, 365 | .action-to-field-locale, 366 | .action-to-field-scope { 367 | display: none; 368 | } 369 | .action-field-values-container { 370 | display: inline-block; 371 | .action-field-values-value { 372 | .AknFieldContainer-header { 373 | display: block; 374 | } 375 | .AknFieldContainer-inputContainer { 376 | padding-right: 30px; 377 | } 378 | .remove-value.btn { 379 | display: inline-block; 380 | } 381 | } 382 | .add-value.btn { 383 | margin-top: 10px; 384 | display: inline-block; 385 | cursor: pointer; 386 | } 387 | } 388 | .action-value { 389 | display: none; 390 | } 391 | } 392 | &.rule-action-type-set { 393 | .action-from-field, 394 | .action-from-field-locale, 395 | .action-from-field-scope, 396 | .action-to-field, 397 | .action-to-field-locale, 398 | .action-to-field-scope { 399 | display: none; 400 | } 401 | } 402 | &.rule-action-type-copy { 403 | .action-field-locale, .action-field-scope, .action-field-value, .action-field, .action-value { 404 | display: none; 405 | } 406 | &.from_is_localizable { 407 | .action-from-field-locale { 408 | display: inline-block; 409 | width: 100%; 410 | } 411 | } 412 | &.from_is_scopable { 413 | .action-from-field-scope { 414 | display: inline-block; 415 | width: 100%; 416 | } 417 | } 418 | &.to_is_localizable { 419 | .action-to-field-locale { 420 | display: inline-block; 421 | width: 100%; 422 | } 423 | } 424 | &.to_is_scopable { 425 | .action-to-field-scope { 426 | display: inline-block; 427 | width: 100%; 428 | } 429 | } 430 | } 431 | } 432 | } 433 | } 434 | } 435 | -------------------------------------------------------------------------------- /Basecom/Bundle/RulesEngineBundle/Resources/public/js/edit.js: -------------------------------------------------------------------------------- 1 | define( 2 | ['jquery'], 3 | function ($) { 4 | 'use strict'; 5 | return function (attributesMap) { 6 | if ($('.basecom-rule').length) { 7 | init(); 8 | var select = $('.basecom-rule select'); 9 | select.select2(); 10 | select.trigger('change'); 11 | $('#select2-drop-mask').hide(); 12 | } 13 | 14 | function init () { 15 | var actionContainers = $('.AknFieldContainer.rule-action'); 16 | var conditionContainers = $('.AknFieldContainer.rule-condition'); 17 | 18 | var actionCounter = actionContainers.length; 19 | var conditionCounter = conditionContainers.length; 20 | 21 | function addRemoveEvents () { 22 | var actionContainers = $('.AknFieldContainer.rule-action'); 23 | var conditionContainers = $('.AknFieldContainer.rule-condition'); 24 | 25 | actionContainers.find('.remove').remove(); 26 | conditionContainers.find('.remove').remove(); 27 | 28 | actionContainers.append('
Delete Action
'); 29 | conditionContainers.append('
Delete Condition
'); 30 | 31 | conditionContainers.each(function (index, conditionContainer) { 32 | var removeButton = $(conditionContainer).find('.remove'); 33 | removeButton.unbind('click'); 34 | removeButton.on('click', function () { 35 | var conditionContainer = removeButton.closest('.AknFieldContainer.rule-condition'); 36 | conditionContainer.remove(); 37 | }); 38 | }); 39 | 40 | actionContainers.each(function (index, actionContainer) { 41 | var removeButton = $(actionContainer).find('.remove'); 42 | removeButton.unbind('click'); 43 | removeButton.on('click', function () { 44 | var actionContainer = removeButton.closest('.AknFieldContainer.rule-action'); 45 | actionContainer.remove(); 46 | }); 47 | }); 48 | } 49 | 50 | conditionContainers.each(function (index, conditionContainer) { 51 | addValueEvents(conditionContainer, 'condition'); 52 | }); 53 | actionContainers.each(function (index, actionContainer) { 54 | addValueEvents(actionContainer, 'action'); 55 | }); 56 | 57 | function addValueEvents (container, type) { 58 | var removeButton = $(container).find('.remove'); 59 | removeButton.unbind('click'); 60 | removeButton.on('click', function () { 61 | var conditionContainer = removeButton.closest('.AknFieldContainer.rule-' + type); 62 | conditionContainer.remove(); 63 | }); 64 | 65 | var valueContainer = $(container).find('.AknFieldContainer.' + type + '-field-values-container'); 66 | var values = valueContainer.find('.AknFieldContainer.' + type + '-field-values-value'); 67 | 68 | valueContainer.find('.add-value.btn').remove(); 69 | valueContainer.append('
Add Value
'); 70 | 71 | values.each(function (index, value) { 72 | handleNewValue(value, type); 73 | }); 74 | 75 | var addButton = $(container).find('.btn.add-value'); 76 | valueContainer = addButton.parent().find('.' + type + '-field-values-container'); 77 | 78 | if (0 >= values.length) { 79 | addInitialValue(valueContainer, type); 80 | } 81 | 82 | addButton.unbind('click'); 83 | addButton.on('click', function (event) { 84 | var addButton = $(event.target); 85 | var valueContainer = addButton.parent().find('.' + type + '-field-values-container'); 86 | var values = valueContainer.find('.AknFieldContainer.' + type + '-field-values-value'); 87 | var prototype = valueContainer.attr('data-prototype'); 88 | var newValue = $(prototype.replace(/__name__/g, values.length)); 89 | valueContainer.append(newValue); 90 | handleNewValue(newValue, type); 91 | }); 92 | } 93 | 94 | function handleNewValue (valueContainer, type) { 95 | var removeButtons = $(valueContainer).find('.btn.remove-value'); 96 | 97 | if (0 < removeButtons.length) { 98 | return; 99 | } 100 | 101 | $(valueContainer).append('
'); 102 | 103 | removeButtons = $(valueContainer).find('.btn.remove-value'); 104 | removeButtons.unbind('click'); 105 | removeButtons.on('click', function (event) { 106 | var removeButton = $(event.target); 107 | var valueContainer = removeButton.closest('.AknFieldContainer.' + type + '-field-values-value'); 108 | valueContainer.remove(); 109 | }); 110 | } 111 | 112 | addRemoveEvents(); 113 | 114 | var addConditionButton = $('#create-add-another-condition'); 115 | var addActionButton = $('#create-add-another-action'); 116 | 117 | addConditionButton.unbind('click'); 118 | addConditionButton.on('click', function (e) { 119 | e.preventDefault(); 120 | e.stopPropagation(); 121 | 122 | var conditionList = $('#rule_definition_conditions'); 123 | 124 | var newWidget = conditionList.attr('data-prototype'); 125 | newWidget = newWidget.replace(/__name__/g, conditionCounter); 126 | newWidget = newWidget.replace(/label__/g, ''); 127 | var newLi = $(newWidget); 128 | conditionCounter++; 129 | newLi.appendTo(conditionList); 130 | 131 | addDefinitionItemEventListeners(); 132 | addRemoveEvents(); 133 | addValueEvents(newLi, 'condition'); 134 | $('select').select2(); 135 | }); 136 | addActionButton.unbind('click'); 137 | addActionButton.click(function (e) { 138 | e.preventDefault(); 139 | e.stopPropagation(); 140 | 141 | var actionList = $('#rule_definition_actions'); 142 | 143 | var newWidget = actionList.attr('data-prototype'); 144 | newWidget = newWidget.replace(/__name__/g, actionCounter); 145 | newWidget = newWidget.replace(/label__/g, ''); 146 | var newLi = $(newWidget); 147 | actionCounter++; 148 | newLi.appendTo(actionList); 149 | 150 | addDefinitionItemEventListeners(); 151 | addRemoveEvents(); 152 | $('select').select2(); 153 | }); 154 | 155 | var group = $('.group'); 156 | group.unbind('click'); 157 | group.click(function () { 158 | $(this).parent().find('tr:not(.group)').toggle(); 159 | $(this).find('i').toggleClass('icon-expand-alt icon-collapse-alt'); 160 | }); 161 | 162 | var editBtn = $('.attribute-requirement:not(.identifier) i'); 163 | editBtn.unbind('click'); 164 | editBtn.on('click', function () { 165 | $(this).toggleClass('icon-ok required').toggleClass('icon-circle non-required'); 166 | 167 | var $input = $(this).siblings('input[type="checkbox"]').eq(0); 168 | var checked = $input.is(':checked'); 169 | $(this).attr('data-original-title', $(this).parent().data((checked ? 'not-' : '') + 'required-title')).tooltip('show'); 170 | $input.prop('checked', !checked).trigger('change'); 171 | }); 172 | 173 | function addInitialValue (valueContainer, type) { 174 | var values = valueContainer.find('.AknFieldContainer.' + type + '-field-values-value'); 175 | var prototype = valueContainer.attr('data-prototype'); 176 | var newValue = $(prototype.replace(/__name__/g, values.length)); 177 | valueContainer.append(newValue); 178 | handleNewValue(newValue, type); 179 | } 180 | 181 | function addDefinitionItemEventListeners () { 182 | function handleTypeSelection (select, ruleType) { 183 | select = $(select); 184 | var selectedType = select.val(); 185 | var ruleDefinitionItem = select.closest('.rule-' + ruleType); 186 | var isMultiOption; 187 | select.children('option').each(function (index, option) { 188 | var type = $(option).attr('value'); 189 | ruleDefinitionItem.removeClass('rule-' + ruleType + '-type-' + type); 190 | }); 191 | 192 | ruleDefinitionItem.addClass('rule-' + ruleType + '-type-' + selectedType); 193 | var valueContainer = select.closest('.rule-' + ruleType).find('.' + ruleType + '-field-values-container:not(.AknFieldContainer)'); 194 | 195 | if (ruleType === 'condition') { 196 | isMultiOption = false; 197 | 198 | if (selectedType === "in" || selectedType === "not_in" || selectedType === "between" || selectedType === "not_between") { 199 | isMultiOption = true; 200 | } 201 | 202 | var values = valueContainer.find('.AknFieldContainer.condition-field-values-value'); 203 | if ( 204 | (0 === values.length || (1 < values.length && !isMultiOption)) || 205 | ((selectedType === "between" || selectedType === "not_between") && 2 !== values.length)) { 206 | 207 | values.remove(); 208 | addInitialValue(valueContainer, ruleType); 209 | if (selectedType === "between" || selectedType === "not_between") { 210 | addInitialValue(valueContainer, ruleType); 211 | } 212 | } 213 | } 214 | if (ruleType === 'action') { 215 | isMultiOption = selectedType === "add" || selectedType === "remove"; 216 | 217 | values = valueContainer.find('.AknFieldContainer.action-field-values-value'); 218 | 219 | if (0 === values.length || (1 < values.length && !isMultiOption)) { 220 | values.remove(); 221 | addInitialValue(valueContainer, ruleType); 222 | } 223 | } 224 | } 225 | 226 | $('select.condition-type-select').each(function (index, select) { 227 | handleTypeSelection(select, 'condition'); 228 | $(select).unbind('change'); 229 | $(select).on('change', function () { 230 | handleTypeSelection(select, 'condition'); 231 | }); 232 | }); 233 | 234 | $('select.action-type-select').each(function (index, select) { 235 | handleTypeSelection(select, 'action'); 236 | $(select).unbind('change'); 237 | $(select).on('change', function () { 238 | handleTypeSelection(select, 'action'); 239 | }); 240 | }); 241 | 242 | $('select.condition-field').each(function (index, select) { 243 | handleFieldSelection(select, 'condition'); 244 | $(select).unbind('change'); 245 | $(select).on('change', function () { 246 | handleFieldSelection(select, 'condition'); 247 | }); 248 | }); 249 | $('select.action-field').each(function (index, select) { 250 | handleFieldSelection(select, 'action'); 251 | $(select).unbind('change'); 252 | $(select).on('change', function () { 253 | handleFieldSelection(select, 'action'); 254 | }); 255 | }); 256 | $('select.action-from-field').each(function (index, select) { 257 | handleCopyFieldSelection(select); 258 | $(select).unbind('change'); 259 | $(select).on('change', function () { 260 | handleCopyFieldSelection(select, 'from'); 261 | }); 262 | }); 263 | $('select.action-to-field').each(function (index, select) { 264 | handleCopyFieldSelection(select); 265 | $(select).unbind('change'); 266 | $(select).on('change', function () { 267 | handleCopyFieldSelection(select, 'to'); 268 | }); 269 | }); 270 | 271 | function handleCopyFieldSelection (select, type) { 272 | select = $(select); 273 | var selectedField = select.val(); 274 | var selectField; 275 | 276 | if (!attributesMap.hasOwnProperty(selectedField)) { 277 | return; 278 | } 279 | 280 | var attributeData = attributesMap[selectedField]; 281 | var ruleDefinitionItem = select.closest('.rule-action'); 282 | 283 | if (attributeData.is_localizable) { 284 | ruleDefinitionItem.addClass(type + '_is_localizable'); 285 | } else { 286 | ruleDefinitionItem.removeClass(type + '_is_localizable'); 287 | selectField = $(ruleDefinitionItem).find('.action-' + type + '-field-locale select'); 288 | $(selectField).select2("val", ""); 289 | } 290 | if (attributeData.is_scopable) { 291 | ruleDefinitionItem.addClass(type + '_is_scopable'); 292 | } else { 293 | ruleDefinitionItem.removeClass(type + '_is_scopable'); 294 | selectField = $(ruleDefinitionItem).find('.action-' + type + '-field-scope select'); 295 | $(selectField).select2("val", ""); 296 | } 297 | } 298 | 299 | function handleFieldSelection (select, ruleType) { 300 | select = $(select); 301 | var selectedField = select.val(); 302 | var selectField; 303 | 304 | var ruleDefinitionItem = select.closest('.rule-' + ruleType); 305 | 306 | if (selectedField === 'completeness') { 307 | ruleDefinitionItem.addClass('is_localizable'); 308 | ruleDefinitionItem.addClass('is_scopable'); 309 | } else if (attributesMap.hasOwnProperty(selectedField)) { 310 | 311 | var attributeData = attributesMap[selectedField]; 312 | 313 | if (attributeData.is_localizable) { 314 | ruleDefinitionItem.addClass('is_localizable'); 315 | } else { 316 | ruleDefinitionItem.removeClass('is_localizable'); 317 | selectField = $(ruleDefinitionItem).find('.' + ruleType + '-field-locale select'); 318 | $(selectField).select2("val", ""); 319 | } 320 | if (attributeData.is_scopable) { 321 | ruleDefinitionItem.addClass('is_scopable'); 322 | } else { 323 | ruleDefinitionItem.removeClass('is_scopable'); 324 | selectField = $(ruleDefinitionItem).find('.' + ruleType + '-field-scope select'); 325 | $(selectField).select2("val", ""); 326 | } 327 | if (attributeData.is_metric) { 328 | ruleDefinitionItem.addClass('is_metric'); 329 | } else { 330 | ruleDefinitionItem.removeClass('is_metric'); 331 | selectField = $(ruleDefinitionItem).find('.' + ruleType + '-field-unit input'); 332 | $(selectField).val(''); 333 | } 334 | if (attributeData.is_price) { 335 | ruleDefinitionItem.addClass('is_price'); 336 | } else { 337 | ruleDefinitionItem.removeClass('is_price'); 338 | selectField = $(ruleDefinitionItem).find('.' + ruleType + '-field-unit input'); 339 | $(selectField).val(''); 340 | } 341 | } else { 342 | ruleDefinitionItem.removeClass('is_localizable'); 343 | ruleDefinitionItem.removeClass('is_metric'); 344 | ruleDefinitionItem.removeClass('is_scopable'); 345 | ruleDefinitionItem.removeClass('is_price'); 346 | 347 | selectField = $(ruleDefinitionItem).find('.' + ruleType + '-field-locale select'); 348 | $(selectField).select2("val", ""); 349 | selectField = $(ruleDefinitionItem).find('.' + ruleType + '-field-unit input'); 350 | $(selectField).val(''); 351 | selectField = $(ruleDefinitionItem).find('.' + ruleType + '-field-scope select'); 352 | $(selectField).select2("val", ""); 353 | } 354 | } 355 | } 356 | 357 | addDefinitionItemEventListeners(); 358 | addRemoveEvents(); 359 | } 360 | 361 | init(); 362 | }; 363 | } 364 | ); 365 | -------------------------------------------------------------------------------- /Basecom/Bundle/RulesEngineBundle/Resources/public/less/index.less: -------------------------------------------------------------------------------- 1 | @import "./public/bundles/basecomrulesengine/css/rules.less"; -------------------------------------------------------------------------------- /Basecom/Bundle/RulesEngineBundle/Resources/public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/basecom-archive/archive-akeneo-rulesui/89ff656996b296979b2358961b2d34fed8ac71b8/Basecom/Bundle/RulesEngineBundle/Resources/public/logo.png -------------------------------------------------------------------------------- /Basecom/Bundle/RulesEngineBundle/Resources/translations/jsmessages.de.yml: -------------------------------------------------------------------------------- 1 | basecom: 2 | rules_engine: 3 | menu_entry: 'Regel hinzufügen' 4 | edit: 'Regel bearbeiten' 5 | flash: 6 | rule: 7 | removed: 'Regel entfernt' 8 | saved: 'Regel gespeichert' 9 | error: 10 | save: 'Regel konnte nicht gespeichert werden' 11 | copy: 'Das Kopieren von 2 gleichen Feldern ist nicht gestattet' 12 | btn: 13 | create: 14 | rule: 'Neue Rule erstellen' 15 | add-another-action: 'Neue Action hinzufügen' 16 | add-another-condition: 'Neue Condition hinzufügen' 17 | pim_title: 18 | basecom_rule_edit: 'Regel bearbeiten' 19 | basecom_rule_create: 'neue Regel erstellen' 20 | -------------------------------------------------------------------------------- /Basecom/Bundle/RulesEngineBundle/Resources/translations/jsmessages.en.yml: -------------------------------------------------------------------------------- 1 | basecom: 2 | rules_engine: 3 | menu_entry: 'Add Rule' 4 | edit: 'Edit rule' 5 | flash: 6 | rule: 7 | removed: 'rule successfully removed' 8 | saved: 'rule saved' 9 | error: 10 | save: 'could not save rule' 11 | copy: 'it is not allowed to copy from / to the same field' 12 | btn: 13 | create: 14 | rule: 'Add new rule' 15 | add-another-action: 'Add new action' 16 | add-another-condition: 'Add new condition' 17 | pim_title: 18 | basecom_rule_edit: 'edit Rule' 19 | basecom_rule_create: 'create Rule' 20 | -------------------------------------------------------------------------------- /Basecom/Bundle/RulesEngineBundle/Resources/translations/messages.de.yml: -------------------------------------------------------------------------------- 1 | basecom: 2 | rules_engine: 3 | acl: 4 | remove_permissions: 'Regel entfernen' 5 | edit_permissions: 'Regel bearbeiten' 6 | create_permissions: 'Regel erstellen' 7 | menu_entry: 'Regel hinzufügen' 8 | edit: 'Regel bearbeiten' 9 | flash: 10 | rule: 11 | removed: 'Regel entfernt' 12 | saved: 'Regel gespeichert' 13 | error: 14 | save: 'Regel konnte nicht gespeichert werden' 15 | copy: 'Das kopieren von 2 gleichen feldern ist nicht gestattet' 16 | btn: 17 | create: 18 | rule: 'Neue Rule erstellen' 19 | add-another-action: 'Neue Action hinzufügen' 20 | add-another-condition: 'Neue Condition hinzufügen' 21 | pim_enrich: 22 | create: 23 | add-another-action: 'Neue Action hinzufügen' 24 | add-another-condition: 'Neue Condition hinzufügen' 25 | pim_title: 26 | basecom_rule_edit: 'Regel bearbeiten' 27 | basecom_rule_create: 'neue Regel erstellen' 28 | -------------------------------------------------------------------------------- /Basecom/Bundle/RulesEngineBundle/Resources/translations/messages.en.yml: -------------------------------------------------------------------------------- 1 | basecom: 2 | rules_engine: 3 | acl: 4 | remove_permissions: 'Remove rule' 5 | edit_permissions: 'Edit rule' 6 | create_permissions: 'Create rule' 7 | menu_entry: 'Add Rule' 8 | edit: 'Edit rule' 9 | flash: 10 | rule: 11 | removed: 'rule successfully removed' 12 | saved: 'rule saved' 13 | error: 14 | save: 'could not save rule' 15 | copy: 'it is not allowed to copy from / to the same field' 16 | btn: 17 | create: 18 | rule: 'Add new rule' 19 | add-another-action: 'Add new action' 20 | add-another-condition: 'Add new condition' 21 | pim_enrich: 22 | create: 23 | add-another-action: 'Add new action' 24 | add-another-condition: 'Add new condition' 25 | pim_title: 26 | basecom_rule_edit: 'edit Rule' 27 | basecom_rule_create: 'create Rule' 28 | -------------------------------------------------------------------------------- /Basecom/Bundle/RulesEngineBundle/Resources/views/OverwriteRule/index.html.twig: -------------------------------------------------------------------------------- 1 | {% import 'PimDataGridBundle::macros.html.twig' as dataGrid %} 2 | 3 | {% block content %} 4 | {% import 'BasecomRulesEngine::macros.html.twig' as elements %} 5 | {% if resource_granted('pimee_catalog_rule_rule_execute_permissions') %} 6 | {% set buttons %} 7 | {{ elements.confirmLink( 8 | path('pimee_catalog_rule_rule_execute'), 9 | path('pimee_catalog_rule_rule_index'), 10 | 'pimee_catalog_rule.notification.rule.launched'|trans, 11 | 'Execute rules', 12 | 'play', 13 | 'pimee_catalog_rule.popin.execute_rules.confirm_content'|trans, 14 | 'btn btn-primary execute-all-rules', 15 | 'pimee_catalog_rule.popin.execute_rules.confirm_title'|trans 16 | ) }} 17 | {{ elements.createBtn('rule', path('basecom_rule_create'), null, 'pim_enrich_channel_create', 'plus') }} 18 | {{ elements.createBtn( 19 | 'rule', 20 | path('basecom_rule_create'), 21 | 'dialog', 22 | 'basecom_rule_create', 23 | 'plus' 24 | ) }} 25 | {% endset %} 26 | 27 | {{ elements.page_header('pimee_catalog_rule.datagrid.rule-grid.title'|trans, buttons) }} 28 | {% else %} 29 | {{ elements.page_header('pimee_catalog_rule.datagrid.rule-grid.title'|trans) }} 30 | {% endif %} 31 | 32 | {{ dataGrid.renderGrid('rule-grid') }} 33 | {% endblock %} 34 | -------------------------------------------------------------------------------- /Basecom/Bundle/RulesEngineBundle/Resources/views/Rule/_actions.html.twig: -------------------------------------------------------------------------------- 1 | {% for action in value.actions %} 2 |

3 | {% if action.type in ["copy", "copy_value"] %} 4 | {% set parameters = { 5 | '%from_field%': action.from_field|append_locale_and_scope_context(action.from_locale|default, action.from_scope|default)|highlight, 6 | '%to_field%': action.to_field|append_locale_and_scope_context(action.to_locale|default, action.to_scope|default)|highlight 7 | } %} 8 | {% elseif action.type in ["add", "remove"] %} 9 | {% set parameters = { 10 | '%field%': action.field|append_locale_and_scope_context(action.options.locale|default, action.options.scope|default)|highlight, 11 | '%value%': action.items|present_rule_action_value(action.field)|highlight, 12 | } %} 13 | {% else %} 14 | {% set parameters = { 15 | '%field%': action.field|append_locale_and_scope_context(action.locale|default, action.scope|default)|highlight, 16 | '%value%': action.value|present_rule_action_value(action.field)|highlight 17 | } %} 18 | {% endif %} 19 | 20 | {{ ('pimee_catalog_rule.actions.type.' ~ action.type) |trans(parameters)|raw }} 21 |

22 | {% endfor %} 23 | -------------------------------------------------------------------------------- /Basecom/Bundle/RulesEngineBundle/Resources/views/Rule/edit.html.twig: -------------------------------------------------------------------------------- 1 | {% form_theme form 'BasecomRulesEngine::form_themes.html.twig' %} 2 | {% block content %} 3 | {% import 'PimUIBundle:Default:page_elements.html.twig' as elements %} 4 | {% import 'BasecomRulesEngine::macros.html.twig' as bscElements %} 5 |
6 | {% set action = path('basecom_rule_create') %} 7 | {% if ruleDefinition.id is not null %} 8 | {% set action = path('basecom_rule_edit', {id: ruleDefinition.id}) %} 9 | {% endif %} 10 | {% if ruleDefinition.id is defined and ruleDefinition.id is not null %} 11 | {% set action = path('basecom_rule_edit', {'id': ruleDefinition.id}) %} 12 | {% endif %} 13 | {{ form_start(form, { 14 | 'action': action, 15 | 'attr': { 16 | 'data-updated-title': 'confirmation.leave'|trans, 17 | 'data-updated-message': 'confirmation.discard changes'|trans({ '%entity%': 'family.title'|trans }) 18 | } 19 | }) }} 20 | {% set buttons %} 21 | {{ bscElements.createBtn('basecom-logo', null, {'id' : 'basecom-logo'}) }} 22 | {% if ruleDefinition.id is defined and ruleDefinition.id is not null %} 23 | {{ elements.deleteLink( 24 | path('pimee_catalog_rule_rule_delete', { id: ruleDefinition.id }), 25 | 'pim_enrich_attribute_remove', 26 | path('pimee_catalog_rule_rule_index'), 27 | 'pim_common.confirm_deletion'|trans({ '%name%': form.vars.name }), 28 | 'basecom.flash.rule.removed'|trans, 29 | null, 30 | 'AknButton AknButton--important' 31 | ) }} 32 | {% endif %} 33 | {{ elements.submitBtn('', 'ok') }} 34 | {% endset %} 35 | 36 | {% if ruleDefinition.id is defined and ruleDefinition.id is not null %} 37 | {{ elements.page_header({'title': 'basecom.rules_engine.edit'|trans, 'buttons': buttons}) }} 38 | {% else %} 39 | {{ elements.page_header({'title': 'basecom.rules_engine.menu_entry'|trans, 'buttons': buttons}) }} 40 | {% endif %} 41 | 42 |
43 | 44 | {% for formError in form.vars.errors %} 45 |
46 | {{ formError.message }} 47 |
48 | {% endfor %} 49 | 50 | {{ form_row(form.code) }} 51 | {{ form_row(form.priority) }} 52 | {{ form_row(form.type) }} 53 |
54 |
55 | {{ form_row(form.conditions) }} 56 |
57 |
58 | {{ form_row(form.actions) }} 59 |
60 |
61 |
62 | {{ form_end(form) }} 63 |
64 | {% endblock %} 65 | {% set messages = [] %} 66 | {% for type,items in app.session.flashbag.all %} 67 | {% set typeMessages = [] %} 68 | {% for index,item in items %} 69 | {% set typeMessages = typeMessages|merge({ (index): item|trans }) %} 70 | {% endfor %} 71 | {% set messages = messages|merge({ (type): typeMessages }) %} 72 | {% endfor %} 73 | 74 | 104 | -------------------------------------------------------------------------------- /Basecom/Bundle/RulesEngineBundle/Resources/views/form_themes.html.twig: -------------------------------------------------------------------------------- 1 | {% block _rule_definition_actions_row %} 2 | {% import 'BasecomRulesEngine::macros.html.twig' as elements %} 3 | {{ form_label(form) }} 4 | {{ elements.createBtn('add-another-action', null,{'id' : 'create-add-another-action'}) }} 5 |
6 |
7 | {{ form_widget(form) }} 8 |
9 | {% endblock %} 10 | {% block _rule_definition_actions_entry_row %} 11 |
12 |
13 |
14 | {{ form_row(form.type) }} 15 | {{ form_row(form.field) }} 16 | {{ form_row(form.value) }} 17 | {{ form_row(form.fromField) }} 18 | {{ form_row(form.fromLocale) }} 19 | {{ form_row(form.fromScope) }} 20 | {{ form_row(form.toField) }} 21 | {{ form_row(form.toLocale) }} 22 | {{ form_row(form.toScope) }} 23 | {{ form_row(form.locale) }} 24 | {{ form_row(form.scope) }} 25 | {{ form_row(form.unit) }} 26 | {{ form_row(form.items) }} 27 |
28 |
29 |
30 | {% endblock %} 31 | 32 | {% block _rule_definition_conditions_row %} 33 | {{ form_label(form) }} 34 | {% import 'BasecomRulesEngine::macros.html.twig' as elements %} 35 | {{ elements.createBtn('add-another-condition', null,{'id' : 'create-add-another-condition'}) }} 36 |
37 |
38 | {{ form_widget(form) }} 39 |
40 | {% endblock %} 41 | {% block _rule_definition_conditions_entry_row %} 42 |
43 |
44 |
45 | {{ form_row(form.field) }} 46 | {{ form_row(form.locale) }} 47 | {{ form_row(form.scope) }} 48 | {{ form_row(form.operator) }} 49 | {{ form_row(form.values) }} 50 | {{ form_row(form.unit) }} 51 | {{ form_row(form.currency) }} 52 |
53 |
54 |
55 | {% endblock %} 56 | -------------------------------------------------------------------------------- /Basecom/Bundle/RulesEngineBundle/Resources/views/macros.html.twig: -------------------------------------------------------------------------------- 1 | {% macro createBtn(entity, url, attr, acl, icon) %} 2 | {% spaceless %} 3 | {% if acl is null or resource_granted(acl) %} 4 | {% if attr == 'dialog' %} 5 | {% set buttonId = 'create-'~(entity|replace({' ': '-', '.': '-'})|lower) %} 6 | {% set attr = { 'id': buttonId, 'data-form-url': url } %} 7 | {% set url = null %} 8 | {% endif %} 9 | {% set title = ('btn.create.' ~ entity)|trans|capitalize %} 10 | 11 | {{ title }} 12 | 13 | {% if buttonId is defined %} 14 | 25 | {% endif %} 26 | {% endif %} 27 | {% endspaceless %} 28 | {% endmacro %} 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) basecom 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Akeneo rules engine graphical interface 2 | ## Requirements 3 | 4 | Akeneo PIM Enterprise Edition 4.0.~ 5 | 6 | ## Installation 7 | 8 | ### Install via composer 9 | If you want to install this bundle via composer you can either use the following command: 10 | ``` 11 | $ composer require basecom/akeneo-rules-ui 12 | ``` 13 | 14 | or include the bundle in your `composer.json` with the desired version. 15 | 16 | ### After installation 17 | Enable the bundle in the `config/bundles.php` file like: 18 | 19 | ```php 20 | return [ 21 | // ... 22 | Basecom\Bundle\RulesEngineBundle\BasecomRulesEngine::class => ['all' => true], 23 | ] 24 | ``` 25 | Enable the route in the 'config/routes/routes.yml' file 26 | 27 | ```php 28 | basecom_rules_routing: 29 | resource: "@BasecomRulesEngine/Resources/config/routing/rules.yml" 30 | ``` 31 | 32 | Clear you cache: 33 | 34 | ```bash 35 | bin/console cache:clear --no-warmup --env=prod 36 | bin/console pim:install:assets --env=prod 37 | bin/console cache:warmup --env=prod 38 | yarn run less 39 | yarn run webpack 40 | ``` 41 | 42 | 43 | ## Documentation 44 | 45 | - OverwriteRuleController.php overwrites the standard Akeneo RuleController to extend the view with a edit button in the rule overview. 46 | 47 | - Operator Between and not Between is disabled 48 | 49 | https://docs.akeneo.com/master/cookbook/rule/general_information_on_rule_format.html#enrichment-rule-structure 50 | 51 | ### Available Operators Conditions List 52 | - STARTS WITH 53 | - ENDS WITH 54 | - CONTAINS 55 | - DOES NOT CONTAIN 56 | - EMPTY 57 | - NOT EMPTY 58 | - EQUAL ( = ) 59 | - NOT EQUAL ( != ) 60 | - IN 61 | - NOT IN 62 | - UNCLASSIFIED 63 | - IN OR UNCLASSIFIED 64 | - IN CHILDREN 65 | - NOT IN CHILDREN 66 | - GREATER ( > ) 67 | - GREATER OR EQUAL ( >= ) 68 | - SMALLER ( < ) 69 | - SMALLER OR EQUAL ( <= ) 70 | 71 | #### Operator 72 | STARTS WITH 73 | 74 | ##### Requirements 75 | - Attribute (no Simple or Multliselect) 76 | - Locale (optional) 77 | - Scope (optional) 78 | - Value 79 | 80 | #### Operator 81 | ENDS WITH 82 | 83 | ##### Requirements 84 | - Attribute (no Simple or Multliselect) 85 | - Locale (optional) 86 | - Scope (optional) 87 | - Value 88 | 89 | #### Operator 90 | CONTAINS 91 | 92 | ##### Requirements 93 | - Attribute (no Simple or Multliselect) 94 | - Locale (optional) 95 | - Scope (optional) 96 | - Value 97 | 98 | #### Operator 99 | DOES NOT CONTAIN 100 | 101 | ##### Requirements 102 | - Attribute (no Simple or Multliselect) 103 | - Locale (optional) 104 | - Scope (optional) 105 | - Value 106 | 107 | #### Operator 108 | EMPTY 109 | 110 | ##### Requirements 111 | - Attribute, Family (family.code), Groups (groups.code) 112 | - Locale (optional) 113 | - Scope (optional) 114 | 115 | #### Operator 116 | NOT EMPTY 117 | 118 | ##### Requirements 119 | - Attribute, Family (family.code), Groups (groups.code) 120 | - Locale (optional) 121 | - Scope (optional) 122 | 123 | #### Operator 124 | EQUAL ( = ) 125 | 126 | ##### Requirements 127 | - Attribute, created, updated, enabled, completeness 128 | - Value (dates format: yyyy-mm-dd) (enabled and yes/no format = true or false) 129 | - Locale (optional) 130 | - Scope (optional) 131 | - Unit (optional, only if a metric Attribute is selected) 132 | 133 | #### Operator 134 | NOT EQUAL ( != ) 135 | 136 | ##### Requirements 137 | - Attribute (Number or Metric), created, updated, enabled, completeness 138 | - Value (created, updated dates format: yyyy-mm-dd)(enabled format = true or false) 139 | - Locale (optional) 140 | - Scope (optional) 141 | - Unit (optional, only if a metric Attribute is selected) 142 | 143 | #### Operator 144 | IN 145 | 146 | ##### Requirements 147 | - Simple, Multiselect Attribute, Category (categories.code), Family (family.code), Groups (groups.code) 148 | - Locale (optional) 149 | - Scope (optional) 150 | - One or more value 151 | 152 | 153 | #### Operator 154 | NOT IN 155 | 156 | ##### Requirements 157 | - Simple, Multiselect Attribute, Category (categories.code), Family (family.code), Groups (groups.code) 158 | - Locale (optional) 159 | - Scope (optional) 160 | - One or more value 161 | 162 | #### Operator 163 | UNCLASSIFIED 164 | 165 | ##### Requirements 166 | Only available on Categories 167 | - Field = categories.code 168 | - No Attributes have to be selected 169 | 170 | #### Operator 171 | IN OR UNCLASSIFIED 172 | 173 | ##### Requirements 174 | Only available on Categories 175 | - Field = categories.code 176 | - Category code 177 | 178 | #### Operator 179 | IN CHILDREN 180 | 181 | ##### Requirements 182 | Only available on Categories 183 | - Field = categories.code 184 | - Category code 185 | 186 | #### Operator 187 | NOT IN CHILDREN 188 | 189 | ##### Requirements 190 | Only available on Categories 191 | - Field = categories.code 192 | - Category code 193 | 194 | #### Operator 195 | GREATER ( > ) 196 | 197 | ##### Requirements 198 | - Number, Price, Metric, Date Attribute, completeness 199 | - Value (dates format: yyyy-mm-dd) 200 | - Locale (optional) 201 | - Scope (optional) 202 | - Unit (optional, only if a metric Attribute is selected) 203 | 204 | #### Operator 205 | GREATER OR EQUAL ( >= ) 206 | 207 | ##### Requirements 208 | - Number, Price, Metric, Date Attribute 209 | - Value (dates format: yyyy-mm-dd) 210 | - Locale (optional) 211 | - Scope (optional) 212 | - Unit (optional, only if a metric Attribute is selected) 213 | 214 | #### Operator 215 | SMALLER ( < ) 216 | 217 | ##### Requirements 218 | - Number, Price, Metric, Date Attribute, completeness 219 | - Value (dates format: yyyy-mm-dd) 220 | - Locale (optional) 221 | - Scope (optional) 222 | - Unit (optional, only if a metric Attribute is selected) 223 | 224 | #### Operator 225 | SMALLER OR EQUAL ( <= ) 226 | 227 | ##### Requirements 228 | - Number, Price, Metric, Date Attribute 229 | - Value (dates format: yyyy-mm-dd) 230 | - Locale (optional) 231 | - Scope (optional) 232 | - Unit (optional, only if a metric Attribute is selected) 233 | 234 | 235 | ### Available Operators Actions List 236 | - add 237 | - set 238 | - copy 239 | - remove 240 | 241 | #### Operator 242 | add 243 | 244 | ##### Requirements 245 | - field: attribute code. 246 | - locale: local code for which value is assigned (optional). 247 | - scope: channel code for which value is assigned (optional). 248 | - values: attribute values to add. 249 | 250 | #### Operator 251 | set 252 | 253 | ##### Requirements 254 | - field: attribute code. 255 | - locale (optional) 256 | - scope (optional) 257 | - value: attribute value. 258 | 259 | #### Operator 260 | copy 261 | 262 | ##### Requirements 263 | - from_field: code of the attribute to be copied. 264 | - from_locale: locale code of the value to be copied (optional). 265 | - from_scope: channel code of the value to be copied (optional). 266 | - to_field: attribute code the value will be copied into. 267 | - to_locale: locale code the value will be copied into (optional). 268 | - to_scope: channel code the value will be copied into (optional). 269 | 270 | #### Operator 271 | remove 272 | 273 | ##### Requirements 274 | - field: attribute code. 275 | - locale: local code for which value is assigned (optional). 276 | - scope: channel code for which value is assigned (optional). 277 | - values: attribute values to remove. 278 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "basecom/akeneo-rules-ui", 3 | "type": "symfony-bundle", 4 | "description": "This bundle provides an UI for the Akeneo enterprise rules feature", 5 | "homepage": "https://www.basecom.de", 6 | "license": "MIT", 7 | "authors": [ 8 | { 9 | "name": "Peter van der Zwaag", 10 | "email": "vanderzwaag@basecom.de" 11 | }, 12 | { 13 | "name": "Amir El Sayed", 14 | "email": "elsayed@basecom.de" 15 | }, 16 | { 17 | "name": "Justus Klein", 18 | "email": "klein@basecom.de" 19 | }, 20 | { 21 | "name": "Jordan Kniest", 22 | "email": "j.kniest@basecom.de" 23 | }, 24 | { 25 | "name": "Andrej Eichwald", 26 | "email": "a.eichwald@basecom.de" 27 | }, 28 | { 29 | "name": "basecom GmbH & Co. KG", 30 | "email": "info@basecom.de" 31 | } 32 | ], 33 | "require": { 34 | "php": ">=7.0.0", 35 | "akeneo/pim-enterprise-dev": "4.0.*" 36 | }, 37 | "autoload": { 38 | "psr-0": { 39 | "Basecom\\Bundle\\RulesEngineBundle": "" 40 | } 41 | } 42 | } 43 | --------------------------------------------------------------------------------