1: <?php
2: /**
3: * This code is licensed under AGPLv3 license or Afterlogic Software License
4: * if commercial version of the product was purchased.
5: * For full statements of the licenses see LICENSE-AFTERLOGIC and LICENSE-AGPL3 files.
6: */
7:
8: namespace Aurora\System\Classes;
9:
10: use Aurora\System\EventEmitter;
11: use Aurora\System\Validator;
12: use Illuminate\Database\Eloquent\Model as Eloquent;
13: use Illuminate\Database\Eloquent\Relations\BelongsTo;
14:
15: class Model extends Eloquent
16: {
17: use DisabledModulesTrait;
18:
19: /**
20: * The module name of the model.
21: *
22: * @var string
23: */
24: protected $moduleName = '';
25:
26: /**
27: * The primary key for the model.
28: *
29: * @var string
30: */
31: protected $primaryKey = 'Id';
32:
33: /**
34: * The parent type for the model.
35: *
36: * @var string
37: */
38: protected $parentType = null;
39:
40: /**
41: * The parent key for the model.
42: *
43: * @var string
44: */
45: protected $parentKey = null;
46:
47: /**
48: *
49: * @var string
50: */
51: protected $foreignModel = '';
52:
53: /**
54: *
55: * @var string
56: */
57: protected $foreignModelIdColumn = '';
58:
59: /**
60: * Inherited attributes.
61: *
62: * @var array
63: */
64: protected $parentInheritedAttributes = [];
65:
66: /**
67: * @var array
68: */
69: protected $validationRules = [];
70:
71: /**
72: * The name of the "created at" column.
73: *
74: * @var string|null
75: */
76: public const CREATED_AT = 'CreatedAt';
77:
78: /**
79: * The name of the "updated at" column.
80: *
81: * @var string|null
82: */
83: public const UPDATED_AT = 'UpdatedAt';
84:
85: protected function castAttribute($key, $value)
86: {
87: if (is_null($value)) {
88: switch ($this->getCastType($key)) {
89: case 'array':
90: $value = [];
91: break;
92:
93: case 'string':
94: $value = '';
95: break;
96:
97: case 'boolean':
98: $value = false;
99: break;
100: }
101:
102: return $value;
103: }
104:
105: return parent::castAttribute($key, $value);
106: }
107:
108: public function getInheritedAttributes()
109: {
110: $aArgs = ['ClassName' => get_class($this)];
111: $aResult = [];
112:
113: \Aurora\System\EventEmitter::getInstance()->emit($this->moduleName, 'getInheritedAttributes', $aArgs, $aResult);
114: return $aResult;
115: }
116:
117: /**
118: * @param string $key
119: * @return bool
120: */
121: public function isInheritedAttribute($key)
122: {
123: $aInheritedAttributes = $this->getInheritedAttributes();
124: return in_array($key, $aInheritedAttributes);
125: }
126:
127: /**
128: * @param string $key
129: * @return mixed
130: */
131: public function getInheritedValue($key)
132: {
133: $value = null;
134: $parent = $this->parent();
135: if ($parent instanceof BelongsTo && isset($this->parent)) {
136: $value = $this->parent->$key;
137: }
138: if ($value === null && is_subclass_of($this->parentType, \Aurora\System\AbstractSettings::class)) {
139: if ($this->parentType === \Aurora\System\Settings::class) {
140: $value = \Aurora\System\Api::GetSettings()->GetValue($key);
141: }
142: if ($this->parentType === \Aurora\System\Module\Settings::class) {
143: if (strpos($key, '::') !== false) {
144: list($moduleName, $key) = \explode('::', $key);
145: } else {
146: $moduleName = $this->moduleName;
147: }
148: $oModule = \Aurora\System\Api::GetModule($moduleName);
149: if ($oModule instanceof \Aurora\System\Module\AbstractModule) {
150: $value = $oModule->getConfig($key, $value);
151: }
152: }
153: }
154:
155: return $value;
156: }
157:
158: public function getOrphanIds()
159: {
160: if (!$this->foreignModel || !$this->foreignModelIdColumn) {
161: return ['status' => -1, 'message' => 'Foreign field doesn\'t exist'];
162: }
163: $tableName = $this->getTable();
164: $foreignObject = new $this->foreignModel();
165: $foreignTable = $foreignObject->getTable();
166: $foreignPK = $foreignObject->primaryKey;
167:
168: $orphanIds = self::pluck($this->primaryKey)->diff(
169: self::leftJoin($foreignTable, "$tableName.$this->foreignModelIdColumn", '=', "$foreignTable.$foreignPK")->whereNotNull("$foreignTable.$foreignPK")->pluck("$tableName.$this->primaryKey")
170: )->all();
171:
172: $message = $orphanIds ? "$tableName table has orphans: " . count($orphanIds) . "." : "Orphans were not found.";
173: $oResult = ['status' => $orphanIds ? 1 : 0, 'message' => $message, 'orphansIds' => $orphanIds];
174:
175: return $oResult;
176: }
177:
178: /**
179: * Dynamically set attributes on the model.
180: *
181: * @param string $key
182: * @param mixed $value
183: * @return void
184: */
185: public function __set($key, $value)
186: {
187: if (strpos($key, '::') !== false) {
188: $this->setExtendedProp($key, $value);
189: } else {
190: parent::__set($key, $value);
191: }
192: }
193:
194: public function getEntityIdAttribute()
195: {
196: return $this->Id;
197: }
198:
199: /**
200: * Dynamically retrieve attributes on the model.
201: *
202: * @param string $key
203: * @return mixed
204: */
205: public function __get($key)
206: {
207: $value = parent::__get($key);
208: if ($key !== 'Properties') {
209: if (isset($this->Properties[$key])) {
210: $value = $this->Properties[$key];
211: }
212: if ($value === null && $this->isInheritedAttribute($key)) {
213: $value = $this->getInheritedValue($key);
214: }
215: }
216: return $value;
217: }
218:
219: /**
220: * Determine if an attribute or relation exists on the model.
221: *
222: * @param string $key
223: * @return bool
224: */
225: public function __isset($key)
226: {
227: $value = parent::__isset($key);
228: if (!$value) {
229: $value = isset($this->Properties[$key]);
230: }
231:
232: return $value;
233: }
234:
235: /**
236: * Unset an attribute on the model.
237: *
238: * @param string $key
239: * @return void
240: */
241: public function __unset($key)
242: {
243: parent::__unset($key);
244: if (isset($this->Properties[$key])) {
245: unset($this->Properties[$key]);
246: }
247: }
248:
249: public function getExtendedProp($key, $default = null)
250: {
251: $mResult = null;
252: if (isset($this->Properties[$key])) {
253: $mResult = $this->Properties[$key];
254: } else {
255: $mResult = $default;
256: }
257:
258: return $mResult;
259: }
260:
261: public function setExtendedProp($key, $value)
262: {
263: $properties = $this->Properties;
264: $properties[$key] = $value;
265: $this->Properties = $properties;
266: }
267:
268: public function unsetExtendedProp($key)
269: {
270: $properties = $this->Properties;
271: if (isset($properties[$key])) {
272: unset($properties[$key]);
273: }
274: $this->Properties = $properties;
275: }
276:
277: public function setExtendedProps($props)
278: {
279: $properties = is_array($this->Properties) ? $this->Properties : [];
280: $this->Properties = array_merge($properties, $props);
281: }
282:
283: /**
284: * @return BelongsTo
285: */
286: public function parent()
287: {
288: $result = null;
289: if (isset($this->parentType) && is_subclass_of($this->parentType, \Aurora\System\Classes\Model::class)) {
290: $result = $this->belongsTo($this->parentType, $this->parentKey, $this->primaryKey);
291: }
292:
293: return $result;
294: }
295:
296: protected function isEncryptAttribute($attributeName)
297: {
298: $result = false;
299: $casts = $this->getCasts();
300: if (isset($casts[$attributeName]) && $casts[$attributeName] === \Aurora\System\Casts\Encrypt::class) {
301: $result = true;
302: }
303:
304: return $result;
305: }
306:
307: /**
308: * @return array
309: */
310: public function toResponseArray()
311: {
312: $array = $this->toArray();
313:
314: if (!isset($array['UUID'])) {
315: $array['UUID'] = '';
316: }
317: $array['ParentUUID'] = '';
318: $array['ModuleName'] = $this->moduleName;
319:
320: $parentInheritedAttributes = $this->getInheritedAttributes();
321: if (count($parentInheritedAttributes) > 0) {
322: foreach ($parentInheritedAttributes as $attribute) {
323: $value = null;
324: if (isset($array[$attribute])) {
325: $value = $array[$attribute];
326: }
327: if ($value === null) {
328: $array[$attribute] = $this->getInheritedValue($attribute);
329: }
330: }
331: }
332: if (isset($array['Properties'])) {
333: foreach ($array['Properties'] as $key => $value) {
334: if ($value !== null) {
335: $aArgs = ['Model' => $this, 'PropertyName' => $key];
336: EventEmitter::getInstance()->emit('System', 'CastExtendedProp', $aArgs, $value);
337: $array[$key] = $value;
338: }
339: }
340: unset($array['Properties']);
341: }
342:
343: foreach ($array as $key => $value) {
344: if ($this->isEncryptAttribute($key)) {
345: $array[$key] = '*****';
346: }
347: }
348:
349: return $array;
350: }
351:
352: public function validate()
353: {
354: Validator::validate($this->getAttributes(), $this->validationRules);
355:
356: return true;
357: }
358:
359: /**
360: * Returns a pseudo-random v4 UUID
361: *
362: * This function is based on a comment by Andrew Moore on php.net
363: *
364: * @see http://www.php.net/manual/en/function.uniqid.php#94959
365: * @return string
366: */
367: public function generateUUID()
368: {
369: return sprintf(
370: '%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
371:
372: // 32 bits for "time_low"
373: mt_rand(0, 0xffff),
374: mt_rand(0, 0xffff),
375:
376: // 16 bits for "time_mid"
377: mt_rand(0, 0xffff),
378:
379: // 16 bits for "time_hi_and_version",
380: // four most significant bits holds version number 4
381: mt_rand(0, 0x0fff) | 0x4000,
382:
383: // 16 bits, 8 bits for "clk_seq_hi_res",
384: // 8 bits for "clk_seq_low",
385: // two most significant bits holds zero and one for variant DCE1.1
386: mt_rand(0, 0x3fff) | 0x8000,
387:
388: // 48 bits for "node"
389: mt_rand(0, 0xffff),
390: mt_rand(0, 0xffff),
391: mt_rand(0, 0xffff)
392: );
393: }
394:
395: /**
396: * Checks if a string is a valid UUID.
397: *
398: * @param string $uuid
399: * @return bool
400: */
401: public function validateUUID($uuid)
402: {
403: return preg_match(
404: '/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/i',
405: $uuid
406: ) == true;
407: }
408:
409: /**
410: *
411: * @param type $aProperties
412: */
413: public function populate($aProperties)
414: {
415: foreach ($aProperties as $key => $value) {
416: if (in_array($key, $this->fillable) || strpos($key, '::') !== false) {
417: $this->$key = $value;
418: }
419: }
420: }
421:
422: /**
423: *
424: * @return string
425: */
426: public function getName()
427: {
428: return \get_class($this);
429: }
430:
431: /**
432: * Save the model to the database.
433: *
434: * @param array $options
435: * @return bool
436: */
437: public function save(array $options = [])
438: {
439: if ($this->validate()) {
440: return parent::save($options);
441: }
442:
443: return false;
444: }
445: }
446: