Passed
Push — master ( 8d3a6f...fda82f )
by Laurent
01:57
created

app_Record   B

Complexity

Total Complexity 50

Size/Duplication

Total Lines 504
Duplicated Lines 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 155
dl 0
loc 504
rs 8.4
c 2
b 0
f 0
wmc 50

21 Methods

Rating   Name   Duplication   Size   Complexity  
A importProperty() 0 8 2
A getController() 0 6 1
A isDeletable() 0 4 1
A importDate() 0 13 3
A replaceWith() 0 52 5
A requireDeletable() 0 8 3
A setApp() 0 4 1
A uploadPath() 0 14 2
A isRestorable() 0 4 1
A isUpdatable() 0 4 1
B delete() 0 70 8
A isReadable() 0 4 1
A App() 0 8 2
A isRemovable() 0 4 1
A getClassName() 0 4 1
A requireUpdatable() 0 8 3
A __call() 0 20 3
A requireReadable() 0 8 3
A getRelatedRecords() 0 32 3
A getRef() 0 7 2
A getViews() 0 18 3

How to fix   Complexity   

Complex Class

Complex classes like app_Record 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_Record, 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
        return explode('_', $setName);
97
    }
98
99
    /**
100
     * Similar to the ORM_RecordSet::join() method but instanciates the
101
     * joined RecordSet using App methods.
102
     *
103
     * @see ORM_RecordSet::join()
104
     */
105
    public function join($fkFieldName)
106
    {
107
        $fkField = $this->getField($fkFieldName);
108
        if (!($fkField instanceof ORM_FkField)) {
109
            return $this;
110
        }
111
        $setName = $fkField->getForeignSetName();
112
113
        if (!$setName || 'Set' === $setName) {
114
            throw new Exception('The set name is missing on foreign key field '.$fkFieldName);
115
        }
116
117
        list($prefix, $appSetName) = $this->extractSetPrefixAndName($setName);
118
119
        $set = $this->App()->$appSetName();
120
        $set->setName($fkField->getName());
121
        $set->setDescription($fkField->getDescription());
122
123
        $this->aField[$fkFieldName] = $set;
124
        $set->setParentSet($this);
125
        return $this;
126
    }
127
128
129
//     /**
130
//      *
131
//      * @param string $accessName
132
//      * @param string $type
133
//      */
134
//     public function addAccessRight($accessName, $type = 'acl')
135
//     {
136
//         $this->accessRights[$accessName] = $type;
137
//     }
138
139
140
//     /**
141
//      * @return array
142
//      */
143
//     public function getAccessRights()
144
//     {
145
//         return $this->accessRights;
146
//     }
147
148
149
150
    /**
151
     * @return app_CustomField[]
152
     */
153
    public function getCustomFields()
154
    {
155
        $App = $this->App();
156
157
        if (null === $this->customFields) {
158
            $this->customFields = array();
159
160
            if (isset($App->CustomField)) {
161
                $customFieldSet = $App->CustomFieldSet();
162
                $object = mb_substr(get_class($this), mb_strlen($App->classPrefix), -mb_strlen('Set'));
163
                try {
164
                    $customFields = $customFieldSet->select($customFieldSet->object->is($object));
165
166
                    foreach ($customFields as $customfield) {
167
                        $this->customFields[] = $customfield;
168
169
                        /*@var $customfield app_CustomField */
170
171
                    }
172
                } catch (ORM_BackEndSelectException $e) {
173
                    // table does not exist, this error is thrown by the install program while creating the sets
174
                }
175
            }
176
        }
177
178
        return $this->customFields;
179
    }
180
181
182
183
184
    /**
185
     * @return app_CustomField[]
186
     */
187
    public function selectCustomFields()
188
    {
189
        $App = $this->App();
190
191
        $customFieldSet= $App->CustomFieldSet();
192
        $object = mb_substr(get_class($this), mb_strlen($App->classPrefix), -mb_strlen('Set'));
193
194
        $customFields = $customFieldSet->select($customFieldSet->object->is($object));
195
196
        return $customFields;
197
    }
198
199
200
    /**
201
     * @return self
202
     */
203
    public function addCustomFields()
204
    {
205
        $customFields = $this->selectCustomFields();
206
        foreach ($customFields as $customField) {
207
            /*@var $customField app_CustomField */
208
            $description = $customField->name;
209
            $ormField = $customField->getORMField()
210
                ->setDescription($description);
211
            if ($ormField instanceof ORM_FkField) {
212
                $this->hasOne($customField->fieldname, $ormField->getForeignSetName())
213
                    ->setDescription($description);
214
            } else {
215
                $this->addFields($ormField);
216
            }
217
        }
218
219
        return $this;
220
    }
221
222
223
    /**
224
     * Similar to ORM_RecordSet::get() method  but throws a app_NotFoundException if the
225
     * record is not found.
226
     *
227
     * @since 1.0.18
228
     *
229
     * @throws ORM_Exception
230
     * @throws app_NotFoundException
231
     *
232
     * @param ORM_Criteria|string $mixedParam    Criteria for selecting records
233
     *                                           or the value for selecting record
234
     * @param string              $sPropertyName The name of the property on which
235
     *                                           the value applies. If not
236
     *                                           specified or null, the set's
237
     *                                           primary key will be used.
238
     *
239
     * @return app_Record
240
     */
241
    public function request($mixedParam = null, $sPropertyName = null)
242
    {
243
        $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

243
        /** @scrutinizer ignore-call */ 
244
        $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...
244
        if (!isset($record)) {
245
            // This will remove the default criteria for TraceableRecords and
246
            // fetch even 'deleted' ones.
247
            $this->setDefaultCriteria(null);
248
            $record = $this->get($mixedParam, $sPropertyName);
249
            if (isset($record)) {
250
                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

250
                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...
251
            }
252
            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

252
            throw new app_NotFoundException($this, /** @scrutinizer ignore-type */ $mixedParam);
Loading history...
253
        }
254
        return $record;
255
    }
256
257
258
259
    /**
260
     * @param app_AccessManager $accessManager
261
     * @return self
262
     */
263
    public function setAccessManager(app_AccessManager $accessManager)
264
    {
265
        $this->accessManager = $accessManager;
266
        return $this;
267
    }
268
269
270
    /**
271
     * @return app_AccessManager
272
     */
273
    public function getAccessManager()
274
    {
275
        return $this->accessManager;
276
    }
277
278
279
    /**
280
     * Defines if records can be created by the current user.
281
     *
282
     * @return boolean
283
     */
284
    public function isCreatable()
285
    {
286
        return false;
287
    }
288
289
290
291
    /**
292
     *
293
     * @return ORM_Criteria
294
     */
295
    public function isReadable()
296
    {
297
        return $this->hasAccess('read');
298
    }
299
300
    /**
301
     *
302
     * @return ORM_Criteria
303
     */
304
    public function isUpdatable()
305
    {
306
        return $this->hasAccess('update');
307
    }
308
309
    /**
310
     *
311
     * @return ORM_Criteria
312
     */
313
    public function isDeletable()
314
    {
315
        return $this->hasAccess('delete');
316
    }
317
318
319
320
    /**
321
     * Returns a criterion matching records that can be put to trash by the current user.
322
     *
323
     * @return ORM_Criterion
324
     */
325
    public function isRemovable()
326
    {
327
        return $this->isUpdatable();
328
    }
329
330
331
    /**
332
     * Returns a criterion matching records that can be restored from the trash by the current user.
333
     *
334
     * @return ORM_Criterion
335
     */
336
    public function isRestorable()
337
    {
338
        return $this->isUpdatable();
339
    }
340
341
    /**
342
     * Returns a criterion matching records deletable by the current user.
343
     *
344
     * @param string    $accessType
345
     * @param int|null  $user
346
     *
347
     * @return ORM_Criterion
348
     */
349
    public function hasAccess($accessType, $user = null)
350
    {
351
        $accessManager = $this->getAccessManager();
352
        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...
353
    }
354
}
355
356
357
358
/**
359
 * @property int        $id
360
 *
361
 * @method app_RecordSet getParentSet()
362
 */
363
class app_Record extends ORM_Record implements app_Object_Interface
364
{
365
    /**
366
     * @var Func_App
367
     */
368
    protected $app = null;
369
370
371
372
373
    /**
374
     * Returns the value of the specified field or an iterator if $sFieldName represents a 'Many relation'.
375
     *
376
     * @param string $sFieldName The name of the field or relation for which the value must be returned.
377
     * @param array  $args       Optional arguments.
378
     *
379
     * @return mixed The value of the field or null if the field is not a part of the record.
380
     */
381
    public function __call($sFieldName, $args)
382
    {
383
        $value = $this->oParentSet->getBackend()->getRecordValue($this, $sFieldName);
384
        $field = $this->oParentSet->$sFieldName;
385
        if (!is_null($value) && $field instanceof ORM_FkField) {
386
387
            $sClassName = $field->getForeignSetName();
388
389
            $App = $this->App();
390
            $prefixLength = mb_strlen($App->classPrefix);
391
            $methodName = mb_substr($sClassName, $prefixLength);
392
            $set = $App->$methodName();
393
394
            $set->setName($field->getName());
395
            $set->setDescription($field->getDescription());
396
397
            $record = $set->get($value);
398
            return $record;
399
        }
400
        return $value;
401
    }
402
403
    /**
404
     * {@inheritDoc}
405
     * @see app_Object_Interface::setApp()
406
     */
407
    public function setApp(Func_App $app)
408
    {
409
        $this->app = $app;
410
        return $this;
411
    }
412
413
    /**
414
     * Get APP object to use with this record
415
     *
416
     * @return Func_App
417
     */
418
    public function App()
419
    {
420
        if (!isset($this->app)) {
421
            // If the app object was not specified (through the setApp() method),
422
            // we set it as parent set's App.
423
            $this->setApp($this->getParentSet()->App());
424
        }
425
        return $this->app;
426
    }
427
428
429
    /**
430
     * Returns the base class name of a record.
431
     * For example xxx_Contact will return 'Contact'.
432
     *
433
     * @since 1.0.40
434
     * @return string
435
     */
436
    public function getClassName()
437
    {
438
        list(, $classname) = explode('_', get_class($this));
439
        return $classname;
440
    }
441
442
443
    /**
444
     * Returns the string reference corresponding to the record.
445
     *
446
     * @return string 	A reference string (e.g. Contact:12)
447
     */
448
    public function getRef()
449
    {
450
        if (!isset($this->id)) {
451
            throw new app_Exception('Trying to get the reference string of a record without an id.');
452
        }
453
        $classname = $this->getClassName();
454
        return $classname . ':' . $this->id;
455
    }
456
457
458
    /**
459
     * @return app_Controller
460
     */
461
    public function getController()
462
    {
463
        $App = $this->App();
464
465
        $ctrlName = $this->getClassName();
466
        return $App->Controller()->$ctrlName();
467
    }
468
469
470
    /**
471
     * Deletes the record with respect to referential integrity.
472
     *
473
     * Uses referential integrity as defined by hasManyRelation to delete/update
474
     * referenced elements.
475
     *
476
     * @see app_RecordSet::hasMany()
477
     *
478
     * @return self
479
     */
480
    public function delete($deletedStatus = null)
481
    {
482
        $App = $this->App();
483
484
        if (!isset($deletedStatus)) {
485
            $deletedStatus = app_TraceableRecord::DELETED_STATUS_DELETED;
486
        }
487
488
        $set = $this->getParentSet();
489
        $recordIdName = $set->getPrimaryKey();
490
        $recordId = $this->$recordIdName;
491
492
        // Uses referential integrity as defined by hasManyRelation to delete/update
493
        // referenced elements.
494
        $manyRelations = $set->getHasManyRelations();
495
496
497
        foreach ($manyRelations as $manyRelation) {
498
            /* @var $manyRelation ORM_ManyRelation */
499
500
            $foreignSetClassName = $manyRelation->getForeignSetClassName();
501
            $foreignSetFieldName = $manyRelation->getForeignFieldName();
502
            $method = mb_substr($foreignSetClassName, mb_strlen($App->classPrefix));
503
            $foreignSet = $App->$method();
504
505
            switch ($manyRelation->getOnDeleteMethod()) {
506
507
                case ORM_ManyRelation::ON_DELETE_SET_NULL:
508
509
                    $foreignRecords = $foreignSet->select($foreignSet->$foreignSetFieldName->is($recordId));
510
511
                    foreach ($foreignRecords as $foreignRecord) {
512
                        $foreignRecord->$foreignSetFieldName = 0;
513
                        $foreignRecord->save();
514
                    }
515
516
                    break;
517
518
                case ORM_ManyRelation::ON_DELETE_CASCADE:
519
520
                    $foreignRecords = $foreignSet->select($foreignSet->$foreignSetFieldName->is($recordId));
521
522
                    foreach ($foreignRecords as $foreignRecord) {
523
                        $foreignRecord->delete();
524
                    }
525
526
                    break;
527
528
                case ORM_ManyRelation::ON_DELETE_NO_ACTION:
529
                default:
530
                    break;
531
532
            }
533
        }
534
535
536
        // We remove all links to and from this record.
537
        $linkSet = $App->LinkSet();
538
539
        $linkSet->delete(
540
            $linkSet->sourceClass->is(get_class($this))->_AND_($linkSet->sourceId->is($recordId))
541
            ->_OR_(
542
                $linkSet->targetClass->is(get_class($this))->_AND_($linkSet->targetId->is($recordId))
543
            )
544
        );
545
546
547
        $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

547
        $set->/** @scrutinizer ignore-call */ 
548
              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...
548
549
        return $this;
550
    }
551
552
553
554
    /**
555
     * Reassociates all data asociated to the record to another
556
     * specified one.
557
     *
558
     * @param	int		$id
559
     *
560
     * @return self
561
     */
562
    public function replaceWith($id)
563
    {
564
        $App = $this->App();
565
566
        $set = $this->getParentSet();
567
        $recordIdName = $set->getPrimaryKey();
568
        $recordId = $this->$recordIdName;
569
570
        // Use referential integrity as defined by hasManyRelation to delete/update
571
        // referenced elements.
572
        $manyRelations = $set->getHasManyRelations();
573
574
575
        foreach ($manyRelations as $manyRelation) {
576
            /* @var $manyRelation ORM_ManyRelation */
577
578
            $foreignSetClassName = $manyRelation->getForeignSetClassName();
579
            $foreignSetFieldName = $manyRelation->getForeignFieldName();
580
            $method = mb_substr($foreignSetClassName, mb_strlen($App->classPrefix));
581
            $foreignSet = $App->$method();
582
            // $foreignSet = new $foreignSetClassName($App);
583
            $foreignRecords = $foreignSet->select($foreignSet->$foreignSetFieldName->is($recordId));
584
585
            foreach ($foreignRecords as $foreignRecord) {
586
                $foreignRecord->$foreignSetFieldName = $id;
587
                $foreignRecord->save();
588
            }
589
        }
590
591
592
        // We replace all links to and from this record.
593
        $linkSet = $App->LinkSet();
594
595
        $links = $linkSet->select(
596
            $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

596
        /** @scrutinizer ignore-call */ 
597
        $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...
597
        );
598
599
        foreach ($links as $link) {
600
            $link->sourceId = $id;
601
            $link->save();
602
        }
603
604
        $links = $linkSet->select(
605
            $linkSet->targetClass->is(get_class($this))->_AND_($linkSet->targetId->is($recordId))
606
        );
607
608
        foreach ($links as $link) {
609
            $link->targetId = $id;
610
            $link->save();
611
        }
612
613
        return $this;
614
    }
615
616
617
    /**
618
     *
619
     *
620
     * @return array
621
     */
622
    public function getRelatedRecords()
623
    {
624
        $App = $this->App();
625
626
        $set = $this->getParentSet();
627
        $recordIdName = $set->getPrimaryKey();
628
        $recordId = $this->$recordIdName;
629
630
        // Use referential integrity as defined by hasManyRelation to delete/update
631
        // referenced elements.
632
        $manyRelations = $set->getHasManyRelations();
633
634
        $relatedRecords = array();
635
636
        foreach ($manyRelations as $manyRelation) {
637
            /* @var $manyRelation ORM_ManyRelation */
638
639
            $foreignSetClassName = $manyRelation->getForeignSetClassName();
640
            $foreignSetFieldName = $manyRelation->getForeignFieldName();
641
642
            $method = mb_substr($foreignSetClassName, mb_strlen($App->classPrefix));
643
            $foreignSet = $App->$method();
644
            // $foreignSet = new $foreignSetClassName($App);
645
            $foreignRecords = $foreignSet->select($foreignSet->$foreignSetFieldName->is($recordId));
646
647
648
            if ($foreignRecords->count() > 0) {
649
                $relatedRecords[$foreignSetClassName] = $foreignRecords;
650
            }
651
        }
652
653
        return $relatedRecords;
654
    }
655
656
657
658
659
660
661
    /**
662
     * Upload path for record attachments
663
     *
664
     * @return bab_Path
665
     */
666
    public function uploadPath()
667
    {
668
        $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

668
        $path = $this->App()->/** @scrutinizer ignore-call */ uploadPath();
Loading history...
669
670
        if (null === $path)
671
        {
672
            throw new Exception('Missing upload path information');
673
            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...
674
        }
675
676
        $path->push(get_class($this));
677
        $path->push($this->id);
678
679
        return $path;
680
    }
681
682
683
684
685
686
687
    /**
688
     * import a value into a traceable record property if the value is not equal
689
     *
690
     * @param	string	$name		property name
691
     * @param	mixed	$value		value to set
692
     *
693
     * @return int		1 : the value has been modified | 0 : no change
694
     */
695
    protected function importProperty($name, $value)
696
    {
697
        if (((string) $this->$name) !== ((string) $value)) {
698
            $this->$name = $value;
699
            return 1;
700
        }
701
702
        return 0;
703
    }
704
705
706
707
    /**
708
     * import a value into a tracable record property if the value is not equal, try with multiple date format
709
     * this method work for date field 0000-00-00
710
     *
711
     * @param	string	$name		property name
712
     * @param	mixed	$value		value to set
713
     *
714
     * @return int		1 : the value has been modified | 0 : no change
715
     */
716
    protected function importDate($name, $value)
717
    {
718
        if (preg_match('/[0-9]{4}-[0-9]{2}-[0-9]{2}/',$value)) {
719
            return $this->importProperty($name, $value);
720
        }
721
722
        // try in DD/MM/YYYY format
723
724
        if (preg_match('/(?P<day>[0-9]+)\/(?P<month>[0-9]+)\/(?P<year>[0-9]{2,4})/',$value, $matches)) {
725
726
            $value = sprintf('%04d-%02d-%02d', (int) $matches['year'], (int) $matches['month'], (int) $matches['day']);
727
728
            return $this->importProperty($name, $value);
729
        }
730
731
    }
732
733
734
735
736
    /**
737
     *
738
     * @return string[]
739
     */
740
    public function getViews()
741
    {
742
        $App = $this->App();
743
744
        $customSectionSet = $App->CustomSectionSet();
745
        $customSections = $customSectionSet->select($customSectionSet->object->is($this->getClassName()));
746
        $customSections->groupBy($customSectionSet->view);
747
748
        $views = array();
749
        foreach ($customSections as $customSection) {
750
            $views[] = $customSection->view;
751
        }
752
753
        if (empty($views)) {
754
            $views[] = '';
755
        }
756
757
        return $views;
758
    }
759
760
761
762
    /**
763
     * Checks if the record is readable by the current user.
764
     * @since 1.0.21
765
     * @return bool
766
     */
767
    public function isReadable()
768
    {
769
        $set = $this->getParentSet();
770
        return $set->select($set->isReadable()->_AND_($set->id->is($this->id)))->count() == 1;
771
    }
772
773
774
    /**
775
     * Checks if the record is updatable by the current user.
776
     * @since 1.0.21
777
     * @return bool
778
     */
779
    public function isUpdatable()
780
    {
781
        $set = $this->getParentSet();
782
        return $set->select($set->isUpdatable()->_AND_($set->id->is($this->id)))->count() == 1;
783
    }
784
785
    /**
786
     * Checks if the record is deletable by the current user.
787
     * @since 1.0.21
788
     * @return bool
789
     */
790
    public function isDeletable()
791
    {
792
        $set = $this->getParentSet();
793
        return $set->select($set->isDeletable()->_AND_($set->id->is($this->id)))->count() == 1;
794
    }
795
796
797
    /**
798
     * Checks if the record can be put to the trash by the current user.
799
     * @return bool
800
     */
801
    public function isRemovable()
802
    {
803
        $set = $this->getParentSet();
804
        return $set->select($set->isRemovable()->_AND_($set->id->is($this->id)))->count() == 1;
805
    }
806
807
808
    /**
809
     * Checks if the record can be restored from the trash by the current user.
810
     * @return bool
811
     */
812
    public function isRestorable()
813
    {
814
        $set = $this->getParentSet();
815
        return $set->select($set->isRestorable()->_AND_($set->id->is($this->id)))->count() == 1;
816
    }
817
818
819
    /**
820
     * Ensures that the record is readable by the current user or throws an exception.
821
     * @since 1.0.40
822
     * @param string $message
823
     * @throws app_AccessException
824
     */
825
    public function requireReadable($message = null)
826
    {
827
        if (!$this->isReadable()) {
828
            $App = $this->App();
829
            if (!isset($message)) {
830
                $message = $App->translate('Access denied');
831
            }
832
            throw new app_AccessException($message);
833
        }
834
    }
835
836
    /**
837
     * Ensures that the record is updatable by the current user or throws an exception.
838
     * @since 1.0.40
839
     * @param string $message
840
     * @throws app_AccessException
841
     */
842
    public function requireUpdatable($message = null)
843
    {
844
        if (!$this->isUpdatable()) {
845
            $App = $this->App();
846
            if (!isset($message)) {
847
                $message = $App->translate('Access denied');
848
            }
849
            throw new app_AccessException($message);
850
        }
851
    }
852
853
    /**
854
     * Ensures that the record is deletable by the current user or throws an exception.
855
     * @since 1.0.40
856
     * @param string $message
857
     * @throws app_AccessException
858
     */
859
    public function requireDeletable($message = null)
860
    {
861
        if (!$this->isDeletable()) {
862
            $App = $this->App();
863
            if (!isset($message)) {
864
                $message = $App->translate('Access denied');
865
            }
866
            throw new app_AccessException($message);
867
        }
868
    }
869
}
870
871