| @copyright 2007-2026 (c) AVE.cms | @link https://ave-cms.ru | @version 3.3 */ namespace App\Content\Requests; defined('BASEPATH') || die('Direct access to this location is not allowed.'); use App\Common\FileCacheInvalidator; use App\Content\ContentTables; use App\Frontend\PublicPageContext; use App\Frontend\RequestRepository; use DB; /** Compiles request condition trees into the transitional runtime SQL template. */ class RequestConditionCompiler { public static function compile($requestId, $persist = false) { $requestId = (int) $requestId; $request = DB::query( 'SELECT * FROM %b WHERE Id = %i LIMIT 1', ContentTables::table('request'), $requestId )->getAssoc(); if (!$request) { return array(); } $conditions = self::objects(DB::query( 'SELECT * FROM %b WHERE request_id = %i AND condition_status = %s ORDER BY condition_position ASC, Id ASC', ContentTables::table('request_conditions'), $requestId, '1' )->getAll()); $groups = self::groups($requestId, $conditions); $numeric = self::numericFields((int) $request['rubric_id']); $snippets = array(); $index = 0; foreach ($conditions as $condition) { $fieldId = (int) $condition->condition_field_id; $value = trim((string) $condition->condition_value); if ($value === '') { continue; } if (!array_key_exists($fieldId, $numeric)) { $numeric[$fieldId] = (bool) DB::query( 'SELECT rubric_field_numeric FROM %b WHERE Id = %i LIMIT 1', ContentTables::table('rubric_fields'), $fieldId )->getValue(); } $fieldSql = $numeric[$fieldId] ? 'rf.field_number_value' : 'rf.field_value'; $groupId = isset($condition->condition_group_id) ? (int) $condition->condition_group_id : (int) $groups[0]->Id; $descriptor = RequestConditionValue::descriptor($condition); if ($descriptor !== null && RequestConditionValue::tagKey($value) !== '') { $snippets[] = self::declarativeConditionSnippet( $requestId, $groupId, $fieldId, (string) $condition->condition_compare, $fieldSql, $descriptor ); $index++; continue; } $value = str_ireplace(array('[field]', '[numeric_field]'), $fieldSql, $value); $snippets[] = self::conditionSnippet( $requestId, $index++, $groupId, $fieldId, (string) $condition->condition_compare, $fieldSql, $value ); } $result = array(); if ($snippets) { $result = array( 'from' => '', 'where' => '' . implode('', $snippets) . self::groupSnippet($groups), ); } if ($persist) { DB::Update( ContentTables::table('request'), array('request_where_cond' => serialize($result)), 'Id = %i', $requestId ); if (class_exists('App\\Frontend\\RequestRepository', false)) { RequestRepository::reset(); } FileCacheInvalidator::request( $requestId, isset($request['request_alias']) ? $request['request_alias'] : '' ); } return $result; } /** Returns null for corrupted or foreign serialized payloads. */ public static function decode($value) { if (!is_string($value) || $value === '') { return null; } $decoded = @unserialize($value, array('allowed_classes' => false)); if (!is_array($decoded)) { return null; } $from = isset($decoded['from']) && is_string($decoded['from']) ? $decoded['from'] : ''; $where = isset($decoded['where']) && is_string($decoded['where']) ? $decoded['where'] : ''; return ($from !== '' || $where !== '') ? array('from' => $from, 'where' => $where) : array(); } /** Applies old req_ID[field] selectors only to the current visitor. */ public static function applyRuntimeSelections($requestId, array $compiled) { if (defined('ACP')) { return $compiled; } $requestId = (int) $requestId; $document = PublicPageContext::document(); $sessionKey = 'doc_' . ($document ? (int) $document->Id : 0); $postKey = 'req_' . $requestId; if (isset($_POST[$postKey]) && is_array($_POST[$postKey])) { $_SESSION[$sessionKey][$postKey] = $_POST[$postKey]; } elseif (isset($_SESSION[$sessionKey][$postKey]) && is_array($_SESSION[$sessionKey][$postKey])) { $_POST[$postKey] = $_SESSION[$sessionKey][$postKey]; } if (empty($_POST[$postKey]) || !is_array($_POST[$postKey])) { return $compiled; } $parts = array(); foreach ($_POST[$postKey] as $fieldId => $value) { $fieldId = (int) $fieldId; if ($fieldId <= 0 || $value === '' || !isset($_SESSION['val_' . $fieldId])) { continue; } $allowed = (array) $_SESSION['val_' . $fieldId]; if (!in_array($value, $allowed)) { continue; } $parts[] = "EXISTS (SELECT 1 FROM %%CONTENT_PREFIX%%_document_fields rq_select WHERE rq_select.document_id = a.Id AND rq_select.rubric_field_id = '" . $fieldId . "' AND rq_select.field_value = '" . DB::safe((string) $value) . "')"; } if (!$parts) { return $compiled; } $selectionSql = '(' . implode(' AND ', $parts) . ')'; $compiled['from'] = isset($compiled['from']) ? (string) $compiled['from'] : ''; $compiled['where'] = !empty($compiled['where']) ? '(' . $compiled['where'] . ') AND ' . $selectionSql : $selectionSql; return $compiled; } protected static function groups($requestId, array $conditions) { try { $groups = self::objects(DB::query( 'SELECT * FROM %b WHERE request_id = %i ORDER BY parent_id ASC, group_position ASC, Id ASC', ContentTables::table('request_condition_groups'), (int) $requestId )->getAll()); } catch (\Throwable $e) { $groups = array(); } if ($groups) { return $groups; } $operator = $conditions && isset($conditions[0]->condition_join) && $conditions[0]->condition_join === 'OR' ? 'OR' : 'AND'; return array((object) array('Id' => -1, 'parent_id' => 0, 'group_operator' => $operator)); } protected static function numericFields($rubricId) { $result = array(); foreach (DB::query( 'SELECT Id, rubric_field_numeric FROM %b WHERE rubric_id = %i', ContentTables::table('rubric_fields'), (int) $rubricId )->getAll() as $row) { $row = (array) $row; $result[(int) $row['Id']] = !empty($row['rubric_field_numeric']); } return $result; } protected static function objects(array $rows) { foreach ($rows as $key => $row) { $rows[$key] = is_object($row) ? $row : (object) $row; } return $rows; } protected static function conditionSnippet($requestId, $index, $groupId, $fieldId, $type, $fieldSql, $valueTemplate) { $valueCode = var_export(' ?>' . $valueTemplate . '': case '>': $comparison = $left . " > '" . '" . $rqValue . "' . "'"; break; case 'N<=': case '<=': $comparison = $left . " <= '" . '" . $rqValue . "' . "'"; break; case 'N>=': case '>=': $comparison = $left . " >= '" . '" . $rqValue . "' . "'"; break; case '!=': $comparison = $left . " != '" . '" . $rqValue . "' . "'"; break; case '%%': $comparison = $left . " LIKE '%" . '" . $rqValue . "' . "%'"; break; case '%': $comparison = $left . " LIKE '" . '" . $rqValue . "' . "%'"; break; case '--': $comparison = $left . " NOT LIKE '%" . '" . $rqValue . "' . "%'"; break; case '!-': $comparison = $left . " NOT LIKE '" . '" . $rqValue . "' . "%'"; break; case 'IN=': $comparison = $left . ' IN (' . '" . $rqValue . "' . ')'; break; case 'NOTIN=': $comparison = $left . ' NOT IN (' . '" . $rqValue . "' . ')'; break; case 'ANY': $comparison = $left . '=ANY(' . '" . $rqValue . "' . ')'; break; case 'FRE': $comparison = '(' . '" . $rqValue . "' . ')'; break; case 'SEGMENT': case 'INTERVAL': $strict = $type === 'INTERVAL'; $low = $strict ? '>' : '>='; $high = $strict ? '<' : '<='; $body = "\$rqValue = trim(\\App\\Common\\StoredPhpRuntime::evaluate($valueCode, get_defined_vars())); \$rqRange = explode(',', \$rqValue, 2); if (count(\$rqRange) === 2 && trim(\$rqRange[0]) !== '' && trim(\$rqRange[1]) !== '') { \$rqParts[$groupId][] = \"$prefix$left $low '\" . (float)\$rqRange[0] . \"' AND $left $high '\" . (float)\$rqRange[1] . \"')\"; }"; return self::guardedSnippet($requestId, $fieldId, $body); case '==': default: $comparison = $left . " = '" . '" . $rqValue . "' . "'"; break; } $body = "\$rqValue = trim(\\App\\Common\\StoredPhpRuntime::evaluate($valueCode, get_defined_vars())); if (\$rqValue !== '') { \$rqParts[$groupId][] = \"$prefix$comparison)\"; }"; return self::guardedSnippet($requestId, $fieldId, $body); } protected static function declarativeConditionSnippet($requestId, $groupId, $fieldId, $type, $fieldSql, array $descriptor) { $source = var_export($descriptor, true); $field = var_export($fieldSql, true); $operator = var_export($type, true); $prefix = var_export( "EXISTS (SELECT 1 FROM %%CONTENT_PREFIX%%_document_fields rf WHERE rf.document_id = a.Id AND rf.rubric_field_id = '$fieldId' AND ", true ); $body = '$rqResolved = \\App\\Content\\Requests\\RequestConditionValue::resolve(' . $source . ', $_REQUEST); ' . 'if ($rqResolved !== null) { ' . '$rqComparison = \\App\\Content\\Requests\\RequestConditionValue::legacyComparison(' . $field . ', ' . $operator . ', $rqResolved); ' . 'if ($rqComparison !== \'\') $rqParts[' . (int) $groupId . '][] = ' . $prefix . ' . $rqComparison . ")"; }'; return self::guardedSnippet($requestId, $fieldId, $body); } protected static function guardedSnippet($requestId, $fieldId, $body) { return ""; } protected static function groupSnippet(array $groups) { $byParent = array(); $operators = array(); $rootId = 0; foreach ($groups as $group) { $groupId = (int) $group->Id; $parentId = (int) $group->parent_id; $operators[$groupId] = isset($group->group_operator) && $group->group_operator === 'OR' ? 'OR' : 'AND'; $byParent[$parentId][] = $groupId; if ($parentId === 0 && $rootId === 0) { $rootId = $groupId; } } $order = array(); $visited = array(); $walk = function ($groupId) use (&$walk, &$order, &$visited, $byParent) { if (isset($visited[$groupId])) { return; } $visited[$groupId] = true; foreach (isset($byParent[$groupId]) ? $byParent[$groupId] : array() as $childId) { $walk($childId); } $order[] = $groupId; }; $walk($rootId); $code = '"; } }