Passed
Push — master ( fdfb7f...fc199c )
by Andreas
09:42
created

midcom_core_query::add_constraint()   A

Complexity

Conditions 6
Paths 7

Size

Total Lines 22
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 6.4425

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 6
eloc 12
c 1
b 0
f 0
nc 7
nop 3
dl 0
loc 22
ccs 10
cts 13
cp 0.7692
crap 6.4425
rs 9.2222
1
<?php
2
/**
3
 * @package midcom
4
 * @author CONTENT CONTROL http://www.contentcontrol-berlin.de/
5
 * @copyright CONTENT CONTROL http://www.contentcontrol-berlin.de/
6
 * @license http://www.gnu.org/licenses/gpl.html GNU General Public License
7
 */
8
9
use Doctrine\ORM\Query\Expr\Composite;
10
use Doctrine\ORM\QueryBuilder;
11
12
/**
13
 * Common base class for collector and querybuilder
14
 *
15
 * @package midcom
16
 */
17
abstract class midcom_core_query
18
{
19
    /**
20
     * Set this element to true to hide all items which are currently invisible according
21
     * to the approval/scheduling settings made using Metadata. This must be set before executing
22
     * the query.
23
     *
24
     * NOTE: Approval checks not implemented in collector yet
25
     *
26
     * Be aware, that this setting will currently not use the QB to filter the objects accordingly,
27
     * since there is no way yet to filter against parameters. This will mean some performance
28
     * impact.
29
     */
30
    public bool $hide_invisible = true;
31
32
    /**
33
     * Flag that tracks whether deleted visibility check have already been added
34
     */
35
    protected bool $_visibility_checks_added = false;
36
37
    /**
38
     * The number of records to return to the client at most.
39
     */
40
    protected int $_limit = 0;
41
42
    /**
43
     * The offset of the first record the client wants to have available.
44
     */
45
    protected int $_offset = 0;
46
47
    /**
48
     * Type that the application expects to retrieve from this instance.
49
     */
50
    protected string $_real_class;
51
52
    /**
53
     * The number of records found by the last execute() run. This is -1 as long as no
54
     * query has been executed. This member is read-only.
55
     */
56
    protected int $count = -1;
57
58
    /**
59
     * The query backend, should be set in constructor. Currently collector or querybuilder
60
     */
61
    protected \midgard\portable\query $_query;
62
63
    /**
64
     * The number of objects for which access was denied.
65
     */
66
    public int $denied = 0;
67
68
    /**
69
     * User id for ACL checks. This is set when executing to avoid unnecessary overhead
70
     */
71
    private ?string $_user_id = null;
72
73
    /**
74
     * Class resolution into the MidCOM DBA system.
75
     * Currently, Midgard requires the actual MgdSchema base classes to be used
76
     * when dealing with the query, so we internally note the corresponding class
77
     * information to be able to do correct typecasting later.
78
     *
79
     * @return string MgdSchema class name
80
     */
81 496
    protected function _convert_class(string $classname) : string
82
    {
83 496
        $this->_real_class = $classname;
84 496
        $mgdschema_class = midcom::get()->dbclassloader->get_mgdschema_class_name_for_midcom_class($classname);
85 496
        if (!$mgdschema_class) {
86
            throw new midcom_error(
87
                "Cannot create a midcom_core_query instance for the type {$classname}: Does not seem to be a DBA class name."
88
            );
89
        }
90 496
        return $mgdschema_class;
91
    }
92
93 205
    protected function is_readable(string $guid) : bool
94
    {
95 205
        return !$this->_user_id
96 205
            || midcom::get()->auth->acl->can_do_byguid('midgard:read', $guid, $this->_real_class, $this->_user_id);
97
    }
98
99 490
    protected function _add_visibility_checks()
100
    {
101 490
        if (!midcom::get()->auth->admin) {
102 489
            $this->_user_id = midcom::get()->auth->acl->get_user_id();
103
        }
104
105 490
        if (   $this->hide_invisible
106 490
            && !$this->_visibility_checks_added) {
107 490
            if (!midcom::get()->config->get('show_hidden_objects')) {
108
                $this->add_constraint('metadata.hidden', '=', false);
109
                $now = date('Y-m-d H:i:s');
110
                $this->add_constraint('metadata.schedulestart', '<=', $now);
111
                $this->begin_group('OR');
112
                    $this->add_constraint('metadata.scheduleend', '>=', $now);
113
                    $this->add_constraint('metadata.scheduleend', '<=', '0001-01-01 00:00:00');
114
                $this->end_group();
115
            }
116
117 490
            if (!midcom::get()->config->get('show_unapproved_objects')) {
118
                $this->add_constraint('metadata.isapproved', '=', true);
119
            }
120 490
            $this->_visibility_checks_added = true;
121
        }
122
    }
123
124
    /**
125
     * Resets some internal variables for re-execute
126
     */
127 489
    protected function _reset()
128
    {
129 489
        $this->count = -1;
130 489
        $this->denied = 0;
131
    }
132
133 490
    protected function prepare_execute() : bool
134
    {
135 490
        $ret = $this->_real_class::_on_execute($this);
136 490
        if (!$ret) {
137
            debug_add('The _on_execute callback returned false');
138
        }
139 490
        return $ret;
140
    }
141
142 22
    public function get_doctrine() : QueryBuilder
143
    {
144 22
        return $this->_query->get_doctrine();
145
    }
146
147 9
    public function get_current_group() : Composite
148
    {
149 9
        return $this->_query->get_current_group();
150
    }
151
152
    /**
153
     * Add a constraint to the query.
154
     *
155
     * @param string $operator The operator to use for the constraint, currently supported are
156
     *     <, <=, =, <>, >=, >, LIKE. LIKE uses the percent sign ('%') as a
157
     *     wildcard character.
158
     * @param mixed $value The value to compare against. It should be of the same type as the
159
     *     queried property.
160
     */
161 487
    public function add_constraint(string $field, string $operator, $value)
162
    {
163 487
        $this->_reset();
164
        // Add check against null values, Core MC is too stupid to get this right.
165 487
        if ($value === null) {
166
            throw new midcom_error("Cannot add constraint on field '{$field}' with null value.");
167
        }
168
        // Deal with empty arrays, which would produce invalid queries
169
        // This is done here to avoid repetitive code in callers, and because
170
        // it's easy enough to generalize: IN empty set => always false, NOT IN empty set => always true
171 487
        if ($value === []) {
172 27
            if ($operator == 'NOT IN') {
173 1
                return;
174
            }
175 26
            if ($operator == 'IN') {
176 26
                $this->add_constraint('id', '=', 0);
177 26
                return;
178
            }
179
        }
180 487
        if (!$this->_query->add_constraint($field, $operator, $value)) {
181
            debug_add("Class = '{$this->_real_class}, Field = '{$field}', Operator = '{$operator}'");
182
            throw new midcom_error("Failed to execute add_constraint: " . midcom_connection::get_error_string());
183
        }
184
    }
185
186
    /**
187
     * Add a constraint against another DB column to the query.
188
     *
189
     * @param string $operator The operator to use for the constraint, currently supported are
190
     *     <, <=, =, <>, >=, >, LIKE. LIKE uses the percent sign ('%') as a
191
     *     wildcard character.
192
     * @param string $compare_field The field to compare against.
193
     */
194
    public function add_constraint_with_property(string $field, string $operator, string $compare_field)
195
    {
196
        $this->_reset();
197
        if (!$this->_query->add_constraint_with_property($field, $operator, $compare_field)) {
198
            debug_add("Class = '{$this->_real_class}, Field = '{$field}', Operator = '{$operator}', compare_field: '{$compare_field}'");
199
            throw new midcom_error("Failed to execute add_constraint_with_property: " . midcom_connection::get_error_string());
200
        }
201
    }
202
203
    /**
204
     * Creates a new logical group within the query. They are set in parentheses in the final
205
     * SQL and will thus be evaluated with precedence over the normal out-of-group constraints.
206
     *
207
     * While the call lets you decide whether all constraints within the group are AND'ed or OR'ed,
208
     * only OR constraints make logically sense in this context, which is why this proxy function
209
     * sets 'OR' as the default operator.
210
     *
211
     * @param string $operator One of 'OR' or 'AND' denoting the logical operation with which all
212
     *     constraints in the group are concatenated.
213
     */
214 223
    public function begin_group(string $operator)
215
    {
216 223
        if (!$this->_query->begin_group($operator)) {
217
            throw new midcom_error("Failed to execute begin_group {$operator}");
218
        }
219
    }
220
221
    /**
222
     * Ends a group previously started with begin_group().
223
     */
224 223
    public function end_group()
225
    {
226 223
        if (!$this->_query->end_group()) {
227
            throw new midcom_error("Failed to execute end_group");
228
        }
229
    }
230
231
    /**
232
     * Limits the resultset to contain at most the specified number of records.
233
     * Set the limit to zero to retrieve all available records.
234
     */
235 162
    public function set_limit(int $limit)
236
    {
237 162
        $this->_reset();
238 162
        $this->_limit = $limit;
239
    }
240
241
    /**
242
     * Sets the offset of the first record to retrieve. This is a zero based index,
243
     * so if you want to retrieve from the very first record, the correct offset would
244
     * be zero, not one.
245
     */
246 14
    public function set_offset(int $offset)
247
    {
248 14
        $this->_reset();
249
250 14
        $this->_offset = $offset;
251
    }
252
253
    /**
254
     * Add an ordering constraint to the query builder.
255
     *
256
     * @param string $direction One of 'ASC' or 'DESC' indicating ascending or descending
257
     *     ordering. The default is 'ASC'.
258
     */
259 357
    public function add_order(string $field, string $direction = 'ASC')
260
    {
261 357
        if (!$this->_query->add_order($field, $direction)) {
262
            throw new midcom_error("Failed to execute add_order for column '{$field}': " . midcom_connection::get_error_string());
263
        }
264
    }
265
266
    /**
267
     * Get the DBA class we're currently working on
268
     */
269
    public function get_classname() : string
270
    {
271
        return $this->_real_class;
272
    }
273
274
    abstract public function execute();
275
276
    abstract public function count();
277
278
    abstract public function count_unchecked();
279
}
280