Issues (34)

Security Analysis    no request data  

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

src/Laravel/RevisionableTrait.php (6 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php namespace Sofa\Revisionable\Laravel;
2
3
use App;
4
use DateTime;
5
6
/**
7
 * @property int   revisionsCount
8
 * @property array relations
9
 * @property array original
10
 * @property array attributes
11
 * @property array attributes
12
 * @property array revisionableConnection
13
 *
14
 * @method void created(\Closure|string $callback)
15
 * @method void updated(\Closure|string $callback)
16
 * @method void deleted(\Closure|string $callback)
17
 * @method void restored(\Closure|string $callback)
18
 * @method string getTable()
19
 * @method void load()
20
 * @method mixed getRelation($relation)
21
 * @method \Illuminate\Database\Eloquent\Relations\HasOne hasOne($related, $foreignKey = null, $localKey = null)
22
 * @method \Illuminate\Database\Eloquent\Relations\HasMany hasMany($related, $foreignKey = null, $localKey = null)
23
 */
24
trait RevisionableTrait
25
{
26
    /**
27
     * Revisionable Logger instance.
28
     *
29
     * @var \Sofa\Revisionable\Logger
30
     */
31
    protected static $revisionableLogger;
32
33
    /**
34
     * Revisioning switch.
35
     *
36
     * @var boolean
37
     */
38
    protected $revisioned = true;
39
40
    /**
41
     * Boot revisionable trait for the model.
42
     *
43
     * @return void
44
     */
45
    public static function bootRevisionableTrait()
46
    {
47
        static::bootLogger();
48
49
        static::registerListeners();
50
    }
51
52
    /**
53
     * Register event listeners.
54
     *
55
     * @return void
56
     */
57
    public static function registerListeners()
58
    {
59
        foreach (static::getRevisionableEvents() as $event) {
60
            static::{"register{$event}Listener"}();
61
        }
62
    }
63
64
    /**
65
     * Register listener for created event.
66
     *
67
     * @return void
68
     */
69
    protected static function registerCreatedListener()
70
    {
71
        static::created('Sofa\Revisionable\Listener@onCreated');
72
    }
73
74
    /**
75
     * Register listener for updated event.
76
     *
77
     * @return void
78
     */
79
    protected static function registerUpdatedListener()
80
    {
81
        static::updated('Sofa\Revisionable\Listener@onUpdated');
82
    }
83
84
    /**
85
     * Register listener for deleted event.
86
     *
87
     * @return void
88
     */
89
    protected static function registerDeletedListener()
90
    {
91
        static::deleted('Sofa\Revisionable\Listener@onDeleted');
92
    }
93
94
    /**
95
     * Register listener for restored event.
96
     *
97
     * @return void
98
     */
99
    protected static function registerRestoredListener()
100
    {
101
        if (method_exists(get_called_class(), 'restored')) {
102
            static::restored('Sofa\Revisionable\Listener@onRestored');
103
        }
104
    }
105
106
    /**
107
     * Boot Revisionable Logger.
108
     *
109
     * @return void
110
     */
111
    public static function bootLogger()
112
    {
113
        if (!static::$revisionableLogger) {
114
            static::setRevisionableLogger(App::make('revisionable.logger'));
115
        }
116
    }
117
118
    /**
119
     * Set logger instance.
120
     *
121
     * @param mixed $logger
122
     */
123
    public static function setRevisionableLogger($logger)
124
    {
125
        static::$revisionableLogger = $logger;
126
    }
127
128
    /**
129
     * Get logger instance.
130
     *
131
     * @return \Sofa\Revisionable\Logger
132
     */
133
    public static function getRevisionableLogger()
134
    {
135
        return static::$revisionableLogger;
136
    }
137
138
    /**
139
     * Get the connection for revision logs.
140
     *
141
     * @return \Illuminate\Database\ConnectionInterface
142
     */
143
    public function getRevisionableConnection()
144
    {
145
        $connection = (isset($this->revisionableConnection)) ? $this->revisionableConnection : null;
146
147
        return static::resolveConnection($connection);
148
    }
149
150
    /**
151
     * Get an array of updated revisionable attributes.
152
     *
153
     * @return array
154
     */
155
    public function getDiff()
156
    {
157
        $old = $this->getOldAttributes();
158
159
        $new = $this->getNewAttributes();
160
161
        return array_diff_assoc($new, $old);
162
    }
163
164
    /**
165
     * Get an array of original revisionable attributes.
166
     *
167
     * @return array
168
     */
169
    public function getOldAttributes()
170
    {
171
        $attributes = $this->getRevisionableItems($this->original);
172
173
        return $this->prepareAttributes($attributes);
174
    }
175
176
    /**
177
     * Get an array of current revisionable attributes.
178
     *
179
     * @return array
180
     */
181
    public function getNewAttributes()
182
    {
183
        $attributes = $this->getRevisionableItems($this->attributes);
184
185
        return $this->prepareAttributes($attributes);
186
    }
187
188
    /**
189
     * Stringify revisionable attributes.
190
     *
191
     * @param  array  $attributes
192
     * @return array
193
     */
194
    protected function prepareAttributes(array $attributes)
195
    {
196
        return array_map(function ($attribute) {
197
            return ($attribute instanceof DateTime)
198
                ? $this->fromDateTime($attribute)
0 ignored issues
show
It seems like fromDateTime() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
199
                : (string) $attribute;
200
        }, $attributes);
201
    }
202
203
    /**
204
     * Get an array of revisionable attributes.
205
     *
206
     * @param  array  $values
207
     * @return array
208
     */
209
    public function getRevisionableItems(array $values)
210
    {
211
        if (count($this->getRevisionable()) > 0) {
212
            return array_intersect_key($values, array_flip($this->getRevisionable()));
213
        }
214
215
        return array_diff_key($values, array_flip($this->getNonRevisionable()));
216
    }
217
218
    /**
219
     * Events being tracked.
220
     *
221
     * @var array
222
     */
223
    protected static function getRevisionableEvents()
224
    {
225
        return (isset(static::$revisionableEvents))
226
            ? (array) static::$revisionableEvents
227
            : ['Created', 'Updated', 'Deleted', 'Restored'];
228
    }
229
230
    /**
231
     * Attributes being revisioned.
232
     *
233
     * @var array
234
     */
235
    public function getRevisionable()
236
    {
237
        return (isset($this->revisionable))
238
            ? (array) $this->revisionable
239
            : [];
240
    }
241
242
    /**
243
     * Attributes hidden from revisioning if revisionable are not provided.
244
     *
245
     * @var array
246
     */
247
    public function getNonRevisionable()
248
    {
249
        return (isset($this->nonRevisionable))
250
            ? (array) $this->nonRevisionable
251
            : ['created_at', 'updated_at', 'deleted_at'];
252
    }
253
254
    /**
255
     * Determine if model should be revisioned.
256
     *
257
     * @return boolean
258
     */
259
    public function isRevisioned()
260
    {
261
        return $this->revisioned;
262
    }
263
264
    /**
265
     * Disable revisioning for current instance.
266
     *
267
     * @return void
268
     */
269
    public function disableRevisioning()
270
    {
271
        $this->revisioned = false;
272
    }
273
274
    /**
275
     * Enable revisioning for current instance.
276
     *
277
     * @return void
278
     */
279
    public function enableRevisioning()
280
    {
281
        $this->revisioned = true;
282
    }
283
284
    /**
285
     * Model has many Revision
286
     *
287
     * @return \Illuminate\Database\Eloquent\Relations\HasMany
288
     */
289
    public function revisions()
290
    {
291
        return $this->hasMany('Sofa\Revisionable\Laravel\Revision', 'row_id')
292
            ->latest()
293
            ->where('table_name', $this->getTable());
294
    }
295
296
    /**
297
     * Accessor for revisions property
298
     *
299
     * @return \Illuminate\Database\Eloquent\Collection
300
     */
301
    public function getRevisionsAttribute()
302
    {
303
        $this->loadRelationIfNecessary('revisions');
304
305
        $collection = $this->getRelation('revisions');
306
307
        $presenter = $this->getRevisionPresenter();
308
309
        $default = $this->getDefaultRevisionPresenter();
310
311
        if (is_subclass_of($presenter, $default) || $presenter == $default) {
312
            return $presenter::make($collection, $this);
313
        }
314
315
        return $collection;
316
    }
317
318
    /**
319
     * Get record version at given timestamp.
320
     *
321
     * @param  \Carbon\Carbon|string $timestamp
322
     *
323
     * @return \Sofa\Revisionable\Laravel\Revision|\Sofa\Revisionable\laravel\Presenter|null
324
     */
325
    public function revisionSnapshot($timestamp)
326
    {
327
        $revision = $this->revisions()->where('created_at', '<=', $timestamp)->first();
328
329
        return ($revision) ? $this->wrapRevision($revision) : null;
330
    }
331
332
    /**
333
     * Get record version at given step in history.
334
     *
335
     * @param  integer $step
336
     *
337
     * @return \Sofa\Revisionable\Laravel\Revision|\Sofa\Revisionable\laravel\Presenter|null
338
     */
339
    public function revisionStep($step)
340
    {
341
        $revision = $this->revisions()->skip($step)->first();
342
343
        return ($revision) ? $this->wrapRevision($revision) : null;
344
    }
345
346
    /**
347
     * Wrap revision model with the presenter if provided.
348
     *
349
     * @param  \Sofa\Revisionable\Laravel\Revision $revision
350
     * @return \Sofa\Revisionable\Laravel\Presenter|\Sofa\Revisionable\Laravel\Revision
351
     */
352
    public function wrapRevision(Revision $revision)
353
    {
354
        $presenter = $this->getRevisionPresenter();
355
356
        return (is_subclass_of($presenter, $this->getDefaultRevisionPresenter())
357
                || $presenter == $this->getDefaultRevisionPresenter())
358
                    ? $presenter::make($revision, $this)
359
                    : $revision;
360
    }
361
362
    /**
363
     * Load revisions relation if not loaded.
364
     *
365
     * @param  string $relation
366
     *
367
     * @return void
368
     */
369
    protected function loadRelationIfNecessary($relation)
370
    {
371
        if (!array_key_exists($relation, $this->relations)) {
372
            $this->load($relation);
0 ignored issues
show
The call to RevisionableTrait::load() has too many arguments starting with $relation.

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.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
373
        }
374
    }
375
376
    /**
377
     * Revisionable has one Revision count.
378
     *
379
     * @return \Illuminate\Database\Eloquent\Relations\HasOne
380
     */
381
    public function revisionsCount()
382
    {
383
        return $this->hasOne('Sofa\Revisionable\Laravel\Revision', 'row_id')
384
            ->where('table_name', $this->getTable())
385
            ->selectRaw('count(*) as aggregate, row_id')
386
            ->groupBy('row_id');
387
    }
388
389
    /**
390
     * Convenient accessor for revisionsCount relation.
391
     *
392
     * @return integer
393
     */
394
    public function getRevisionsCountAttribute()
395
    {
396
        if (!array_key_exists('revisionsCount', $this->relations)) {
397
            $this->load('revisionsCount');
0 ignored issues
show
The call to RevisionableTrait::load() has too many arguments starting with 'revisionsCount'.

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.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
398
        }
399
400
        $relation = $this->getRelation('revisionsCount');
401
402
        return ($relation) ? (int) $relation->aggregate : 0;
403
    }
404
405
    /**
406
     * Determine if model has any revisions history.
407
     *
408
     * @return boolean
409
     */
410
    public function hasRevisions()
411
    {
412
        return (bool) $this->revisionsCount;
413
    }
414
415
    /**
416
     * Determine if model has any revisions history.
417
     *
418
     * @return boolean
419
     */
420
    public function hasHistory()
421
    {
422
        return $this->hasRevisions();
423
    }
424
425
    /**
426
     * User has one oldestRevision
427
     *
428
     * @return \Illuminate\Database\Eloquent\Relations\HasOne
429
     */
430
    public function oldestRevision()
431
    {
432
        return $this->hasOne('Sofa\Revisionable\Laravel\Revision', 'row_id')
433
            ->where('table_name', $this->getTable())
434
            ->oldest();
435
    }
436
437
    /**
438
     * Accessor for oldestRevision property
439
     *
440
     * @return \Sofa\Revisionable\Laravel\Revision|\Sofa\Revisionable\Laravel\Presenter|null
441
     */
442 View Code Duplication
    public function getOldestRevisionAttribute()
0 ignored issues
show
This method seems to be duplicated in your project.

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

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

Loading history...
443
    {
444
        $this->loadRelationIfNecessary('oldestRevision');
445
446
        $revision = $this->getRelation('oldestRevision');
447
448
        return ($revision) ? $this->wrapRevision($revision) : null;
449
    }
450
451
    /**
452
     * User has one latestRevision
453
     *
454
     * @return \Illuminate\Database\Eloquent\Relations\HasOne
455
     */
456
    public function latestRevision()
457
    {
458
        return $this->hasOne('Sofa\Revisionable\Laravel\Revision', 'row_id')
459
            ->where('table_name', $this->getTable())
460
            ->latest();
461
    }
462
463
    /**
464
     * Accessor for latestRevision property
465
     *
466
     * @return \Sofa\Revisionable\Laravel\Revision|\Sofa\Revisionable\Laravel\Presenter|null
467
     */
468 View Code Duplication
    public function getLatestRevisionAttribute()
0 ignored issues
show
This method seems to be duplicated in your project.

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

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

Loading history...
469
    {
470
        $this->loadRelationIfNecessary('latestRevision');
471
472
        $revision = $this->getRelation('latestRevision');
473
474
        return ($revision) ? $this->wrapRevision($revision) : null;
475
    }
476
477
    /**
478
     * Set revision presenter class or true for default.
479
     *
480
     * @param string|true $class
481
     */
482
    public function setRevisionPresenter($class)
483
    {
484
        $this->revisionPresenter = $class;
0 ignored issues
show
The property revisionPresenter does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
485
    }
486
487
    /**
488
     * Get revision presenter class for the model.
489
     *
490
     * @return string|null
491
     */
492
    public function getRevisionPresenter()
493
    {
494
        if (!isset($this->revisionPresenter)) {
495
            return null;
496
        }
497
498
        // Use default or custom presenter class if provided.
499
        return ($this->revisionPresenter === true)
500
            ? $this->getDefaultRevisionPresenter()
501
            : $this->revisionPresenter;
502
    }
503
504
    /**
505
     * Get default revision presenter from the package.
506
     *
507
     * @return string
508
     */
509
    public function getDefaultRevisionPresenter()
510
    {
511
        return 'Sofa\Revisionable\Laravel\Presenter';
512
    }
513
}
514