references_XoopsPersistableObjectHandler   F
last analyzed

Complexity

Total Complexity 131

Size/Duplication

Total Lines 777
Duplicated Lines 10.94 %

Coupling/Cohesion

Components 1
Dependencies 1

Importance

Changes 0
Metric Value
dl 85
loc 777
rs 1.823
c 0
b 0
f 0
wmc 131
lcom 1
cbo 1

22 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 16 3
A setCachingOptions() 0 4 1
A _getIdForCache() 0 6 1
A create() 0 9 2
A get() 0 21 4
B getObjects() 13 35 9
B convertResultSet() 20 40 8
B getIds() 13 36 8
B getList() 13 49 11
A getItemsFromIds() 0 14 4
C getCount() 8 51 11
B getSum() 8 34 6
A delete() 5 27 5
A quickInsert() 0 12 1
F insert() 5 97 28
B updateAll() 0 29 7
A isDuplicated() 0 19 4
A deleteAll() 0 18 5
A compareObjects() 0 13 3
A getDistincts() 0 30 5
A getItems() 0 19 4
A forceCacheClean() 0 6 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like references_XoopsPersistableObjectHandler often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use references_XoopsPersistableObjectHandler, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * ****************************************************************************
4
 * myshop - MODULE FOR XOOPS
5
 * Copyright (c) Hervé Thouzard of Instant Zero (http://www.instant-zero.com)
6
 *
7
 * You may not change or alter any portion of this comment or credits
8
 * of supporting developers from this source code or any supporting source code
9
 * which is considered copyrighted (c) material of the original comment or credit authors.
10
 * This program is distributed in the hope that it will be useful,
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13
 *
14
 * @copyright       Jan Pedersen
15
 * @copyright       Hervé Thouzard of Instant Zero (http://www.instant-zero.com)
16
 * @license         http://www.fsf.org/copyleft/gpl.html GNU public license
17
 * @package         references
18
 * @author          Jan Pedersen
19
 * @author          Hervé Thouzard of Instant Zero (http://www.instant-zero.com)
20
 *
21
 * ****************************************************************************
22
 */
23
24
defined('XOOPS_ROOT_PATH') || exit('XOOPS root path not defined');
25
26
class references_Object extends XoopsObject
27
{
28
    public function toArray($format = 's')
29
    {
30
        $ret = array();
31
        foreach ($this->vars as $k => $v) {
32
            $ret[$k] = $this->getVar($k, $format);
33
        }
34
35
        return $ret;
36
    }
37
38
    // TODO: Rajouter une méthode intsert() et delete()
39
40
    /**
41
     * Permet de valoriser un champ de la table comme si c'était une propriété de la classe
42
     *
43
     * @example $enregistrement->nom_du_champ = 'ma chaine'
44
     *
45
     * @param string $key   Le nom du champ à traiter
46
     * @param mixed  $value La valeur à lui attribuer
47
     * @return void
48
     */
49
    public function __set($key, $value)
50
    {
51
        return $this->setVar($key, $value);
52
    }
53
54
    /**
55
     * Permet d'accéder aux champs de la table comme à des propriétés de la classe
56
     *
57
     * @example echo $enregistrement->nom_du_champ;
58
     *
59
     * @param string $key Le nom du champ que l'on souhaite récupérer
60
     * @return mixed
61
     */
62
    public function __get($key)
63
    {
64
        return $this->getVar($key);
65
    }
66
}
67
68
/**
69
 * Persistable Object Handler class.
70
 * This class is responsible for providing data access mechanisms to the data source
71
 * of derived class objects. Original Author : Mithrandir
72
 *
73
 */
74
class references_XoopsPersistableObjectHandler extends XoopsObjectHandler
75
{
76
    /**#@+
77
     * Information about the class, the handler is managing
78
     *
79
     * @var string
80
     */
81
    public    $table;
82
    public    $keyName;
83
    public    $className;
84
    public    $identifierName;
85
    protected $cacheOptions = array();
86
    /**#@-*/
87
88
    /**
89
     * Constructor - called from child classes
90
     * @param object $db           {@link XoopsDatabase} object
91
     * @param string $tablename    Name of database table
92
     * @param string $classname    Name of Class, this handler is managing
93
     * @param string $keyname      Name of the property, holding the key
94
     * @param string $idenfierName Name of the property, holding the label
95
     * @param array  $cacheOptions Optional, options for the cache
0 ignored issues
show
Documentation introduced by
Should the type for parameter $cacheOptions not be array|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
96
     *
97
     */
98
    public function __construct(&$db, $tablename, $classname, $keyname, $idenfierName = '', $cacheOptions = null)
99
    {
100
        parent::__construct($db);
101
        $this->table     = $db->prefix($tablename);
102
        $this->keyName   = $keyname;
103
        $this->className = $classname;
104
        if (trim($idenfierName) != '') {
105
            $this->identifierName = $idenfierName;
106
        }
107
        // To diable cache, add this line after the first one : 'caching' => false,
108
        if (is_null($cacheOptions)) {
109
            $this->setCachingOptions(array('cacheDir' => REFERENCES_CACHE_PATH, 'lifeTime' => null, 'automaticSerialization' => true, 'fileNameProtection' => false));
110
        } else {
111
            $this->setCachingOptions($cacheOptions);
112
        }
113
    }
114
115
    public function setCachingOptions($cacheOptions)
116
    {
117
        $this->cacheOptions = $cacheOptions;
118
    }
119
120
    /**
121
     * Generates a unique ID for a Sql Query
122
     *
123
     * @param  string  $query The SQL query for which we want a unidque ID
124
     * @param  integer $start Which record to start at
125
     * @param  integer $limit Max number of objects to fetch
126
     * @return string  An MD5 of the query
127
     */
128
    protected function _getIdForCache($query, $start, $limit)
129
    {
130
        $id = md5($query . '-' . (string)$start . '-' . (string)$limit);
131
132
        return $id;
133
    }
134
135
    /**
136
     * create a new object
137
     *
138
     * @param bool $isNew Flag the new objects as "new"?
139
     *
140
     * @return object
141
     */
142
    public function &create($isNew = true)
143
    {
144
        $obj = new $this->className();
145
        if ($isNew === true) {
146
            $obj->setNew();
147
        }
148
149
        return $obj;
150
    }
151
152
    /**
153
     * retrieve an object
154
     *
155
     * @param  mixed $id        ID of the object - or array of ids for joint keys. Joint keys MUST be given in the same order as in the constructor
156
     * @param  bool  $as_object whether to return an object or an array
157
     * @return mixed reference to the object, FALSE if failed
158
     */
159
    public function &get($id, $as_object = true)
160
    {
161
        if (is_array($this->keyName)) {
162
            $criteria = new CriteriaCompo();
163
            $vnb      = count($this->keyName);
164
            for ($i = 0; $i < $vnb; ++$i) {
165
                $criteria->add(new Criteria($this->keyName[$i], (int)$id[$i]));
166
            }
167
        } else {
168
            $criteria = new Criteria($this->keyName, (int)$id);
169
        }
170
        $criteria->setLimit(1);
171
        $obj_array = $this->getObjects($criteria, false, $as_object);
172
        if (count($obj_array) != 1) {
173
            $ret = null;
174
        } else {
175
            $ret =& $obj_array[0];
176
        }
177
178
        return $ret;
179
    }
180
181
    /**
182
     * retrieve objects from the database
183
     *
184
     * @param null|CriteriaElement $criteria  {@link CriteriaElement} conditions to be met
185
     * @param bool                 $id_as_key use the ID as key for the array?
186
     * @param bool                 $as_object return an array of objects?
187
     *
188
     * @param string               $fields
189
     * @param bool                 $autoSort
190
     * @return array
0 ignored issues
show
Documentation introduced by
Should the return type not be array|string?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
191
     */
192
    public function &getObjects(CriteriaElement $criteria = null, $id_as_key = false, $as_object = true, $fields = '*', $autoSort = true)
193
    {
194
        require_once __DIR__ . '/lite.php';
195
        $ret   = array();
196
        $limit = $start = 0;
197
        $sql   = 'SELECT ' . $fields . ' FROM ' . $this->table;
198 View Code Duplication
        if (isset($criteria) && is_subclass_of($criteria, 'criteriaelement')) {
0 ignored issues
show
Bug introduced by
Due to PHP Bug #53727, is_subclass_of returns inconsistent results on some PHP versions for interfaces; you could instead use ReflectionClass::implementsInterface.
Loading history...
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
199
            $sql .= ' ' . $criteria->renderWhere();
200
            if ($criteria->groupby != '') {
201
                $sql .= $criteria->getGroupby();
202
            }
203
            if ($criteria->getSort() != '') {
204
                $sql .= ' ORDER BY ' . $criteria->getSort() . ' ' . $criteria->getOrder();
205
            } elseif ($this->identifierName != '' && $autoSort) {
206
                $sql .= ' ORDER BY ' . $this->identifierName;
207
            }
208
            $limit = $criteria->getLimit();
209
            $start = $criteria->getStart();
210
        }
211
        $Cache_Lite = new references_Cache_Lite($this->cacheOptions);
212
        $id         = $this->_getIdForCache($sql, $start, $limit);
213
        $cacheData  = $Cache_Lite->get($id);
214
        if ($cacheData === false) {
215
            $result = $this->db->query($sql, $limit, $start);
216
            if (!$result) {
217
                return $ret;
218
            }
219
            $ret = $this->convertResultSet($result, $id_as_key, $as_object, $fields);
220
            $Cache_Lite->save($ret);
0 ignored issues
show
Documentation introduced by
$ret is of type array, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
221
222
            return $ret;
223
        } else {
224
            return $cacheData;
225
        }
226
    }
227
228
    /**
229
     * Convert a database resultset to a returnable array
230
     *
231
     * @param object  $result    database resultset
232
     * @param boolean $id_as_key - should NOT be used with joint keys
233
     * @param boolean $as_object
234
     * @param string  $fields    Requested fields from the query
235
     *
236
     * @return array
237
     */
238
    protected function convertResultSet($result, $id_as_key = false, $as_object = true, $fields = '*')
239
    {
240
        $ret = array();
241
        while ($myrow = $this->db->fetchArray($result)) {
242
            $obj =& $this->create(false);
243
            $obj->assignVars($myrow);
244
            if (!$id_as_key) {
245 View Code Duplication
                if ($as_object) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
246
                    $ret[] =& $obj;
247
                } else {
248
                    $row     = array();
249
                    $vars    = $obj->getVars();
250
                    $tbl_tmp = array_keys($vars);
251
                    foreach ($tbl_tmp as $i) {
252
                        $row[$i] = $obj->getVar($i);
253
                    }
254
                    $ret[] = $row;
255
                }
256
            } else {
257
                if ($as_object) {
258
                    if ($fields === '*') {
259
                        $ret[$myrow[$this->keyName]] =& $obj;
260
                    } else {
261
                        $ret[] =& $obj;
262
                    }
263 View Code Duplication
                } else {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
264
                    $row     = array();
265
                    $vars    = $obj->getVars();
266
                    $tbl_tmp = array_keys($vars);
267
                    foreach ($tbl_tmp as $i) {
268
                        $row[$i] = $obj->getVar($i);
269
                    }
270
                    $ret[$myrow[$this->keyName]] = $row;
271
                }
272
            }
273
            unset($obj);
274
        }
275
276
        return $ret;
277
    }
278
279
    /**
280
     * get IDs of objects matching a condition
281
     *
282
     * @param  object $criteria {@link CriteriaElement} to match
0 ignored issues
show
Documentation introduced by
Should the type for parameter $criteria not be object|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
283
     * @return array  of object IDs
0 ignored issues
show
Documentation introduced by
Should the return type not be array|string?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
284
     */
285
    public function getIds($criteria = null)
286
    {
287
        require_once __DIR__ . '/lite.php';
288
        $limit = $start = 0;
289
290
        $Cache_Lite = new references_Cache_Lite($this->cacheOptions);
291
        $sql        = 'SELECT ' . $this->keyName . ' FROM ' . $this->table;
292 View Code Duplication
        if (isset($criteria) && is_subclass_of($criteria, 'criteriaelement')) {
0 ignored issues
show
Bug introduced by
Due to PHP Bug #53727, is_subclass_of returns inconsistent results on some PHP versions for interfaces; you could instead use ReflectionClass::implementsInterface.
Loading history...
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
293
            $sql .= ' ' . $criteria->renderWhere();
294
            if ($criteria->groupby != '') {
295
                $sql .= $criteria->getGroupby();
296
            }
297
            if ($criteria->getSort() != '') {
298
                $sql .= ' ORDER BY ' . $criteria->getSort() . ' ' . $criteria->getOrder();
299
            } elseif ($this->identifierName != '') {
300
                $sql .= ' ORDER BY ' . $this->identifierName;
301
            }
302
            $limit = $criteria->getLimit();
303
            $start = $criteria->getStart();
304
        }
305
306
        $id        = $this->_getIdForCache($sql, $start, $limit);
307
        $cacheData = $Cache_Lite->get($id);
308
        if ($cacheData === false) {
309
            $result = $this->db->query($sql, $limit, $start);
310
            $ret    = array();
311
            while ($myrow = $this->db->fetchArray($result)) {
312
                $ret[] = $myrow[$this->keyName];
313
            }
314
            $Cache_Lite->save($ret);
0 ignored issues
show
Documentation introduced by
$ret is of type array, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
315
316
            return $ret;
317
        } else {
318
            return $cacheData;
319
        }
320
    }
321
322
    /**
323
     * Retrieve a list of objects as arrays - DON'T USE WITH JOINT KEYS
324
     *
325
     * @param object $criteria {@link CriteriaElement} conditions to be met
0 ignored issues
show
Documentation introduced by
Should the type for parameter $criteria not be object|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
326
     * @return array
0 ignored issues
show
Documentation introduced by
Should the return type not be array|string?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
327
     */
328
    public function getList($criteria = null)
329
    {
330
        require_once __DIR__ . '/lite.php';
331
        $limit      = $start = 0;
332
        $Cache_Lite = new references_Cache_Lite($this->cacheOptions);
333
334
        $ret = array();
335
336
        $sql = 'SELECT ' . $this->keyName;
337
        if (!empty($this->identifierName)) {
338
            $sql .= ', ' . $this->identifierName;
339
        }
340
        $sql .= ' FROM ' . $this->table;
341 View Code Duplication
        if (isset($criteria) && is_subclass_of($criteria, 'criteriaelement')) {
0 ignored issues
show
Bug introduced by
Due to PHP Bug #53727, is_subclass_of returns inconsistent results on some PHP versions for interfaces; you could instead use ReflectionClass::implementsInterface.
Loading history...
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
342
            $sql .= ' ' . $criteria->renderWhere();
343
            if ($criteria->groupby != '') {
344
                $sql .= $criteria->getGroupby();
345
            }
346
            if ($criteria->getSort() != '') {
347
                $sql .= ' ORDER BY ' . $criteria->getSort() . ' ' . $criteria->getOrder();
348
            } elseif ($this->identifierName != '') {
349
                $sql .= ' ORDER BY ' . $this->identifierName;
350
            }
351
            $limit = $criteria->getLimit();
352
            $start = $criteria->getStart();
353
        }
354
355
        $id        = $this->_getIdForCache($sql, $start, $limit);
356
        $cacheData = $Cache_Lite->get($id);
357
        if ($cacheData === false) {
358
            $result = $this->db->query($sql, $limit, $start);
359
            if (!$result) {
360
                $Cache_Lite->save($ret);
0 ignored issues
show
Documentation introduced by
$ret is of type array, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
361
362
                return $ret;
363
            }
364
365
            $myts = MyTextSanitizer::getInstance();
366
            while ($myrow = $this->db->fetchArray($result)) {
367
                //identifiers should be textboxes, so sanitize them like that
368
                $ret[$myrow[$this->keyName]] = empty($this->identifierName) ? 1 : $myts->htmlSpecialChars($myrow[$this->identifierName]);
369
            }
370
            $Cache_Lite->save($ret);
0 ignored issues
show
Documentation introduced by
$ret is of type array, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
371
372
            return $ret;
373
        } else {
374
            return $cacheData;
375
        }
376
    }
377
378
    /**
379
     * Retourne des éléments selon leur ID
380
     *
381
     * @param array $ids Les ID des éléments à retrouver
382
     * @param null  $additionnal
383
     * @return array Tableau d'objets
0 ignored issues
show
Documentation introduced by
Should the return type not be array|string?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
384
     */
385
    public function getItemsFromIds($ids, $additionnal = null)
386
    {
387
        $ret = array();
388
        if (is_array($ids) && count($ids) > 0) {
389
            $criteria = new CriteriaCompo();
390
            $criteria->add(new Criteria($this->keyName, '(' . implode(',', $ids) . ')', 'IN'));
391
            if (!is_null($additionnal)) {
392
                $criteria->add($additionnal);
393
            }
394
            $ret = $this->getObjects($criteria, true);
395
        }
396
397
        return $ret;
398
    }
399
400
    /**
401
     * count objects matching a condition
402
     *
403
     * @param  object $criteria {@link CriteriaElement} to match
0 ignored issues
show
Documentation introduced by
Should the type for parameter $criteria not be object|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
404
     * @return int    count of objects
405
     */
406
    public function getCount($criteria = null)
407
    {
408
        $field   = '';
409
        $groupby = false;
410
        $limit   = $start = 0;
411
        require_once __DIR__ . '/lite.php';
412
413
        if (isset($criteria) && is_subclass_of($criteria, 'criteriaelement')) {
0 ignored issues
show
Bug introduced by
Due to PHP Bug #53727, is_subclass_of returns inconsistent results on some PHP versions for interfaces; you could instead use ReflectionClass::implementsInterface.
Loading history...
414
            if ($criteria->groupby != '') {
415
                $groupby = true;
416
                $field   = $criteria->groupby . ', '; //Not entirely secure unless you KNOW that no criteria's groupby clause is going to be mis-used
417
            }
418
        }
419
        $sql = 'SELECT ' . $field . 'COUNT(*) FROM ' . $this->table;
420 View Code Duplication
        if (isset($criteria) && is_subclass_of($criteria, 'criteriaelement')) {
0 ignored issues
show
Bug introduced by
Due to PHP Bug #53727, is_subclass_of returns inconsistent results on some PHP versions for interfaces; you could instead use ReflectionClass::implementsInterface.
Loading history...
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
421
            $sql .= ' ' . $criteria->renderWhere();
422
            if ($criteria->groupby != '') {
423
                $sql .= $criteria->getGroupby();
424
            }
425
            $limit = $criteria->getLimit();
426
            $start = $criteria->getStart();
427
        }
428
        $Cache_Lite = new references_Cache_Lite($this->cacheOptions);
429
        $id         = $this->_getIdForCache($sql, $start, $limit);
430
        $cacheData  = $Cache_Lite->get($id);
431
        if ($cacheData === false) {
432
            $result = $this->db->query($sql, $limit, $start);
433
            if (!$result) {
434
                $ret = 0;
435
                $Cache_Lite->save($ret);
436
437
                return $ret;
438
            }
439
            if ($groupby == false) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
440
                list($count) = $this->db->fetchRow($result);
441
                $Cache_Lite->save($count);
442
443
                return $count;
444
            } else {
445
                $ret = array();
446
                while (list($id, $count) = $this->db->fetchRow($result)) {
447
                    $ret[$id] = $count;
448
                }
449
                $Cache_Lite->save($ret);
0 ignored issues
show
Documentation introduced by
$ret is of type array, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
450
451
                return $ret;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $ret; (array) is incompatible with the return type documented by references_XoopsPersistableObjectHandler::getCount of type integer.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
452
            }
453
        } else {
454
            return $cacheData;
455
        }
456
    }
457
458
    /**
459
     * Retourne le total d'un champ
460
     *
461
     * @param  string $field    Le champ dont on veut calculer le total
462
     * @param  object $criteria {@link CriteriaElement} to match
0 ignored issues
show
Documentation introduced by
Should the type for parameter $criteria not be object|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
463
     * @return integer le total
464
     */
465
    public function getSum($field, $criteria = null)
466
    {
467
        $limit = $start = 0;
468
        require_once __DIR__ . '/lite.php';
469
470
        $sql = 'SELECT Sum(' . $field . ') as cpt FROM ' . $this->table;
471 View Code Duplication
        if (isset($criteria) && is_subclass_of($criteria, 'criteriaelement')) {
0 ignored issues
show
Bug introduced by
Due to PHP Bug #53727, is_subclass_of returns inconsistent results on some PHP versions for interfaces; you could instead use ReflectionClass::implementsInterface.
Loading history...
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
472
            $sql .= ' ' . $criteria->renderWhere();
473
            if ($criteria->groupby != '') {
474
                $sql .= $criteria->getGroupby();
475
            }
476
            $limit = $criteria->getLimit();
477
            $start = $criteria->getStart();
478
        }
479
        $Cache_Lite = new references_Cache_Lite($this->cacheOptions);
480
        $id         = $this->_getIdForCache($sql, $start, $limit);
481
        $cacheData  = $Cache_Lite->get($id);
482
        if ($cacheData === false) {
483
            $result = $this->db->query($sql, $limit, $start);
484
            if (!$result) {
485
                $ret = 0;
486
                $Cache_Lite->save($ret);
487
488
                return $ret;
489
            }
490
            $row   = $this->db->fetchArray($result);
491
            $count = $row['cpt'];
492
            $Cache_Lite->save($count);
493
494
            return $count;
495
        } else {
496
            return $cacheData;
497
        }
498
    }
499
500
    /**
501
     * delete an object from the database
502
     *
503
     * @param  XoopsObject $obj reference to the object to delete
504
     * @param  bool        $force
505
     * @return bool   FALSE if failed.
506
     */
507
    public function delete(XoopsObject $obj, $force = false)
508
    {
509
        if (is_array($this->keyName)) {
510
            $clause = array();
511
            $vnb    = count($this->keyName);
512
            for ($i = 0; $i < $vnb; ++$i) {
513
                $clause[] = $this->keyName[$i] . ' = ' . $obj->getVar($this->keyName[$i]);
514
            }
515
            $whereclause = implode(' AND ', $clause);
516
        } else {
517
            $whereclause = $this->keyName . ' = ' . $obj->getVar($this->keyName);
518
        }
519
        $sql = 'DELETE FROM ' . $this->table . ' WHERE ' . $whereclause;
520 View Code Duplication
        if (false != $force) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison !== instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
521
            $result = $this->db->queryF($sql);
522
        } else {
523
            $result = $this->db->query($sql);
524
        }
525
        // Clear cache
526
        $this->forceCacheClean();
527
528
        if (!$result) {
529
            return false;
530
        }
531
532
        return true;
533
    }
534
535
    /**
536
     * Quickly insert a record like this $myobject_handler->quickInsert('field1' => field1value, 'field2' => $field2value)
537
     *
538
     * @param  array $vars  Array containing the fields name and value
0 ignored issues
show
Documentation introduced by
Should the type for parameter $vars not be array|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
539
     * @param  bool  $force whether to force the query execution despite security settings
540
     * @return bool  @link insert's value
541
     */
542
    public function quickInsert($vars = null, $force = true)
543
    {
544
        $object = $this->create(true);
545
        $object->setVars($vars);
546
        $retval =& $this->insert($object, $force);
547
        unset($object);
548
549
        // Clear cache
550
        $this->forceCacheClean();
551
552
        return $retval;
553
    }
554
555
    /**
556
     * insert a new object in the database
557
     *
558
     * @param  XoopsObject $obj         reference to the object
559
     * @param  bool        $force       whether to force the query execution despite security settings
560
     * @param  bool        $checkObject check if the object is dirty and clean the attributes
561
     * @param bool         $ignoreInsert
562
     * @return bool FALSE if failed, TRUE if already present and unchanged or successful
563
     */
564
565
    public function insert(XoopsObject $obj, $force = false, $checkObject = true, $ignoreInsert = false)
566
    {
567
        if ($checkObject != false) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison !== instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
568
            if (!is_object($obj)) {
569
                trigger_error('Error, not object');
570
571
                return false;
572
            }
573
            /**
574
             * @TODO: Change to if (!(class_exists($this->className) && $obj instanceof $this->className)) when going fully PHP5
575
             */
576
            if (!is_a($obj, $this->className)) {
577
                $obj->setErrors(get_class($obj) . ' Differs from ' . $this->className);
578
579
                return false;
580
            }
581
            if (!$obj->isDirty()) {
582
                $obj->setErrors('Not dirty'); //will usually not be outputted as errors are not displayed when the method returns true, but it can be helpful when troubleshooting code - Mith
583
584
                return true;
585
            }
586
        }
587
        if (!$obj->cleanVars()) {
588
            foreach ($obj->getErrors() as $oneerror) {
589
                trigger_error($oneerror);
590
            }
591
592
            return false;
593
        }
594
        foreach ($obj->cleanVars as $k => $v) {
595
            if ($obj->vars[$k]['data_type'] == XOBJ_DTYPE_INT) {
596
                $cleanvars[$k] = (int)$v;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$cleanvars was never initialized. Although not strictly required by PHP, it is generally a good practice to add $cleanvars = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
597
            } elseif (is_array($v)) {
598
                $cleanvars[$k] = $this->db->quoteString(implode(',', $v));
0 ignored issues
show
Bug introduced by
The variable $cleanvars does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
599
            } else {
600
                $cleanvars[$k] = $this->db->quoteString($v);
601
            }
602
        }
603
        if (isset($cleanvars['dohtml'])) {        // Modification Herv� to be able to use dohtml
604
            unset($cleanvars['dohtml']);
605
        }
606
        if ($obj->isNew()) {
607
            if (!is_array($this->keyName)) {
608
                if ($cleanvars[$this->keyName] < 1) {
609
                    $cleanvars[$this->keyName] = $this->db->genId($this->table . '_' . $this->keyName . '_seq');
610
                }
611
            }
612
            $ignore = '';
613
            if ($ignoreInsert) {
614
                $ignore = 'IGNORE';
615
            }
616
            $sql = "INSERT $ignore INTO " . $this->table . ' (' . implode(',', array_keys($cleanvars)) . ') VALUES (' . implode(',', array_values($cleanvars)) . ')';
617
        } else {
618
            $sql = 'UPDATE ' . $this->table . ' SET';
619
            foreach ($cleanvars as $key => $value) {
620
                if ((!is_array($this->keyName) && $key == $this->keyName) || (is_array($this->keyName) && in_array($key, $this->keyName))) {
621
                    continue;
622
                }
623
                if (isset($notfirst)) {
624
                    $sql .= ',';
625
                }
626
                $sql .= ' ' . $key . ' = ' . $value;
627
                $notfirst = true;
628
            }
629
            if (is_array($this->keyName)) {
630
                $whereclause = '';
631
                $vnb         = count($this->keyName);
632
                for ($i = 0; $i < $vnb; ++$i) {
633
                    if ($i > 0) {
634
                        $whereclause .= ' AND ';
635
                    }
636
                    $whereclause .= $this->keyName[$i] . ' = ' . $obj->getVar($this->keyName[$i]);
637
                }
638
            } else {
639
                $whereclause = $this->keyName . ' = ' . $obj->getVar($this->keyName);
640
            }
641
            $sql .= ' WHERE ' . $whereclause;
642
        }
643
644 View Code Duplication
        if (false != $force) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison !== instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
645
            $result = $this->db->queryF($sql);
646
        } else {
647
            $result = $this->db->query($sql);
648
        }
649
650
        // Clear cache
651
        $this->forceCacheClean();
652
653
        if (!$result) {
654
            return false;
655
        }
656
        if ($obj->isNew() && !is_array($this->keyName)) {
657
            $obj->assignVar($this->keyName, $this->db->getInsertId());
658
        }
659
660
        return true;
661
    }
662
663
    /**
664
     * Change a value for objects with a certain criteria
665
     *
666
     * @param string $fieldname  Name of the field
667
     * @param string $fieldvalue Value to write
668
     * @param object $criteria   {@link CriteriaElement}
0 ignored issues
show
Documentation introduced by
Should the type for parameter $criteria not be object|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
669
     *
670
     * @param bool   $force
671
     * @return bool
672
     */
673
    public function updateAll($fieldname, $fieldvalue, $criteria = null, $force = false)
674
    {
675
        $set_clause = $fieldname . ' = ';
676
        if (is_numeric($fieldvalue)) {
677
            $set_clause .= $fieldvalue;
678
        } elseif (is_array($fieldvalue)) {
679
            $set_clause .= $this->db->quoteString(implode(',', $fieldvalue));
680
        } else {
681
            $set_clause .= $this->db->quoteString($fieldvalue);
682
        }
683
        $sql = 'UPDATE ' . $this->table . ' SET ' . $set_clause;
684
        if (isset($criteria) && is_subclass_of($criteria, 'criteriaelement')) {
0 ignored issues
show
Bug introduced by
Due to PHP Bug #53727, is_subclass_of returns inconsistent results on some PHP versions for interfaces; you could instead use ReflectionClass::implementsInterface.
Loading history...
685
            $sql .= ' ' . $criteria->renderWhere();
686
        }
687
        if ($force) {
688
            $result = $this->db->queryF($sql);
689
        } else {
690
            $result = $this->db->query($sql);
691
        }
692
693
        // Clear cache
694
        $this->forceCacheClean();
695
696
        if (!$result) {
697
            return false;
698
        }
699
700
        return true;
701
    }
702
703
    //  check if target object is attempting to use duplicated info
704
    public function isDuplicated(&$obj, $field = '', $error = '')
705
    {
706
        if (empty($field)) {
707
            return false;
708
        }
709
        $criteria = new CriteriaCompo();
710
        $criteria->add(new Criteria($field, $obj->getVar($field)));
711
        //  one more condition if target object exisits in database
712
        if (!$obj->isNew()) {
713
            $criteria->add(new Criteria($this->_key, $obj->getVar($this->_key), '!='));
714
        }
715
        if ($this->getCount($criteria)) {
716
            $obj->setErrors($error);
717
718
            return true;
719
        }
720
721
        return false;
722
    }
723
724
    /**
725
     * delete all objects meeting the conditions
726
     *
727
     * @param  object $criteria {@link CriteriaElement} with conditions to meet
0 ignored issues
show
Documentation introduced by
Should the type for parameter $criteria not be object|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
728
     * @return bool
729
     */
730
    public function deleteAll($criteria = null)
731
    {
732
        if (isset($criteria) && is_subclass_of($criteria, 'criteriaelement')) {
0 ignored issues
show
Bug introduced by
Due to PHP Bug #53727, is_subclass_of returns inconsistent results on some PHP versions for interfaces; you could instead use ReflectionClass::implementsInterface.
Loading history...
733
            $sql = 'DELETE FROM ' . $this->table;
734
            $sql .= ' ' . $criteria->renderWhere();
735
            if (!$this->db->queryF($sql)) {
736
                return false;
737
            }
738
            $rows = $this->db->getAffectedRows();
739
740
            // Clear cache
741
            $this->forceCacheClean();
742
743
            return $rows > 0 ? $rows : true;
744
        }
745
746
        return false;
747
    }
748
749
    /**
750
     * Compare two objects and returns, in an array, the differences
751
     *
752
     * @param  XoopsObject $old_object The first object to compare
753
     * @param  XoopsObject $new_object The new object
754
     * @return array       differences  key = fieldname, value = array('old_value', 'new_value')
755
     */
756
    public function compareObjects($old_object, $new_object)
757
    {
758
        $ret       = array();
759
        $vars_name = array_keys($old_object->getVars());
760
        foreach ($vars_name as $one_var) {
761
            if ($old_object->getVar($one_var, 'f') == $new_object->getVar($one_var, 'f')) {
762
            } else {
763
                $ret[$one_var] = array($old_object->getVar($one_var), $new_object->getVar($one_var));
764
            }
765
        }
766
767
        return $ret;
768
    }
769
770
    /**
771
     * Get distincted values of a field in the table
772
     *
773
     * @param  string $field    Field's name
774
     * @param  object $criteria {@link CriteriaElement} conditions to be met
0 ignored issues
show
Documentation introduced by
Should the type for parameter $criteria not be object|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
775
     * @param  string $format   Format in wich we want the datas
776
     * @return array  containing the distinct values
0 ignored issues
show
Documentation introduced by
Should the return type not be array|string?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
777
     */
778
    public function getDistincts($field, $criteria = null, $format = 's')
779
    {
780
        require_once __DIR__ . '/lite.php';
781
        $limit = $start = 0;
782
        $sql   = 'SELECT ' . $this->keyName . ', ' . $field . ' FROM ' . $this->table;
783
        if (isset($criteria) && is_subclass_of($criteria, 'criteriaelement')) {
0 ignored issues
show
Bug introduced by
Due to PHP Bug #53727, is_subclass_of returns inconsistent results on some PHP versions for interfaces; you could instead use ReflectionClass::implementsInterface.
Loading history...
784
            $sql .= ' ' . $criteria->renderWhere();
785
            $limit = $criteria->getLimit();
786
            $start = $criteria->getStart();
787
        }
788
        $sql .= ' GROUP BY ' . $field . ' ORDER BY ' . $field;
789
790
        $Cache_Lite = new references_Cache_Lite($this->cacheOptions);
791
        $id         = $this->_getIdForCache($sql, $start, $limit);
792
        $cacheData  = $Cache_Lite->get($id);
793
        if ($cacheData === false) {
794
            $result = $this->db->query($sql, $limit, $start);
795
            $ret    = array();
796
            $obj    = new $this->className();
797
            while ($myrow = $this->db->fetchArray($result)) {
798
                $obj->setVar($field, $myrow[$field]);
799
                $ret[$myrow[$this->keyName]] = $obj->getVar($field, $format);
800
            }
801
            $Cache_Lite->save($ret);
0 ignored issues
show
Documentation introduced by
$ret is of type array, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
802
803
            return $ret;
804
        } else {
805
            return $cacheData;
806
        }
807
    }
808
809
    /**
810
     * A generic shortcut to getObjects
811
     *
812
     * @author Herve Thouzard - Instant Zero
813
     *
814
     * @param  integer $start   Starting position
815
     * @param  integer $limit   Maximum count of elements to return
816
     * @param  string  $sort    Field to use for the sort
817
     * @param  string  $order   Sort order
818
     * @param  boolean $idAsKey Do we have to return an array whoses keys are the record's ID ?
819
     * @return array   Array of current objects
0 ignored issues
show
Documentation introduced by
Should the return type not be array|string?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
820
     */
821
    public function getItems($start = 0, $limit = 0, $sort = '', $order = 'ASC', $idAsKey = true)
822
    {
823
        if (trim($order) == '') {
824
            if (isset($this->identifierName) && trim($this->identifierName) != '') {
825
                $order = $this->identifierName;
826
            } else {
827
                $order = $this->keyName;
828
            }
829
        }
830
        $items   = array();
0 ignored issues
show
Unused Code introduced by
$items is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

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.

Loading history...
831
        $critere = new Criteria($this->keyName, 0, '<>');
832
        $critere->setLimit($limit);
833
        $critere->setStart($start);
834
        $critere->setSort($sort);
835
        $critere->setOrder($order);
836
        $items = $this->getObjects($critere, $idAsKey);
837
838
        return $items;
839
    }
840
841
    /**
842
     * Forces the cache to be cleaned
843
     */
844
    public function forceCacheClean()
845
    {
846
        require_once __DIR__ . '/lite.php';
847
        $Cache_Lite = new references_Cache_Lite($this->cacheOptions);
848
        $Cache_Lite->clean();
849
    }
850
}
851