/
www
/
wwwroot
/
alo88.autos
/
wp-content
/
plugins
/
wp-content-crawler
/
app
/
Objects
/
Filtering
/
Filter
/
Upload File
HOME
<?php /** * Created by PhpStorm. * User: turgutsaricam * Date: 02/04/2020 * Time: 10:29 * * @since 1.11.0 */ namespace WPCCrawler\Objects\Filtering\Filter; use Illuminate\Support\Arr; use WPCCrawler\Exceptions\CommandNotExistException; use WPCCrawler\Objects\Events\Enums\EventKey; use WPCCrawler\Objects\Filtering\Commands\ActionCommands\Base\AbstractActionCommand; use WPCCrawler\Objects\Filtering\Commands\Base\AbstractBaseCommand; use WPCCrawler\Objects\Filtering\Commands\CommandService; use WPCCrawler\Objects\Filtering\Commands\ConditionCommands\Base\AbstractConditionCommand; use WPCCrawler\Objects\Filtering\Enums\FilterOptionKey; use WPCCrawler\Objects\Filtering\FilterDependencyProvider\FilterDependencyProvider; use WPCCrawler\Objects\Filtering\FilteringUtils; use WPCCrawler\Objects\Filtering\Interfaces\Configable; use WPCCrawler\Objects\Filtering\Interfaces\Verbosable; use WPCCrawler\Objects\Informing\Informer; class Filter implements Verbosable, Configable { /** * @var string|null Human-readable title for this filter. This will be used when showing users something related to * this filter */ private $title; /** * @var FilterCondition|null Each filter can have only one condition. A condition have many commands and conditions in it. */ private $condition; /** @var AbstractActionCommand[] Actions that will be performed when the condition holds */ private $actions; /** @var string One of the constants defined in {@link EventKey}. Defines when the condition should be checked. */ private $conditionEventKey; /** @var string One of the constants defined in {@link EventKey}. Defines when the actions will be run. */ private $actionEventKey; /** * @var null|bool Whether this filter's condition was met or not. Stores the result of the last run * {@link checkCondition()}. If the condition was met, this is true. If not, this is false. If the condition * has not been checked yet, this is null. */ private $conditionCheckResult = null; /** * @var array Key-value pairs where keys are field keys and values are sequential arrays storing the dot keys of the * subjects matched by the conditions. This will be used to assign allowed subjects to the action * commands that are constrained. * E.g. ['post.preparedTags' => ['preparedTags.1', 'preparedTags.3'], ...] */ private $allowedSubjectMap; /** @var bool True if this filter is enabled. Otherwise, false. */ private $enabled = true; /** * @var bool True if extra logging should be done while executing the command. This is intended to be used when * testing the settings such that extra information about the command is needed to debug it. */ private $verbose = false; /** * @param FilterCondition|null $condition See {@link condition} * @param array|null $actions See {@link actions} * @param string|null $conditionEventKey See {@link conditionEventKey} * @param string|null $actionEventKey See {@link actionEventKey} * @param string|null $title See {@link title} * @since 1.11.0 * @since 1.11.1 The constructor is made final */ final public function __construct($condition, ?array $actions, ?string $actionEventKey = null, ?string $conditionEventKey = null, ?string $title = null) { $this->condition = $condition; $this->actions = $actions ?: []; $this->actionEventKey = $actionEventKey ?: EventKey::POST_DATA_READY; $this->conditionEventKey = $conditionEventKey ?: EventKey::POST_DATA_READY; $this->title = $title; } /** * @return string|null See {@link title} * @since 1.11.0 */ public function getTitle(): ?string { return $this->title; } /** * @return FilterCondition|null See {@link condition} * @since 1.11.0 */ public function getCondition(): ?FilterCondition { return $this->condition; } /** * @return AbstractActionCommand[] See {@link actions} * @since 1.11.0 */ public function getActions(): array { return $this->actions; } /** * @return bool True if this filter has at least one action. * @since 1.11.0 */ public function hasActions(): bool { return !empty($this->getActions()); } /** * @return string See {@link conditionEventKey} * @since 1.11.0 */ public function getConditionEventKey(): string { return $this->conditionEventKey; } /** * @param string $conditionEventKey See {@link conditionEventKey} * @return $this * @since 1.11.0 */ public function setConditionEventKey(string $conditionEventKey): self { $this->conditionEventKey = $conditionEventKey; return $this; } /** * @return string See {@link actionEventKey} * @since 1.11.0 */ public function getActionEventKey(): string { return $this->actionEventKey; } /** * @param string $actionEventKey See {@link actionEventKey} * @return $this * @since 1.11.0 */ public function setActionEventKey(string $actionEventKey): self { $this->actionEventKey = $actionEventKey; return $this; } /** * @return bool|null See {@link conditionCheckResult} * @since 1.11.0 */ public function getConditionCheckResult(): ?bool { return $this->conditionCheckResult; } /** * @return array See {@link allowedSubjectMap} * @since 1.11.0 */ public function getAllowedSubjectMap(): array { return $this->allowedSubjectMap ?: []; } /** * @param FilterDependencyProvider $provider Provider that will be used to inject the dependencies to the commands * @return bool True if the condition evaluates to true. Otherwise, false. * @since 1.11.0 */ public function checkCondition(FilterDependencyProvider $provider): bool { // If this filter is disabled, mark it as not met and stop. if ($this->isDisabled()) { $this->conditionCheckResult = false; return $this->conditionCheckResult; } // If there is no condition, we consider this condition as met. $condition = $this->getCondition(); if (!$condition) return $this->conditionCheckResult = true; // If there are no actions, this filter is not useful. Otherwise, check if the condition is met. $this->conditionCheckResult = $this->getActions() ? $condition->isMet($provider) : false; // If the condition is met, execute the condition commands whose subject keys are needed by action commands. $this->maybeExecuteNotExecutedConditionActions($provider); // Get the subject item map $this->allowedSubjectMap = $this->conditionCheckResult ? $condition->getSuitableSubjectItemMap() : []; return $this->conditionCheckResult ?: false; } /** * Execute the actions, if this filter is enabled. * * @param FilterDependencyProvider $provider Provider that will be used to inject the dependencies to the commands * @since 1.11.0 */ public function executeActions(FilterDependencyProvider $provider): void { // If there is no action or this filter is disabled, stop. if (!$this->hasActions() || $this->isDisabled()) return; $allowedSubjectMap = $this->getAllowedSubjectMap(); $actions = $this->getActions(); foreach($actions as $action) { $provider->injectDependencies($action); // If this action's subjects should be constrained, assign the allowed subjects to the action. if ($action->shouldLimitSubjects()) { $fieldKey = $action->getFieldKey(); if ($fieldKey) $action->setAllowedSubjects($allowedSubjectMap[$fieldKey] ?? null); } // Execute the action and then invalidate the dependencies $action->execute(); $provider->invalidateDependencies($action); // Retrieve the allowed subjects from the action command and update the allowed subject map used here. By // this way, the next command can have the allowed subjects added by the current command. if ($action->shouldLimitSubjects()) { $fieldKey = $action->getFieldKey(); if ($fieldKey) $allowedSubjectMap[$fieldKey] = $action->getAllowedSubjects(); } } } /** * @return bool See {@link enabled} * @since 1.11.0 */ public function isEnabled(): bool { return $this->enabled; } /** * @return bool Reverse of {@link enabled} * @since 1.11.0 */ public function isDisabled(): bool { return !$this->isEnabled(); } /** * @param bool $enabled See {@link enabled} * @since 1.11.0 */ public function setEnabled(bool $enabled): void { $this->enabled = $enabled; } /* * PROTECTED METHODS */ /** * If the condition of this filter was met, executes the not-executed condition commands that have the same subject * keys as one of the action commands so that the action commands that need the subjects from the condition * commands can get them. This method is necessary because {@link FilterCondition::isMet()} stops if one of the * condition commands inside an "or" block evaluates to true. In that case, the other commands in the "or" block is * not executed. If there is an action command that need the matched subject keys from that not-executed condition * command, that action command is not able to do so. This method handles that situation. * * @param FilterDependencyProvider $provider Provider that will be used to inject dependencies of the commands * @uses getConditionCheckResult() to check if this filter's condition was met. It must return true for * this method to be able to do its job. * @since 1.11.0 */ protected function maybeExecuteNotExecutedConditionActions(FilterDependencyProvider $provider): void { // If the condition was not met, no need to execute them. Because, the actions will not run. $condition = $this->getCondition(); if (!$condition || !$this->getConditionCheckResult()) return; // The condition was met. Get the subjects of the actions that need matching subjects from the condition // commands. $subjectKeys = []; foreach($this->getActions() as $action) { if (!$action->shouldLimitSubjects()) continue; $fieldKey = $action->getFieldKey(); if (!$fieldKey) continue; $subjectKeys[] = $fieldKey; } // Find the condition commands that have one of the subject keys and that were not executed. Then, execute them. $condition->forEachCommand(function(AbstractConditionCommand $cmd) use (&$subjectKeys, $provider) { // If the subject keys are not null, then this command was executed before. We do not want to run the // executed commands again. Also, the command's field key should be a field key that is used by one of the // actions that require subject keys from the condition commands. if ($cmd->getSuitableSubjectItems() !== null || !in_array($cmd->getFieldKey(), $subjectKeys)) return; // Execute the condition command. We are not interested in the result. We only want to execute it so that // its subject keys can be retrieved later. FilteringUtils::doesConditionCommandApply($cmd, $provider); $logger = $cmd->getLogger(); if ($logger) $logger ->addMessage(_wpcc("This command is run after the filter condition's execution is finished. This command is run because its subject values are needed by an action command.")); }); } /* * INTERFACE METHODS */ /** * @return bool See {@link verbose} * @since 1.11.0 */ public function isVerbose(): bool { return $this->verbose; } /** * @param bool $verbose See {@link verbose} * @return Filter * @since 1.11.0 */ public function setVerbose(bool $verbose): self { $this->verbose = $verbose; return $this; } public function toConfig(): array { $condition = $this->getCondition(); $result = [ FilterOptionKey::CONFIG => [ FilterOptionKey::CONFIG_ENABLED => $this->isEnabled() ? '1' : '0', ], FilterOptionKey::TITLE => $this->getTitle(), FilterOptionKey::FILTER_IF => $condition ? $condition->toConfig() : [], FilterOptionKey::FILTER_THEN => [ FilterOptionKey::OPTIONS => [ FilterOptionKey::EVENT => $this->getActionEventKey(), ], FilterOptionKey::ITEMS => array_map(function(AbstractActionCommand $cmd) { return $cmd->toConfig(); }, $this->getActions()), ], ]; // The root filter condition must have a condition event key. Add it. Arr::set( $result, sprintf('%1$s.%2$s.%3$s', FilterOptionKey::FILTER_IF, FilterOptionKey::OPTIONS, FilterOptionKey::EVENT), $this->getConditionEventKey() ); return $result; } /* * STATIC METHODS */ /** * @param array|null $options Options that will be used to create a filter * @param bool|null $verbose See {@link FilteringUtils::getVerbose()} * @return Filter|null * @since 1.11.0 */ public static function fromOptions(?array $options, ?bool $verbose = null): ?Filter { if ($options === null) return null; $title = Arr::get($options, FilterOptionKey::TITLE); $filterIf = Arr::get($options, FilterOptionKey::FILTER_IF); $filterThen = Arr::get($options, FilterOptionKey::FILTER_THEN); $config = Arr::get($options, FilterOptionKey::CONFIG); if (!is_string($title)) $title = null; if (!is_array($filterIf)) $filterIf = []; if (!is_array($filterThen)) $filterThen = []; if (!is_array($config)) $config = []; $enabled = Arr::get($config, FilterOptionKey::CONFIG_ENABLED, '1') === '1'; $thenActions = Arr::get($filterThen, FilterOptionKey::ITEMS, []); if (!is_array($thenActions)) $thenActions = []; $eventDotKey = FilterOptionKey::OPTIONS . '.' . FilterOptionKey::EVENT; $conditionEventKey = Arr::get($filterIf, $eventDotKey); $actionEventKey = Arr::get($filterThen, $eventDotKey); if (!is_string($conditionEventKey)) $conditionEventKey = null; if (!is_string($actionEventKey)) $actionEventKey = null; $verbose = FilteringUtils::getVerbose($verbose); $commandService = CommandService::getInstance(); $actions = []; foreach($thenActions as $commandOption) { $command = static::createCommandFromOptions($commandService, $commandOption, AbstractActionCommand::class, $verbose); if ($command === null) continue; $actions[] = $command; } // If there are no actions, return null, since a filter without actions is not usable. if (!$actions) return null; $filter = new static(FilterCondition::fromOptions($filterIf, $verbose), $actions, $actionEventKey, $conditionEventKey, $title); $filter->setVerbose($verbose); $filter->setEnabled($enabled); return $filter; } /** * Create a command from options * * @param CommandService $commandService Service that will be used to get command instances * @param array|null $options Options that will be used to create a command by using * {@link AbstractBaseCommand::fromOptions()} method * @param string|null $type Class name of the expected command type. If this is null, all commands are * allowed. For example, you can set this to {@link AbstractConditionCommand} * to get only condition commands. * @param bool|null $verbose See {@link FilteringUtils::getVerbose()} * @return AbstractBaseCommand|null If the options are valid, a new command. Otherwise, null. * @since 1.11.0 */ public static function createCommandFromOptions(CommandService $commandService, ?array $options, ?string $type = null, ?bool $verbose = null): ?AbstractBaseCommand { if ($options === null) return null; $commandKey = Arr::pull($options, FilterOptionKey::COMMAND); if (!is_string($commandKey)) return null; try { $cmdInstance = $commandService->getCommand($commandKey); // If there is a type, make sure the command is of the given type. If not, return null. if ($type !== null && !is_a($cmdInstance, $type)) return null; } catch (CommandNotExistException $e) { Informer::addInfo(sprintf(_wpcc('Command with key "%1$s" does not exist.'), $commandKey)) ->setException($e) ->addAsLog(); return null; } return $cmdInstance::fromOptions($options, FilteringUtils::getVerbose($verbose)); } }