Passed
Pull Request — master (#1598)
by Michael
09:04
created

XoopsModelWrite::updateAll()   A

Complexity

Conditions 6
Paths 12

Size

Total Lines 19
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 13
nc 12
nop 4
dl 0
loc 19
rs 9.2222
c 0
b 0
f 0
1
<?php
2
/**
3
 * Object write handler class.
4
 *
5
 * You may not change or alter any portion of this comment or credits
6
 * of supporting developers from this source code or any supporting source code
7
 * which is considered copyrighted (c) material of the original comment or credit authors.
8
 * This program is distributed in the hope that it will be useful,
9
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
11
 *
12
 * @copyright       (c) 2000-2025 XOOPS Project (https://xoops.org)
13
 * @license             GNU GPL 2 (https://www.gnu.org/licenses/gpl-2.0.html)
14
 * @package             kernel
15
 * @subpackage          model
16
 * @since               2.3.0
17
 * @author              Taiwen Jiang <[email protected]>
18
 */
19
if (!defined('XOOPS_ROOT_PATH')) {
20
    throw new \RuntimeException('Restricted access');
21
}
22
23
/**
24
 * Object write handler class.
25
 *
26
 * @author Taiwen Jiang <[email protected]>
27
 * @author Simon Roberts <[email protected]>
28
 *
29
 * {@link XoopsModelAbstract}
30
 */
31
class XoopsModelWrite extends XoopsModelAbstract
32
{
33
    /**
34
     * Clean values of all variables of the object for storage.
35
     * also add slashes and quote string wherever needed
36
     *
37
     * CleanVars only contains changed and cleaned variables
38
     * Reference is used for PHP4 compliance
39
     *
40
     * @param $object
41
     *
42
     * @return bool true if successful
43
     * @access public
44
     */
45
    public function cleanVars($object)
46
    {
47
        $myts     = \MyTextSanitizer::getInstance();
48
        $errors = [];
49
50
        $vars              = $object->getVars();
51
        $object->cleanVars = [];
52
        foreach ($vars as $k => $v) {
53
            if (!$v['changed']) {
54
                continue;
55
            }
56
            $cleanv = $v['value'];
57
            switch ($v['data_type']) {
58
                case XOBJ_DTYPE_TIMESTAMP:
59
                    $cleanv = !is_string($cleanv) && is_numeric($cleanv) ? date(_DBTIMESTAMPSTRING, $cleanv) : date(_DBTIMESTAMPSTRING, strtotime($cleanv));
60
                    $cleanv = str_replace('\\"', '"', $this->handler->db->quote($cleanv));
61
                    break;
62
                case XOBJ_DTYPE_TIME:
63
                    $cleanv = !is_string($cleanv) && is_numeric($cleanv) ? date(_DBTIMESTRING, $cleanv) : date(_DBTIMESTRING, strtotime($cleanv));
64
                    $cleanv = str_replace('\\"', '"', $this->handler->db->quote($cleanv));
65
                    break;
66
                case XOBJ_DTYPE_DATE:
67
                    $cleanv = !is_string($cleanv) && is_numeric($cleanv) ? date(_DBDATESTRING, $cleanv) : date(_DBDATESTRING, strtotime($cleanv));
68
                    $cleanv = str_replace('\\"', '"', $this->handler->db->quote($cleanv));
69
                    break;
70
                case XOBJ_DTYPE_UNICODE_TXTBOX:
71
                    if ($v['required'] && $cleanv != '0' && $cleanv == '') {
72
                        $errors[] = sprintf(_XOBJ_ERR_REQUIRED, $k);
73
                        continue 2;
74
                    }
75
                    $cleanv = xoops_convert_encode($cleanv);
76
                    if (isset($v['maxlength']) && strlen($cleanv) > (int)$v['maxlength']) {
77
                        $errors[] = sprintf(_XOBJ_ERR_SHORTERTHAN, $k, (int)$v['maxlength']);
78
                        continue 2;
79
                    }
80
81
                        $cleanv = $myts->censorString($cleanv);
82
83
                    $cleanv = str_replace('\\"', '"', $this->handler->db->quote($cleanv));
84
                    break;
85
86
                case XOBJ_DTYPE_UNICODE_TXTAREA:
87
                    if ($v['required'] && $cleanv != '0' && $cleanv == '') {
88
                        $errors[] = sprintf(_XOBJ_ERR_REQUIRED, $k);
89
                        continue 2;
90
                    }
91
                    $cleanv = xoops_convert_encode($cleanv);
92
                        $cleanv = $myts->censorString($cleanv);
93
94
                    $cleanv = str_replace('\\"', '"', $this->handler->db->quote($cleanv));
95
                    break;
96
97
                case XOBJ_DTYPE_TXTBOX:
98
                    if ($v['required'] && $cleanv != '0' && $cleanv == '') {
99
                        $errors[] = sprintf(_XOBJ_ERR_REQUIRED, $k);
100
                        continue 2;
101
                    }
102
                    if (isset($v['maxlength']) && strlen($cleanv) > (int)$v['maxlength']) {
103
                        $errors[] = sprintf(_XOBJ_ERR_SHORTERTHAN, $k, (int)$v['maxlength']);
104
                        continue 2;
105
                    }
106
107
                        $cleanv = $myts->censorString($cleanv);
108
                    $cleanv = str_replace('\\"', '"', $this->handler->db->quote($cleanv));
109
                    break;
110
111
                case XOBJ_DTYPE_TXTAREA:
112
                    if ($v['required'] && $cleanv != '0' && $cleanv == '') {
113
                        $errors[] = sprintf(_XOBJ_ERR_REQUIRED, $k);
114
                        continue 2;
115
                    }
116
117
                        $cleanv = $myts->censorString($cleanv);
118
                    $cleanv = str_replace('\\"', '"', $this->handler->db->quote($cleanv));
119
                    break;
120
121
                case XOBJ_DTYPE_SOURCE:
122
                    $cleanv = trim($cleanv);
123
                    $cleanv = str_replace('\\"', '"', $this->handler->db->quote($cleanv));
124
                    break;
125
                // Should not be used!
126
                case XOBJ_DTYPE_UNICODE_EMAIL:
127
                    $cleanv = trim($cleanv);
128
                    if ($v['required'] && $cleanv == '') {
129
                        $errors[] = sprintf(_XOBJ_ERR_REQUIRED, $k);
130
                        continue 2;
131
                    }
132
                    $cleanv = str_replace('\\"', '"', $this->handler->db->quote(xoops_convert_encode($cleanv)));
133
                    break;
134
135
                case XOBJ_DTYPE_EMAIL:
136
                    $cleanv = trim($cleanv);
137
                    if ($v['required'] && $cleanv == '') {
138
                        $errors[] = sprintf(_XOBJ_ERR_REQUIRED, $k);
139
                        continue 2;
140
                    }
141
                    if ($cleanv != '' && !preg_match("/^[_a-z0-9-]+(\.[_a-z0-9-]+)*@[a-z0-9-]+([\.][a-z0-9-]+)+$/i", $cleanv)) {
142
                        $errors[] = 'Invalid Email';
143
                        continue 2;
144
                    }
145
                    $cleanv = str_replace('\\"', '"', $this->handler->db->quote($cleanv));
146
                    break;
147
148
                // Should not be used!
149
                case XOBJ_DTYPE_UNICODE_URL:
150
                    $cleanv = trim($cleanv);
151
                    if ($v['required'] && $cleanv == '') {
152
                        $errors[] = sprintf(_XOBJ_ERR_REQUIRED, $k);
153
                        continue 2;
154
                    }
155
                    if ($cleanv != '' && !preg_match("/^http[s]*:\/\//i", $cleanv)) {
156
                        $cleanv = XOOPS_PROT . $cleanv;
157
                    }
158
                    $cleanv = str_replace('\\"', '"', $this->handler->db->quote(xoops_convert_encode($cleanv)));
159
                    break;
160
                case XOBJ_DTYPE_URL:
161
                    $cleanv = trim($cleanv);
162
                    if ($v['required'] && $cleanv == '') {
163
                        $errors[] = sprintf(_XOBJ_ERR_REQUIRED, $k);
164
                        continue 2;
165
                    }
166
                    if ($cleanv != '' && !preg_match("/^http[s]*:\/\//i", $cleanv)) {
167
                        $cleanv = XOOPS_PROT . $cleanv;
168
                    }
169
                    $cleanv = str_replace('\\"', '"', $this->handler->db->quote($cleanv));
170
                    break;
171
172
                // Should not be used!
173
                case XOBJ_DTYPE_UNICODE_OTHER:
174
                    $cleanv = str_replace('\\"', '"', $this->handler->db->quote(xoops_convert_encode($cleanv)));
175
                    break;
176
177
                case XOBJ_DTYPE_OTHER:
178
                    $cleanv = str_replace('\\"', '"', $this->handler->db->quote($cleanv));
179
                    break;
180
181
                case XOBJ_DTYPE_INT:
182
                    $cleanv = (int)$cleanv;
183
                    break;
184
185
                case XOBJ_DTYPE_FLOAT:
186
                    $cleanv = (float)$cleanv;
187
                    break;
188
189
                case XOBJ_DTYPE_DECIMAL:
190
                    $cleanv = (float)$cleanv;
191
                    break;
192
193
                // Should not be used!
194
                case XOBJ_DTYPE_UNICODE_ARRAY:
195
                    if (!$v['not_gpc']) {
196
                        $cleanv = array_map([&$myts, 'stripSlashesGPC'], $cleanv);
197
                    }
198
                    foreach (array_keys($cleanv) as $key) {
199
                        $cleanv[$key] = str_replace('\\"', '"', addslashes($cleanv[$key]));
200
                    }
201
                    // TODO: Not encoding safe, should try base64_encode -- phppp
202
                    $cleanv = "'" . serialize(array_walk($cleanv, 'xoops_aw_encode')) . "'";
203
                    break;
204
205
                case XOBJ_DTYPE_ARRAY:
206
                    $cleanv = (array)$cleanv;
207
                    if (!$v['not_gpc']) {
208
                        $cleanv = array_map([&$myts, 'stripSlashesGPC'], $cleanv);
209
                    }
210
                    // TODO: Not encoding safe, should try base64_encode -- phppp
211
                    $cleanv = $this->handler->db->quote(serialize($cleanv));
212
                    break;
213
214
                case XOBJ_DTYPE_STIME:
215
                case XOBJ_DTYPE_MTIME:
216
                case XOBJ_DTYPE_LTIME:
217
                    $cleanv = !is_string($cleanv) ? (int)$cleanv : strtotime($cleanv);
218
                    break;
219
220
                default:
221
                    $cleanv = str_replace('\\"', '"', $this->handler->db->quote($cleanv));
222
                    break;
223
            }
224
            $object->cleanVars[$k] = $cleanv;
225
        }
226
        if (!empty($errors)) {
227
            $object->setErrors($errors);
228
        }
229
        $object->unsetDirty();
230
231
        return empty($errors) ? true : false;
232
    }
233
234
    /**
235
     * insert an object into the database
236
     *
237
     * @param  object $object {@link XoopsObject} reference to object
238
     * @param  bool   $force  flag to force the query execution despite security settings
239
     * @return mixed  object ID
240
     */
241
    public function insert($object, $force = true)
242
    {
243
        if (!$object->isDirty()) {
244
            trigger_error("Data entry is not inserted - the object '" . get_class($object) . "' is not dirty", E_USER_NOTICE);
245
246
            return $object->getVar($this->handler->keyName);
247
        }
248
        if (!$this->cleanVars($object)) {
249
            trigger_error("Insert failed in method 'cleanVars' of object '" . get_class($object) . "'", E_USER_WARNING);
250
251
            return $object->getVar($this->handler->keyName);
252
        }
253
        $queryFunc = empty($force) ? 'query' : 'exec';
0 ignored issues
show
Unused Code introduced by
The assignment to $queryFunc is dead and can be removed.
Loading history...
254
255
        if ($object->isNew()) {
256
            $sql = 'INSERT INTO `' . $this->handler->table . '`';
257
            $queryFunc = 'exec';
258
            if (!empty($object->cleanVars)) {
259
                $keys = array_keys($object->cleanVars);
260
                $vals = array_values($object->cleanVars);
261
                $sql .= ' (`' . implode('`, `', $keys) . '`) VALUES (' . implode(',', $vals) . ')';
262
            } else {
263
                trigger_error("Data entry is not inserted - no variable is changed in object of '" . get_class($object) . "'", E_USER_NOTICE);
264
265
                return $object->getVar($this->handler->keyName);
266
            }
267
            if (!$result = $this->handler->db->{$queryFunc}($sql)) {
0 ignored issues
show
Unused Code introduced by
The assignment to $result is dead and can be removed.
Loading history...
268
                return false;
269
            }
270
            if (!$object->getVar($this->handler->keyName) && $object_id = $this->handler->db->getInsertId()) {
271
                $object->assignVar($this->handler->keyName, $object_id);
272
            }
273
        } elseif (!empty($object->cleanVars)) {
274
            $keys = [];
275
            foreach ($object->cleanVars as $k => $v) {
276
                $keys[] = " `{$k}` = {$v}";
277
            }
278
            $sql = 'UPDATE `' . $this->handler->table . '` SET ' . implode(',', $keys) . ' WHERE `' . $this->handler->keyName . '` = ' . $this->handler->db->quote($object->getVar($this->handler->keyName));
279
            $queryFunc = 'exec';
280
            if (!$result = $this->handler->db->{$queryFunc}($sql)) {
281
                return false;
282
            }
283
        }
284
285
        return $object->getVar($this->handler->keyName);
286
    }
287
288
    /**
289
     * delete an object from the database
290
     *
291
     * @param  object $object {@link XoopsObject} reference to the object to delete
292
     * @param  bool   $force
293
     * @return bool   FALSE if failed.
294
     */
295
    public function delete($object, $force = false)
296
    {
297
        if (is_array($this->handler->keyName)) {
298
            $clause = [];
299
            $thishandlerkeyNameCount = count($this->handler->keyName);
300
            for ($i = 0; $i < $thishandlerkeyNameCount; ++$i) {
301
                $clause[] = '`' . $this->handler->keyName[$i] . '` = ' . $this->handler->db->quote($object->getVar($this->handler->keyName[$i]));
302
            }
303
            $whereclause = implode(' AND ', $clause);
304
        } else {
305
            $whereclause = '`' . $this->handler->keyName . '` = ' . $this->handler->db->quote($object->getVar($this->handler->keyName));
306
        }
307
        $sql       = 'DELETE FROM `' . $this->handler->table . '` WHERE ' . $whereclause;
308
//        $queryFunc = empty($force) ? 'query' : 'exec';
309
        $queryFunc = 'exec';
310
        $result    = $this->handler->db->{$queryFunc}($sql);
311
312
        return empty($result) ? false : true;
313
    }
314
315
    /**
316
     * delete all objects matching the conditions
317
     *
318
     * @param  CriteriaElement|CriteriaCompo $criteria {@link CriteriaElement} with conditions to meet
319
     * @param  bool   $force    force to delete
320
     * @param  bool   $asObject delete in object way: instantiate all objects and delete one by one
321
     * @return bool|int
322
     */
323
    public function deleteAll(?CriteriaElement $criteria = null, $force = true, $asObject = false)
324
    {
325
        if ($asObject) {
326
            $objects = $this->handler->getAll($criteria);
327
            $num     = 0;
328
            foreach (array_keys($objects) as $key) {
329
                $num += $this->delete($objects[$key], $force) ? 1 : 0;
330
            }
331
            unset($objects);
332
333
            return $num;
334
        }
335
//        $queryFunc = empty($force) ? 'query' : 'exec';
336
        $queryFunc = 'exec';
337
        $sql       = 'DELETE FROM ' . $this->handler->table;
338
        if (!empty($criteria)) {
339
            if (is_subclass_of($criteria, 'CriteriaElement')) {
340
                $sql .= ' ' . $criteria->renderWhere();
0 ignored issues
show
Bug introduced by
The method renderWhere() does not exist on CriteriaElement. Did you maybe mean render()? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

340
                $sql .= ' ' . $criteria->/** @scrutinizer ignore-call */ renderWhere();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
341
            } else {
342
                return false;
343
            }
344
        }
345
        if (!$this->handler->db->{$queryFunc}($sql)) {
346
            return false;
347
        }
348
349
        return $this->handler->db->getAffectedRows();
350
    }
351
352
    /**
353
     * Change a field for objects with a certain criteria
354
     *
355
     * @param  string $fieldname  Name of the field
356
     * @param  mixed  $fieldvalue Value to write
357
     * @param  CriteriaElement|CriteriaCompo  $criteria   {@link CriteriaElement}
358
     * @param  bool   $force      force to query
359
     * @return bool
360
     */
361
    public function updateAll($fieldname, $fieldvalue, ?CriteriaElement $criteria = null, $force = false)
362
    {
363
        $set_clause = "`{$fieldname}` = ";
364
        if (is_numeric($fieldvalue)) {
365
            $set_clause .= $fieldvalue;
366
        } elseif (is_array($fieldvalue)) {
367
            $set_clause .= $this->handler->db->quote(implode(',', $fieldvalue));
368
        } else {
369
            $set_clause .= $this->handler->db->quote($fieldvalue);
370
        }
371
        $sql = 'UPDATE `' . $this->handler->table . '` SET ' . $set_clause;
372
        if (isset($criteria) && \method_exists($criteria, 'renderWhere')) {
373
            $sql .= ' ' . $criteria->renderWhere();
374
        }
375
//        $queryFunc = empty($force) ? 'query' : 'exec';
376
        $queryFunc = 'exec';
377
        $result    = $this->handler->db->{$queryFunc}($sql);
378
379
        return empty($result) ? false : true;
380
    }
381
}
382