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\EAV;
9:
10: /**
11: * @license https://www.gnu.org/licenses/agpl-3.0.html AGPL-3.0
12: * @license https://afterlogic.com/products/common-licensing Afterlogic Software License
13: * @copyright Copyright (c) 2019, Afterlogic Corp.
14: *
15: * @package Api
16: */
17: class Entity
18: {
19: /**
20: * @var int
21: */
22: public $EntityId = 0;
23:
24: /**
25: * @var string
26: */
27: public $UUID;
28:
29: /**
30: * @var string
31: */
32: public $ModuleName;
33:
34: /**
35: *
36: * @var string
37: */
38: public $ParentType = null;
39:
40: /**
41: *
42: * @var string
43: */
44: public $ParentModuleName = null;
45:
46: /**
47: *
48: * @var string
49: */
50: public $ParentUUID = null;
51:
52: /**
53: * @var array
54: */
55: protected $aAttributes = [];
56:
57: /**
58: * @var array
59: *
60: * [
61: * 'AttributeName' => // name of attribute
62: * [
63: * 'string', // type of attribute
64: * '', // default value for attribute
65: * false, // is overrided attribute
66: * false // is restricted attribute
67: * ],
68: * ]
69: *
70: */
71: protected $aStaticMap = [];
72:
73: /**
74: * @var array
75: */
76: protected $aMap = null;
77:
78: /**
79: * @var string[]
80: */
81: protected static $aTypes = [
82: 'int',
83: 'string',
84: 'text',
85: 'bool',
86: 'datetime',
87: 'mediumblob',
88: 'double',
89: 'bigint',
90: 'nodb'
91: ];
92:
93: /**
94: * @var array
95: */
96: public static $aSystemAttributes = [
97: 'entityid' => 'int',
98: 'uuid' => 'string',
99: 'modulename' => 'string',
100: 'parentuuid' => 'string',
101: 'entitytype' => 'string'
102: ];
103:
104: /**
105: *
106: * @param string $sClassName
107: * @param string $sModuleName
108: * @return \Aurora\System\EAV\Entity
109: */
110: public static function createInstance($sClassName, $sModuleName = '')
111: {
112: return class_exists($sClassName) ? (new $sClassName($sModuleName)) : new self($sModuleName);
113: }
114:
115: /**
116: * @param string $sModuleName = ''
117: */
118: public function __construct($sModuleName = '')
119: {
120: $this->ModuleName = $sModuleName;
121: $this->UUID = self::generateUUID();
122:
123: $this->initDefaults();
124: }
125:
126: /**
127: * Undocumented function
128: *
129: * @return void
130: */
131: protected function initDefaults()
132: {
133: foreach ($this->getMap() as $sKey => $aMap) {
134: $oAttribute = $this->initAttribute($sKey, $aMap[1]);
135: if ($oAttribute) {
136: $oAttribute->IsDefault = true;
137: $this->setAttribute($oAttribute);
138: }
139: }
140: }
141:
142: /**
143: *
144: * @param string $sModuleName
145: * @return string
146: */
147: public function setModule($sModuleName)
148: {
149: return $this->ModuleName = $sModuleName;
150: }
151:
152: /**
153: *
154: * @return string
155: */
156: public function getName()
157: {
158: return \get_class($this);
159: }
160:
161: /**
162: *
163: * @return string
164: */
165: public function getModule()
166: {
167: return $this->ModuleName;
168: }
169:
170: public function isModuleDisabled($sModuleName)
171: {
172: $aDisabledModules = $this->getDisabledModules();
173:
174: return in_array($sModuleName, $aDisabledModules);
175: }
176:
177: public function getDisabledModules()
178: {
179: $sDisabledModules = isset($this->{'@DisabledModules'}) ? \trim($this->{'@DisabledModules'}) : '';
180: $aDisabledModules = !empty($sDisabledModules) ? [$sDisabledModules] : [];
181: if (substr_count($sDisabledModules, "|") > 0) {
182: $aDisabledModules = explode("|", $sDisabledModules);
183: }
184:
185: return $aDisabledModules;
186: }
187:
188: public function clearDisabledModules()
189: {
190: $this->{'@DisabledModules'} = '';
191: $this->saveAttribute('@DisabledModules');
192: }
193:
194: public function disableModule($sModuleName)
195: {
196: $aDisabledModules = $this->getDisabledModules();
197: if (!in_array($sModuleName, $aDisabledModules)) {
198: $aDisabledModules[] = $sModuleName;
199: // clear array from empty values
200: $aDisabledModules = array_filter($aDisabledModules, function ($var) {
201: return !empty($var);
202: });
203: $this->{'@DisabledModules'} = implode("|", $aDisabledModules);
204: $this->saveAttribute('@DisabledModules');
205: }
206: }
207:
208: public function disableModules($aModules)
209: {
210: $aDisabledModules = $this->getDisabledModules();
211: foreach ($aModules as $sModuleName) {
212: if (!in_array($sModuleName, $aDisabledModules)) {
213: $aDisabledModules[] = $sModuleName;
214: // clear array from empty values
215: $aDisabledModules = array_filter($aDisabledModules, function ($var) {
216: return !empty($var);
217: });
218: $this->{'@DisabledModules'} = implode("|", $aDisabledModules);
219: }
220: }
221: $this->saveAttribute('@DisabledModules');
222: }
223:
224: public function enableModule($sModuleName)
225: {
226: $aDisabledModules = $this->getDisabledModules();
227:
228: if (($iKey = array_search($sModuleName, $aDisabledModules)) !== false) {
229: unset($aDisabledModules[$iKey]);
230: $this->{'@DisabledModules'} = implode("|", $aDisabledModules);
231: $this->saveAttribute('@DisabledModules');
232: }
233: }
234:
235: public function enableModules($aModules)
236: {
237: $aDisabledModules = $this->getDisabledModules();
238:
239: foreach ($aModules as $sModuleName) {
240: if (($iKey = array_search($sModuleName, $aDisabledModules)) !== false) {
241: unset($aDisabledModules[$iKey]);
242: $this->{'@DisabledModules'} = implode("|", $aDisabledModules);
243: }
244: }
245: $this->saveAttribute('@DisabledModules');
246: }
247:
248: /**
249: * Returns a pseudo-random v4 UUID
250: *
251: * This function is based on a comment by Andrew Moore on php.net
252: *
253: * @see http://www.php.net/manual/en/function.uniqid.php#94959
254: * @return string
255: */
256: public static function generateUUID()
257: {
258: return sprintf(
259: '%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
260:
261: // 32 bits for "time_low"
262: mt_rand(0, 0xffff),
263: mt_rand(0, 0xffff),
264:
265: // 16 bits for "time_mid"
266: mt_rand(0, 0xffff),
267:
268: // 16 bits for "time_hi_and_version",
269: // four most significant bits holds version number 4
270: mt_rand(0, 0x0fff) | 0x4000,
271:
272: // 16 bits, 8 bits for "clk_seq_hi_res",
273: // 8 bits for "clk_seq_low",
274: // two most significant bits holds zero and one for variant DCE1.1
275: mt_rand(0, 0x3fff) | 0x8000,
276:
277: // 48 bits for "node"
278: mt_rand(0, 0xffff),
279: mt_rand(0, 0xffff),
280: mt_rand(0, 0xffff)
281: );
282: }
283:
284: /**
285: * Checks if a string is a valid UUID.
286: *
287: * @param string $uuid
288: * @return bool
289: */
290: public static function validateUUID($uuid)
291: {
292: return preg_match(
293: '/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/i',
294: $uuid
295: ) == true;
296: }
297:
298: /**
299: * @return array
300: */
301: public static function getTypes()
302: {
303: return self::$aTypes;
304: }
305:
306: /**
307: * @param array $aValues
308: * @return void
309: */
310: public function setValues($aValues)
311: {
312: foreach ($aValues as $sKey => $mValue) {
313: $this->{$sKey} = $mValue;
314: }
315: }
316:
317: /**
318: * @param string $sPropertyName
319: * @return array
320: */
321: public function isStringAttribute($sPropertyName)
322: {
323: return in_array(
324: $this->getType($sPropertyName),
325: [
326: 'string',
327: 'text',
328: 'datetime',
329: 'mediumblob'
330: ]
331: );
332: }
333:
334: public function isSystemAttribute($sAttribute)
335: {
336: return in_array(strtolower($sAttribute), array_keys(self::$aSystemAttributes));
337: }
338:
339: /**
340: * @param string $sPropertyName
341: * @return array
342: */
343: public function isEncryptedAttribute($sPropertyName)
344: {
345: $bResult = false;
346: $aMapItem = $this->getMapItem($sPropertyName);
347: if ($aMapItem !== null && is_array($aMapItem)) {
348: $bResult = ($aMapItem[0] === 'encrypted');
349: }
350:
351: return $bResult;
352: }
353:
354: /**
355: * @param string $sPropertyName
356: * @return bool
357: */
358: public function isExtendedAttribute($sPropertyName)
359: {
360: $bResult = false;
361: $aMapItem = $this->getMapItem($sPropertyName);
362: if ($aMapItem !== null && is_array($aMapItem)) {
363: $bResult = (isset($aMapItem['@Extended']) && $aMapItem['@Extended'] === true) ;
364: }
365:
366: return $bResult;
367: }
368:
369: /**
370: * @param string $sPropertyName
371: * @return bool
372: */
373: public function canInheridAttribute($sPropertyName)
374: {
375: $bResult = false;
376: $aMapItem = $this->getMapItem($sPropertyName);
377: if ($aMapItem !== null && is_array($aMapItem)) {
378: $bResult = (isset($aMapItem[3]) && $aMapItem[3] === true) ;
379: }
380:
381: return $bResult;
382: }
383:
384: /**
385: * @param string $sAttribute
386: * @param mixed $mValue
387: * @return void
388: */
389: public function setAttributeValue($sAttribute, $mValue)
390: {
391: $oAttribute = $this->initAttribute($sAttribute, $mValue);
392: $oAttribute->IsDefault = false;
393: $this->setAttribute($oAttribute);
394: }
395:
396: /**
397: * @param string $sName
398: *
399: * @throws Exception
400: *
401: * @return mixed
402: */
403: public function getAttributeValue($sName)
404: {
405: $mValue = null;
406: $oAttribute = $this->getAttribute($sName);
407: if ($oAttribute instanceof Attribute) {
408: $oAttribute->setType($oAttribute->Type);
409: if ($oAttribute->IsEncrypt) {
410: $oAttribute->Decrypt();
411: }
412: $mValue = $oAttribute->Value;
413:
414: if ($this->isDefaultValue($sName, $mValue) && isset($this->ParentType)) {
415: if (is_subclass_of($this->ParentType, \Aurora\System\EAV\Entity::class)) {
416: if (isset($this->ParentUUID)) {
417: $oEntity = \Aurora\System\Managers\Eav::getInstance()->getEntity($this->ParentUUID);
418: if (isset($oEntity) && $oEntity->canInheridAttribute($sName)) {
419: $mValue = $oEntity->{$sName};
420: $oAttribute->Inherited = true;
421: }
422: }
423: } elseif (is_subclass_of($this->ParentType, \Aurora\System\AbstractSettings::class)) {
424: if ($this->ParentType === \Aurora\System\Settings::class) {
425: $mValue = \Aurora\System\Api::GetSettings()->GetValue($sName);
426: $oAttribute->Inherited = true;
427: }
428: if ($this->ParentType === \Aurora\System\Module\Settings::class) {
429: if ($this->isExtendedAttribute($sName)) {
430: list($sModuleName, $sName) = \explode('::', $sName);
431: } else {
432: $sModuleName = $this->ModuleName;
433: }
434: $oModule = \Aurora\System\Api::GetModule($sModuleName);
435: if ($oModule instanceof \Aurora\System\Module\AbstractModule) {
436: $mValue = $oModule->getConfig($sName, $mValue);
437: $oAttribute->Inherited = true;
438: }
439: }
440: }
441: }
442: } else {
443: $aMapItem = $this->getMapItem($sName);
444: if (isset($aMapItem)) {
445: $oAttribute = Attribute::createInstance($sName, $aMapItem[1], $aMapItem[0]);
446: if ($oAttribute->IsEncrypt) {
447: $oAttribute->Decrypt();
448: }
449: $this->setAttribute($oAttribute);
450: $mValue = $oAttribute->Value;
451: }
452: }
453:
454: return $mValue;
455: }
456:
457: /**
458: * @param string $sName
459: * @return bool
460: */
461: public function __isset($sName)
462: {
463: return ($this->getMapItem($sName) !== null) || isset($this->aAttributes[$sName]);
464: }
465:
466: /**
467: * @param string $sAttribute
468: * @param mixed $mValue
469: * @return void
470: */
471: public function __set($sAttribute, $mValue)
472: {
473: $this->setAttributeValue($sAttribute, $mValue);
474: }
475:
476: /**
477: * @param string $sName
478: *
479: * @throws Exception
480: *
481: * @return mixed
482: */
483: public function __get($sName)
484: {
485: return $this->getAttributeValue($sName);
486: }
487:
488: /**
489: *
490: * @param type $aProperties
491: */
492: public function populate($aProperties)
493: {
494: $aMap = $this->getMap();
495: foreach ($aProperties as $sKey => $mValue) {
496: if (isset($aMap[$sKey])) {
497: $this->setAttributeValue($sKey, $mValue);
498: }
499: }
500: }
501:
502: public function resetToDefaults()
503: {
504: foreach ($this->aAttributes as $oAttrinbute) {
505: $this->setAttributeValue($oAttrinbute->Name, $this->getDefaultValue($oAttrinbute->Name));
506: }
507: }
508:
509: public function resetToDefault($sAttribute)
510: {
511: $mResult = \Aurora\System\Managers\Eav::getInstance()->deleteAttribute(
512: $this->getType($sAttribute),
513: $this->EntityId,
514: $sAttribute
515: );
516:
517: if ($mResult) {
518: $this->{$sAttribute} = $this->getDefaultValue($sAttribute);
519: }
520: }
521:
522: /**
523: * @return string
524: */
525: public function getType($sAttribute)
526: {
527: $mType = 'string';
528:
529: if ($this->isSystemAttribute($sAttribute)) {
530: if (isset(self::$aSystemAttributes[\strtolower($sAttribute)])) {
531: $mType = self::$aSystemAttributes[\strtolower($sAttribute)];
532: }
533: } else {
534: $aMap = $this->getMap();
535: if (isset($aMap[$sAttribute])) {
536: $mType = $aMap[$sAttribute][0];
537: if ($mType === 'encrypted') {
538: $mType = 'string';
539: }
540: }
541: }
542:
543: return $mType;
544: }
545:
546: /**
547: *
548: * @param type $sAttribute
549: * @param type $mValue
550: * @return type
551: */
552: public function isDefaultValue($sAttribute, $mValue)
553: {
554: $oAttribute = $this->getAttribute($sAttribute);
555: return ($oAttribute && $oAttribute->IsDefault);
556: }
557:
558: /**
559: *
560: * @param type $sAttribute
561: * @return type
562: */
563: public function getDefaultValue($sAttribute)
564: {
565: $mResult = null;
566: $aMap = $this->getMap();
567: if (isset($aMap[$sAttribute])) {
568: $mResult = $aMap[$sAttribute][1];
569: }
570:
571: return $mResult;
572: }
573:
574: /**
575: *
576: * @param type $sAttribute
577: * @return type
578: */
579: public function isOverridedAttribute($sAttribute)
580: {
581: $bOverride = false;
582: $oAttribute = $this->getAttribute($sAttribute);
583: if ($oAttribute instanceof Attribute) {
584: $bOverride = $oAttribute->Override;
585: }
586: $aMap = $this->getMap();
587: return ((isset($aMap[$sAttribute]) && isset($aMap[$sAttribute][2]) && $aMap[$sAttribute][2] === true) || $bOverride);
588: }
589:
590: /**
591: * @return bool
592: */
593: public function validate()
594: {
595: return true;
596: }
597:
598: /**
599: * @return array
600: */
601: public function getMap()
602: {
603: $aStaticMap = $this->getStaticMap();
604: $aExtendedObject = \Aurora\System\ObjectExtender::getInstance()->getObject($this->getName());
605: $this->aMap = array_merge(
606: $aStaticMap,
607: $aExtendedObject
608: );
609:
610: return $this->aMap;
611: }
612:
613: /**
614: * @return array
615: */
616: protected function getMapItem($sName)
617: {
618: $aMap = $this->getMap();
619: return isset($aMap[$sName]) ? $aMap[$sName] : null;
620: }
621:
622: /**
623: * @param string $sAttribute
624: * @param mixed $mValue
625: * @return Attribute
626: */
627: public function initAttribute($sAttribute, $mValue)
628: {
629: if (!($mValue instanceof Attribute)) {
630: if ($this->issetAttribute($sAttribute)) {
631: $oAttribute = $this->getAttribute($sAttribute);
632: if ($oAttribute->Encrypted) {
633: $oAttribute->Encrypted = false;
634: }
635: $oAttribute->Value = $mValue;
636: $oAttribute->setType($oAttribute->Type);
637: $mValue = $oAttribute;
638: } else {
639: $mValue = Attribute::createInstance(
640: $sAttribute,
641: $mValue,
642: $this->getType($sAttribute),
643: $this->isEncryptedAttribute($sAttribute),
644: $this->EntityId,
645: false,
646: $this->isExtendedAttribute($sAttribute)
647: );
648: }
649: }
650: if ($mValue->IsEncrypt) {
651: $mValue->Encrypt();
652: }
653: $mValue->Inherited = false;
654:
655: return $mValue;
656: }
657:
658: /**
659: * @return bool
660: */
661: public function issetAttribute($sAttributeName)
662: {
663: return isset($this->aAttributes[$sAttributeName]);
664: }
665:
666: /**
667: *
668: * @param \Aurora\System\EAV\Attribute $oAttribute
669: */
670: private function setAttribute(Attribute $oAttribute)
671: {
672: if (!$this->isSystemAttribute($oAttribute->Name)) {
673: $oAttribute->EntityId = $this->EntityId;
674: $this->aAttributes[$oAttribute->Name] = $oAttribute;
675: }
676: }
677:
678: /**
679: *
680: * @param array $aAttributes
681: */
682: public function setOverridedAttributes($aAttributes)
683: {
684: foreach ($aAttributes as $sAttribute) {
685: $oAttribute = $this->getAttribute($sAttribute);
686: if ($oAttribute instanceof Attribute) {
687: $oAttribute->Override = true;
688: }
689: }
690: }
691:
692: /**
693: * @return \Aurora\System\EAV\Attribute
694: */
695: public function getAttribute($sAttributeName)
696: {
697: return isset($this->aAttributes[$sAttributeName]) ? $this->aAttributes[$sAttributeName] : false;
698: }
699:
700: /**
701: * @param bool
702: * @return array
703: */
704: public function getAttributes($bOnlyOverrided = false)
705: {
706: $aAttributes = array();
707: if ($bOnlyOverrided) {
708: $aAttributes = $this->getOverridedAttributes();
709: } else {
710: $aAttributes = $this->aAttributes;
711: }
712:
713: return $aAttributes;
714: }
715:
716: public function getAttributesType()
717: {
718: $this->getMap();
719: }
720:
721: /**
722: * @param bool
723: * @return array
724: */
725: public function getOverridedAttributes()
726: {
727: $self = $this;
728: return array_filter(
729: $this->aAttributes,
730: function ($oAttribute) use ($self) {
731: return $self->isOverridedAttribute($oAttribute->Name);
732: }
733: );
734: }
735:
736: /**
737: * @return array
738: */
739: public function getAttributesKeys()
740: {
741: $this->aAttributes['@DisabledModules'] = '';
742: return array_keys($this->aAttributes);
743: }
744:
745: /**
746: * @return int
747: */
748: public function countAttributes()
749: {
750: return count($this->aAttributes);
751: }
752:
753: /**
754: * @param array
755: */
756: public function setStaticMap()
757: {
758: foreach ($this->getMap() as $sKey => $aMap) {
759: $oAttribute = $this->initAttribute($sKey, $aMap[1]);
760: if ($oAttribute) {
761: $this->setAttribute($oAttribute);
762: }
763: }
764: }
765:
766: /**
767: * @return array
768: */
769: public function getStaticMap()
770: {
771: return is_array($this->aStaticMap) ? $this->aStaticMap : [];
772: }
773:
774: /**
775: * @return array
776: */
777: public function toArray()
778: {
779: $aResult = [
780: 'EntityId' => $this->EntityId,
781: 'UUID' => $this->UUID,
782: 'ParentUUID' => $this->ParentUUID,
783: 'ModuleName' => $this->ModuleName
784: ];
785:
786: foreach ($this->aAttributes as $oAttribute) {
787: if (!$this->isRestrictedAttribute($oAttribute->Name)) {
788: if ($oAttribute->Encrypted && !empty($this->{$oAttribute->Name})) {
789: // Dummy encrypted attribute could be passed to client side by toResponseArray method.
790: $aResult[$oAttribute->Name] = '*****';
791: } else {
792: $aResult[$oAttribute->Name] = $this->{$oAttribute->Name};
793: }
794: }
795: }
796: return $aResult;
797: }
798:
799: /**
800: * alias to toArray
801: *
802: * @return array
803: */
804: public function toResponseArray()
805: {
806: return $this->toArray();
807: }
808:
809: public static function extend($sModuleName, $aMap)
810: {
811: \Aurora\System\ObjectExtender::getInstance()->extend($sModuleName, static::class, $aMap);
812: }
813:
814: public function save()
815: {
816: return \Aurora\System\Managers\Eav::getInstance()->saveEntity($this);
817: }
818:
819: public function delete()
820: {
821: return \Aurora\System\Managers\Eav::getInstance()->deleteEntity($this->EntityId);
822: }
823:
824: public function saveAttribute($sName)
825: {
826: $bResult = false;
827:
828: $oAttribute = $this->getAttribute($sName);
829: if ($oAttribute instanceof Attribute) {
830: $bResult = $oAttribute->save($this);
831: }
832:
833: return $bResult;
834: }
835:
836: public function saveAttributes($aAttributes)
837: {
838: $bResult = false;
839:
840: $aAttributeOjects = [];
841: foreach ($aAttributes as $sName) {
842: $aAttributeOjects[$sName] = $this->getAttribute($sName);
843: }
844:
845: if (count($aAttributeOjects) > 0) {
846: $bResult = \Aurora\System\Managers\Eav::getInstance()->setAttributes($this, $aAttributeOjects);
847: }
848:
849: return $bResult;
850: }
851:
852: public function isNodbAttribute($sAttributeName)
853: {
854: return $this->getType($sAttributeName) === 'nodb';
855: }
856:
857: public function isRestrictedAttribute($sAttributeName)
858: {
859: $aMap = $this->getMap();
860: return ((isset($aMap[$sAttributeName]) && isset($aMap[$sAttributeName][3]) && $aMap[$sAttributeName][3] === true));
861: }
862:
863: public function populateFromDB($aEntity)
864: {
865: foreach ($aEntity as $sAttrKey => $mValue) {
866: if (!$this->isSystemAttribute($sAttrKey)) {
867: $bIsEncrypted = $this->isEncryptedAttribute($sAttrKey);
868: $oAttribute = \Aurora\System\EAV\Attribute::createInstance(
869: $sAttrKey,
870: $mValue,
871: $this->getType($sAttrKey),
872: $bIsEncrypted,
873: $this->EntityId
874: );
875: $oAttribute->Encrypted = $bIsEncrypted;
876: $oAttribute->CanInherit = $this->canInheridAttribute($sAttrKey);
877: $this->{$sAttrKey} = $oAttribute;
878: } else {
879: settype($mValue, $this->getType($sAttrKey));
880: $this->{$sAttrKey} = $mValue;
881: }
882: }
883: }
884: }
885: