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/Neopoints/momsecurity.be/app/Komma/Dynamic/Componentables/ComponentableService.php
<?php


namespace App\Komma\Dynamic\Componentables;

use App\Helpers\KommaHelpers;
use App\Komma\Dynamic\Component\ComponentAttributeKey;
use App\Komma\Dynamic\Component\Component;
use App\Komma\Dynamic\Component\ComponentableTrait;
use App\Komma\Dynamic\Component\ComponentSaveState;
use App\Komma\Dynamic\Component\ComponentableInterface;
use App\Komma\Dynamic\ComponentType\ComponentType;
use App\Komma\Dynamic\ComponentType\ComponentTypeResolver;
use App\Komma\Kms\Core\Attributes\Attribute;
use App\Komma\Kms\Core\Attributes\AutocompleteInput;
use App\Komma\Kms\Core\Sections\AttributeKey;
use App\Komma\Languages\Models\Language;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\MorphToMany;
use Illuminate\Support\Collection;

/**
 * Class ComponentableService
 *
 * Knows which models (Componentables) are linked to specific components.
 * So that it can save and load those models to these components
 *
 * @package App\Komma\Dynamic\Component
 */
class ComponentableService implements ComponentableServiceInterface
{
    /**
     * @param Component $component
     * @param ComponentType $componentType
     * @param ComponentSaveState $componentSaveState
     * @param AttributeKey $componentAreaAttributeKey
     * @return Component
     */
    public function saveComponentablesForComponent(Component $component, ComponentType $componentType, ComponentSaveState $componentSaveState, AttributeKey $componentAreaAttributeKey): Component
    {
        $componentType->componentableTypes()->get()->each(function(ComponentableType $componentableType, $key) use($componentAreaAttributeKey, $componentSaveState, $component) {
            //Get the input data for the componentable type
            $autoCompleteInput = $this->getComponentableAutoCompleteInputForComponent(collect(), $componentAreaAttributeKey, $component, $key);
            $componentableIdsString = \Input::get($autoCompleteInput->getKey());

            $componentableIds = [];
            if($componentableIdsString !== '') {
                //Convert the id string (example; "1,2,3") to an array containing those integers.
                $componentableIdsStringArray = explode(',', $componentableIdsString);
                $componentableIds = array_map(function ($item) {
                    return (int)$item;
                }, $componentableIdsStringArray);
            }

            $componentableMethod = self::getAndValidateComponentableMethodNameOnComponentClassUsingComponentable($componentableType->componentable_type);
            $component->$componentableMethod()->sync($componentableIds);
        });

        return $component;
    }

    /**
     * Return an array containing auto complete input field names as keys. And a comma seperated string representing the selected items for them.
     *
     * @param Component $component
     * @param ComponentSaveState $componentSaveState
     * @param AttributeKey $componentAreaAttributeKey
     * @return array
     */
    public function loadComponentablesDataForComponent(Component $component, ComponentSaveState $componentSaveState, AttributeKey $componentAreaAttributeKey): array
    {
        $componentablesData = [];

        $componentType = ComponentTypeResolver::resolve($componentSaveState->getComponentTypeId());
        $componentType->componentableTypes()->get()->each(function(ComponentableType $componentableType, $key) use($componentAreaAttributeKey, $componentSaveState, $component, &$componentablesData) {
            $autoCompleteInput = $this->getComponentableAutoCompleteInputForComponent(collect(), $componentAreaAttributeKey, $component, $key);

            $methodName = self::getAndValidateComponentableMethodNameOnComponentClassUsingComponentable($componentableType->componentable_type);
            $modelIds = $component->$methodName()->get()->map(function (Model $componentable, $key) use($componentAreaAttributeKey, $componentSaveState, $component) {
                return $componentable->id;
            })->toArray();

            $componentablesData[(string) $autoCompleteInput->getKey()] = implode(',', $modelIds);
        });

        return $componentablesData;
    }

    /**
     * Returns an AutocompleteInput instance with the selectable items you pass as the first parameter as a Collection.`
     * They must be SelectOptions. The second parameter must be the key of a ComponentAreaAttribute, represented as a string.
     * The last parameter must be a ComponentSaveState. Both this parameter and the ComponentAreaAttribute key will be used
     * to generate a unique key for the autoCompleteInput
     *
     * @param Collection $itemsCollection an array of select options
     * @param AttributeKey $componentAreaAttributeKey
     * @param int $componentId
     * @param int $componentableNumber
     * @return AutocompleteInput
     */
    public static function getComponentableAutoCompleteInputForComponent(Collection $itemsCollection, AttributeKey $componentAreaAttributeKey, int $componentId, int $componentableNumber) {
        $componentAreaAttributeKey = clone $componentAreaAttributeKey; //Leaves the original component area key like it is
        $componentAreaAttributeKey->setValuePart($componentAreaAttributeKey->getValuePart().'_componentable');
//        $attributeKey = ComponentAttributeKey::createInstanceFromAttributeKey($componentAreaAttributeKey, $componentId, $componentAreaAttributeKey->getValuePart());
        $attributeKey = (new ComponentAttributeKey())
            ->setComponentId($componentId)
            ->setAttributeReference($componentAreaAttributeKey->getValuePart())
            ->setValuePart($componentAreaAttributeKey->getValuePart())
            ->setTranslationIso2($componentAreaAttributeKey->getTranslationIso2())
            ->setAttributeShortClassName($componentAreaAttributeKey->getTranslationIso2());

        $attribute = new AutocompleteInput();
        $attribute->setItems($itemsCollection->toArray());
        $attribute->setLabelText(__('components.link_to'));

        if($attributeKey->getTranslationIso2()) {
            $language = Language::where('iso_2', '=', $attributeKey->getTranslationIso2())->first();
            $attribute->setAssociatedLanguage($language);
        }

        $attribute->mapValueFrom(Attribute::ValueFromItself, str_random());
        $attribute->swapKeyTo($attributeKey);

        return $attribute;
    }

    /**
     * Validates and returns the componentable method name on the components class using a HasComponentsInteface implementation.
     * For example: The User class implements the HasComponentsInterface by using a trait. Because it uses that trait it has
     * a components method that refers to the component class. Because of that, the component class must have a method called users that
     * references to the Users class. The system relies on that relation. The Users method in this example is also known as the "Componentable Method name".
     *
     * @param $componentable
     * @return string
     * @throws \ReflectionException
     */
    public static function getAndValidateComponentableMethodNameOnComponentClassUsingComponentable(string $componentable)
    {
        if(!is_a($componentable, ComponentableInterface::class, true)) throw new \InvalidArgumentException('The class or class name you pass must be an implementation of "'.ComponentableInterface::class.'"');

        //   Validate that $hasComponentsInterfaceImplementation (Users for example) is a method on the Components model that returns a morphToMany relation
        $methodName = KommaHelpers::getShortNameFromClass($componentable, true);
        $methodNamePlural = str_plural($methodName);
        if(!method_exists(Component::class, $methodNamePlural)) throw new \RuntimeException('Method "'.$methodNamePlural.'" does not exist on "'.Component::class.'". We expect this method because a "'.Component::class.'" can have many "'.$componentable.'". Create the method and let it return a "componentable" "'.MorphToMany::class.'" query builder instance like this: return $this->morphToMany('.$componentable.'::class, \'componentable\');');
        $reflectionMethod = new \ReflectionMethod(Component::class, $methodNamePlural);
        $reflectionType = $reflectionMethod->getReturnType();
        $type = $reflectionType->getName();
        if($type !== MorphToMany::class) throw new \RuntimeException('Method "'.$methodNamePlural.'" does exist on "'.Component::class.'" but does not return a "'.MorphToMany::class.'". Fix the method so that it returns a "componentable" "'.MorphToMany::class.'" query builder instance like this: return $this->morphToMany('.$componentable.'::class, \'componentable\');');

        return $methodNamePlural;
    }

    /**
     * validate the that the ComponentableInterface has a components method and return the name of the components method.
     *
     * @param $componentable
     * @throws \ReflectionException
     */
    public static function getAndValidateComponentsMethodNameOnComponentable(string $componentable)
    {
        if(!is_a($componentable, ComponentableInterface::class, true)) throw new \InvalidArgumentException('The class or class name you pass must be an implementation of "'.ComponentableInterface::class.'"');

        $methodName = KommaHelpers::getShortNameFromClass(Component::class, true);
        $methodNamePlural = str_plural($methodName);
        if(!method_exists($componentable, $methodNamePlural)) throw new \RuntimeException('Method "'.$methodNamePlural.'" does not exist on "'.$componentable.'". We expect this method because a "'.$componentable.'" can have many "'.Component::class.'". Create the method and let it return a "componentable" "'.BelongsToMany::class.'" query builder instance like this: return $this->morphedByMany('.Component::class.'::class, \'componentable\');. Pro tip: Fix this by USEing the "'.ComponentableTrait::class.'" and the the "'.ComponentableInterface::class.'" on the class');
        $reflectionMethod = new \ReflectionMethod($componentable, $methodNamePlural);
        $reflectionType = $reflectionMethod->getReturnType();
        $type = $reflectionType->getName();
        if($type !== BelongsToMany::class) throw new \RuntimeException('Method "'.$methodNamePlural.'" does exist on "'.$componentable.'" but does not return a "'.BelongsToMany::class.'". Fix the method so that it returns a "componentable" "'.BelongsToMany::class.'" query builder instance like this: return $this->morphedByMany('.Component::class.'::class, \'componentable\');. Pro tip: Fix this by USEing the "'.ComponentableTrait::class.'" and the the "'.ComponentableInterface::class." on the class");
    }
}