PersistableObjectHandler::getModuleConfig()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 0
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php namespace XoopsModules\Smartobject;
2
3
/**
4
 * Contains the basis classes for managing any objects derived from SmartObjects
5
 *
6
 * @license    GNU
7
 * @author     marcan <[email protected]>
8
 * @link       http://smartfactory.ca The SmartFactory
9
 * @package    SmartObject
10
 * @subpackage SmartObjectCore
11
 */
12
13
//use \CriteriaElement;
14
use XoopsModules\Smartobject;
15
16
// defined('XOOPS_ROOT_PATH') || die('Restricted access');
17
18
/**
19
 * Persistable SmartObject Handler class.
20
 *
21
 * This class is responsible for providing data access mechanisms to the data source
22
 * of derived class objects as well as some basic operations inherant to objects manipulation
23
 *
24
 * @package SmartObject
25
 * @author  marcan <[email protected]>
26
 * @credit  Jan Keller Pedersen <[email protected]> - IDG Danmark A/S <www.idg.dk>
27
 * @link    http://smartfactory.ca The SmartFactory
28
 */
29
30
class PersistableObjectHandler extends \XoopsObjectHandler
31
{
32
    public $_itemname;
33
34
    /**
35
     * Name of the table use to store this {@link SmartObject}
36
     *
37
     * Note that the name of the table needs to be free of the database prefix.
38
     * For example "smartsection_categories"
39
     * @var string
40
     */
41
    public $table;
42
43
    /**
44
     * Name of the table key that uniquely identify each {@link SmartObject}
45
     *
46
     * For example: "categoryid"
47
     * @var string
48
     */
49
    public $keyName;
50
51
    /**
52
     * Name of the class derived from {@link BaseSmartObject} and which this handler is handling
53
     *
54
     * Note that this string needs to be lowercase
55
     *
56
     * For example: "smartsectioncategory"
57
     * @var string
58
     */
59
    public $className;
60
61
    /**
62
     * Name of the field which properly identify the {@link SmartObject}
63
     *
64
     * For example: "name" (this will be the category's name)
65
     * @var string
66
     */
67
    public $identifierName;
68
69
    /**
70
     * Name of the field which will be use as a summary for the object
71
     *
72
     * For example: "summary"
73
     * @var string
74
     */
75
    public $summaryName;
76
77
    /**
78
     * Page name use to basically manage and display the {@link SmartObject}
79
     *
80
     * This page needs to be the same in user side and admin side
81
     *
82
     * For example category.php - we will deduct smartsection/category.php as well as smartsection/admin/category.php
83
     * @todo this could probably be automatically deducted from the class name - for example, the class SmartsectionCategory will have "category.php" as it's managing page
84
     * @var string
85
     */
86
    public $_page;
87
88
    /**
89
     * Full path of the module using this {@link SmartObject}
90
     *
91
     * <code>XOOPS_URL . "/modules/smartsection/"</code>
92
     * @todo this could probably be automatically deducted from the class name as it is always prefixed with the module name
93
     * @var string
94
     */
95
    public $_modulePath;
96
97
    public $_moduleUrl;
98
99
    public $_moduleName;
100
101
    public $_uploadUrl;
102
103
    public $_uploadPath;
104
105
    public $_allowedMimeTypes = 0;
106
107
    public $_maxFileSize = 1000000;
108
109
    public $_maxWidth = 500;
110
111
    public $_maxHeight = 500;
112
113
    public $highlightFields = [];
114
115
    /**
116
     * Array containing the events name and functions
117
     *
118
     * @var array
119
     */
120
    public $eventArray = [];
121
122
    /**
123
     * Array containing the permissions that this handler will manage on the objects
124
     *
125
     * @var array
126
     */
127
    public $permissionsArray = false;
128
129
    public $generalSQL = false;
130
131
    public $_eventHooks     = [];
132
    public $_disabledEvents = [];
133
134
    /**
135
     * Constructor - called from child classes
136
     *
137
     * @param \XoopsDatabase $db           {@link XoopsDatabase}
138
     * @param string         $itemname     Name of the class derived from <a href='psi_element://SmartObject'>SmartObject</a> and which this handler is handling and which this handler is handling
139
     * @param string         $keyname      Name of the table key that uniquely identify each {@link SmartObject}
140
     * @param string         $idenfierName Name of the field which properly identify the {@link SmartObject}
141
     * @param string         $summaryName
142
     * @param string         $modulename
143
     * @internal param string $tablename Name of the table use to store this <a href='psi_element://SmartObject'>SmartObject</a>
144
     * @internal param string $page Page name use to basically manage and display the <a href='psi_element://SmartObject'>SmartObject</a>
145
     * @internal param string $moduleName name of the module
146
     */
147
    public function __construct(\XoopsDatabase $db, $itemname, $keyname, $idenfierName, $summaryName, $modulename)
148
    {
149
        parent::__construct($db);
150
151
        $this->_itemname      = $itemname;
152
        $this->_moduleName    = $modulename;
153
        $this->table          = $db->prefix($modulename . '_' . $itemname);
154
        $this->keyName        = $keyname;
155
//        $this->className      = ucfirst($modulename) . ucfirst($itemname);
156
        $this->className      = $itemname;
157
158
        $this->identifierName = $idenfierName;
159
        $this->summaryName    = $summaryName;
160
        $this->_page          = $itemname . '.php';
161
        $this->_modulePath    = XOOPS_ROOT_PATH . '/modules/' . $this->_moduleName . '/';
162
        $this->_moduleUrl     = XOOPS_URL . '/modules/' . $this->_moduleName . '/';
163
        $this->_uploadPath    = XOOPS_UPLOAD_PATH . '/' . $this->_moduleName . '/';
164
        $this->_uploadUrl     = XOOPS_UPLOAD_URL . '/' . $this->_moduleName . '/';
165
    }
166
167
    /**
168
     * @param $event
169
     * @param $method
170
     */
171
    public function addEventHook($event, $method)
172
    {
173
        $this->_eventHooks[$event] = $method;
174
    }
175
176
    /**
177
     * Add a permission that this handler will manage for its objects
178
     *
179
     * Example: $this->addPermission('view', _AM_SSHOP_CAT_PERM_READ, _AM_SSHOP_CAT_PERM_READ_DSC);
180
     *
181
     * @param string      $perm_name   name of the permission
182
     * @param string      $caption     caption of the control that will be displayed in the form
183
     * @param bool|string $description description of the control that will be displayed in the form
184
     */
185
    public function addPermission($perm_name, $caption, $description = false)
186
    {
187
//        require_once SMARTOBJECT_ROOT_PATH . 'class/smartobjectpermission.php';
188
189
        $this->permissionsArray[] = [
190
            'perm_name'   => $perm_name,
191
            'caption'     => $caption,
192
            'description' => $description
193
        ];
194
    }
195
196
    /**
197
     * @param $criteria
198
     * @param $perm_name
199
     * @return bool
200
     */
201
    public function setGrantedObjectsCriteria($criteria, $perm_name)
202
    {
203
        $smartPermissionsHandler = new PermissionHandler($this);
204
        $grantedItems            = $smartPermissionsHandler->getGrantedItems($perm_name);
205
        if (count($grantedItems) > 0) {
206
            $criteria->add(new \Criteria($this->keyName, '(' . implode(', ', $grantedItems) . ')', 'IN'));
207
208
            return true;
209
        } else {
210
            return false;
211
        }
212
    }
213
214
    /**
215
     * @param bool $_uploadPath
216
     * @param bool $_allowedMimeTypes
217
     * @param bool $_maxFileSize
218
     * @param bool $_maxWidth
219
     * @param bool $_maxHeight
220
     */
221
    public function setUploaderConfig(
222
        $_uploadPath = false,
223
        $_allowedMimeTypes = false,
224
        $_maxFileSize = false,
225
        $_maxWidth = false,
226
        $_maxHeight = false
227
    ) {
228
        $this->_uploadPath       = $_uploadPath ?: $this->_uploadPath;
229
        $this->_allowedMimeTypes = $_allowedMimeTypes ?: $this->_allowedMimeTypes;
0 ignored issues
show
Documentation Bug introduced by
It seems like $_allowedMimeTypes ?: $this->_allowedMimeTypes can also be of type boolean. However, the property $_allowedMimeTypes is declared as type integer. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

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

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
230
        $this->_maxFileSize      = $_maxFileSize ?: $this->_maxFileSize;
0 ignored issues
show
Documentation Bug introduced by
It seems like $_maxFileSize ?: $this->_maxFileSize can also be of type boolean. However, the property $_maxFileSize is declared as type integer. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

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

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
231
        $this->_maxWidth         = $_maxWidth ?: $this->_maxWidth;
0 ignored issues
show
Documentation Bug introduced by
It seems like $_maxWidth ?: $this->_maxWidth can also be of type boolean. However, the property $_maxWidth is declared as type integer. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

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

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
232
        $this->_maxHeight        = $_maxHeight ?: $this->_maxHeight;
0 ignored issues
show
Documentation Bug introduced by
It seems like $_maxHeight ?: $this->_maxHeight can also be of type boolean. However, the property $_maxHeight is declared as type integer. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

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

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
233
    }
234
235
    /**
236
     * create a new {@link Smartobject\BaseSmartObject}
237
     *
238
     * @param bool $isNew Flag the new objects as "new"?
239
     *
240
     * @return Smartobject\BaseSmartObject {@link Smartobject\BaseSmartObject}
241
     */
242
    public function create($isNew = true)
243
    {
244
//        $obj0 = new $this->className($this);
245
246
        $obj =  new $this->className;
247
248
        $obj->setImageDir($this->getImageUrl(), $this->getImagePath());
249
        if (!$obj->handler) {
250
            $obj->Handler = $this;
251
        }
252
253
        if (true === $isNew) {
254
            $obj->setNew();
255
        }
256
257
        return $obj;
258
    }
259
260
    /**
261
     * @return string
262
     */
263
    public function getImageUrl()
264
    {
265
        return $this->_uploadUrl . $this->_itemname . '/';
266
    }
267
268
    /**
269
     * @return string
270
     */
271
    public function getImagePath()
272
    {
273
        $dir = $this->_uploadPath . $this->_itemname;
274
        if (!file_exists($dir)) {
275
            Smartobject\Utility::mkdirAsAdmin($dir);
276
        }
277
278
        return $dir . '/';
279
    }
280
281
    /**
282
     * retrieve a {@link SmartObject}
283
     *
284
     * @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
285
     * @param  bool  $as_object whether to return an object or an array
286
     * @param  bool  $debug
287
     * @param  bool  $criteria
288
     * @return mixed reference to the <a href='psi_element://SmartObject'>SmartObject</a>, FALSE if failed
289
     *                          FALSE if failed
290
     */
291
    public function get($id, $as_object = true, $debug = false, $criteria = false)
292
    {
293
        if (!$criteria) {
294
            $criteria = new \CriteriaCompo();
295
        }
296
        if (is_array($this->keyName)) {
297
            for ($i = 0, $iMax = count($this->keyName); $i < $iMax; ++$i) {
298
                /**
299
                 * In some situations, the $id is not an INTEGER. SmartObjectTag is an example.
300
                 * Is the fact that we removed the (int)() represents a security risk ?
301
                 */
302
                //$criteria->add(new \Criteria($this->keyName[$i], ($id[$i]), '=', $this->_itemname));
303
                $criteria->add(new \Criteria($this->keyName[$i], $id[$i], '=', $this->_itemname));
304
            }
305
        } else {
306
            //$criteria = new \Criteria($this->keyName, (int)($id), '=', $this->_itemname);
307
            /**
308
             * In some situations, the $id is not an INTEGER. SmartObjectTag is an example.
309
             * Is the fact that we removed the (int)() represents a security risk ?
310
             */
311
            $criteria->add(new \Criteria($this->keyName, $id, '=', $this->_itemname));
312
        }
313
        $criteria->setLimit(1);
314
        if ($debug) {
315
            $obj_array = $this->getObjectsD($criteria, false, $as_object);
316
        } else {
317
            $obj_array =& $this->getObjects($criteria, false, $as_object);
318
            //patch: weird bug of indexing by id even if id_as_key = false;
319
            if (!isset($obj_array[0]) && is_object($obj_array[$id])) {
320
                $obj_array[0] = $obj_array[$id];
321
                unset($obj_array[$id]);
322
                $obj_array[0]->unsetNew();
0 ignored issues
show
Bug introduced by
The method unsetNew cannot be called on $obj_array[0] (of type null).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
323
            }
324
        }
325
326
        if (1 != count($obj_array)) {
327
            $obj = $this->create();
328
329
            return $obj;
330
        }
331
332
        return $obj_array[0];
333
    }
334
335
    /**
336
     * retrieve a {@link SmartObject}
337
     *
338
     * @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
339
     * @param  bool  $as_object whether to return an object or an array
340
     * @return mixed reference to the {@link SmartObject}, FALSE if failed
341
     */
342
    public function &getD($id, $as_object = true)
343
    {
344
        return $this->get($id, $as_object, true);
345
    }
346
347
    /**
348
     * retrieve objects from the database
349
     *
350
     * @param null|\CriteriaElement $criteria  {@link CriteriaElement} conditions to be met
351
     * @param bool            $id_as_key use the ID as key for the array?
352
     * @param bool            $as_object return an array of objects?
353
     *
354
     * @param  bool           $sql
355
     * @param  bool           $debug
356
     * @return array
357
     */
358
    public function getObjects(
359
        \CriteriaElement $criteria = null,
360
        $id_as_key = false,
361
        $as_object = true,
362
        $sql = false,
363
        $debug = false
364
    ) {
365
        $ret   = [];
366
        $limit = $start = 0;
367
368
        if ($this->generalSQL) {
369
            $sql = $this->generalSQL;
370
        } elseif (!$sql) {
371
            $sql = 'SELECT * FROM ' . $this->table . ' AS ' . $this->_itemname;
372
        }
373
374 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...
375
            $sql .= ' ' . $criteria->renderWhere();
376
            if ('' !== $criteria->getSort()) {
377
                $sql .= ' ORDER BY ' . $criteria->getSort() . ' ' . $criteria->getOrder();
378
            }
379
            $limit = $criteria->getLimit();
380
            $start = $criteria->getStart();
381
        }
382
        if ($debug) {
383
            xoops_debug($sql);
384
        }
385
386
        $result = $this->db->query($sql, $limit, $start);
387
        if (!$result) {
388
            return $ret;
389
        }
390
391
        return $this->convertResultSet($result, $id_as_key, $as_object);
392
    }
393
394
    /**
395
     * @param        $sql
396
     * @param        $criteria
397
     * @param  bool  $force
398
     * @param  bool  $debug
399
     * @return array
400
     */
401
    public function query($sql, $criteria, $force = false, $debug = false)
402
    {
403
        $ret = [];
404
405 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...
406
            $sql .= ' ' . $criteria->renderWhere();
407
            if ($criteria->groupby) {
408
                $sql .= $criteria->getGroupby();
409
            }
410
            if ('' !== $criteria->getSort()) {
411
                $sql .= ' ORDER BY ' . $criteria->getSort() . ' ' . $criteria->getOrder();
412
            }
413
        }
414
        if ($debug) {
415
            xoops_debug($sql);
416
        }
417
418
        if ($force) {
419
            $result = $this->db->queryF($sql);
420
        } else {
421
            $result = $this->db->query($sql);
422
        }
423
424
        if (!$result) {
425
            return $ret;
426
        }
427
428
        while (false !== ($myrow = $this->db->fetchArray($result))) {
429
            $ret[] = $myrow;
430
        }
431
432
        return $ret;
433
    }
434
435
    /**
436
     * retrieve objects with debug mode - so will show the query
437
     *
438
     * @param CriteriaElement $criteria  {@link CriteriaElement} conditions to be met
439
     * @param bool            $id_as_key use the ID as key for the array?
440
     * @param bool            $as_object return an array of objects?
441
     *
442
     * @param  bool           $sql
443
     * @return array
444
     */
445
    public function getObjectsD(CriteriaElement $criteria = null, $id_as_key = false, $as_object = true, $sql = false)
446
    {
447
        return $this->getObjects($criteria, $id_as_key, $as_object, $sql, true);
448
    }
449
450
    /**
451
     * @param $arrayObjects
452
     * @return array|bool
453
     */
454
    public function getObjectsAsArray($arrayObjects)
455
    {
456
        $ret = [];
457
        foreach ($arrayObjects as $key => $object) {
458
            $ret[$key] = $object->toArray();
459
        }
460
        if (count($ret > 0)) {
461
            return $ret;
462
        } else {
463
            return false;
464
        }
465
    }
466
467
    /**
468
     * Convert a database resultset to a returnable array
469
     *
470
     * @param object $result    database resultset
471
     * @param bool   $id_as_key - should NOT be used with joint keys
472
     * @param bool   $as_object
473
     *
474
     * @return array
475
     */
476
    public function convertResultSet($result, $id_as_key = false, $as_object = true)
477
    {
478
        $ret = [];
479
        while (false !== ($myrow = $this->db->fetchArray($result))) {
480
            $obj = $this->create(false);
481
            $obj->assignVars($myrow);
482
            if (!$id_as_key) {
483
                if ($as_object) {
484
                    $ret[] =& $obj;
485
                } else {
486
                    $ret[] = $obj->toArray();
487
                }
488
            } else {
489
                if ($as_object) {
490
                    $value =& $obj;
491
                } else {
492
                    $value = $obj->toArray();
493
                }
494
                if ('parentid' === $id_as_key) {
495
                    $ret[$obj->getVar('parentid', 'e')][$obj->getVar($this->keyName)] =& $value;
496
                } else {
497
                    $ret[$obj->getVar($this->keyName)] = $value;
498
                }
499
            }
500
            unset($obj);
501
        }
502
503
        return $ret;
504
    }
505
506
    /**
507
     * @param  null $criteria
508
     * @param  int  $limit
509
     * @param  int  $start
510
     * @return array
511
     */
512
    public function getListD($criteria = null, $limit = 0, $start = 0)
513
    {
514
        return $this->getList($criteria, $limit, $start, true);
515
    }
516
517
    /**
518
     * Retrieve a list of objects as arrays - DON'T USE WITH JOINT KEYS
519
     *
520
     * @param CriteriaElement $criteria {@link CriteriaElement} conditions to be met
521
     * @param int             $limit    Max number of objects to fetch
522
     * @param int             $start    Which record to start at
523
     *
524
     * @param  bool           $debug
525
     * @return array
526
     */
527
    public function getList(CriteriaElement $criteria = null, $limit = 0, $start = 0, $debug = false)
528
    {
529
        $ret = [];
530
        if (null === $criteria) {
531
            $criteria = new \CriteriaCompo();
532
        }
533
534
        if ('' === $criteria->getSort()) {
535
            $criteria->setSort($this->getIdentifierName());
536
        }
537
538
        $sql = 'SELECT ' . (is_array($this->keyName) ? implode(', ', $this->keyName) : $this->keyName);
539
        if (!empty($this->identifierName)) {
540
            $sql .= ', ' . $this->getIdentifierName();
541
        }
542
        $sql .= ' FROM ' . $this->table . ' AS ' . $this->_itemname;
543 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...
544
            $sql .= ' ' . $criteria->renderWhere();
545
            if ('' !== $criteria->getSort()) {
546
                $sql .= ' ORDER BY ' . $criteria->getSort() . ' ' . $criteria->getOrder();
547
            }
548
            $limit = $criteria->getLimit();
549
            $start = $criteria->getStart();
550
        }
551
552
        if ($debug) {
553
            xoops_debug($sql);
554
        }
555
556
        $result = $this->db->query($sql, $limit, $start);
557
        if (!$result) {
558
            return $ret;
559
        }
560
561
        $myts = \MyTextSanitizer::getInstance();
562
        while (false !== ($myrow = $this->db->fetchArray($result))) {
563
            //identifiers should be textboxes, so sanitize them like that
564
            $ret[$myrow[$this->keyName]] = empty($this->identifierName) ? 1 : $myts->displayTarea($myrow[$this->identifierName]);
565
        }
566
567
        return $ret;
568
    }
569
570
    /**
571
     * count objects matching a condition
572
     *
573
     * @param  CriteriaElement $criteria {@link CriteriaElement} to match
574
     * @return int             count of objects
575
     */
576
    public function getCount(CriteriaElement $criteria = null)
577
    {
578
        $field   = '';
579
        $groupby = false;
580
        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...
581
            if ('' !== $criteria->groupby) {
582
                $groupby = true;
583
                $field   = $criteria->groupby . ', '; //Not entirely secure unless you KNOW that no criteria's groupby clause is going to be mis-used
584
            }
585
        }
586
        /**
587
         * if we have a generalSQL, lets used this one.
588
         * This needs to be improved...
589
         */
590
        if ($this->generalSQL) {
591
            $sql = $this->generalSQL;
592
            $sql = str_replace('SELECT *', 'SELECT COUNT(*)', $sql);
593
        } else {
594
            $sql = 'SELECT ' . $field . 'COUNT(*) FROM ' . $this->table . ' AS ' . $this->_itemname;
595
        }
596
        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...
597
            $sql .= ' ' . $criteria->renderWhere();
598
            if ('' !== $criteria->groupby) {
599
                $sql .= $criteria->getGroupby();
600
            }
601
        }
602
603
        $result = $this->db->query($sql);
604
        if (!$result) {
605
            return 0;
606
        }
607
        if (false === $groupby) {
608
            list($count) = $this->db->fetchRow($result);
609
610
            return $count;
611
        } else {
612
            $ret = [];
613
            while (false !== (list($id, $count) = $this->db->fetchRow($result))) {
614
                $ret[$id] = $count;
615
            }
616
617
            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 XoopsModules\Smartobject...ObjectHandler::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...
618
        }
619
    }
620
621
    /**
622
     * delete an object from the database
623
     *
624
     * @param \XoopsObject $obj reference to the object to delete
625
     * @param  bool        $force
626
     * @return bool        FALSE if failed.
627
     */
628
    public function delete(\XoopsObject $obj, $force = false)
629
    {
630
        $eventResult = $this->executeEvent('beforeDelete', $obj);
631
        if (!$eventResult) {
632
            $obj->setErrors('An error occured during the BeforeDelete event');
633
634
            return false;
635
        }
636
637
        if (is_array($this->keyName)) {
638
            $clause = [];
639
            for ($i = 0, $iMax = count($this->keyName); $i < $iMax; ++$i) {
640
                $clause[] = $this->keyName[$i] . ' = ' . $obj->getVar($this->keyName[$i]);
641
            }
642
            $whereclause = implode(' AND ', $clause);
643
        } else {
644
            $whereclause = $this->keyName . ' = ' . $obj->getVar($this->keyName);
645
        }
646
        $sql = 'DELETE FROM ' . $this->table . ' WHERE ' . $whereclause;
647 View Code Duplication
        if (false !== $force) {
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...
648
            $result = $this->db->queryF($sql);
649
        } else {
650
            $result = $this->db->query($sql);
651
        }
652
        if (!$result) {
653
            return false;
654
        }
655
656
        $eventResult = $this->executeEvent('afterDelete', $obj);
657
        if (!$eventResult) {
658
            $obj->setErrors('An error occured during the AfterDelete event');
659
660
            return false;
661
        }
662
663
        return true;
664
    }
665
666
    /**
667
     * @param $event
668
     */
669
    public function disableEvent($event)
670
    {
671
        if (is_array($event)) {
672
            foreach ($event as $v) {
673
                $this->_disabledEvents[] = $v;
674
            }
675
        } else {
676
            $this->_disabledEvents[] = $event;
677
        }
678
    }
679
680
    /**
681
     * @return array
682
     */
683
    public function getPermissions()
684
    {
685
        return $this->permissionsArray;
686
    }
687
688
    /**
689
     * insert a new object in the database
690
     *
691
     * @param \XoopsObject $obj         reference to the object
692
     * @param  bool        $force       whether to force the query execution despite security settings
693
     * @param  bool        $checkObject check if the object is dirty and clean the attributes
694
     * @param  bool        $debug
695
     * @return bool        FALSE if failed, TRUE if already present and unchanged or successful
696
     */
697
    public function insert(\XoopsObject $obj, $force = false, $checkObject = true, $debug = false)
698
    {
699
        if (false !== $checkObject) {
700
            if (!is_object($obj)) {
701
                return false;
702
            }
703
            /**
704
             * @TODO: Change to if (!(class_exists($this->className) && $obj instanceof $this->className)) when going fully PHP5
705
             */
706
            if (!is_a($obj, $this->className)) {
707
                $obj->setError(get_class($obj) . ' Differs from ' . $this->className);
708
709
                return false;
710
            }
711
            if (!$obj->isDirty()) {
712
                $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
713
714
                return true;
715
            }
716
        }
717
718
        if ($obj->seoEnabled) {
719
            // Auto create meta tags if empty
720
            $smartobjectMetagen = new MetaGen($obj->title(), $obj->getVar('meta_keywords'), $obj->summary());
721
722
            if (!$obj->getVar('meta_keywords') || !$obj->getVar('meta_description')) {
723
                if (!$obj->meta_keywords()) {
724
                    $obj->setVar('meta_keywords', $smartobjectMetagen->_keywords);
725
                }
726
727
                if (!$obj->meta_description()) {
728
                    $obj->setVar('meta_description', $smartobjectMetagen->_meta_description);
729
                }
730
            }
731
732
            // Auto create short_url if empty
733
            if (!$obj->short_url()) {
734
                $obj->setVar('short_url', $smartobjectMetagen->generateSeoTitle($obj->title('n'), false));
735
            }
736
        }
737
738
        $eventResult = $this->executeEvent('beforeSave', $obj);
739
        if (!$eventResult) {
740
            $obj->setErrors('An error occured during the BeforeSave event');
741
742
            return false;
743
        }
744
745 View Code Duplication
        if ($obj->isNew()) {
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...
746
            $eventResult = $this->executeEvent('beforeInsert', $obj);
747
            if (!$eventResult) {
748
                $obj->setErrors('An error occured during the BeforeInsert event');
749
750
                return false;
751
            }
752
        } else {
753
            $eventResult = $this->executeEvent('beforeUpdate', $obj);
754
            if (!$eventResult) {
755
                $obj->setErrors('An error occured during the BeforeUpdate event');
756
757
                return false;
758
            }
759
        }
760
        if (!$obj->cleanVars()) {
761
            $obj->setErrors('Variables were not cleaned properly.');
762
763
            return false;
764
        }
765
        $fieldsToStoreInDB = [];
766
        foreach ($obj->cleanVars as $k => $v) {
767
            if (XOBJ_DTYPE_INT == $obj->vars[$k]['data_type']) {
768
                $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...
769
            } elseif (is_array($v)) {
770
                $cleanvars[$k] = $this->db->quoteString(implode(',', $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...
771
            } else {
772
                $cleanvars[$k] = $this->db->quoteString($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...
773
            }
774
            if ($obj->vars[$k]['persistent']) {
775
                $fieldsToStoreInDB[$k] = $cleanvars[$k];
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...
776
            }
777
        }
778
        if ($obj->isNew()) {
779
            if (!is_array($this->keyName)) {
780
                if ($cleanvars[$this->keyName] < 1) {
781
                    $cleanvars[$this->keyName] = $this->db->genId($this->table . '_' . $this->keyName . '_seq');
782
                }
783
            }
784
785
            $sql = 'INSERT INTO ' . $this->table . ' (' . implode(',', array_keys($fieldsToStoreInDB)) . ') VALUES (' . implode(',', array_values($fieldsToStoreInDB)) . ')';
786
        } else {
787
            $sql = 'UPDATE ' . $this->table . ' SET';
788
            foreach ($fieldsToStoreInDB as $key => $value) {
789
                if ((!is_array($this->keyName) && $key == $this->keyName)
790
                    || (is_array($this->keyName)
791
                        && in_array($key, $this->keyName))) {
792
                    continue;
793
                }
794
                if (isset($notfirst)) {
795
                    $sql .= ',';
796
                }
797
                $sql      .= ' ' . $key . ' = ' . $value;
798
                $notfirst = true;
799
            }
800
            if (is_array($this->keyName)) {
801
                $whereclause = '';
802
                for ($i = 0, $iMax = count($this->keyName); $i < $iMax; ++$i) {
803
                    if ($i > 0) {
804
                        $whereclause .= ' AND ';
805
                    }
806
                    $whereclause .= $this->keyName[$i] . ' = ' . $obj->getVar($this->keyName[$i]);
807
                }
808
            } else {
809
                $whereclause = $this->keyName . ' = ' . $obj->getVar($this->keyName);
810
            }
811
            $sql .= ' WHERE ' . $whereclause;
812
        }
813
814
        if ($debug) {
815
            xoops_debug($sql);
816
        }
817
818 View Code Duplication
        if (false !== $force) {
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...
819
            $result = $this->db->queryF($sql);
820
        } else {
821
            $result = $this->db->query($sql);
822
        }
823
824
        if (!$result) {
825
            $obj->setErrors($this->db->error());
826
827
            return false;
828
        }
829
830
        if ($obj->isNew() && !is_array($this->keyName)) {
831
            $obj->assignVar($this->keyName, $this->db->getInsertId());
832
        }
833
        $eventResult = $this->executeEvent('afterSave', $obj);
834
        if (!$eventResult) {
835
            $obj->setErrors('An error occured during the AfterSave event');
836
837
            return false;
838
        }
839
840 View Code Duplication
        if ($obj->isNew()) {
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...
841
            $obj->unsetNew();
842
            $eventResult = $this->executeEvent('afterInsert', $obj);
843
            if (!$eventResult) {
844
                $obj->setErrors('An error occured during the AfterInsert event');
845
846
                return false;
847
            }
848
        } else {
849
            $eventResult = $this->executeEvent('afterUpdate', $obj);
850
            if (!$eventResult) {
851
                $obj->setErrors('An error occured during the AfterUpdate event');
852
853
                return false;
854
            }
855
        }
856
857
        return true;
858
    }
859
860
    /**
861
     * @param       $obj
862
     * @param  bool $force
863
     * @param  bool $checkObject
864
     * @param  bool $debug
865
     * @return bool
866
     */
867
    public function insertD($obj, $force = false, $checkObject = true, $debug = false)
868
    {
869
        return $this->insert($obj, $force, $checkObject, true);
870
    }
871
872
    /**
873
     * Change a value for objects with a certain criteria
874
     *
875
     * @param string          $fieldname  Name of the field
876
     * @param string          $fieldvalue Value to write
877
     * @param CriteriaElement $criteria   {@link CriteriaElement}
878
     *
879
     * @param  bool           $force
880
     * @return bool
881
     */
882
    public function updateAll($fieldname, $fieldvalue, CriteriaElement $criteria = null, $force = false)
883
    {
884
        $set_clause = $fieldname . ' = ';
885
        if (is_numeric($fieldvalue)) {
886
            $set_clause .= $fieldvalue;
887
        } elseif (is_array($fieldvalue)) {
888
            $set_clause .= $this->db->quoteString(implode(',', $fieldvalue));
889
        } else {
890
            $set_clause .= $this->db->quoteString($fieldvalue);
891
        }
892
        $sql = 'UPDATE ' . $this->table . ' SET ' . $set_clause;
893
        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...
894
            $sql .= ' ' . $criteria->renderWhere();
895
        }
896 View Code Duplication
        if (false !== $force) {
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...
897
            $result = $this->db->queryF($sql);
898
        } else {
899
            $result = $this->db->query($sql);
900
        }
901
        if (!$result) {
902
            return false;
903
        }
904
905
        return true;
906
    }
907
908
    /**
909
     * delete all objects meeting the conditions
910
     *
911
     * @param  CriteriaElement $criteria {@link CriteriaElement} with conditions to meet
912
     * @return bool
913
     */
914
915
    public function deleteAll(CriteriaElement $criteria = null)
916
    {
917
        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...
918
            $sql = 'DELETE FROM ' . $this->table;
919
            $sql .= ' ' . $criteria->renderWhere();
920
            if (!$this->db->query($sql)) {
921
                return false;
922
            }
923
            $rows = $this->db->getAffectedRows();
924
925
            return $rows > 0 ? $rows : true;
926
        }
927
928
        return false;
929
    }
930
931
    /**
932
     * @return mixed
933
     */
934
    public function getModuleInfo()
935
    {
936
        return Smartobject\Utility::getModuleInfo($this->_moduleName);
0 ignored issues
show
Documentation introduced by
$this->_moduleName is of type string, but the function expects a boolean.

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...
937
    }
938
939
    /**
940
     * @return bool
941
     */
942
    public function getModuleConfig()
943
    {
944
        return Smartobject\Utility::getModuleConfig($this->_moduleName);
0 ignored issues
show
Documentation introduced by
$this->_moduleName is of type string, but the function expects a boolean.

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...
945
    }
946
947
    /**
948
     * @return string
949
     */
950
    public function getModuleItemString()
951
    {
952
        $ret = $this->_moduleName . '_' . $this->_itemname;
953
954
        return $ret;
955
    }
956
957
    /**
958
     * @param $object
959
     */
960
    public function updateCounter($object)
961
    {
962
        if (isset($object->vars['counter'])) {
963
            $new_counter = $object->getVar('counter') + 1;
964
            $sql         = 'UPDATE ' . $this->table . ' SET counter=' . $new_counter . ' WHERE ' . $this->keyName . '=' . $object->id();
965
            $this->query($sql, null, true);
966
        }
967
    }
968
969
    /**
970
     * Execute the function associated with an event
971
     * This method will check if the function is available
972
     *
973
     * @param  string $event name of the event
974
     * @param         $executeEventObj
975
     * @return mixed  result of the execution of the function or FALSE if the function was not executed
976
     * @internal param object $obj $object on which is performed the event
977
     */
978
    public function executeEvent($event, &$executeEventObj)
979
    {
980
        if (!in_array($event, $this->_disabledEvents)) {
981
            if (method_exists($this, $event)) {
982
                $ret = $this->$event($executeEventObj);
983
            } else {
984
                // check to see if there is a hook for this event
985
                if (isset($this->_eventHooks[$event])) {
986
                    $method = $this->_eventHooks[$event];
987
                    // check to see if the method specified by this hook exists
988
                    if (method_exists($this, $method)) {
989
                        $ret = $this->$method($executeEventObj);
0 ignored issues
show
Unused Code introduced by
$ret 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...
990
                    }
991
                }
992
                $ret = true;
993
            }
994
995
            return $ret;
996
        }
997
998
        return true;
999
    }
1000
1001
    /**
1002
     * @param  bool $withprefix
1003
     * @return string
1004
     */
1005
    public function getIdentifierName($withprefix = true)
1006
    {
1007
        if ($withprefix) {
1008
            return $this->_itemname . '.' . $this->identifierName;
1009
        } else {
1010
            return $this->identifierName;
1011
        }
1012
    }
1013
}
1014