/home/ivoiecob/email.hirewise-va.com/system/Classes/Model.php
<?php
/**
 * This code is licensed under AGPLv3 license or Afterlogic Software License
 * if commercial version of the product was purchased.
 * For full statements of the licenses see LICENSE-AFTERLOGIC and LICENSE-AGPL3 files.
 */

namespace Aurora\System\Classes;

use Aurora\System\EventEmitter;
use Aurora\System\Validator;
use Illuminate\Database\Eloquent\Model as Eloquent;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

/**
 * Aurora\System\Classes\Model
 *
 * @property int    $Id             Object primary key
 * @property array  $Properties     Custom properties for use by other modules
 *
 * @property-read mixed $entity_id
 * @method static \Illuminate\Database\Eloquent\Builder|\Aurora\System\Classes\Model firstWhere(Closure|string|array|\Illuminate\Database\Query\Expression $column, mixed $operator = null, mixed $value = null, string $boolean = 'and')
 * @method static \Illuminate\Database\Eloquent\Builder|Model newModelQuery()
 * @method static \Illuminate\Database\Eloquent\Builder|Model newQuery()
 * @method static \Illuminate\Database\Eloquent\Builder|Model query()
 * @method static \Illuminate\Database\Eloquent\Builder|\Aurora\System\Classes\Model where(Closure|string|array|\Illuminate\Database\Query\Expression $column, mixed $operator = null, mixed $value = null, string $boolean = 'and')
 * @method static \Illuminate\Database\Eloquent\Builder|\Aurora\System\Classes\Model whereIn(string $column, mixed $values, string $boolean = 'and', bool $not = false)
 * @mixin \Eloquent
 */
class Model extends Eloquent
{
    use DisabledModulesTrait;

    /**
     * The module name of the model.
     *
     * @var string
     */
    protected $moduleName = '';

    /**
     * The primary key for the model.
     *
     * @var string
     */
    protected $primaryKey = 'Id';

    /**
     * The parent type for the model.
     *
     * @var string
     */
    protected $parentType = null;

    /**
     * The parent key for the model.
     *
     * @var string
     */
    protected $parentKey = null;

    /**
     *
     * @var string
     */
    protected $foreignModel = '';

    /**
     *
     * @var string
     */
    protected $foreignModelIdColumn  = '';

    /**
     *
     * @var string
     */
    protected $foreignModelPrimaryKey  = '';

    /**
     * Inherited attributes.
     *
     * @var array
     */
    protected $parentInheritedAttributes = [];

    /**
     * @var array
     */
    protected $validationRules = [];

    /**
     * @var array
     */
    protected $validationMessages = [];

    /**
     * The name of the "created at" column.
     *
     * @var string|null
     */
    public const CREATED_AT = 'CreatedAt';

    /**
     * The name of the "updated at" column.
     *
     * @var string|null
     */
    public const UPDATED_AT = 'UpdatedAt';

    protected static $inheritedAttributes = [];

    protected function castAttribute($key, $value)
    {
        if (is_null($value)) {
            switch ($this->getCastType($key)) {
                case 'array':
                    $value = [];
                    break;

                case 'string':
                    $value = '';
                    break;

                case 'boolean':
                    $value = false;
                    break;
            }

            return $value;
        }

        return parent::castAttribute($key, $value);
    }

    public function getInheritedAttributes()
    {
        return InheritedAttributes::getAttributes(static::class);
    }

    /**
     * @param  string  $key
     * @return bool
     */
    public function isInheritedAttribute($key)
    {
        return InheritedAttributes::hasAttribute(static::class, $key);
    }

    /**
     * @param  string  $key
     * @return mixed
     */
    public function getInheritedValue($key)
    {
        $value = null;
        $parent = $this->parent();
        if ($parent instanceof BelongsTo && isset($this->parent)) {
            $value = $this->parent->$key;
        }
        if ($value === null && is_subclass_of($this->parentType, \Aurora\System\AbstractSettings::class)) {
            if ($this->parentType === \Aurora\System\Settings::class) {
                $value = \Aurora\System\Api::GetSettings()->GetValue($key);
            }
            if ($this->parentType === \Aurora\System\Module\Settings::class) {
                if (strpos($key, '::') !== false) {
                    [$moduleName, $key] = \explode('::', $key);
                } else {
                    $moduleName = $this->moduleName;
                }
                $oModule = \Aurora\System\Api::GetModule($moduleName);
                if ($oModule instanceof \Aurora\System\Module\AbstractModule) {
                    $value = $oModule->getConfig($key, $value);
                }
            }
        }

        return $value;
    }

    public function getOrphanIds()
    {
        if (!$this->foreignModel || !$this->foreignModelIdColumn) {
            return ['status' => -1, 'message' => 'Foreign field doesn\'t exist'];
        }

        $container = \Aurora\System\Api::GetContainer();
        $connection = $container['capsule']->getConnection($this->getConnectionName());
        $tableName = $this->getTable();
        $foreignObject = new $this->foreignModel();
        $foreignConnection = $container['capsule']->getConnection($foreignObject->getConnectionName());
        $foreignTable = $foreignObject->getTable();

        // Adding prefix is required because current module work with custom DB connection without prefix. See MtaConnector module
        if ($connection->getName() != 'default' && $foreignConnection->getName() == 'default') {
            $foreignTable = $foreignConnection->getTablePrefix() . $foreignTable;
        }

        $foreignPK = !empty($this->foreignModelPrimaryKey) ? $this->foreignModelPrimaryKey : $foreignObject->primaryKey;

        $query = self::query();
        if ($this->foreignModelIdColumn === 'UserId' || $this->foreignModelIdColumn === 'IdUser') {
            $query = $query->where("$tableName.$this->foreignModelIdColumn", '<>', -1);
        }

        $orphanIds = $query->pluck($this->primaryKey)->diff(
            self::query()->leftJoin($foreignTable, "$tableName.$this->foreignModelIdColumn", '=', "$foreignTable.$foreignPK")
                ->whereNotNull("$foreignTable.$foreignPK")->pluck("$tableName.$this->primaryKey")
        )->all();

        $message = $orphanIds ? "$tableName table has orphans: " . count($orphanIds) . "." : "Orphans were not found.";
        $oResult = ['status' => $orphanIds ? 1 : 0, 'message' => $message, 'orphansIds' => $orphanIds];

        return $oResult;
    }

    /**
     * Dynamically set attributes on the model.
     *
     * @param  string  $key
     * @param  mixed  $value
     * @return void
     */
    public function __set($key, $value)
    {
        if (strpos($key, '::') !== false) {
            $this->setExtendedProp($key, $value);
        } else {
            parent::__set($key, $value);
        }
    }

    public function getEntityIdAttribute()
    {
        return $this->Id;
    }

    /**
     * Dynamically retrieve attributes on the model.
     *
     * @param  string  $key
     * @return mixed
     */
    public function __get($key)
    {
        $value = parent::__get($key);
        if ($key !== 'Properties') {
            if (isset($this->Properties[$key])) {
                $value = $this->Properties[$key];
            }
            if ($value === null && $this->isInheritedAttribute($key)) {
                $value = $this->getInheritedValue($key);
            }
        }
        return $value;
    }

    /**
     * Determine if an attribute or relation exists on the model.
     *
     * @param  string  $key
     * @return bool
     */
    public function __isset($key)
    {
        $value = parent::__isset($key);
        if (!$value) {
            $value = isset($this->Properties[$key]);
        }

        return $value;
    }

    /**
     * Unset an attribute on the model.
     *
     * @param  string  $key
     * @return void
     */
    public function __unset($key)
    {
        parent::__unset($key);
        if (isset($this->Properties[$key])) {
            unset($this->Properties[$key]);
        }
    }

    public function getExtendedProp($key, $default = null)
    {
        $mResult = null;
        if (isset($this->Properties[$key])) {
            $mResult = $this->Properties[$key];
        } else {
            $mResult = ($this->isInheritedAttribute($key)) ? $this->getInheritedValue($key) : $default;
        }

        return $mResult;
    }

    public function getExtendedProps()
    {
        return $this->Properties;
    }

    public function setExtendedProp($key, $value)
    {
        $properties = $this->Properties;
        $properties[$key] = $value;
        $this->Properties = $properties;
    }

    public function unsetExtendedProp($key)
    {
        $properties = $this->Properties;
        if (isset($properties[$key])) {
            unset($properties[$key]);
        }
        $this->Properties = $properties;
    }

    public function setExtendedProps($props)
    {
        $properties = is_array($this->Properties) ? $this->Properties : [];
        $this->Properties = array_merge($properties, $props);
    }

    /**
     * @return BelongsTo
     */
    public function parent()
    {
        $result = null;
        if (isset($this->parentType) && is_subclass_of($this->parentType, \Aurora\System\Classes\Model::class)) {
            $result = $this->belongsTo($this->parentType, $this->parentKey, $this->primaryKey);
        }

        return $result;
    }

    protected function isEncryptAttribute($attributeName)
    {
        $result = false;
        $casts = $this->getCasts();
        if (isset($casts[$attributeName]) && $casts[$attributeName] === \Aurora\System\Casts\Encrypt::class) {
            $result = true;
        }

        return $result;
    }

    /**
     * Convert the model instance to an array that can be sent with the response payload.
     *
     * @return array
     */
    public function toResponseArray()
    {
        $array = $this->attributesToArray();

        $relations = $this->getRelations();
        if (count($relations) > 0) {
            foreach ($relations as $key => $attribute) {
                $array[$key] = $attribute->toResponseArray();
            }
        }

        $parentInheritedAttributes = $this->getInheritedAttributes();
        if (count($parentInheritedAttributes) > 0) {
            foreach ($parentInheritedAttributes as $attribute) {
                $value = null;
                if (isset($array[$attribute])) {
                    $value = $array[$attribute];
                }
                if ($value === null) {
                    $array[$attribute] = $this->getInheritedValue($attribute);
                }
            }
        }

        if (isset($array['Properties'])) {
            foreach ($array['Properties'] as $key => $value) {
                if ($value !== null) {
                    $aArgs = ['Model' => $this, 'PropertyName'  => $key];
                    EventEmitter::getInstance()->emit('System', 'CastExtendedProp', $aArgs, $value);
                    $array[$key] = $value;
                }
            }
            unset($array['Properties']);
        }

        foreach ($array as $key => $value) {
            if ($this->isEncryptAttribute($key)) {
                $array[$key] = '*****';
            }
        }

        if (!isset($array['UUID'])) {
            $array['UUID'] = '';
        }
        $array['ParentUUID'] = '';
        $array['ModuleName'] = $this->moduleName;

        return $array;
    }

    public function validate()
    {
        Validator::validate($this->getAttributes(), $this->validationRules, $this->validationMessages);

        return true;
    }

    /**
     * Returns a pseudo-random v4 UUID
     *
     * This function is based on a comment by Andrew Moore on php.net
     *
     * @see http://www.php.net/manual/en/function.uniqid.php#94959
     * @return string
     */
    public function generateUUID()
    {
        return sprintf(
            '%04x%04x-%04x-%04x-%04x-%04x%04x%04x',

            // 32 bits for "time_low"
            mt_rand(0, 0xffff),
            mt_rand(0, 0xffff),

            // 16 bits for "time_mid"
            mt_rand(0, 0xffff),

            // 16 bits for "time_hi_and_version",
            // four most significant bits holds version number 4
            mt_rand(0, 0x0fff) | 0x4000,

            // 16 bits, 8 bits for "clk_seq_hi_res",
            // 8 bits for "clk_seq_low",
            // two most significant bits holds zero and one for variant DCE1.1
            mt_rand(0, 0x3fff) | 0x8000,

            // 48 bits for "node"
            mt_rand(0, 0xffff),
            mt_rand(0, 0xffff),
            mt_rand(0, 0xffff)
        );
    }

    /**
     * Checks if a string is a valid UUID.
     *
     * @param string $uuid
     * @return bool
     */
    public function validateUUID($uuid)
    {
        return preg_match(
            '/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/i',
            $uuid
        ) == true;
    }

    /**
     *
     * @param array $aProperties
     */
    public function populate($aProperties)
    {
        foreach ($aProperties as $key => $value) {
            if (in_array($key, $this->fillable) || strpos($key, '::') !== false) {
                $this->$key = $value;
            }
        }
    }

    /**
     *
     * @return string
     */
    public function getName()
    {
        return \get_class($this);
    }

    /**
     * Save the model to the database.
     *
     * @param  array  $options
     * @return bool
     */
    public function save(array $options = [])
    {
        if ($this->validate()) {
            return parent::save($options);
        }

        return false;
    }
}