Passed
Pull Request — master ( #1 )
by Robin
02:24
created

app_RecordSet   D

Complexity

Total Complexity 58

Size/Duplication

Total Lines 550
Duplicated Lines 0 %

Importance

Changes 6
Bugs 2 Features 0
Metric Value
eloc 171
c 6
b 2
f 0
dl 0
loc 550
rs 4.5599
wmc 58

25 Methods

Rating   Name   Duplication   Size   Complexity  
A App() 0 3 1
A setApp() 0 4 1
A __construct() 0 8 1
A request() 0 14 3
A setAccessManager() 0 4 1
B isTargetOfAny() 0 33 6
A isRemovable() 0 3 1
A isUpdatable() 0 3 1
A haveTagLabels() 0 29 4
A isTargetOf() 0 19 3
A join() 0 20 4
B isSourceOfAny() 0 33 6
A isReadable() 0 3 1
A selectLinkedTo() 0 7 3
A getCustomFields() 0 26 5
A extractSetPrefixAndName() 0 7 2
A isCreatable() 0 3 1
A isRestorable() 0 3 1
A hasAccess() 0 4 1
A isSourceOf() 0 19 3
A selectCustomFields() 0 10 1
A getAccessManager() 0 3 1
A addCustomFields() 0 17 3
A isDeletable() 0 3 1
A selectLinkedFrom() 0 7 3

How to fix   Complexity   

Complex Class

Complex classes like app_RecordSet 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.

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 app_RecordSet, and based on these observations, apply Extract Interface, too.

1
<?php
2
//-------------------------------------------------------------------------
3
// OVIDENTIA http://www.ovidentia.org
4
// Ovidentia is free software; you can redistribute it and/or modify
5
// it under the terms of the GNU General Public License as published by
6
// the Free Software Foundation; either version 2, or (at your option)
7
// any later version.
8
//
9
// This program is distributed in the hope that it will be useful, but
10
// WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12
// See the GNU General Public License for more details.
13
//
14
// You should have received a copy of the GNU General Public License
15
// along with this program; if not, write to the Free Software
16
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
17
// USA.
18
//-------------------------------------------------------------------------
19
/**
20
 * @license http://opensource.org/licenses/gpl-license.php GNU General Public License (GPL)
21
 * @copyright Copyright (c) 2018 by CANTICO ({@link http://www.cantico.fr})
22
 */
23
24
25
$App = app_App();
26
$App->includeBase();
27
28
29
/**
30
 *
31
 * @property ORM_PkField        $id
32
 *
33
 * @method app_Record   get(mixed $criteria)
34
 * @method app_Record   request(mixed $criteria)
35
 * @method app_Record[] select(\ORM_Criteria $criteria = null)
36
 * @method app_Record   newRecord()
37
 */
38
class app_RecordSet extends ORM_RecordSet implements app_Object_Interface
39
{
40
    /**
41
     * @var Func_App
42
     */
43
    protected $app = null;
44
45
    protected $accessRights = null;
46
47
    /**
48
     * @var app_AccessManager
49
     */
50
    private $accessManager = null;
51
52
    /**
53
     * @var array
54
     */
55
    private $customFields = null;
56
57
58
    /**
59
     * @param Func_App $app
60
     */
61
    public function __construct(Func_App $app)
62
    {
63
        parent::__construct();
64
        $this->setApp($app);
65
        $this->accessRights = array();
66
67
        $this->setAccessManager($app->AccessManager());
68
        $this->setPrimaryKey('id');
69
    }
70
71
    /**
72
     * {@inheritDoc}
73
     * @see app_Object_Interface::setApp()
74
     */
75
    public function setApp(Func_App $app)
76
    {
77
        $this->app = $app;
78
        return $this;
79
    }
80
81
82
    /**
83
     * {@inheritDoc}
84
     * @see app_Object_Interface::App()
85
     */
86
    public function App()
87
    {
88
        return $this->app;
89
    }
90
91
    /**
92
     * @param string $setName
93
     */
94
    protected function extractSetPrefixAndName($setName)
95
    {
96
        $pos = strrpos($setName, '\\');
97
        if ($pos === false) {
98
            return explode('_', $setName);
99
        }
100
        return array(substr($setName, 0, $pos), substr($setName, $pos + 1));
101
    }
102
103
    /**
104
     * Similar to the ORM_RecordSet::join() method but instanciates the
105
     * joined RecordSet using App methods.
106
     *
107
     * @see ORM_RecordSet::join()
108
     */
109
    public function join($fkFieldName)
110
    {
111
        $fkField = $this->getField($fkFieldName);
112
        if (!($fkField instanceof ORM_FkField)) {
113
            return $this;
114
        }
115
        $setName = $fkField->getForeignSetName();
116
117
        if (!$setName || 'Set' === $setName) {
118
            throw new Exception('The set name is missing on foreign key field '.$fkFieldName);
119
        }
120
121
        list($prefix, $appSetName) = $this->extractSetPrefixAndName($setName);
122
        $set = $this->App()->$appSetName();
123
        $set->setName($fkField->getName());
124
        $set->setDescription($fkField->getDescription());
125
126
        $this->aField[$fkFieldName] = $set;
127
        $set->setParentSet($this);
128
        return $this;
129
    }
130
131
132
//     /**
133
//      *
134
//      * @param string $accessName
135
//      * @param string $type
136
//      */
137
//     public function addAccessRight($accessName, $type = 'acl')
138
//     {
139
//         $this->accessRights[$accessName] = $type;
140
//     }
141
142
143
//     /**
144
//      * @return array
145
//      */
146
//     public function getAccessRights()
147
//     {
148
//         return $this->accessRights;
149
//     }
150
151
152
153
    /**
154
     * @return app_CustomField[]
155
     */
156
    public function getCustomFields()
157
    {
158
        $App = $this->App();
159
160
        if (null === $this->customFields) {
161
            $this->customFields = array();
162
163
            if (isset($App->CustomField)) {
0 ignored issues
show
Bug Best Practice introduced by
The property CustomField does not exist on Func_App. Since you implemented __get, consider adding a @property annotation.
Loading history...
164
                $customFieldSet = $App->CustomFieldSet();
165
                $object = mb_substr(get_class($this), mb_strlen($App->classPrefix), -mb_strlen('Set'));
166
                try {
167
                    $customFields = $customFieldSet->select($customFieldSet->object->is($object));
168
169
                    foreach ($customFields as $customfield) {
170
                        $this->customFields[] = $customfield;
171
172
                        /*@var $customfield app_CustomField */
173
174
                    }
175
                } catch (ORM_BackEndSelectException $e) {
176
                    // table does not exist, this error is thrown by the install program while creating the sets
177
                }
178
            }
179
        }
180
181
        return $this->customFields;
182
    }
183
184
185
186
187
    /**
188
     * @return app_CustomField[]
189
     */
190
    public function selectCustomFields()
191
    {
192
        $App = $this->App();
193
194
        $customFieldSet= $App->CustomFieldSet();
195
        $object = mb_substr(get_class($this), mb_strlen($App->classPrefix), -mb_strlen('Set'));
196
197
        $customFields = $customFieldSet->select($customFieldSet->object->is($object));
198
199
        return $customFields;
200
    }
201
202
203
    /**
204
     * @return self
205
     */
206
    public function addCustomFields()
207
    {
208
        $customFields = $this->selectCustomFields();
209
        foreach ($customFields as $customField) {
210
            /*@var $customField app_CustomField */
211
            $description = $customField->name;
212
            $ormField = $customField->getORMField()
213
                ->setDescription($description);
214
            if ($ormField instanceof ORM_FkField) {
215
                $this->hasOne($customField->fieldname, $ormField->getForeignSetName())
216
                    ->setDescription($description);
217
            } else {
218
                $this->addFields($ormField);
219
            }
220
        }
221
222
        return $this;
223
    }
224
225
226
    /**
227
     * Similar to ORM_RecordSet::get() method  but throws a app_NotFoundException if the
228
     * record is not found.
229
     *
230
     * @since 1.0.18
231
     *
232
     * @throws ORM_Exception
233
     * @throws app_NotFoundException
234
     *
235
     * @param ORM_Criteria|string $mixedParam    Criteria for selecting records
236
     *                                           or the value for selecting record
237
     * @param string              $sPropertyName The name of the property on which
238
     *                                           the value applies. If not
239
     *                                           specified or null, the set's
240
     *                                           primary key will be used.
241
     *
242
     * @return app_Record
243
     */
244
    public function request($mixedParam = null, $sPropertyName = null)
245
    {
246
        $record = $this->get($mixedParam, $sPropertyName);
0 ignored issues
show
Unused Code introduced by
The call to app_RecordSet::get() has too many arguments starting with $sPropertyName. ( Ignorable by Annotation )

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

246
        /** @scrutinizer ignore-call */ 
247
        $record = $this->get($mixedParam, $sPropertyName);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
247
        if (!isset($record)) {
248
            // This will remove the default criteria for TraceableRecords and
249
            // fetch even 'deleted' ones.
250
            $this->setDefaultCriteria(null);
251
            $record = $this->get($mixedParam, $sPropertyName);
252
            if (isset($record)) {
253
                throw new app_DeletedRecordException($record, $mixedParam);
0 ignored issues
show
Unused Code introduced by
The call to app_DeletedRecordException::__construct() has too many arguments starting with $mixedParam. ( Ignorable by Annotation )

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

253
                throw /** @scrutinizer ignore-call */ new app_DeletedRecordException($record, $mixedParam);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
254
            }
255
            throw new app_NotFoundException($this, $mixedParam);
0 ignored issues
show
Bug introduced by
It seems like $mixedParam can also be of type ORM_Criteria; however, parameter $id of app_NotFoundException::__construct() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

255
            throw new app_NotFoundException($this, /** @scrutinizer ignore-type */ $mixedParam);
Loading history...
256
        }
257
        return $record;
258
    }
259
260
261
262
263
264
265
266
267
268
    /**
269
     * Returns an iterator of traceableRecord linked to the specified source,
270
     * optionally filtered on the specified link type.
271
     *
272
     * @param	app_Record | array		$source			source can be an array of record
273
     * @param	string					$linkType
274
     *
275
     * @return ORM_Iterator
276
     */
277
    public function selectLinkedTo($source, $linkType = null)
278
    {
279
        $linkSet = $this->App()->LinkSet();
280
        if (is_array($source) || ($source instanceof Iterator)) {
281
            return $linkSet->selectForSources($source, $this->getRecordClassName(), $linkType);
282
        } else {
283
            return $linkSet->selectForSource($source, $this->getRecordClassName(), $linkType);
284
        }
285
    }
286
287
    /**
288
     * Returns an iterator of traceableRecord linked to the specified target,
289
     * optionally filtered on the specified link type.
290
     *
291
     * @param	app_Record | array		$target			target can be an array of record
292
     * @param	string					$linkType
293
     *
294
     * @return ORM_Iterator
295
     */
296
    public function selectLinkedFrom($target, $linkType = null)
297
    {
298
        $linkSet = $this->App()->LinkSet();
299
        if (is_array($target) || ($target instanceof Iterator)) {
300
            return $linkSet->selectForTargets($target, $this->getRecordClassName(), $linkType);
301
        } else {
302
            return $linkSet->selectForTarget($target, $this->getRecordClassName(), $linkType);
303
        }
304
    }
305
306
307
    /**
308
     * Returns a criteria usable to select records of this set which are source of app_Links to the specified app_Record.
309
     *
310
     * @param app_Record $target
311
     * @param string     $linkType
312
     * @return ORM_Criteria
313
     */
314
    public function isSourceOf(app_Record $target, $linkType = null)
315
    {
316
        $linkSet = $this->App()->LinkSet();
317
318
        $linkSet->hasOne('sourceId', get_class($this));
319
320
        $criteria =	$linkSet->targetClass->is(get_class($target))
321
            ->_AND_($linkSet->targetId->is($target->id))
322
            ->_AND_($linkSet->sourceClass->is($this->getRecordClassName()));
323
        if (isset($linkType)) {
324
            if (is_array($linkType)) {
0 ignored issues
show
introduced by
The condition is_array($linkType) is always false.
Loading history...
325
                $criteria = $criteria->_AND_($linkSet->type->in($linkType));
326
            } else {
327
                $criteria = $criteria->_AND_($linkSet->type->is($linkType));
328
            }
329
        }
330
        $criteria = $this->id->in($criteria);
331
332
        return $criteria;
333
    }
334
335
336
337
    /**
338
     * Returns a criteria usable to select records of this set which are target of app_Links to the specified app_Record.
339
     *
340
     * @param app_Record            $source
341
     * @param null|string|string[]  $linkType
342
     * @return ORM_Criteria
343
     */
344
    public function isTargetOf(app_Record $source, $linkType = null)
345
    {
346
        $linkSet = $this->App()->LinkSet();
347
348
        $linkSet->hasOne('targetId', get_class($this));
349
350
        $criteria =	$linkSet->sourceClass->is(get_class($source))
351
            ->_AND_($linkSet->sourceId->is($source->id))
352
            ->_AND_($linkSet->targetClass->is($this->getRecordClassName()));
353
        if (isset($linkType)) {
354
            if (is_array($linkType)) {
355
                $criteria = $criteria->_AND_($linkSet->type->in($linkType));
356
            } else {
357
                $criteria = $criteria->_AND_($linkSet->type->is($linkType));
358
            }
359
        }
360
        $criteria = $this->id->in($criteria);
361
362
        return $criteria;
363
    }
364
365
366
    /**
367
     * Returns a criteria usable to select records of this set which are target of app_Links from the specified app_Records.
368
     *
369
     * @since 1.0.23
370
     *
371
     * @param app_Record[]          $sources
372
     * @param null|string|string[]  $linkType
373
     * @return ORM_Criteria
374
     */
375
    public function isTargetOfAny($sources, $linkType = null)
376
    {
377
        $linkSet = $this->App()->LinkSet();
378
        $linkSet->hasOne('targetId', get_class($this));
379
380
        $sourceIdsByClasses = array();
381
        foreach ($sources as $source) {
382
            $sourceClass = get_class($source);
383
            if (!isset($sourceIdsByClasses[$sourceClass])) {
384
                $sourceIdsByClasses[$sourceClass] = array();
385
            }
386
            $sourceIdsByClasses[$sourceClass][] = $source->id;
387
        }
388
389
        $sourcesCriteria = array();
390
        foreach ($sourceIdsByClasses as $sourceClass => $sourceIds) {
391
            $sourcesCriteria[] = $linkSet->sourceClass->is($sourceClass)->_AND_($linkSet->sourceId->in($sourceIds));
392
        }
393
394
        $criteria =	$linkSet->all(
395
            $linkSet->targetClass->is($this->getRecordClassName()),
396
            $linkSet->any($sourcesCriteria)
397
        );
398
        if (isset($linkType)) {
399
            if (is_array($linkType)) {
400
                $criteria = $criteria->_AND_($linkSet->type->in($linkType));
401
            } else {
402
                $criteria = $criteria->_AND_($linkSet->type->is($linkType));
403
            }
404
        }
405
        $criteria = $this->id->in($criteria);
406
407
        return $criteria;
408
    }
409
410
411
    /**
412
     * Returns a criteria usable to select records of this set which are source of app_Links to the specified app_Records.
413
     *
414
     * @since 1.0.23
415
     *
416
     * @param app_Record[]     $targets
417
     * @param string|null      $linkType
418
     * @return ORM_Criteria
419
     */
420
    public function isSourceOfAny($targets, $linkType = null)
421
    {
422
        $linkSet = $this->App()->LinkSet();
423
        $linkSet->hasOne('sourceId', get_class($this));
424
425
        $targetIdsByClasses = array();
426
        foreach ($targets as $target) {
427
            $targetClass = get_class($target);
428
            if (!isset($targetIdsByClasses[$targetClass])) {
429
                $targetIdsByClasses[$targetClass] = array();
430
            }
431
            $targetIdsByClasses[$targetClass][] = $target->id;
432
        }
433
434
        $targetsCriteria = array();
435
        foreach ($targetIdsByClasses as $targetClass => $targetIds) {
436
            $targetsCriteria[] = $linkSet->targetClass->is($targetClass)->_AND_($linkSet->targetId->in($targetIds));
437
        }
438
439
        $criteria =	$linkSet->all(
440
            $linkSet->sourceClass->is($this->getRecordClassName()),
441
            $linkSet->any($targetsCriteria)
442
        );
443
        if (isset($linkType)) {
444
            if (is_array($linkType)) {
0 ignored issues
show
introduced by
The condition is_array($linkType) is always false.
Loading history...
445
                $criteria = $criteria->_AND_($linkSet->type->in($linkType));
446
            } else {
447
                $criteria = $criteria->_AND_($linkSet->type->is($linkType));
448
            }
449
        }
450
        $criteria = $this->id->in($criteria);
451
452
        return $criteria;
453
    }
454
455
456
    /**
457
     * @param app_AccessManager $accessManager
458
     * @return self
459
     */
460
    public function setAccessManager(app_AccessManager $accessManager)
461
    {
462
        $this->accessManager = $accessManager;
463
        return $this;
464
    }
465
466
467
    /**
468
     * @return app_AccessManager
469
     */
470
    public function getAccessManager()
471
    {
472
        return $this->accessManager;
473
    }
474
475
476
    /**
477
     * Defines if records can be created by the current user.
478
     *
479
     * @return boolean
480
     */
481
    public function isCreatable()
482
    {
483
        return false;
484
    }
485
486
487
488
    /**
489
     *
490
     * @return ORM_Criteria
491
     */
492
    public function isReadable()
493
    {
494
        return $this->hasAccess('read');
495
    }
496
497
    /**
498
     *
499
     * @return ORM_Criteria
500
     */
501
    public function isUpdatable()
502
    {
503
        return $this->hasAccess('update');
504
    }
505
506
    /**
507
     *
508
     * @return ORM_Criteria
509
     */
510
    public function isDeletable()
511
    {
512
        return $this->hasAccess('delete');
513
    }
514
515
516
517
    /**
518
     * Returns a criterion matching records that can be put to trash by the current user.
519
     *
520
     * @return ORM_Criterion
521
     */
522
    public function isRemovable()
523
    {
524
        return $this->isUpdatable();
525
    }
526
527
528
    /**
529
     * Returns a criterion matching records that can be restored from the trash by the current user.
530
     *
531
     * @return ORM_Criterion
532
     */
533
    public function isRestorable()
534
    {
535
        return $this->isUpdatable();
536
    }
537
538
    /**
539
     * Returns a criterion matching records deletable by the current user.
540
     *
541
     * @param string    $accessType
542
     * @param int|null  $user
543
     *
544
     * @return ORM_Criterion
545
     */
546
    public function hasAccess($accessType, $user = null)
547
    {
548
        $accessManager = $this->getAccessManager();
549
        return $accessManager->getAccessCriterion($this, $accessType, $user);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $accessManager->g...is, $accessType, $user) returns the type ORM_FalseCriterion which is incompatible with the documented return type ORM_Criterion.
Loading history...
550
    }
551
    
552
    /**
553
     * Returns a criteria usable to select records of this set associated to the specified tags.
554
     *
555
     * @array	$tags		An array of tag ids
556
     * @string	$type		The link type [optional]
557
     * @return ORM_Criteria
558
     */
559
    public function haveTagLabels($tagLabels, $linkType = null)
560
    {
561
        $App = $this->App();
562
        $linkSet = $App->LinkSet();
563
564
        /* @var $tagSet app_RecordSet */
565
        $tagSet = $App->TagSet();
0 ignored issues
show
Bug introduced by
The method TagSet() does not exist on Func_App. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

565
        /** @scrutinizer ignore-call */ 
566
        $tagSet = $App->TagSet();
Loading history...
566
        $tagClassName = $tagSet->getRecordClassName();
567
        
568
        $linkSet->joinTarget($tagClassName);
569
        $criteria = $linkSet->sourceClass->is($this->getRecordClassName())
570
            ->_AND_($linkSet->targetClass->is($tagClassName));
571
        
572
        if (is_array($tagLabels)) {
573
            $criteria = $criteria->_AND_($linkSet->targetId->label->in($tagLabels));
0 ignored issues
show
Bug introduced by
The property label does not seem to exist on ORM_StringField.
Loading history...
574
        } else {
575
            $criteria = $criteria->_AND_($linkSet->targetId->label->is($tagLabels));
576
        }
577
        if (isset($linkType)) {
578
            $criteria = $criteria->_AND_($linkSet->type->is($linkType));
579
        }
580
        $links = $linkSet->select($criteria);
0 ignored issues
show
Unused Code introduced by
The call to app_LinkSet::select() has too many arguments starting with $criteria. ( Ignorable by Annotation )

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

580
        /** @scrutinizer ignore-call */ 
581
        $links = $linkSet->select($criteria);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
581
        
582
        $ids = array();
583
        foreach ($links as $link) {
584
            $ids[$link->sourceId] = $link->sourceId;
585
        }
586
        
587
        return $this->id->in($ids);
588
    }
589
}
590
591
592
593
/**
594
 * @property int        $id
595
 *
596
 * @method app_RecordSet getParentSet()
597
 */
598
class app_Record extends ORM_Record implements app_Object_Interface
599
{
600
    /**
601
     * @var Func_App
602
     */
603
    protected $app = null;
604
605
606
607
608
    /**
609
     * Returns the value of the specified field or an iterator if $sFieldName represents a 'Many relation'.
610
     *
611
     * @param string $sFieldName The name of the field or relation for which the value must be returned.
612
     * @param array  $args       Optional arguments.
613
     *
614
     * @return mixed The value of the field or null if the field is not a part of the record.
615
     */
616
    public function __call($sFieldName, $args)
617
    {
618
        $value = $this->oParentSet->getBackend()->getRecordValue($this, $sFieldName);
619
        $field = $this->oParentSet->$sFieldName;
620
        if (!is_null($value) && $field instanceof ORM_FkField) {
621
622
            $sClassName = $field->getForeignSetName();
623
624
            $App = $this->App();
625
            $prefixLength = mb_strlen($App->classPrefix);
626
            $methodName = mb_substr($sClassName, $prefixLength);
627
            $set = $App->$methodName();
628
629
            $set->setName($field->getName());
630
            $set->setDescription($field->getDescription());
631
632
            $record = $set->get($value);
633
            return $record;
634
        }
635
        return $value;
636
    }
637
638
    /**
639
     * {@inheritDoc}
640
     * @see app_Object_Interface::setApp()
641
     */
642
    public function setApp(Func_App $app)
643
    {
644
        $this->app = $app;
645
        return $this;
646
    }
647
648
    /**
649
     * Get APP object to use with this record
650
     *
651
     * @return Func_App
652
     */
653
    public function App()
654
    {
655
        if (!isset($this->app)) {
656
            // If the app object was not specified (through the setApp() method),
657
            // we set it as parent set's App.
658
            $this->setApp($this->getParentSet()->App());
659
        }
660
        return $this->app;
661
    }
662
663
664
    /**
665
     * Returns the base class name of a record.
666
     * For example xxx_Contact will return 'Contact'.
667
     *
668
     * @since 1.0.40
669
     * @return string
670
     */
671
    public function getClassName()
672
    {
673
        $App = $this->App();
674
        $rClass = new ReflectionClass(get_class($this));
675
        $component = $App->getComponentByName($rClass->getShortName());
676
        if(isset($component)){
677
            return $component->getRecordClassName();
678
//             list(, $classname) = explode('_', $component->getRecordClassName());
679
//             return $classname;
680
        }
681
        list(, $classname) = explode('_', get_class($this));
682
        return $classname;
683
    }
684
685
686
    /**
687
     * Returns the string reference corresponding to the record.
688
     *
689
     * @return string 	A reference string (e.g. Contact:12)
690
     */
691
    public function getRef()
692
    {
693
        if (!isset($this->id)) {
694
            throw new app_Exception('Trying to get the reference string of a record without an id.');
695
        }
696
        $classname = $this->getClassName();
697
        return $classname . ':' . $this->id;
698
    }
699
700
701
    /**
702
     * @return app_Controller
703
     */
704
    public function getController()
705
    {
706
        $App = $this->App();
707
708
        $ctrlName = $this->getClassName();
709
        return $App->Controller()->$ctrlName();
710
    }
711
712
713
    /**
714
     * Deletes the record with respect to referential integrity.
715
     *
716
     * Uses referential integrity as defined by hasManyRelation to delete/update
717
     * referenced elements.
718
     *
719
     * @see app_RecordSet::hasMany()
720
     *
721
     * @return self
722
     */
723
    public function delete($deletedStatus = null)
724
    {
725
        $App = $this->App();
726
727
        if (!isset($deletedStatus)) {
728
            $deletedStatus = app_TraceableRecord::DELETED_STATUS_DELETED;
729
        }
730
731
        $set = $this->getParentSet();
732
        $recordIdName = $set->getPrimaryKey();
733
        $recordId = $this->$recordIdName;
734
735
        // Uses referential integrity as defined by hasManyRelation to delete/update
736
        // referenced elements.
737
        $manyRelations = $set->getHasManyRelations();
738
739
740
        foreach ($manyRelations as $manyRelation) {
741
            /* @var $manyRelation ORM_ManyRelation */
742
743
            $foreignSetClassName = $manyRelation->getForeignSetClassName();
744
            $foreignSetFieldName = $manyRelation->getForeignFieldName();
745
            $method = mb_substr($foreignSetClassName, mb_strlen($App->classPrefix));
746
            $foreignSet = $App->$method();
747
748
            switch ($manyRelation->getOnDeleteMethod()) {
749
750
                case ORM_ManyRelation::ON_DELETE_SET_NULL:
751
752
                    $foreignRecords = $foreignSet->select($foreignSet->$foreignSetFieldName->is($recordId));
753
754
                    foreach ($foreignRecords as $foreignRecord) {
755
                        $foreignRecord->$foreignSetFieldName = 0;
756
                        $foreignRecord->save();
757
                    }
758
759
                    break;
760
761
                case ORM_ManyRelation::ON_DELETE_CASCADE:
762
763
                    $foreignRecords = $foreignSet->select($foreignSet->$foreignSetFieldName->is($recordId));
764
765
                    foreach ($foreignRecords as $foreignRecord) {
766
                        $foreignRecord->delete();
767
                    }
768
769
                    break;
770
771
                case ORM_ManyRelation::ON_DELETE_NO_ACTION:
772
                default:
773
                    break;
774
775
            }
776
        }
777
778
779
        // We remove all links to and from this record.
780
        $linkSet = $App->LinkSet();
781
782
        $linkSet->delete(
783
            $linkSet->sourceClass->is(get_class($this))->_AND_($linkSet->sourceId->is($recordId))
784
            ->_OR_(
785
                $linkSet->targetClass->is(get_class($this))->_AND_($linkSet->targetId->is($recordId))
786
            )
787
        );
788
789
790
        $set->delete($set->$recordIdName->is($recordId), $deletedStatus);
0 ignored issues
show
Unused Code introduced by
The call to ORM_RecordSet::delete() has too many arguments starting with $deletedStatus. ( Ignorable by Annotation )

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

790
        $set->/** @scrutinizer ignore-call */ 
791
              delete($set->$recordIdName->is($recordId), $deletedStatus);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
791
792
        return $this;
793
    }
794
795
796
797
    /**
798
     * Reassociates all data asociated to the record to another
799
     * specified one.
800
     *
801
     * @param	int		$id
802
     *
803
     * @return self
804
     */
805
    public function replaceWith($id)
806
    {
807
        $App = $this->App();
808
809
        $set = $this->getParentSet();
810
        $recordIdName = $set->getPrimaryKey();
811
        $recordId = $this->$recordIdName;
812
813
        // Use referential integrity as defined by hasManyRelation to delete/update
814
        // referenced elements.
815
        $manyRelations = $set->getHasManyRelations();
816
817
818
        foreach ($manyRelations as $manyRelation) {
819
            /* @var $manyRelation ORM_ManyRelation */
820
821
            $foreignSetClassName = $manyRelation->getForeignSetClassName();
822
            $foreignSetFieldName = $manyRelation->getForeignFieldName();
823
            $method = mb_substr($foreignSetClassName, mb_strlen($App->classPrefix));
824
            $foreignSet = $App->$method();
825
            // $foreignSet = new $foreignSetClassName($App);
826
            $foreignRecords = $foreignSet->select($foreignSet->$foreignSetFieldName->is($recordId));
827
828
            foreach ($foreignRecords as $foreignRecord) {
829
                $foreignRecord->$foreignSetFieldName = $id;
830
                $foreignRecord->save();
831
            }
832
        }
833
834
835
        // We replace all links to and from this record.
836
        $linkSet = $App->LinkSet();
837
838
        $links = $linkSet->select(
839
            $linkSet->sourceClass->is(get_class($this))->_AND_($linkSet->sourceId->is($recordId))
0 ignored issues
show
Unused Code introduced by
The call to app_LinkSet::select() has too many arguments starting with $linkSet->sourceClass->i...ourceId->is($recordId)). ( Ignorable by Annotation )

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

839
        /** @scrutinizer ignore-call */ 
840
        $links = $linkSet->select(

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
840
        );
841
842
        foreach ($links as $link) {
843
            $link->sourceId = $id;
844
            $link->save();
845
        }
846
847
        $links = $linkSet->select(
848
            $linkSet->targetClass->is(get_class($this))->_AND_($linkSet->targetId->is($recordId))
849
        );
850
851
        foreach ($links as $link) {
852
            $link->targetId = $id;
853
            $link->save();
854
        }
855
856
        return $this;
857
    }
858
859
860
    /**
861
     *
862
     *
863
     * @return array
864
     */
865
    public function getRelatedRecords()
866
    {
867
        $App = $this->App();
868
869
        $set = $this->getParentSet();
870
        $recordIdName = $set->getPrimaryKey();
871
        $recordId = $this->$recordIdName;
872
873
        // Use referential integrity as defined by hasManyRelation to delete/update
874
        // referenced elements.
875
        $manyRelations = $set->getHasManyRelations();
876
877
        $relatedRecords = array();
878
879
        foreach ($manyRelations as $manyRelation) {
880
            /* @var $manyRelation ORM_ManyRelation */
881
882
            $foreignSetClassName = $manyRelation->getForeignSetClassName();
883
            $foreignSetFieldName = $manyRelation->getForeignFieldName();
884
885
            $method = mb_substr($foreignSetClassName, mb_strlen($App->classPrefix));
886
            $foreignSet = $App->$method();
887
            // $foreignSet = new $foreignSetClassName($App);
888
            $foreignRecords = $foreignSet->select($foreignSet->$foreignSetFieldName->is($recordId));
889
890
891
            if ($foreignRecords->count() > 0) {
892
                $relatedRecords[$foreignSetClassName] = $foreignRecords;
893
            }
894
        }
895
896
        return $relatedRecords;
897
    }
898
899
900
901
902
903
904
    /**
905
     * Upload path for record attachments
906
     *
907
     * @return bab_Path
908
     */
909
    public function uploadPath()
910
    {
911
        $path = $this->App()->uploadPath();
0 ignored issues
show
Bug introduced by
The method uploadPath() does not exist on Func_App. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

911
        $path = $this->App()->/** @scrutinizer ignore-call */ uploadPath();
Loading history...
912
913
        if (null === $path)
914
        {
915
            throw new Exception('Missing upload path information');
916
            return null;
0 ignored issues
show
Unused Code introduced by
return null is not reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
917
        }
918
919
        $path->push(get_class($this));
920
        $path->push($this->id);
921
922
        return $path;
923
    }
924
925
926
927
928
929
930
    /**
931
     * @return bool
932
     */
933
    public function isLinkedTo(app_Record $source, $linkType = null)
934
    {
935
        $linkSet = $this->App()->LinkSet();
936
937
        $criteria =	$linkSet->sourceClass->is(get_class($source))
938
        ->_AND_($linkSet->sourceId->is($source->id))
939
        ->_AND_($linkSet->targetClass->is(get_class($this)))
940
        ->_AND_($linkSet->targetId->is($this->id));
941
        if (isset($linkType)) {
942
            if (is_array($linkType)) {
943
                $criteria = $criteria->_AND_($linkSet->type->in($linkType));
944
            } else {
945
                $criteria = $criteria->_AND_($linkSet->type->is($linkType));
946
            }
947
        }
948
        $links = $linkSet->select($criteria);
0 ignored issues
show
Unused Code introduced by
The call to app_LinkSet::select() has too many arguments starting with $criteria. ( Ignorable by Annotation )

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

948
        /** @scrutinizer ignore-call */ 
949
        $links = $linkSet->select($criteria);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
949
950
        $isLinked = ($links->count() > 0);
951
952
        $linkSet->__destruct();
953
        unset($linkSet);
954
955
        return $isLinked;
956
    }
957
958
959
960
    /**
961
     * @return bool
962
     */
963
    public function isSourceOf(app_Record $target, $linkType = null)
964
    {
965
        $linkSet = $this->App()->LinkSet();
966
967
        $criteria =	$linkSet->all(
968
            $linkSet->targetClass->is(get_class($target)),
969
            $linkSet->targetId->is($target->id),
970
            $linkSet->sourceClass->is(get_class($this)),
971
            $linkSet->sourceId->is($this->id)
972
        );
973
974
        if (isset($linkType)) {
975
            if (is_array($linkType)) {
976
                $criteria = $criteria->_AND_($linkSet->type->in($linkType));
977
            } else {
978
                $criteria = $criteria->_AND_($linkSet->type->is($linkType));
979
            }
980
        }
981
        $links = $linkSet->select($criteria);
0 ignored issues
show
Unused Code introduced by
The call to app_LinkSet::select() has too many arguments starting with $criteria. ( Ignorable by Annotation )

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

981
        /** @scrutinizer ignore-call */ 
982
        $links = $linkSet->select($criteria);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
982
983
        $isLinked = ($links->count() > 0);
984
985
        $linkSet->__destruct();
986
        unset($linkSet);
987
988
        return $isLinked;
989
    }
990
991
992
993
    /**
994
     * Link record to $source
995
     *
996
     * @param app_Record $source
997
     * @param string $linkType
998
     * @param string $data
999
     * @return self
1000
     */
1001
    public function linkTo(app_Record $source, $linkType = '', $data = '')
1002
    {
1003
        $linkSet = $this->App()->LinkSet();
1004
1005
        $link = $linkSet->create($source, $this, $linkType, $data);
1006
1007
        $link->__destruct();
1008
        unset($link);
1009
1010
        $linkSet->__destruct();
1011
        unset($linkSet);
1012
1013
        return $this;
1014
    }
1015
1016
    /**
1017
     * Unlink record from $source
1018
     *
1019
     * @return self
1020
     */
1021
    public function unlinkFrom(app_Record $source, $linkType = null)
1022
    {
1023
        $linkSet = $this->App()->LinkSet();
1024
1025
        $linkSet->deleteLink($source, $this, $linkType);
1026
1027
        return $this;
1028
    }
1029
1030
1031
    /**
1032
     * import a value into a traceable record property if the value is not equal
1033
     *
1034
     * @param	string	$name		property name
1035
     * @param	mixed	$value		value to set
1036
     *
1037
     * @return int		1 : the value has been modified | 0 : no change
1038
     */
1039
    protected function importProperty($name, $value)
1040
    {
1041
        if (((string) $this->$name) !== ((string) $value)) {
1042
            $this->$name = $value;
1043
            return 1;
1044
        }
1045
1046
        return 0;
1047
    }
1048
1049
1050
1051
    /**
1052
     * import a value into a tracable record property if the value is not equal, try with multiple date format
1053
     * this method work for date field 0000-00-00
1054
     *
1055
     * @param	string	$name		property name
1056
     * @param	mixed	$value		value to set
1057
     *
1058
     * @return int		1 : the value has been modified | 0 : no change
1059
     */
1060
    protected function importDate($name, $value)
1061
    {
1062
        if (preg_match('/[0-9]{4}-[0-9]{2}-[0-9]{2}/',$value)) {
1063
            return $this->importProperty($name, $value);
1064
        }
1065
1066
        // try in DD/MM/YYYY format
1067
1068
        if (preg_match('/(?P<day>[0-9]+)\/(?P<month>[0-9]+)\/(?P<year>[0-9]{2,4})/',$value, $matches)) {
1069
1070
            $value = sprintf('%04d-%02d-%02d', (int) $matches['year'], (int) $matches['month'], (int) $matches['day']);
1071
1072
            return $this->importProperty($name, $value);
1073
        }
1074
1075
    }
1076
1077
1078
1079
1080
    /**
1081
     *
1082
     * @return string[]
1083
     */
1084
    public function getViews()
1085
    {
1086
        $App = $this->App();
1087
1088
        $customSectionSet = $App->CustomSectionSet();
1089
        $customSections = $customSectionSet->select($customSectionSet->object->is($this->getClassName()));
1090
        $customSections->groupBy($customSectionSet->view);
1091
1092
        $views = array();
1093
        foreach ($customSections as $customSection) {
1094
            $views[] = $customSection->view;
1095
        }
1096
1097
        if (empty($views)) {
1098
            $views[] = '';
1099
        }
1100
1101
        return $views;
1102
    }
1103
1104
1105
1106
    /**
1107
     * Checks if the record is readable by the current user.
1108
     * @since 1.0.21
1109
     * @return bool
1110
     */
1111
    public function isReadable()
1112
    {
1113
        $set = $this->getParentSet();
1114
        return $set->select($set->isReadable()->_AND_($set->id->is($this->id)))->count() == 1;
1115
    }
1116
1117
1118
    /**
1119
     * Checks if the record is updatable by the current user.
1120
     * @since 1.0.21
1121
     * @return bool
1122
     */
1123
    public function isUpdatable()
1124
    {
1125
        $set = $this->getParentSet();
1126
        return $set->select($set->isUpdatable()->_AND_($set->id->is($this->id)))->count() == 1;
1127
    }
1128
1129
    /**
1130
     * Checks if the record is deletable by the current user.
1131
     * @since 1.0.21
1132
     * @return bool
1133
     */
1134
    public function isDeletable()
1135
    {
1136
        $set = $this->getParentSet();
1137
        return $set->select($set->isDeletable()->_AND_($set->id->is($this->id)))->count() == 1;
1138
    }
1139
1140
1141
    /**
1142
     * Checks if the record can be put to the trash by the current user.
1143
     * @return bool
1144
     */
1145
    public function isRemovable()
1146
    {
1147
        $set = $this->getParentSet();
1148
        return $set->select($set->isRemovable()->_AND_($set->id->is($this->id)))->count() == 1;
1149
    }
1150
1151
1152
    /**
1153
     * Checks if the record can be restored from the trash by the current user.
1154
     * @return bool
1155
     */
1156
    public function isRestorable()
1157
    {
1158
        $set = $this->getParentSet();
1159
        return $set->select($set->isRestorable()->_AND_($set->id->is($this->id)))->count() == 1;
1160
    }
1161
1162
1163
    /**
1164
     * Ensures that the record is readable by the current user or throws an exception.
1165
     * @since 1.0.40
1166
     * @param string $message
1167
     * @throws app_AccessException
1168
     */
1169
    public function requireReadable($message = null)
1170
    {
1171
        if (!$this->isReadable()) {
1172
            $App = $this->App();
1173
            if (!isset($message)) {
1174
                $message = $App->translate('Access denied');
1175
            }
1176
            throw new app_AccessException($message);
1177
        }
1178
    }
1179
1180
    /**
1181
     * Ensures that the record is updatable by the current user or throws an exception.
1182
     * @since 1.0.40
1183
     * @param string $message
1184
     * @throws app_AccessException
1185
     */
1186
    public function requireUpdatable($message = null)
1187
    {
1188
        if (!$this->isUpdatable()) {
1189
            $App = $this->App();
1190
            if (!isset($message)) {
1191
                $message = $App->translate('Access denied');
1192
            }
1193
            throw new app_AccessException($message);
1194
        }
1195
    }
1196
1197
    /**
1198
     * Ensures that the record is deletable by the current user or throws an exception.
1199
     * @since 1.0.40
1200
     * @param string $message
1201
     * @throws app_AccessException
1202
     */
1203
    public function requireDeletable($message = null)
1204
    {
1205
        if (!$this->isDeletable()) {
1206
            $App = $this->App();
1207
            if (!isset($message)) {
1208
                $message = $App->translate('Access denied');
1209
            }
1210
            throw new app_AccessException($message);
1211
        }
1212
    }
1213
}
1214
1215