Passed
Pull Request — master ( #1 )
by Robin
03:25
created

app_TraceableRecordSet::uuid()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 5
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 7
rs 10
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
$App = app_App();
25
$App->includeRecordSet();
26
27
28
/**
29
 *
30
 * @property ORM_IntField       $createdBy
31
 * @property ORM_DateTimeField  $createdOn
32
 * @property ORM_IntField       $modifiedBy
33
 * @property ORM_DateTimeField  $modifiedOn
34
 * @property ORM_IntField       $deletedBy
35
 * @property ORM_DateTimeField  $deletedOn
36
 * @property ORM_EnumField      $deleted
37
 * @property ORM_StringField    $uuid
38
 *
39
 * @method app_TraceableRecord    get(mixed $criteria)
40
 * @method app_TraceableRecord    request(mixed $criteria)
41
 * @method app_TraceableRecord[]  select(\ORM_Criteria $criteria = null)
42
 * @method app_TraceableRecord    newRecord()
43
 */
44
class app_TraceableRecordSet extends app_RecordSet
45
{
46
    /**
47
     * @var bool
48
     */
49
    private $loggable = false;
50
51
    /**
52
     * @var bool
53
     */
54
    private $traceable = true;
55
56
    /**
57
     * @param Func_App $App
58
     */
59
    public function __construct(Func_App $App)
60
    {
61
        parent::__construct($App);
62
63
        $this->addFields(
64
            ORM_UserField('createdBy')
65
                ->setDescription('Created by'),
66
            ORM_DateTimeField('createdOn')
67
                ->setDescription('Created on'),
68
            ORM_UserField('modifiedBy')
69
                ->setDescription('Modified by'),
70
            ORM_DateTimeField('modifiedOn')
71
                ->setDescription('Modified on'),
72
            ORM_IntField('deletedBy')
73
                ->setDescription('Deleted by'),
74
            ORM_DateTimeField('deletedOn')
75
                ->setDescription('Deleted on'),
76
            ORM_EnumField('deleted', app_TraceableRecord::getDeletedStatuses())
77
                ->setDescription('Deleted'),
78
            ORM_StringField('uuid')
79
                ->setDescription('Universally Unique IDentifier')
80
81
        );
82
83
        // This condition will be applied whenever we select or join Records from this RecordSet.
84
        $this->setDefaultCriteria($this->deleted->is(false));
85
    }
86
87
88
    /**
89
     * Defines if the insertions/updates/deletions on the recordSet will be logged.
90
     *
91
     * @param bool $loggable
92
     * @return self
93
     */
94
    protected function setLoggable($loggable)
95
    {
96
        $this->loggable = $loggable;
97
        return $this;
98
    }
99
100
101
    /**
102
     * Checks if the insertions/updates/deletions on the recordSet will be logged.
103
     *
104
     * @return bool
105
     */
106
    protected function isLoggable()
107
    {
108
        return $this->loggable;
109
    }
110
111
112
113
114
    /**
115
     * Defines if the insertions/updates/deletions on the recordSet will be traced.
116
     *
117
     * @param bool $traceable
118
     * @return self
119
     */
120
    public function setTraceable($traceable)
121
    {
122
        $this->traceable = $traceable;
123
        return $this;
124
    }
125
126
127
    /**
128
     * Checks if the insertions/updates/deletions on the recordSet will be traced.
129
     *
130
     * @return bool
131
     */
132
    public function isTraceable()
133
    {
134
        return $this->traceable;
135
    }
136
137
138
    /**
139
     * @param app_TraceableRecord   $record
140
     * @param bool                  $noTrace
141
     */
142
    protected function logSave(app_TraceableRecord $record, $noTrace)
143
    {
144
        if (!$this->isLoggable()) {
145
            return;
146
        }
147
        $App = $this->App();
148
        $userId = bab_getUserId();
149
        $logSet = $App->LogSet();
150
        $log = $logSet->newRecord();
151
        $log->noTrace = $noTrace;
152
        $log->objectClass = get_class($record);
153
        $log->objectId = $record->id;
154
        $now = date('Y-m-d H:i:s');
155
        $log->modifiedOn = $now;
156
        $log->modifiedBy = $userId;
157
        $log->data = $logSet->serialize($record);
0 ignored issues
show
Documentation Bug introduced by
It seems like $logSet->serialize($record) of type array is incompatible with the declared type string of property $data.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
158
        $log->save();
159
    }
160
161
162
    /**
163
     * @param ORM_Criteria          $criteria
164
     * @param bool                  $noTrace
165
     */
166
    protected function logDelete(ORM_Criteria $criteria, $noTrace)
167
    {
168
        if (!$this->isLoggable()) {
169
            return;
170
        }
171
        $App = $this->App();
172
        $userId = bab_getUserId();
173
        $logSet = $App->LogSet();
174
        $deletedRecords = $this->select($criteria);
175
        foreach ($deletedRecords as $record) {
176
            $log = $logSet->newRecord();
177
            $log->noTrace = $noTrace;
178
            $log->objectClass = get_class($record);
179
            $log->objectId = $record->id;
180
            $now = date('Y-m-d H:i:s');
181
            $log->modifiedOn = $now;
182
            $log->modifiedBy = $userId;
183
            $log->data = '';
184
            $log->save();
185
        }
186
    }
187
188
    /**
189
     * Returns an iterator on records matching the specified criteria.
190
     * The iterator will not include records flagged as deleted unless
191
     * the $includeDeleted parameter is set to true.
192
     *
193
     * @param ORM_Criteria  $criteria       Criteria for selecting records.
194
     * @param bool          $includeDeleted True to include delete-flagged records.
195
     *
196
     * @return app_TraceableRecord[]        Iterator on success, null if the backend has not been set
197
     */
198
    public function select(ORM_Criteria $criteria = null, $includeDeleted = false)
199
    {
200
        if ($includeDeleted) {
201
            $this->setDefaultCriteria(null);
202
        }
203
        return parent::select($criteria);
204
    }
205
206
207
    /**
208
     * Returns the first item matching the specified criteria.
209
     * The item will not include records flagged as deleted unless
210
     * the $includeDeleted parameter is set to true.
211
     *
212
     * @param ORM_Criteria	$criteria			Criteria for selecting records.
213
     * @param string		$sPropertyName		The name of the property on which the value applies. If not specified or null, the set's primary key will be used.
214
     * @param bool			$includeDeleted		True to include delete-flagged records.
215
     *
216
     * @return ORM_Item				Iterator on success, null if the backend has not been set
217
     */
218
    /*public function get(ORM_Criteria $criteria = null, $sPropertyName = null, $includeDeleted = false)
219
    {
220
        if ($includeDeleted) {
221
            $this->setDefaultCriteria(null);
222
        }
223
        return parent::get($criteria, $sPropertyName);
224
    }*/
225
226
227
    /**
228
     * Deleted records matching the specified criteria.
229
     * If $deletedStatus is true, records are not permanently deleted
230
     * If $deletedStatus is one of the crm_TraceableRecord::DELETED_STATUS_xxx constants, it is just
231
     * flagged with this status and kept in the database.
232
     *
233
     * @param ORM_Criteria  $criteria		The criteria for selecting the records to delete.
234
     * @param int|bool      $deletedStatus  True to delete permanently the record.
235
     *
236
     * @return bool     True on success, false otherwise
237
     */
238
    public function delete(ORM_Criteria $criteria = null, $deletedStatus = app_TraceableRecord::DELETED_STATUS_DELETED)
239
    {
240
        $definitive = ($deletedStatus === true) || !$this->isTraceable();
241
        $this->logDelete($criteria, $definitive);
0 ignored issues
show
Bug introduced by
It seems like $criteria can also be of type null; however, parameter $criteria of app_TraceableRecordSet::logDelete() does only seem to accept ORM_Criteria, 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

241
        $this->logDelete(/** @scrutinizer ignore-type */ $criteria, $definitive);
Loading history...
242
        if ($definitive) {
243
            return parent::delete($criteria);
244
        }
245
246
        require_once $GLOBALS['babInstallPath'] . '/utilit/dateTime.php';
247
        $now = BAB_DateTime::now()->getIsoDateTime();
248
249
        $records = $this->select($criteria);
250
251
252
        foreach ($records as $record) {
253
            /* @var $record app_TraceableRecord */
254
            // Could be optimized at ORM level
255
            $record->deleted = $deletedStatus;
0 ignored issues
show
Documentation Bug introduced by
It seems like $deletedStatus can also be of type false. However, the property $deleted 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...
256
            $record->deletedOn = $now;
257
            $record->deletedBy = bab_getUserId();
258
259
            if (!parent::save($record)) {
260
                return false;
261
            }
262
        }
263
        return true;
264
    }
265
266
    /**
267
     * Saves a record and keeps traces of the user doing it.
268
     *
269
     * @param app_TraceableRecord	$record			The record to save.
270
     * @param bool					$noTrace		True to bypass the tracing of modifications.
271
     *
272
     * @return boolean				True on success, false otherwise
273
     */
274
    public function save(ORM_Record $record, $noTrace = false)
275
    {
276
        $noTrace = $noTrace || !$this->isTraceable();
277
        $this->logSave($record, $noTrace);
278
        if ($noTrace) {
279
            return parent::save($record);
280
        }
281
282
        require_once $GLOBALS['babInstallPath'] . '/utilit/dateTime.php';
283
284
        $now = BAB_DateTime::now()->getIsoDateTime();
285
286
        // We first check if the record already has a createdBy.
287
        $set = $record->getParentSet();
288
        $primaryKey = $set->getPrimaryKey();
289
290
        if (empty($record->{$primaryKey})) {
291
            $record->initValue('createdBy', bab_getUserId());
292
            $record->initValue('createdOn', $now);
293
            $record->initValue('uuid', $this->uuid());
294
        }
295
296
        $record->initValue('modifiedBy', bab_getUserId());
297
        $record->initValue('modifiedOn', $now);
298
299
        return parent::save($record);
300
    }
301
302
303
304
305
    /**
306
     * Generates a Universally Unique IDentifier, version 4.
307
     * RFC 4122 (http://www.ietf.org/rfc/rfc4122.txt)
308
     * @return string
309
     */
310
    private function uuid()
311
    {
312
        return sprintf( '%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
313
            mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ),
314
            mt_rand( 0, 0x0fff ) | 0x4000,
315
            mt_rand( 0, 0x3fff ) | 0x8000,
316
            mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ) );
317
    }
318
319
    /**
320
     * Get record by UUID or null if the record does not exists or is deleted or if the uuid is empty
321
     * @param	string	$uuid
322
     * @return app_TraceableRecord
323
     */
324
    public function getRecordByUuid($uuid)
325
    {
326
        if ('' === (string) $uuid) {
327
            return null;
328
        }
329
330
331
        $record = $this->get($this->uuid->is($uuid));
332
333
        if (!isset($record)) {
334
            return null;
335
        }
336
337
        if (!($record instanceOf app_TraceableRecord)) {
0 ignored issues
show
introduced by
$record is always a sub-type of app_TraceableRecord.
Loading history...
338
            return null;
339
        }
340
341
        if ($record->deleted) {
342
            return null;
343
        }
344
345
        return $record;
346
    }
347
348
349
350
351
    /**
352
     * Match records created by the specified user or the current connected user if none specified.
353
     *
354
	 * @param int|null $userId
355
	 *
356
	 * @return ORM_IsCriterion
357
	 */
358
    public function isOwn($userId = null)
359
    {
360
        if (!isset($userId)) {
361
            $userId = bab_getUserId();
362
        }
363
        return $this->createdBy->is($userId);
364
    }
365
366
}
367
368
369
/**
370
 * A traceable record automatically stores by whom and when it was created,
371
 * modified and even deleted.
372
 *
373
 * By default "deleted" records (through the standard delete() methods)
374
 * actually stay in the database and are only flagged as deleted.
375
 *
376
 * @property int        $createdBy
377
 * @property string     $createdOn
378
 * @property int        $modifiedBy
379
 * @property string     $modifiedOn
380
 * @property int        $deletedBy
381
 * @property string     $deletedOn
382
 * @property string     $uuid
383
 * @property int       $deleted
384
 */
385
class app_TraceableRecord extends app_Record
386
{
387
    const DELETED_STATUS_EXISTING = 0;      // Normal status of a record
388
    const DELETED_STATUS_DELETED = 1;       // Record is deleted by retrievable by admin
389
    const DELETED_STATUS_IN_TRASH = 2;      // Record is deleted by retrievable by authorized users
390
    const DELETED_STATUS_DRAFT = 3;         // Status is in draft (not yet created)
391
392
    public static function getDeletedStatuses()
393
    {
394
        return array(
395
            self::DELETED_STATUS_DRAFT => app_translate('Draft'),
396
            self::DELETED_STATUS_EXISTING => app_translate('Existing'),
397
            self::DELETED_STATUS_DELETED => app_translate('Deleted'),
398
            self::DELETED_STATUS_IN_TRASH => app_translate('In trash'),
399
        );
400
    }
401
402
    /**
403
     * Saves the record.
404
     *
405
     * @param bool  $noTrace        True to bypass the tracing of modifications.
406
     *
407
     * @return bool True on success, false otherwise
408
     */
409
    public function save($noTrace = false)
410
    {
411
        return $this->getParentSet()->save($this, $noTrace);
0 ignored issues
show
Unused Code introduced by
The call to ORM_RecordSet::save() has too many arguments starting with $noTrace. ( Ignorable by Annotation )

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

411
        return $this->getParentSet()->/** @scrutinizer ignore-call */ save($this, $noTrace);

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...
412
    }
413
}
414