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/SBogers95/rentman.io/app/Komma/Kms/Core/Attributes/AutocompleteInput.php
<?php

namespace App\Komma\Kms\Core\Attributes;

use App\Komma\Kms\Core\Attributes\Models\SelectOption;
use App\Komma\Kms\Core\Attributes\Models\SelectOptionInterface;
use App\Komma\Kms\Core\Attributes\Traits\ExplanationTrait;
use App\Komma\Kms\Core\Attributes\Traits\LabelTrait;
use App\Komma\Kms\Core\Attributes\Traits\PlaceholderTextTrait;
use App\Komma\Kms\Core\Attributes\Traits\ReadOnlyTrait;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\App;

class AutocompleteInput extends Attribute implements Attributable
{
    use LabelTrait;
    use PlaceholderTextTrait;
    use ReadOnlyTrait;
    use ExplanationTrait;

    /**
     * @var string The option (value) that needs to excluded from the list. Usually because it represents something that you currently are in or are using
     */
    private $exclude;

    /** @var SelectOptionInterface[] */
    private $items;

    protected $defaultValue;

    /** @var string */
    protected $autoSaveUrl;

    /** @var int */
    protected $maxItemsToSelect;

    /** @var array first item refers to a class and the second to
     * a method on that class that returns SelectOptionInterface's to
     * fill the autocomplete input with data when rendered
     */
    protected $provider;

    /** @var string Indicates that the model this autocomplete works for can be linked to another model */
    protected $canBeLinkedWith;

    public function __construct($value = null)
    {
        parent::__construct();

        $this->maxItemsToSelect = 20;
        $this->autoSaveUrl = false;
        if ($value) {
            $this->setValue($value);
        }
        $this->canBeLinkedWith = '';
        $this->items = [];
    }

    /**
     * Sets the items you can select
     *
     * @param SelectOptionInterface[] $items
     * @return $this
     */
    public function setItems(array $items)
    {
        $items = array_filter($items, function ($item) {
            return is_a($item, SelectOptionInterface::class);
        });

        $this->items = $items;

        return $this;
    }

    /**
     * Set items through function calling
     */
    public function setItemsThroughFunction(string $function)
    {
    }

    /**
     * Returns the items that you can select.
     * Normally as an array unless you specify the parameter as true.
     * In that latter case it will return a json string.
     *
     * @param bool $asJson
     * @return SelectOptionInterface[]|string
     */
    public function getItems(bool $asJson = false)
    {
        if (! $asJson) {
            return $this->items;
        }

        $jsonPreparementArray = [];
        /** @var SelectOptionInterface $item */
        foreach ($this->items as $item) {
            //Make sure that these keys match the ones in select.blade.php and ???
            $jsonPreparementArray[] = $this->buildJsonRepresentation($item);
        }

        $jsonItems = implode(',', $jsonPreparementArray);
        $json = '['.$jsonItems.']';

        return $json;
    }

    /**
     * Sets the id of the model that needs to be displayed
     *
     * @param string $value
     * @return $this
     */
    public function setValue(string $value)
    {
        $this->value = $value;

        return $this;
    }

    /**
     * Return the value as a string or as a json object for the javascript code.
     *
     * @param bool $asJson
     * @return string
     */
    public function getValue($asJson = false): string
    {
        if ($asJson == false) {
            if (! empty($this->value)) {
                return $this->value;
            }

            return '';
        } else {
            //Return an object
            $jsonItemPreparementArray = [];

            $selectedItemId = $this->value;

            if ($selectedItemId) {
                $jsonItemPreparementArray['id'] = $selectedItemId;
            }

            $json = json_encode($jsonItemPreparementArray);

            return $json;
        }
    }

    /**
     * Builds a json representation of a SelectOptionsInterface implementation.
     * You should use these representation in the javascript code.
     * @param SelectOptionInterface $selectOption
     * @return string
     */
    private function buildJsonRepresentation(SelectOptionInterface $selectOption): string
    {
        $optionJsonPreparementData = [];
        $optionJsonPreparementData['value'] = $selectOption->getValue();
        $optionJsonPreparementData['content'] = $selectOption->getContent();
        $optionJsonPreparementData['htmlContent'] = $selectOption->getHtmlContent();

        $json = json_encode($optionJsonPreparementData);

        return $json;
    }

    /**
     * Returns a view that visually represents this attribute
     */
    public function render()
    {
        //get id from model
        $currentId = null;
        foreach (request()->route()->parameters as $parameter) {
            if (is_a($parameter, Model::class)) {
                $currentId = $parameter->id;
            }
        }

        $this->fillWithDataFromProvider();

        return view('kms/attributes.autocompleteInput', [
            'attribute' => $this,
            'currentId' => $currentId,
            'dataset' => $this->buildAutoCompleteDataJson(),
        ]);
    }

    private function buildAutoCompleteDataJson()
    {
        $dataSet = [];
        $items = $this->getItems();
        foreach ($items as $selectOption) {
            /** @var SelectOptionInterface $selectOption */
            $dataSet[] = [
                'id' => $selectOption->getValue(),
                'value' => str_replace('&nbsp;', '.', $selectOption->getHtmlContent()),
            ];
        }
        $dataSet = json_encode($dataSet);

        return $dataSet;
    }

    /**
     * Pre-fill the autocomplete input based on the provider. The provider is an array like this:
     * [
     *      'App\Komma\Users\Kms\Service',
     *      'getOptionsForSelect'
     * ]
     *
     * And can be given using the canBeLinkedWith method with a parameter like this: "App\Komma\Users\Kms\Service@getOptionsForSelect"
     */
    private function fillWithDataFromProvider()
    {
        if (! isset($this->provider) || count($this->provider) == 0 || (isset($this->items) && count($this->items) > 0)) {
            return;
        } //If the provider is not configured or we already have items, we return;
        if (! class_exists($this->provider[0])) {
            throw new \RuntimeException('AutoCompleteInput: The provider class "'.$this->provider[0].'" does not exist. So the autocomplete input could not be filled automatically.');
        }

        //Validate that the class on the provider exists and that we can call it.
        if (! method_exists($this->provider[0], $this->provider[1]) || ! is_callable([$this->provider[0], $this->provider[1]])) {
            throw new \RuntimeException('AutocompleteInput: The provider class method "'.$this->provider[1].'" was not callable on provider class "'.$this->provider[0].'"');
        }

        /** @var Collection $selectOptions */
        try {
            $providerInstance = App::make($this->provider[0]); //Creates an instance of the provider class.
            $selectOptions = call_user_func([$providerInstance, $this->provider[1]]);
        } catch (\Exception $e) {
            throw new \RuntimeException('AutocompleteInput: Failed using the provider class method "'.$this->provider[1].'" from provider class "'.$this->provider[0].'": '.$e->getMessage());
        }
        if (! is_a($selectOptions, Collection::class)) {
            throw new \RuntimeException('AutocompleteInput: The provider class method "'.$this->provider[1].'" from provider class "'.$this->provider[0].'" did not return a collection ("'.Collection::class.'") while it was expected. It did return: '.typeOf($selectOptions));
        }

        $selectOptions->each(function ($selectOption) {
            if (! is_a($selectOption, SelectOption::class)) {
                throw new \RuntimeException('AutocompleteInput: On or more items from the collection from provider class "'.$this->provider[0].'", method "'.$this->provider[1].'" was not a "'.SelectOption::class.'"');
            }
        });

        $this->items = $selectOptions;
    }

    /**
     * @return mixed
     */
    public function getDefaultValue()
    {
        return $this->defaultValue;
    }

    /**
     * @param mixed $defaultValue
     * @return AutocompleteInput
     */
    public function setDefaultValue($defaultValue): self
    {
        $this->defaultValue = $defaultValue;

        return $this;
    }

    /**
     * @return string
     */
    public function getAutoSaveUrl(): string
    {
        return $this->autoSaveUrl;
    }

    /**
     * This url will receive a comma delimited list of item ids that need to be saved.
     *
     * @param string $autoSaveUrl
     * @return AutocompleteInput
     */
    public function setAutoSaveUrl(string $autoSaveUrl): self
    {
        $this->autoSaveUrl = $autoSaveUrl;

        return $this;
    }

    /**
     * @return int
     */
    public function getMaxItemsToSelect(): int
    {
        return $this->maxItemsToSelect;
    }

    /**
     * @param int $maxItemsToSelect
     * @return AutocompleteInput
     */
    public function setMaxItemsToSelect(int $maxItemsToSelect)
    {
        $this->maxItemsToSelect = $maxItemsToSelect;

        return $this;
    }

    /**
     * Indicates that the Autocomplete input will be pre-filled with data from a provider (for example "Userservice@getOptionsForSelect").
     *
     * @param string $provider
     * @return AutocompleteInput
     */
    public function fillWith(string $provider): self
    {
        $parts = explode('@', $provider);
        if ($parts === false || count($parts) !== 2) {
            throw new \InvalidArgumentException('"Attribute: The provider parameter must contain an @ that separates a class name and a method on that class');
        }
        $this->provider = $parts;

        return $this;
    }

    /**
     * Returns true if the autocomplete input will be filled by the given provider if it was specified. false if not.
     */
    public function isFilledByProvider(): bool
    {
        if (! isset($this->provider)) {
            return false;
        }

        return count($this->provider) == 2;
    }

    /**
     * Define a model using this function as Attributable type.
     * The AttributeableService used by the componentService then knows what kind of model you select
     * using the autocompleteInput.
     *
     * @param string $model
     *
     * @return AutocompleteInput
     */
    public function canBeLinkedWith(string $model): self
    {
        if (! is_a($model, Model::class, true)) {
            throw new \InvalidArgumentException("Attribute: The attribute cannot be linked to a class that is no child of '".Model::class."'");
        }
        $this->canBeLinkedWith = $model;

        return $this;
    }

    /**
     * @return string Returns the name of a model that can be linked to the model the autocomplete works for
     */
    public function getCanBeLinkedWith(): string
    {
        return $this->canBeLinkedWith;
    }
}