HEX
Server: Microsoft-IIS/8.5
System: Windows NT YDAWBH120 6.3 build 9600 (Windows Server 2012 R2 Standard Edition) AMD64
User: tentjecom_web (0)
PHP: 7.4.14
Disabled: NONE
Upload Files
File: D:/HostingSpaces/stafa/werkenbijstafa.nl/app/Komma/Kms/Core/Attributes/Attribute.php
<?php declare(strict_types = 1);

namespace App\Komma\Kms\Core\Attributes;

use App\Komma\Users\Models\KmsUserRole;
use App\Komma\Kms\Core\Sections\AbstractAttributeKey;
use App\Komma\Kms\Core\Sections\AttributeKey;
use App\Komma\Kms\Core\ValidationSet;
use App\Komma\Globalization\Languages\Models\Language;
use Exception;
use Illuminate\Database\Eloquent\Model;

/**
 * Class Attribute
 * @package App\Komma\Kms\Core\Attributes
 */
abstract class Attribute
{
    /** @const int ValueFromItself Indicates that this attribute's value did come from a call to the Attribute::setValue() method. getsValueFromReference refers an internal value in the attribute itself. */
    const ValueFromItself = 0;

    /** @const int ValueFromModel Indicates that this attribute's value did come from the model this attribute works for. getsValueFromReference refers an attribute model field. */
    const ValueFromModel = 1;

    /** @const int ValueFromTranslationModel Indicates that this attribute's value did come from the translation of the model this attribute works for. getsValueFromReference refers an attribute translation model field. */
    const ValueFromTranslationModel = 2;

    //Value 3 is not in use anymore.

    /** @const int ValueFromModelRelation Indicates that this attribute's value does come from a related model. getsValueFromReference is a pipe separated string that concatenates the related model and the field name on the related model that has the value.*/
    const ValueFromModelHasManyRelation = 4;

    /** @const int ValueFromFiles Indicates that this attribute's value did come from files method of the model this attribute works for. getsValueFromReference refers a method on the model that is used to retrieve files. */
    const ValueFromDocuments = 5;

    /** @const int Indicates that this attribute's value does come from a component area **/
    const ValueFromComponentArea = 6;

    /** @const int Indicates that this attribute's value does come from a component. This also means that no attribute key will be generated automatically for the attribute. Instead, you must use setKey method to set it yourself.**/
    const ValueFromComponent = 7;
    const ValueFromTranslationDocuments = 8;

    /** @var int $minimumUserRole One of the Roles class constants that determine which role is needed at minimum to show this attribute in a section. **/
    private $minimumUserRole;

    /**
     * @var AbstractAttributeKey $key An unique reference for this attribute.
     */
    protected $key;

    /**
     * @var string $class The css class to apply on the html for this attribute.
     */
    private $styleClass;

    /**
     * @var ValidationSet $validationSet representing an array (but isn't one) of validation rules and messages.
     */
    private $validationSet;

    /**
     * @var string $value The value associated with the attribute.
     */
    protected $value;

    /**
     * @var Language $associatedLanguage The model associated with this attribute if any.
     * Some attributes are general and don't have a model associated to it
     */
    protected $associatedLanguage;

    /** @var int $getsValueFrom From which value entity (see the "ValueFrom" constants) the attribute gets it's value from */
    private $getsValueFrom;

    /** @var string $getsValueFromReference A reference that identifies a specific type / value reference of a Value entity */
    private $getsValueFromReference;

    /**
     * @var string $fqcn The fully qualified class name of the child.
     */
    private $FQCN;

    /**
     * @var static $allInstances All instances instantiated from this base / parent class.
     */
    private static $allInstances = [];

    /**
     * @var string The short name of the class name of the child. This is the classname without namespace
     */
    private $shortName;

    /** @var string[] an array of string containing data attribute values. The keys are data attribute names */
    private $dataAttributes = [];

    /** @var string[] **/
    private $providers = [];

    /** @var string For debugging */
    private $lastKeyGeneratedFrom = '';

    private static $debugKeyGeneration = false;

    /**
     * Attribute constructor.
     */
    public function __construct()
    {
        $this->FQCN = static::class;
        $FQCNArray = explode("\\", $this->FQCN);
        $this->shortName = $FQCNArray[count($FQCNArray) - 1];

        $this->value = '';
        $this->styleClass = '';
        $this->minimumUserRole = null;
        $this->getsValueFrom = Attribute::ValueFromItself;
        $this->getsValueFromReference = '';

        self::$allInstances[] = $this;
    }

    /**
     * @return string The value associated with the attribute
     */
    public function getValue(): string
    {
        return $this->value;
    }

    /**
     * @param string $value The value associated with the attribute
     * @return $this
     */
    public function setValue(string $value)
    {
        $this->value = $value;
        $this->generateKey();
        return $this;
    }

    /**
     * Returns a view that visually represents this attribute
     */
    abstract public function render();

    /**
     * @return AbstractAttributeKey Returns a unique key that can be used to identify this attribute
     */
    public function getKey(): ?AbstractAttributeKey
    {
        return $this->key;
    }

    /**
     * @return string Gets The css class to apply on the html for this attribute
     */
    public function getStyleClass(): string
    {
        return $this->styleClass;
    }

    /**
     * @param string $styleClass sets The css class to apply on the html for this attribute
     * @return $this
     */
    public function setStyleClass(string $styleClass)
    {
        $this->styleClass = $styleClass;
        return $this;
    }

    /**
     * @return int The minimum user role integer needed to interact with this attribute or null if no minimum role was specified. One of the roles from the abstract Roles class
     */
    public function getMinimumUserRole(): ?int
    {
        return $this->minimumUserRole;
    }

    /**
     * @param int $minimumUserRole The minimum user role integer needed to interact with this attribute. One of the roles from the abstract Roles class
     * @see KmsUserRole
     * @return $this
     */
    public function setMinimumUserRole(int $minimumUserRole)
    {
        if(KmsUserRole::isValidItem($minimumUserRole)) $this->minimumUserRole = $minimumUserRole;
        else throw new \InvalidArgumentException("The User role specified wasn't a valid one.");

        return $this;
    }

    /**
     * @see ValidationSet
     * @return ValidationSet Used for validating this attribute with the native laravel validator
     */
    public function getValidationSet(): ValidationSet
    {
        if(!$this->validationSet) return new ValidationSet(); //Null object pattern to prevent exceptions

        return $this->validationSet;
    }

    /**
     * @param ValidationSet $validationSet Used for validating this attribute with the native laravel validator
     *
     * @see ValidationSet
     * @return $this
     */
    public function setValidationSet(ValidationSet $validationSet): Attribute
    {
        $this->validationSet = $validationSet;
        return $this;
    }

    /**
     * Some attributes get their value from somewhere else. With this method you can specify from where they should get their value
     * and an optional reference that uniquely identifies the thing that has the value. That can be a field on the model that this
     * attribute belongs to for example. Or maybe a method on some class that needs to be used to retrieve the value. Just have a
     * Look at how it is used. Especially at the getsValueFrom and getsValueFromReference method usages
     *
     * @param int $valueEntity one of the "ValueFrom" constants in this Attribute class
     * @param string $reference
     * @see Attribute::getsValueFrom()
     * @see Attribute::$getsValueFromReference()
     * @return Attribute
     */
    public function mapValueFrom(int $valueEntity, string $reference): Attribute
    {
        $this->getsValueFrom = $valueEntity;
        $this->getsValueFromReference = $reference;

        $this->generateKey();
        return $this;
    }

    /**
     * @return int Returns the type of the value entity this attribute gets its value from.
     */
    public function getsValueFrom():int
    {
        return $this->getsValueFrom;
    }

    /**
     * @return string Gets the reference that identifies the specific value entity that has the value for this attribute
     */
    public function getsValueFromReference():string
    {
        return $this->getsValueFromReference;
    }

    /**
     * @return bool returns true if a Language was associated to this attribute. false if not.
     */
    public function hasAssociatedLanguage(): bool
    {
        return $this->associatedLanguage !== null;
    }

    /**
     * @return Model The translation model associated with this attribute if any. Or null if none set
     * Some attributes are general and don't have an translation model associated to it
     */
    public function getAssociatedLanguage(): Model
    {
        return $this->associatedLanguage;
    }

    /**
     * @param Model $associatedLanguage Set the translation model associated with this attribute if any.
     * Some attributes are general and don't have an translation model associated to it
     * @return Attribute
     */
    public function setAssociatedLanguage(Model $associatedLanguage): Attribute
    {
        $this->associatedLanguage = $associatedLanguage;
        $this->generateKey();
        return $this;
    }

    /**
     * Sets a data attribute that can be used in the attributes template
     * The name wil be converted to kebab case and if you start without
     * the data prefix, it will add that to the name
     *
     *
     * @param string $name
     * @param string $value
     * @return Attribute
     */
    public function setDataAttribute(string $name, string $value): Attribute
    {
        $name = kebab_case($name);

        //prefix data- if not already included
        if(substr($name, 0, 4) !== 'data') {
            $name = 'data-'.$name;
        }

        $this->dataAttributes[$name] = $value;
        return $this;
    }

    /**
     * Returns all data attributes and their values in an associative array.
     * You can use these in your attribute blade template.
     *
     * @return string[]
     */
    public function getDataAttributes()
    {
        return $this->dataAttributes;
    }

    /**
     * Generate a key to be able to reference this object (also from javascript).
     *
     * @see Attribute::validateAttributesKeyUniqueness()
     * @see Attribute::setValue()
     * @see Attribute::mapValueFrom()

     */
    private function generateKey()
    {
        if($this->getsValueFrom == self::ValueFromComponent) return;

        $valueKeyPart = '';
        if($this->value != '') {
            $valueKeyPart = $this->value;
        }
        if($this->getsValueFrom >= Attribute::ValueFromItself) {
            $valueKeyPart = $this->getsValueFromReference;
        }
        if($this->getsValueFrom == self::ValueFromModelHasManyRelation) {
            $valueKeyPart = explode("|", $this->getsValueFromReference)[0];
        }

        $translationKeyPart = '';
        if($this->hasAssociatedLanguage())
        {
            $translationKeyPart = $this->getAssociatedLanguage()->iso_2;
        }

        if($valueKeyPart != '') $this->key = (new AttributeKey())->setAttributeShortClassName($this->shortName)->setValuePart($valueKeyPart)->setTranslationIso2($translationKeyPart);

        $this->setLastKeyGeneratedFromStack();
        self::validateAttributesKeyUniqueness();
    }

    /**
     * Make sure we know from which direction the last key was generated from
     */
    private function setLastKeyGeneratedFromStack()
    {
        $this->lastKeyGeneratedFrom = $this->getStackTraceAsString();
    }

    /**
     * Returns a stack trace
     *
     * @return string
     */
    private static function getStackTraceAsString():string
    {
        if(!self::$debugKeyGeneration) return '';
        $e = new Exception();
        $trace = explode("\n", $e->getTraceAsString());
        // reverse array to make steps line up chronologically
//        $trace = array_reverse($trace);
        array_shift($trace); // remove {main}
        array_pop($trace); // remove call to this method
        $length = count($trace);
        if($length > 20) $length = 20;
        $result = array();

        for ($i = 0; $i < $length; $i++)
        {
            $result[] = ($i + 1)  . ')' . substr($trace[$i], strpos($trace[$i], ' ')); // replace '#someNum' with '$i)', set the right ordering
        }
        return  "\t" . implode("\n\t", $result);
    }


    /**
     * Does look at all instantiated attributes and check if the keys inside them are unique.
     * If not throws an \RuntimeException. We want this to be unique to be able to identify
     * all different attributes further in the processing of them.
     *
     * It does not check uniqueness for attributes which have a "value from itself". Attribute::ValueFromItself
     */
    private static function validateAttributesKeyUniqueness()
    {
        $existingKeys = [];

        /** @var Attribute $instance */
        foreach(self::$allInstances as $instance)
        {
            $attributeKey = $instance->getKey();
            if(!$attributeKey) continue;

            //Uniqueness check. If it the attribute key exists in array of existing keys it is not unique and errors may be thrown.
            if(in_array($attributeKey, $existingKeys)) {
                if(self::$debugKeyGeneration) {
                    $attributeInstanceWithSameKeys = array_filter(self::$allInstances, function(Attribute $instanceToCheck) use ($instance) {
                        if(is_a($instanceToCheck, get_class($instance))) {
//                            return true;
                            return (string) $instanceToCheck->getKey() === (string) $instance->getKey();
                        }
                        return false;
                    });
                    dd("Attribute:validateAttributesKeyUniqueness: The attributes key '".$attributeKey."' was not unique. These are the instances with the same keys. Set the static attribute debugKeyGeneration variable to true to add a stack trace inside of them that shows the key generation stack trace.", $attributeInstanceWithSameKeys, self::getStackTraceAsString());
                }
                throw new \InvalidArgumentException("The attributes key '".$attributeKey."' was not unique. Please make sure the keys of all attributes are unique in the section this attribute is used. You do this by giving one of the following methods an unique value or reference that you did not use in another attribute: Attribute::setValue(), Attribute::mapValueFrom(). The attributes class is: ".get_class($instance));
            }

            $existingKeys[] = $attributeKey;
        }
    }

    /**
     * @param AbstractAttributeKey $attributeKey
     */
    public function setKey(AbstractAttributeKey $attributeKey)
    {
        $this->key = $attributeKey;
        $this->setLastKeyGeneratedFromStack();
        self::validateAttributesKeyUniqueness();
    }

    /**
     * Returns all instances of Attributes (and children of Attributes) as an array. Use only for debugging
     */
    public static function getAllInstances()
    {
        return self::$allInstances;
    }

    /**
     * The clone method regenerates the key of the attribute when it is cloned since it is a new one and we need it to
     * be unique to be able to reference it and not another one too.
     *
     * @return void
     */
    public function __clone()
    {
        foreach($this as $key => $val) {
            if (is_object($val) || (is_array($val))) {
                $this->{$key} = unserialize(serialize($val));
            }
        }

        $this->generateKey();
    }
}