1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* Licensed under The GPL-3.0 License |
4
|
|
|
* For full copyright and license information, please see the LICENSE.txt |
5
|
|
|
* Redistributions of files must retain the above copyright notice. |
6
|
|
|
* |
7
|
|
|
* @since 2.0.0 |
8
|
|
|
* @author Christopher Castro <[email protected]> |
9
|
|
|
* @link http://www.quickappscms.org |
10
|
|
|
* @license http://opensource.org/licenses/gpl-3.0.html GPL-3.0 License |
11
|
|
|
*/ |
12
|
|
|
namespace Eav\Model\Behavior\QueryScope; |
13
|
|
|
|
14
|
|
|
use Cake\Database\Expression\Comparison; |
15
|
|
|
use Cake\Database\Expression\UnaryExpression; |
16
|
|
|
use Cake\Database\ExpressionInterface; |
17
|
|
|
use Cake\ORM\Query; |
18
|
|
|
use Cake\ORM\Table; |
19
|
|
|
use Cake\ORM\TableRegistry; |
20
|
|
|
use Eav\Model\Behavior\EavToolbox; |
21
|
|
|
use Eav\Model\Behavior\QueryScope\QueryScopeInterface; |
22
|
|
|
|
23
|
|
|
/** |
24
|
|
|
* Used by EAV Behavior to scope WHERE statements. |
25
|
|
|
*/ |
26
|
|
|
class WhereScope implements QueryScopeInterface |
27
|
|
|
{ |
28
|
|
|
|
29
|
|
|
/** |
30
|
|
|
* The table being managed. |
31
|
|
|
* |
32
|
|
|
* @var \Cake\ORM\Table |
33
|
|
|
*/ |
34
|
|
|
protected $_table = null; |
35
|
|
|
|
36
|
|
|
/** |
37
|
|
|
* Instance of toolbox. |
38
|
|
|
* |
39
|
|
|
* @var \Eav\Model\Behavior\EavToolbox |
40
|
|
|
*/ |
41
|
|
|
protected $_toolbox = null; |
42
|
|
|
|
43
|
|
|
/** |
44
|
|
|
* {@inheritDoc} |
45
|
|
|
*/ |
46
|
|
|
public function __construct(Table $table) |
47
|
|
|
{ |
48
|
|
|
$this->_table = $table; |
49
|
|
|
$this->_toolbox = new EavToolbox($table); |
50
|
|
|
} |
51
|
|
|
|
52
|
|
|
/** |
53
|
|
|
* {@inheritDoc} |
54
|
|
|
* |
55
|
|
|
* Look for virtual columns in query's WHERE clause. |
56
|
|
|
* |
57
|
|
|
* @param \Cake\ORM\Query $query The query to scope |
58
|
|
|
* @param string|null $bundle Consider attributes only for a specific bundle |
59
|
|
|
* @return \Cake\ORM\Query The modified query object |
60
|
|
|
*/ |
61
|
|
|
public function scope(Query $query, $bundle = null) |
62
|
|
|
{ |
63
|
|
|
$whereClause = $query->clause('where'); |
64
|
|
|
if (!$whereClause) { |
65
|
|
|
return $query; |
66
|
|
|
} |
67
|
|
|
|
68
|
|
|
$whereClause->traverse(function (ExpressionInterface $expression) use ($bundle, $query) { |
69
|
|
|
$expression = $this->_alterExpression($expression, $bundle, $query); |
|
|
|
|
70
|
|
|
}); |
71
|
|
|
|
72
|
|
|
return $query; |
73
|
|
|
} |
74
|
|
|
|
75
|
|
|
/** |
76
|
|
|
* Analyzes the given WHERE expression, looks for virtual columns and alters |
77
|
|
|
* the expressions according. |
78
|
|
|
* |
79
|
|
|
* @param \Cake\Database\ExpressionInterface $expression Expression to scope |
80
|
|
|
* @param string $bundle Consider attributes only for a specific bundle |
81
|
|
|
* @param \Cake\ORM\Query $query The query instance this expression comes from |
82
|
|
|
* @return \Cake\Database\ExpressionInterface The altered expression (or not) |
83
|
|
|
*/ |
84
|
|
|
protected function _alterExpression(ExpressionInterface $expression, $bundle, Query $query) |
85
|
|
|
{ |
86
|
|
|
if ($expression instanceof Comparison) { |
87
|
|
|
$expression = $this->_alterComparisonExpression($expression, $bundle, $query); |
88
|
|
|
} elseif ($expression instanceof UnaryExpression) { |
89
|
|
|
debug($expression);die; |
90
|
|
|
// TODO: unary expressions scoping |
91
|
|
|
} |
92
|
|
|
|
93
|
|
|
return $expression; |
94
|
|
|
} |
95
|
|
|
|
96
|
|
|
/** |
97
|
|
|
* Analyzes the given comparison expression and alters it according. |
98
|
|
|
* |
99
|
|
|
* @param \Cake\Database\Expression\Comparison $expression Comparison expression |
100
|
|
|
* @param string $bundle Consider attributes only for a specific bundle |
101
|
|
|
* @param \Cake\ORM\Query $query The query instance this expression comes from |
102
|
|
|
* @return \Cake\Database\Expression\Comparison Scoped expression (or not) |
103
|
|
|
*/ |
104
|
|
|
protected function _alterComparisonExpression(Comparison $expression, $bundle, Query $query) |
105
|
|
|
{ |
106
|
|
|
$field = $expression->getField(); |
107
|
|
|
$column = is_string($field) ? $this->_toolbox->columnName($field) : ''; |
108
|
|
|
|
109
|
|
|
if (empty($column) || |
110
|
|
|
in_array($column, (array)$this->_table->schema()->columns()) || // ignore real columns |
111
|
|
|
!in_array($column, $this->_toolbox->getAttributeNames()) || |
112
|
|
|
!$this->_toolbox->isSearchable($column) // ignore no searchable virtual columns |
113
|
|
|
) { |
114
|
|
|
// nothing to alter |
115
|
|
|
return $expression; |
116
|
|
|
} |
117
|
|
|
|
118
|
|
|
$attr = $this->_toolbox->attributes($bundle)[$column]; |
119
|
|
|
$value = $expression->getValue(); |
120
|
|
|
$type = $this->_toolbox->getType($column); |
121
|
|
|
$conjunction = $expression->getOperator(); |
122
|
|
|
$conditions = [ |
123
|
|
|
'EavValues.eav_attribute_id' => $attr['id'], |
124
|
|
|
"EavValues.value_{$type} {$conjunction}" => $value, |
125
|
|
|
]; |
126
|
|
|
|
127
|
|
|
// subquery scope |
128
|
|
|
$subQuery = TableRegistry::get('Eav.EavValues') |
129
|
|
|
->find() |
130
|
|
|
->select('EavValues.entity_id') |
131
|
|
|
->where($conditions); |
132
|
|
|
|
133
|
|
|
// some variables |
134
|
|
|
$conn = $query->connection(null); |
135
|
|
|
list(, $driverClass) = namespaceSplit(strtolower(get_class($conn->driver()))); |
136
|
|
|
$alias = $this->_table->alias(); |
137
|
|
|
$pk = $this->_table->primaryKey(); |
138
|
|
|
|
139
|
|
|
if (!is_array($pk)) { |
140
|
|
|
$pk = [$pk]; |
141
|
|
|
} |
142
|
|
|
|
143
|
|
|
$pk = array_map(function ($key) use ($alias) { |
144
|
|
|
return "{$alias}.{$key}"; |
145
|
|
|
}, $pk); |
146
|
|
|
|
147
|
|
|
switch ($driverClass) { |
148
|
|
|
case 'sqlite': |
149
|
|
|
$concat = implode(' || ', $pk); |
150
|
|
|
$field = "({$concat} || '')"; |
151
|
|
|
break; |
152
|
|
|
case 'mysql': |
153
|
|
|
case 'postgres': |
154
|
|
|
case 'sqlserver': |
155
|
|
|
default: |
156
|
|
|
$concat = implode(', ', $pk); |
157
|
|
|
$field = "CONCAT({$concat}, '')"; |
158
|
|
|
break; |
159
|
|
|
} |
160
|
|
|
|
161
|
|
|
$ids = $subQuery->all()->extract('entity_id')->toArray(); |
162
|
|
|
$ids = empty($ids) ? ['-1'] : $ids; |
163
|
|
|
$expression->setField($field); |
164
|
|
|
$expression->setValue($ids); |
165
|
|
|
$expression->setOperator('IN'); |
166
|
|
|
|
167
|
|
|
$class = new \ReflectionClass($expression); |
168
|
|
|
$property = $class->getProperty('_type'); |
169
|
|
|
$property->setAccessible(true); |
170
|
|
|
$property->setValue($expression, 'string[]'); |
171
|
|
|
|
172
|
|
|
return $expression; |
173
|
|
|
} |
174
|
|
|
} |
175
|
|
|
|
This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.
Both the
$myVar
assignment in line 1 and the$higher
assignment in line 2 are dead. The first because$myVar
is never used and the second because$higher
is always overwritten for every possible time line.