aboutsummaryrefslogtreecommitdiff
path: root/lib/classes/UserFilterField.php
blob: 3dcbb36fed874e210036b01183ba336f6cac7419 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
<?php

/**
 * UserFilterField.php
 *
 * A specification of a Stud.IP condition that must be fulfilled. One
 * or more instances of the UserFilterField subclasses make up a
 * UserFilter.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of
 * the License, or (at your option) any later version.
 *
 * @author      Thomas Hackl <thomas.hackl@uni-passau.de>
 * @license     http://www.gnu.org/licenses/gpl-2.0.html GPL version 2
 * @category    Stud.IP
 */
class UserFilterField
{
    // --- ATTRIBUTES ---

    /**
     * Which of the valid compare operators is currently chosen?
     */
    public $compareOperator = '';

    /**
     * ID of the UserFilter this field belongs to.
     */
    public $conditionId = '';

    /**
     * Unique ID for this condition field.
     */
    public $id = '';

    /**
     * The set of valid compare operators.
     */
    public $validCompareOperators = [];

    /**
     * All valid values for this field.
     */
    public $validValues = [];

    /**
     * Which of the valid values is currently chosen?
     */
    public $value = null;

    /*
     * Provide some kind of sort order for filter fields. By default,
     * all subclasses without an explicitly given order will be sorted at the end.
     */
    public static $sortOrder = 99;

    public static $isParameterized = false;

    protected static $cached_valid_values;
    protected static $available_filter_fields;

    /**
     * Database tables and fields to get valid values and concrete user values
     * from.
     */
    public $valuesDbTable = '';
    public $valuesDbIdField = '';
    public $valuesDbNameField = '';
    public $userDataDbTable = '';
    public $userDataDbField = '';
    public $relations = [];

    // --- OPERATIONS ---

    public static function getParameterizedTypes()
    {

    }

    /**
     * Which targets are allowed for this filter field?
     * An empty array means: no restrictions
     * @return array
     */
    public static function getTargets()
    {
        return [];
    }

    /**
     * Indicates whether this filter field is active.
     * @return true
     */
    public static function isActive()
    {
        return true;
    }

    /**
     * Standard constructor.
     *
     * @param String $fieldId If a fieldId is given, the corresponding data is
     *                        loaded from database.
     *
     */
    public function __construct($fieldId = '')
    {
        $this->validCompareOperators = [
            '=' => _('ist'),
            '!=' => _('ist nicht')
        ];
        if ($this->valuesDbNameField) {
            if (isset(self::$cached_valid_values[static::class])) {
                $this->validValues = self::$cached_valid_values[static::class];
            } else {
                // Get all available values from database.
                $stmt = DBManager::get()->query(
                    "SELECT DISTINCT `" . $this->valuesDbIdField . "`, `" . $this->valuesDbNameField . "` " .
                    "FROM `" . $this->valuesDbTable . "` ORDER BY `" . $this->valuesDbNameField . "` ASC");
                while ($current = $stmt->fetch(PDO::FETCH_ASSOC)) {
                    $this->validValues[$current[$this->valuesDbIdField]] = $current[$this->valuesDbNameField];
                }
                self::$cached_valid_values[static::class] = $this->validValues;
            }
        }
        if ($fieldId) {
            $this->id = $fieldId;
            $this->load();
        } else {
            $this->id = $this->generateId();
        }
    }

    /**
     * Checks whether the given value fits the configured condition. The
     * value is compared to the currently selected value by using the
     * currently selected compare operator.
     *
     * @param Array values
     * @return Boolean
     */
    public function checkValue($values)
    {
        // Validate compare operator
        if (!isset($this->validCompareOperators[$this->compareOperator])) {
            throw new Exception('Invalid compare operator');
        }

        $result = false;
        foreach ($values as $value) {
            switch ($this->compareOperator) {
                case '=':
                    $result = $value == $this->value;
                    break;
                case '!=':
                    $result = $value != $this->value;
                    break;
                case '<':
                    $result = $value < $this->value;
                    break;
                case '<=':
                    $result = $value <= $this->value;
                    break;
                case '>=':
                    $result = $value >= $this->value;
                    break;
                case '>':
                    $result = $value > $this->value;
                    break;
                default:
                    throw new Exception('Unknown compare operator.');
            }

            if ($result) {
                break;
            }
        }
        return $result;
    }

    /**
     * Deletes the stored data for this condition field from DB.
     */
    public function delete()
    {
        // Delete condition data.
        $stmt = DBManager::get()->prepare("DELETE FROM `userfilter_fields`
            WHERE `field_id`=?");
        $stmt->execute([$this->id]);
    }

    /**
     * Generate a new unique ID.
     *
     * @param String tableName
     */
    public function generateId()
    {
        do {
            $newid = md5(uniqid(get_class($this) . microtime(), true));
            $id = DBManager::get()->fetchColumn("SELECT `field_id`
                FROM `userfilter_fields` WHERE `field_id`=?", [$newid]);
        } while ($id);
        return $newid;
    }

    /**
     * Reads all available UserFilterField subclasses and loads their definitions.
     */
    public static function getAvailableFilterFields(string $context = '', string $target = '')
    {
        if (self::$available_filter_fields === null) {
            $fields = [];
            try {
                $i = new FileSystemIterator(
                    $GLOBALS['STUDIP_BASE_PATH'] . '/lib/classes/UserFilterFields' . ($context !== '' ? '/' . $context : ''),
                    FileSystemIterator::SKIP_DOTS
                );

                foreach ($i as $class) {
                    if ($class->isFile()) {
                        require_once $class;
                    }
                }
            } catch (UnexpectedValueException $e) {
                Log::error($e->getMessage());
            }

            // Get all classes in given context.
            $classes = array_filter(
                get_declared_classes(),
                function ($c) use ($context) {
                    $reflection_class = new \ReflectionClass($c);
                    $namespace = $reflection_class->getNamespaceName();
                    return is_subclass_of($c, UserFilterField::class)
                        && $namespace === 'UserFilterFields' . ($context !== '' ? '\\' . $context : '')
                        && $c::isActive();
                }
            );

            usort($classes, fn ($a, $b) => $a::$sortOrder - $b::$sortOrder);

            // If a target is given, return only matching classes
            if ($target !== '') {
                $classes = array_filter(
                    $classes,
                    function ($c) use ($target) {
                        $targets = $c::getTargets();
                        return count($targets) === 0 || in_array($target, $targets);
                    }
                );
            }

            foreach ($classes as $class) {
                if ($class::$isParameterized) {
                    $fields = array_merge($fields, $class::getParameterizedTypes());
                } else {
                    $filter = new $class();
                    $fields[$class] = $filter->getName();
                }
            }
            self::$available_filter_fields = $fields;
        }
        return self::$available_filter_fields;
    }


    /**
     * Which compare operator is set?
     *
     * @return String
     */
    public function getCompareOperator()
    {
        return $this->compareOperator;
    }

    /**
     * Which compare operator is set?
     *
     * @return String
     */
    public function getCompareOperatorAsText()
    {
        return $this->getValidCompareOperators()[$this->compareOperator] ?? '';
    }

    /**
     * Field ID.
     *
     * @return String
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * Get this field's display name.
     *
     * @return String
     */
    public function getName()
    {
        return _("Nutzerfilterfeld");
    }

    /**
     * Compares all the users' values by using the specified compare operator
     * and returns all users that fulfill the condition. This can be
     * an important information when checking on validity of a combination
     * of conditions.
     *
     * @param Array $restrictions values from other fields that restrict the valid
     *                            values for a user (e.g. a semester of study in
     *                            a given subject)
     * @return Array All users that are affected by the current condition
     *               field.
     */
    public function getUsers($restrictions = [])
    {
        $db = DBManager::get();
        $users = [];
        // Standard query getting the values without respecting other values.
        $select = "SELECT DISTINCT `" . $this->userDataDbTable . "`.`user_id` ";
        $from = "FROM `" . $this->userDataDbTable . "` ";
        $where = "WHERE `" . $this->userDataDbTable . "`.`" . $this->userDataDbField .
            "`" . $this->compareOperator . "?";
        $parameters = [$this->value];
        $joinedTables = [
            $this->userDataDbTable => true
        ];
        // Check if there are restrictions given.
        foreach ($restrictions as $otherField => $restriction) {
            // We only take the value into consideration if it represents a valid restriction.
            if ($this->relations[$otherField]) {
                // Do we need to join in another table?
                if (!$joinedTables[$restriction['table']]) {
                    $joinedTables[$restriction['table']] = true;
                    $from .= " INNER JOIN `" . $restriction['table'] . "` ON (`" .
                        $this->userDataDbTable . "`.`" .
                        $this->relations[$otherField]['local_field'] . "`=`" .
                        $restriction['table'] . "`.`" .
                        $this->relations[$otherField]['foreign_field'] . "`)";
                }
                // Expand WHERE statement with the value from restriction.
                $where .= " AND `" . $restriction['table'] . "`.`" .
                    $restriction['field'] . "`" . $restriction['compare'] . "?";
                $parameters[] = $restriction['value'];
            }
        }
        // Get all the users that fulfill the condition.
        $stmt = $db->prepare($select . $from . $where);
        $stmt->execute($parameters);
        while ($current = $stmt->fetch(PDO::FETCH_ASSOC)) {
            $users[] = $current['user_id'];
        }
        return $users;
    }

    /**
     * Gets the value for the given user that is relevant for this
     * condition field. Here, this method looks up the study degree(s)
     * for the user. These can then be compared with the required degrees
     * whether they fit.
     *
     * @param String $userId User to check.
     * @param array $additional conditions that are required for check.
     * @return array The value(s) for this user.
     */
    public function getUserValues($userId, $additional = null)
    {
        $result = [];
        $query = "SELECT DISTINCT `" . $this->userDataDbField . "` " .
            "FROM `" . $this->userDataDbTable . "` " .
            "WHERE `user_id`=?";
        $parameters = [$userId];
        // Additional requirements given...
        if (is_array($additional)) {

            // Don't use the same database field twice as this can only get ugly.
            $usedFields = [$this->userDataDbField];

            foreach ($additional as $a_condition) {
                if ($a_condition->id != $this->id && $this->userDataDbTable == $a_condition->userDataDbTable &&
                    !in_array($a_condition->userDataDbField, $usedFields)) {
                    $query .= " AND `" . $a_condition->userDataDbField . "` " . $a_condition->compareOperator . "?";
                    $parameters[] = $a_condition->value;
                }
            }
        }
        // Get semester of study for user.
        $stmt = DBManager::get()->prepare($query);
        $stmt->execute($parameters);
        while ($current = $stmt->fetch(PDO::FETCH_ASSOC)) {
            $result[] = $current[$this->userDataDbField];
        }
        return $result;
    }

    /**
     * Returns all valid compare operators.
     *
     * @return Array Array of valid compare operators.
     */
    public function getValidCompareOperators()
    {
        return $this->validCompareOperators;
    }

    /**
     * Returns all valid values. Values can be loaded dynamically from
     * database or be returned as static array.
     *
     * @return Array Valid values in the form $value => $displayname.
     */
    public function getValidValues()
    {
        return $this->validValues;
    }

    /**
     * Which value is set?
     *
     * @return String
     */
    public function getValue()
    {
        return $this->value;
    }

    /**
     * Helper function for loading data from DB.
     */
    public function load()
    {
        $stmt = DBManager::get()->prepare(
            "SELECT * FROM `userfilter_fields` WHERE `field_id`=? LIMIT 1");
        $stmt->execute([$this->id]);
        if ($data = $stmt->fetch(PDO::FETCH_ASSOC)) {
            $this->conditionId = $data['filter_id'];
            $this->value = $data['value'];
            $this->compareOperator = $data['compare_op'];
        }
    }

    /**
     * Sets a new selected compare operator
     *
     * @param String newOperator
     * @return UserFilterField
     */
    public function setCompareOperator($newOperator)
    {
        if (in_array($newOperator, array_keys($this->validCompareOperators))) {
            $this->compareOperator = $newOperator;
            return $this;
        } else {
            return false;
        }
    }

    /**
     * Connects the current field to a UserFilter.
     *
     * @param String $id ID of a UserFilter object.
     * @return UserFilterField
     */
    public function setConditionId($id)
    {
        $this->conditionId = $id;
        return $this;
    }

    /**
     * Sets a new selected value.
     *
     * @param String newValue
     * @return UserFilterField
     */
    public function setValue($newValue)
    {
        if ($this->validValues[$newValue]) {
            $this->value = $newValue;
            return $this;
        } else {
            return false;
        }
    }

    /**
     * Stores data to DB.
     *
     * @param String conditionId The condition this field belongs to.
     */
    public function store()
    {
        // Generate new ID if field entry doesn't exist in DB yet.
        if (!$this->id) {
            $this->id = $this->generateId();
        }
        // Store field data.
        $stmt = DBManager::get()->prepare("INSERT INTO `userfilter_fields`
            (`field_id`, `filter_id`, `type`, `value`, `compare_op`,
            `mkdate`, `chdate`)  VALUES (?, ?, ?, ?, ?, ?, ?)
            ON DUPLICATE KEY UPDATE `filter_id`=VALUES(`filter_id`),
            `type`=VALUES(`type`),`value`=VALUES(`value`),
            `compare_op`=VALUES(`compare_op`), `chdate`=VALUES(`chdate`)");
        $stmt->execute([$this->id, $this->conditionId, get_class($this),
            $this->value, $this->compareOperator, time(), time()]);
    }

    public function __clone()
    {
        $this->id = md5(uniqid(get_class($this)));
        $this->conditionId = null;
    }

} /* end of class UserFilterField */