Issues (44)

Security Analysis    not enabled

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/Models/BaseBitrixModel.php (1 issue)

Labels
Severity

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
2
3
namespace Arrilot\BitrixModels\Models;
4
5
use Arrilot\BitrixModels\Models\Traits\ModelEventsTrait;
6
use Arrilot\BitrixModels\Queries\BaseQuery;
7
use Illuminate\Support\Collection;
8
use LogicException;
9
10
abstract class BaseBitrixModel extends ArrayableModel
11
{
12
    use ModelEventsTrait;
13
14
    /**
15
     * @var string|null
16
     */
17
    protected static $currentLanguage = null;
18
    
19
    /**
20
     * Array of model fields keys that needs to be saved with next save().
21
     *
22
     * @var array
23
     */
24
    protected $fieldsSelectedForSave = [];
25
26
    /**
27
     * Array of errors that are passed to model events.
28
     *
29
     * @var array
30
     */
31
    protected $eventErrors = [];
32
33
    /**
34
     * Have fields been already fetched from DB?
35
     *
36
     * @var bool
37
     */
38
    protected $fieldsAreFetched = false;
39
40
    /**
41
     * Internal part of create to avoid problems with static and inheritance
42
     *
43
     * @param $fields
44
     *
45
     * @throws LogicException
46
     *
47
     * @return static|bool
48
     */
49
    protected static function internalCreate($fields)
50
    {
51
        throw new LogicException('internalCreate is not implemented');
52
    }
53
54
    /**
55
     * Save model to database.
56
     *
57
     * @param array $selectedFields save only these fields instead of all.
58
     *
59
     * @return bool
60
     */
61
    abstract public function save($selectedFields = []);
62
63
    /**
64
     * Determine whether the field should be stopped from passing to "update".
65
     *
66
     * @param string $field
67
     * @param mixed  $value
68
     * @param array  $selectedFields
69
     *
70
     * @return bool
71
     */
72
    abstract protected function fieldShouldNotBeSaved($field, $value, $selectedFields);
73
    
74
    /**
75
     * Get all model attributes from cache or database.
76
     *
77
     * @return array
78
     */
79
    public function get()
80
    {
81
        $this->load();
82
        
83
        return $this->fields;
84
    }
85
86
    /**
87
     * Load model fields from database if they are not loaded yet.
88
     *
89
     * @return $this
90
     */
91
    public function load()
92
    {
93
        if (!$this->fieldsAreFetched) {
94
            $this->refresh();
95
        }
96
        
97
        return $this;
98
    }
99
100
    /**
101
     * Get model fields from cache or database.
102
     *
103
     * @return array
104
     */
105
    public function getFields()
106
    {
107
        if ($this->fieldsAreFetched) {
108
            return $this->fields;
109
        }
110
        
111
        return $this->refreshFields();
112
    }
113
114
    /**
115
     * Refresh model from database and place data to $this->fields.
116
     *
117
     * @return array
118
     */
119
    public function refresh()
120
    {
121
        return $this->refreshFields();
122
    }
123
124
    /**
125
     * Refresh model fields and save them to a class field.
126
     *
127
     * @return array
128
     */
129
    public function refreshFields()
130
    {
131
        if ($this->id === null) {
132
            $this->original = [];
133
            return $this->fields = [];
134
        }
135
        
136
        $this->fields = static::query()->getById($this->id)->fields;
137
        $this->original = $this->fields;
138
        
139
        $this->fieldsAreFetched = true;
140
        
141
        return $this->fields;
142
    }
143
144
    /**
145
     * Fill model fields if they are already known.
146
     * Saves DB queries.
147
     *
148
     * @param array $fields
149
     *
150
     * @return void
151
     */
152
    public function fill($fields)
153
    {
154
        if (!is_array($fields)) {
155
            return;
156
        }
157
        
158
        if (isset($fields['ID'])) {
159
            $this->id = $fields['ID'];
160
        }
161
        
162
        $this->fields = $fields;
163
        
164
        $this->fieldsAreFetched = true;
165
        
166
        if (method_exists($this, 'afterFill')) {
167
            $this->afterFill();
0 ignored issues
show
It seems like you code against a specific sub-type and not the parent class Arrilot\BitrixModels\Models\BaseBitrixModel as the method afterFill() does only exist in the following sub-classes of Arrilot\BitrixModels\Models\BaseBitrixModel: Arrilot\BitrixModels\Models\ElementModel, Arrilot\BitrixModels\Models\UserModel. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
168
        }
169
170
        $this->original = $this->fields;
171
    }
172
173
    /**
174
     * Set current model id.
175
     *
176
     * @param $id
177
     */
178
    protected function setId($id)
179
    {
180
        $this->id = $id;
181
        $this->fields['ID'] = $id;
182
    }
183
184
    /**
185
     * Create new item in database.
186
     *
187
     * @param $fields
188
     *
189
     * @throws LogicException
190
     *
191
     * @return static|bool
192
     */
193
    public static function create($fields)
194
    {
195
        return static::internalCreate($fields);
196
    }
197
198
    /**
199
     * Get count of items that match $filter.
200
     *
201
     * @param array $filter
202
     *
203
     * @return int
204
     */
205
    public static function count(array $filter = [])
206
    {
207
        return static::query()->filter($filter)->count();
208
    }
209
210
    /**
211
     * Get item by its id.
212
     *
213
     * @param int $id
214
     *
215
     * @return static|bool
216
     */
217
    public static function find($id)
218
    {
219
        return static::query()->getById($id);
220
    }
221
222
    /**
223
     * Update model.
224
     *
225
     * @param array $fields
226
     *
227
     * @return bool
228
     */
229
    public function update(array $fields = [])
230
    {
231
        $keys = [];
232
        foreach ($fields as $key => $value) {
233
            array_set($this->fields, $key, $value);
234
            $keys[] = $key;
235
        }
236
237
        return $this->save($keys);
238
    }
239
240
    /**
241
     * Create an array of fields that will be saved to database.
242
     *
243
     * @param $selectedFields
244
     *
245
     * @return array|null
246
     */
247
    protected function normalizeFieldsForSave($selectedFields)
248
    {
249
        $fields = [];
250
        if ($this->fields === null) {
251
            return [];
252
        }
253
254
        foreach ($this->fields as $field => $value) {
255
            if (!$this->fieldShouldNotBeSaved($field, $value, $selectedFields)) {
256
                $fields[$field] = $value;
257
            }
258
        }
259
260
        return $fields ?: null;
261
    }
262
263
    /**
264
     * Instantiate a query object for the model.
265
     *
266
     * @throws LogicException
267
     *
268
     * @return BaseQuery
269
     */
270
    public static function query()
271
    {
272
        throw new LogicException('public static function query() is not implemented');
273
    }
274
275
    /**
276
     * Handle dynamic static method calls into a new query.
277
     *
278
     * @param  string  $method
279
     * @param  array  $parameters
280
     * @return mixed
281
     */
282
    public static function __callStatic($method, $parameters)
283
    {
284
        return static::query()->$method(...$parameters);
285
    }
286
287
    /**
288
     * Returns the value of a model property.
289
     *
290
     * This method will check in the following order and act accordingly:
291
     *
292
     *  - a property defined by a getter: return the getter result
293
     *
294
     * Do not call this method directly as it is a PHP magic method that
295
     * will be implicitly called when executing `$value = $component->property;`.
296
     * @param string $name the property name
297
     * @return mixed the property value
298
     * @throws \Exception if the property is not defined
299
     * @see __set()
300
     */
301
    public function __get($name)
302
    {
303
        // Если уже сохранен такой релейшн, то возьмем его
304
        if (isset($this->related[$name]) || array_key_exists($name, $this->related)) {
305
            return $this->related[$name];
306
        }
307
308
        // Если нет сохраненных данных, ищем подходящий геттер
309
        $getter = $name;
310
        if (method_exists($this, $getter)) {
311
            // read property, e.g. getName()
312
            $value = $this->$getter();
313
314
            // Если геттер вернул запрос, значит $name - релейшен. Нужно выполнить запрос и сохранить во внутренний массив
315
            if ($value instanceof BaseQuery) {
316
                $this->related[$name] = $value->findFor();
317
                return $this->related[$name];
318
            }
319
        }
320
321
        throw new \Exception('Getting unknown property: ' . get_class($this) . '::' . $name);
322
    }
323
324
    /**
325
     * Получить запрос для релейшена по имени
326
     * @param string $name - название релейшена, например `orders` для релейшена, определенного через метод getOrders()
327
     * @param bool $throwException - кидать ли исключение в случае ошибки
328
     * @return BaseQuery - запрос для подгрузки релейшена
329
     * @throws \InvalidArgumentException
330
     */
331
    public function getRelation($name, $throwException = true)
332
    {
333
        $getter = $name;
334
        try {
335
            $relation = $this->$getter();
336
        } catch (\BadMethodCallException $e) {
337
            if ($throwException) {
338
                throw new \InvalidArgumentException(get_class($this) . ' has no relation named "' . $name . '".', 0, $e);
339
            }
340
341
            return null;
342
        }
343
344
        if (!$relation instanceof BaseQuery) {
345
            if ($throwException) {
346
                throw new \InvalidArgumentException(get_class($this) . ' has no relation named "' . $name . '".');
347
            }
348
349
            return null;
350
        }
351
352
        return $relation;
353
    }
354
355
    /**
356
     * Reset event errors back to default.
357
     */
358
    protected function resetEventErrors()
359
    {
360
        $this->eventErrors = [];
361
    }
362
363
    /**
364
     * Declares a `has-one` relation.
365
     * The declaration is returned in terms of a relational [[BaseQuery]] instance
366
     * through which the related record can be queried and retrieved back.
367
     *
368
     * A `has-one` relation means that there is at most one related record matching
369
     * the criteria set by this relation, e.g., a customer has one country.
370
     *
371
     * For example, to declare the `country` relation for `Customer` class, we can write
372
     * the following code in the `Customer` class:
373
     *
374
     * ```php
375
     * public function country()
376
     * {
377
     *     return $this->hasOne(Country::className(), 'ID', 'PROPERTY_COUNTRY');
378
     * }
379
     * ```
380
     *
381
     * Note that in the above, the 'ID' key in the `$link` parameter refers to an attribute name
382
     * in the related class `Country`, while the 'PROPERTY_COUNTRY' value refers to an attribute name
383
     * in the current BaseBitrixModel class.
384
     *
385
     * Call methods declared in [[BaseQuery]] to further customize the relation.
386
     *
387
     * @param string $class the class name of the related record
388
     * @param string $foreignKey
389
     * @param string $localKey
390
     * @return BaseQuery the relational query object.
391
     */
392
    public function hasOne($class, $foreignKey, $localKey = 'ID')
393
    {
394
        return $this->createRelationQuery($class, $foreignKey, $localKey, false);
395
    }
396
397
    /**
398
     * Declares a `has-many` relation.
399
     * The declaration is returned in terms of a relational [[BaseQuery]] instance
400
     * through which the related record can be queried and retrieved back.
401
     *
402
     * A `has-many` relation means that there are multiple related records matching
403
     * the criteria set by this relation, e.g., a customer has many orders.
404
     *
405
     * For example, to declare the `orders` relation for `Customer` class, we can write
406
     * the following code in the `Customer` class:
407
     *
408
     * ```php
409
     * public function orders()
410
     * {
411
     *     return $this->hasMany(Order::className(), 'PROPERTY_COUNTRY_VALUE', 'ID');
412
     * }
413
     * ```
414
     *
415
     * Note that in the above, the 'customer_id' key in the `$link` parameter refers to
416
     * an attribute name in the related class `Order`, while the 'id' value refers to
417
     * an attribute name in the current BaseBitrixModel class.
418
     *
419
     * Call methods declared in [[BaseQuery]] to further customize the relation.
420
     *
421
     * @param string $class the class name of the related record
422
     * @param string $foreignKey
423
     * @param string $localKey
424
     * @return BaseQuery the relational query object.
425
     */
426
    public function hasMany($class, $foreignKey, $localKey = 'ID')
427
    {
428
        return $this->createRelationQuery($class, $foreignKey, $localKey, true);
429
    }
430
431
    /**
432
     * Creates a query instance for `has-one` or `has-many` relation.
433
     * @param string $class the class name of the related record.
434
     * @param string $foreignKey
435
     * @param string $localKey
436
     * @param bool $multiple whether this query represents a relation to more than one record.
437
     * @return BaseQuery the relational query object.
438
     * @see hasOne()
439
     * @see hasMany()
440
     */
441
    protected function createRelationQuery($class, $foreignKey, $localKey, $multiple)
442
    {
443
        /* @var $class BaseBitrixModel */
444
        /* @var $query BaseQuery */
445
        $query = $class::query();
446
        $query->foreignKey = $localKey;
447
        $query->localKey = $foreignKey;
448
        $query->primaryModel = $this;
449
        $query->multiple = $multiple;
450
        return $query;
451
    }
452
453
    /**
454
     * Записать модели как связанные
455
     * @param string $name - название релейшена
456
     * @param Collection|BaseBitrixModel $records - связанные модели
457
     * @see getRelation()
458
     */
459
    public function populateRelation($name, $records)
460
    {
461
        $this->related[$name] = $records;
462
    }
463
    
464
    /**
465
     * Setter for currentLanguage.
466
     *
467
     * @param $language
468
     * @return mixed
469
     */
470
    public static function setCurrentLanguage($language)
471
    {
472
        self::$currentLanguage = $language;
473
    }
474
    
475
    /**
476
     * Getter for currentLanguage.
477
     *
478
     * @return string
479
     */
480
    public static function getCurrentLanguage()
481
    {
482
        return self::$currentLanguage;
483
    }
484
    
485
    /**
486
     * Get value from language field according to current language.
487
     *
488
     * @param $field
489
     * @return mixed
490
     */
491
    protected function getValueFromLanguageField($field)
492
    {
493
        $key = $field . '_' . $this->getCurrentLanguage();
494
495
        return isset($this->fields[$key]) ? $this->fields[$key] : null;
496
    }
497
}
498