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/SBogers10/rentman2019.komma.pro/app/Komma/Kms/Core/Sections/SectionService.php
<?php

namespace App\Komma\Kms\Core\Sections;

use App\Helpers\KommaHelpers;
use App\Komma\Documents\Kms\DocumentableInterface;
use App\Komma\Documents\Kms\DocumentServiceInterface;
use App\Komma\Dynamic\ComponentArea\ComponentAreaService;
use App\Komma\Dynamic\ComponentArea\ComponentAreaServiceInterface;
use App\Komma\Kms\Core\AbstractTranslatableModel;
use App\Komma\Kms\Core\AbstractTranslationModel;
use App\Komma\Kms\Core\Attributes\Attribute;
use App\Komma\Kms\Core\Attributes\ComponentArea;
use App\Komma\Kms\Core\Attributes\Documents;
use App\Komma\Kms\Core\Attributes\Models\SelectOptionInterface;
use App\Komma\Kms\Core\Attributes\Models\Traits\HasThumbnailInterface;
use App\Komma\Kms\Core\Entities\DisplayNameInterface;
use App\Komma\Kms\Core\Entities\DisplayNameTrait;
use App\Komma\Kms\Core\HasRoutesInterface;
use App\Komma\Kms\Core\NestedSets\Nodes\AbstractTranslatableTreeModel;
use App\Komma\Kms\Core\NestedSets\Nodes\TreeModelInterface;
use App\Komma\Kms\SidebarListItem;
use App\Komma\Languages\Kms\LanguageService;
use App\Komma\Languages\Models\Language;
use App\Komma\Pages\Models\PageTranslation;
use App\Komma\Shop\Catalog\Kms\CatalogService;
use App\Komma\Sites\HasSitesInterface;
use App\Komma\Sites\SiteServiceInterface;
use Illuminate\Database\Eloquent\Collection as DatabaseCollection;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;
use Illuminate\Support\Collection as BaseCollection;
use Illuminate\Support\Facades\App;
use InvalidArgumentException;

/**
 * @see PageRepository Based of the deprecated PageRepository and KmsRepository
 *
 * Class SectionService
 */
class SectionService implements SectionServiceInterface
{
    /** @var bool */
    protected $sortable = true;

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

    /** @var AbstractTranslatableModel */
    protected $forModel;

    /**
     * @var AbstractTranslationModel
     */
    protected $forTranslationModel;

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

    /**
     * @var DocumentServiceInterface
     */
    protected $documentService;

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

    /**
     * @var bool
     */
    protected $orderByDisplayName = false;

    /**
     * @var bool
     */
    protected $orderReverse = false;

    /** @var SiteServiceInterface */
    protected $siteService;

    /** @var ComponentAreaService */
    private $componentAreaService;

    protected $isSiteService = false; //Only set to true by the site service to prevent an infinite loop

    private $afterSaveQueue;

    private $afterSaveQueueTranslations;

    public function __construct()
    {
        if (! $this->forModelName) {
            throw new \RuntimeException("Please set the forModelName variable in the '".get_class($this)."' class");
        }

        if (is_a((new $this->forModelName), AbstractTranslatableModel::class)) {
            $translationModelInterface = (new $this->forModelName)->translations()->getRelated();

            if (! is_a($translationModelInterface, AbstractTranslationModel::class)) {
                throw new \RuntimeException("The method 'translations' method from the class '".$this->forModelName."' does not return a relation resolving to an instance of '".AbstractTranslationModel::class."'");
            }
            $this->forTranslationModel = $translationModelInterface;

            $this->forTranslationModelName = get_class($translationModelInterface);
        }

        $this->siteService = ($this->isSiteService == false) ? App::make(SiteServiceInterface::class) : $this;

        $this->documentService = App::make(DocumentServiceInterface::class);

        $this->componentAreaService = App::make(ComponentAreaServiceInterface::class);

        $this->afterSaveQueue = collect();
        $this->afterSaveQueueTranslations = collect();
    }

    /**
     * This method will get a model based on the id.
     * When the id is NULL or the model does not exist, we will generate a new.
     *
     * @see KmsSection::loadModel()
     * @param int $modelId
     * @return Model
     */
    public function getModel($modelId = null): Model
    {
        if ($modelId == null || ! $model = $this->forModelName::find($modelId)) {
            return $this->newModel();
        } else {
            $this->makeAndInjectEmptyTranslationsIntoTranslatableIfNeeded($model);
        }

        $model = $this->setThumbnail($model);

        return $model;
    }

    /**
     * Gets the name of the model the service does its work for.
     * WARNING! Prevent usage of this method.
     *
     * @return string
     */
    public function getForModelName(): string
    {
        return $this->forModelName;
    }

    /**
     * Gets the name of the model the service does its work for.
     * WARNING! Prevent usage of this method.
     *
     * @return string
     */
    public function getForTranslationModelName(): string
    {
        return $this->forTranslationModelName;
    }

    /**
     * This method will create a new TranslatableModelInterface instance with its translations.
     *
     * @param null $siteId
     * @return Model
     */
    public function newModel($siteId = null): Model
    {
        //Create a new TranslatableModelInterface
        $model = new $this->forModelName;
        $model = $this->makeAndInjectEmptyTranslationsIntoTranslatableIfNeeded($model);

        $site = $this->siteService->getCurrentSite();

        if ($this->siteService && isset($site)) {
            if (method_exists($model, 'site')) {
                $model->site()->associate($site);
            }
        }

        //Set the thumbnail
        /** @var HasThumbnailInterface|Model $model */
        $model = $model->generateThumbnail();

        return $model;
    }

    /**
     * Make and injects empty but linked translations into a translatable
     * for each kms site language so that you can assume their presence. If the database already has a translation
     * it will be loaded instead of making an empty translation
     * Notice that they are not saved to the database. You need to save them yourself.
     *
     * @param Model $model
     * @return Model
     */
    public function makeAndInjectEmptyTranslationsIntoTranslatableIfNeeded(Model $model)
    {
        if (! is_a($model, AbstractTranslatableModel::class)) {
            return $model;
        }
//        if($model->exists == false) throw new \RuntimeException('Cannot create and inject empty translations for a translatable model which isn\'t saved. Because the translation is linked to the translatable by the translatable id which is only available after it is saved.');

        $languages = LanguageService::getAvailableLanguages()->get();
        if ($model->exists) {
            $model->load('translations');
        }

        $translationModels = new Collection();
        foreach ($languages as $language) {
            //Check if the translation already exists.
            $translation = false;
            if ($model->exists) {
                foreach ($model->translations as $translationFromModel) {
                    if ($translationFromModel->language_id == $language->id) {
                        $translation = $translationFromModel;
                        break;
                    }
                }
            }

            //If it does not exist we create an empty one
            if (! $translation) {
                /** @var Language $language */
                /** @var AbstractTranslationModel $translation */
                $translation = new $this->forTranslationModel();

                //set the language
                $translation->language_id = $language->id;
            }

            //Push empty or loaded models into the collection
            $translationModels->push($translation);
        }

        $model->setRelation('translations', $translationModels);

        return $model;
    }

    /**
     * Gets the model query
     *
     * @return \Illuminate\Database\Query\Builder
     */
    public function models()
    {
        return $this->forModelName::query();
    }

    /**
     * Gets the model query
     *
     * @return \Illuminate\Database\Query\Builder
     */
    public function translationModels()
    {
        if (! $this->forTranslationModelName) {
            return null;
        }

        return $this->forTranslationModelName::query();
    }

    /**
     * Get model tree
     *
     * @return TreeModelInterface
     * @deprecated Use getRootModelForTree instead
     */
    public function getModelTree($siteId = null): TreeModelInterface
    {
        //Create the tree based on the site id
        $tree = $this->getRootModelForTree();

        return $tree;
    }

    /**
     * Returns all models for the sidebar menu in the backend
     *
     * @return array
     */
    public function getModelsForSideBar():array
    {
        if (! is_a($this->forModelName, DisplayNameInterface::class, true)) {
            throw new \RuntimeException('Please let class "'.$this->forModelName.'" implement '.DisplayNameInterface::class.'. it is needed to build a sidebar.');
        }
        if (! is_a($this->forModelName, HasThumbnailInterface::class, true)) {
            throw new \RuntimeException('Please let class "'.$this->forModelName.'" implement '.HasThumbnailInterface::class.'. It is needed to build a sidebar');
        }

        if (isset($this->orderBy)) {
            if ($this->orderReverse) {
                $models = $this->forModelName::orderBy($this->orderBy, 'desc');
            } else {
                $models = $this->forModelName::orderBy($this->orderBy);
            }
        } else {
            $models = $this->forModelName::orderBy('id');
        }

        //Only load the models from the current site. Or load all when we currently have the default site
        $site = $this->siteService->getCurrentSite();
        if (new $this->forModelName instanceof HasSitesInterface && $site->exists) {
            $models->whereHas('Sites', function ($query) use ($site) {
                $query->where('site_id', '=', $site->id);
            });
        }

        // Get the models
        $models = $models->get();

        // Preload translation to prevent additional queries later on
        if (is_a(new $this->forModelName, AbstractTranslatableModel::class)) {
            $models = $models->load('translation');
        }

        //Preload documents to prevent additional queries later on
        if (is_a(new $this->forModelName, DocumentableInterface::class)) {
            $models = $models->load('documents');
        }

        $sidebarList = [];
        foreach ($models as $model) {
            /** @var Model|HasThumbnailInterface|DisplayNameInterface $model */
            if ($model->getAttribute('lft') == 1) {
                continue;
            } //Skip the root model if it is one

            $sidebarListItem = new SidebarListItem();
            $sidebarListItem->setThumbnail($model);
            $sidebarListItem->alsoSearchInAttributesOfModel($model);
            $model->generateThumbnail();

            //Set the values for the sidebar
            $sidebarListItem->setId($model->id);

            $sidebarListItem->setStatus($model->active);
            $title = $model->getSidebarName();
            $sidebarListItem->setName($title);
            $sidebarListItem->setThumbnail($model->getThumbnail());

            // Make sure there is always a unique key
            $key = $title;
            if (isset($sidebarList[$key])) {
                $key = $title.$model->id;
            }

            $sidebarList[$key] = $sidebarListItem;
        }

        if ($this->orderByDisplayName) {
            ksort($sidebarList);
            if ($this->orderReverse) {
                $sidebarList = array_reverse($sidebarList);
            }
        }

        return $sidebarList;
    }

    /**
     * Converts TreeModelInterface instance and their children to Select option models
     *
     * @param TreeModelInterface|BaseCollection $parents
     * @param int $indents
     * @return BaseCollection
     */
    private function convertAllModelsToSelectOptionModels($parents, $indents = 0): BaseCollection
    {
        $selectOptions = collect();

        if (! is_a($parents, BaseCollection::class)) {
            $parents = collect([$parents]);
        }

        $parents->each(function (TreeModelInterface $parent) use (&$selectOptions, $indents) {
            $childrenArray = $parent->getChildren();
            $selectOptions->push($this->convertTreeModelToSelectOption($parent, $indents));

            foreach ($childrenArray as $child) {
                $this->convertAllModelsToSelectOptionModels(collect([$child]), $indents + 1)->each(function (SelectOptionInterface $childChild) use ($selectOptions) {
                    $selectOptions->push($childChild);
                });
            }
        });

        return $selectOptions;
    }

    /**
     * Convert a TreeModel to a SelectOption
     * @param TreeModelInterface $treeModel
     * @param int $indents
     * @return SelectOptionInterface
     */
    private function convertTreeModelToSelectOption(TreeModelInterface $treeModel, $indents = 0): SelectOptionInterface
    {
        /** @var TreeModelInterface $child */
        if (! method_exists($treeModel, 'getSidebarName')) {
            throw new \RuntimeException('Could not get the name of an instance using a call to a getDisplayName method. Please let the class "'.get_class($treeModel).'" use the '.DisplayNameTrait::class.' or implement the method yourself.');
        }

        $displayName = ($treeModel->lft > 1) ? $treeModel->getSidebarName() : '/';

        $indentString = str_repeat('&nbsp;&nbsp;', $indents);
        /** @var SelectOptionInterface $selectOption */
        $selectOption = \App::make(SelectOptionInterface::class);
        $selectOption
            ->setValue($treeModel->id)
            ->setContent($displayName || '')
            ->setHtmlContent($indentString.$displayName);

        return $selectOption;
    }

    /**
     * This method will build an model tree.
     * Used by the Controllers get and set-structure method
     *
     * @return Model|TreeModelInterface
     */
    public function getRootModelForTree():?Model
    {
        if (! is_a(new $this->forModelName, TreeModelInterface::class) && is_a(new $this->forModelName, AbstractTranslatableTreeModel::class)) {
            throw new \RuntimeException('The model must either implement '.TreeModelInterface::class.' or '.AbstractTranslatableTreeModel::class.' but did implement none.');
        }

        if (! method_exists($this->forModelName, 'getSideBarName')) {
            throw new \RuntimeException('Could not get the name of an instance using a call to a getSideBarName method. Please let the class "'.$this->forModelName.'" use the '.DisplayNameTrait::class.' or implement the method yourself.');
        }

        //Take the current site into account
        $site = $this->siteService->getCurrentSite();
        if ($site->exists) {
            //Only load models that belong to the current site
            $modelQuery = $this->forModelName::whereHas('Site', function ($query) use ($site) {
                $query->where('site_id', '=', $site->id);
            });
        } else {
            //Models that don't have a site automatically belong to the default site. So load them
//            $modelQuery = $this->forModelName::doesntHave('Site');
            $modelQuery = $this->forModelName::query();
        }

        //Only get the root model
        $modelQuery->where('lft', '=', 1);

        //Eager load some commonly used relations when possible
        if (is_a(new $this->forModelName, AbstractTranslatableTreeModel::class)) {
            $modelQuery = $modelQuery->with('translation', 'translations');
        } elseif (is_a(new $this->forModelName, DocumentableInterface::class)) {
            $modelQuery = $modelQuery->with('images');
        }

        $rootModel = $modelQuery->first();
        if (is_a(HasThumbnailInterface::class, $this->forModelName)) {
            /** @var HasThumbnailInterface|Model|TreeModelInterface $rootModel */
            $rootModel->generateThumbnail();
        }

        return $rootModel;
    }

    /**
     * This method will load entities between
     * the given left and right boundaries
     * For the given site- and lang-id
     *
     * @param $lft
     * @param $rgt
     * @param $siteId
     * @param $langId
     * @return mixed
     */
    public function getModelsBetween($lft, $rgt, $siteId = null, $langId = null)
    {
        //If siteId is null set currentSiteId
        if ($siteId == null) {
            $siteId = $this->siteService->getCurrentSite()->id;
        }
        //If LangId is null set current LanguageId
        if ($langId == null) {
            $langId = $this->siteService->getCurrentSiteDefaultLanguage();
        }

        $table = (new $this->forModelName)->getTable();
        $shortClassName = KommaHelpers::getShortNameFromClass($this->forModelName, true);

        //Create the db query
        $records = \DB::table($table)
            ->select($table.'.lft', $table.'.rgt', $this->forTranslationModel->getTable().'.name', $table.'.id as id', $this->forTranslationModel->getTable().'.id as '.$this->forTranslationModel->getTable().'_id', $this->forTranslationModel->getTable().'.description', $this->forTranslationModel->getTable().'.slug')
            ->leftJoin($this->forTranslationModel->getTable(), $table.'.id', '=', $this->forTranslationModel->getTable().'.'.$shortClassName.'_id')
            ->whereBetween($table.'.lft', [$lft + 1, $rgt - 1])
            ->where($table.'.site_id', '=', $siteId)
            ->where(function ($query) use ($langId) {
                $query->whereNull($this->forTranslationModel->getTable().'.language_id')
                    ->orWhere($this->forTranslationModel->getTable().'.language_id', '=', $langId);
            })
            ->orderBy($table.'.lft', 'asc')
            ->get();

        return $records;
    }

    /**
     * This method will save an model
     *
     * @param Model $model or null
     * @param DatabaseCollection $sectionTabItems These must be filled with data. This is something you need to do yourself.
     *
     * @return Model
     */
    public function saveModel(Model $model = null, DatabaseCollection $sectionTabItems): Model
    {
        if ($model == null) {
            $model = new $this->forModelName;
            $model->active = 1;
        }
        $shortModelClassName = KommaHelpers::getShortNameFromClass($model, true);

//        if($model->exists == false) $model->save();

        $editedModel = false;
        $sectionTabItems->each(
            function ($sectionTabItem, $key) use ($model, &$editedModel, $shortModelClassName) {
                /** @var SectionTabItem $sectionTabItem */
                $attribute = $sectionTabItem->getAttribute();

//                if(is_a($attribute, Dynamic::class)) \Log::info("KmsRepository::69. Dynamic attribute with key '".$attribute->getKey()."' Gets it's value from '".$attribute->getsValueFrom()."'. Value: '".$attribute->getValue()."'"); //debugging helper
//                if(is_a($attribute, Images::class)) dd($attribute); //debugging helper
//                if(is_a($attribute, Select::class)) dd($attribute); //debugging helper

                $reference = $attribute->getsValueFromReference();
                switch ($attribute->getsValueFrom()) {
                    case Attribute::ValueFromModel:
                        $value = $attribute->getValue();
                        $model->$reference = $value;

                        break;
                    case Attribute::ValueFromTranslationModel:
                        $value = $attribute->getValue();
                        /** @var Language $language */
                        $language = $attribute->getAssociatedLanguage();

                        $translation = $this->getTranslationModelForModelByLanguage($model, $language);

                        $this->doAfterSave(function ($model) use ($translation, $value, $language, $reference) {
                            if (! $translation) {
                                throw new \RuntimeException('Woops. Tried to set a value on a non existing translatables AbstractTranslation model implementation but failed since the AbstractTranslation model implementation did not exist.');
                            }
                            $translation->$reference = $value;
                        });
                        break;
                    case Attribute::ValueFromModelHasManyRelation:
                        $this->doAfterSave(function ($model) use ($reference, $attribute, $sectionTabItem) {
                            $data = explode('|', $reference);
                            $valueRelationMethod = $data[0];

                            if (method_exists($model, $valueRelationMethod) === false) {
                                throw new \RuntimeException("The value reference method name ('".$valueRelationMethod."') does not exist on the model with class name: ".(get_class($model)).'. Please check the attribute configuration in the section.');
                            }
                            if (count($data) != 2) {
                                throw new \InvalidArgumentException("The value reference from attribute '".get_class($sectionTabItem->getAttribute())."' must be a pipe seperated relation|relationAttribute pair. But now it contains more pipes and that is not allowed.");
                            }

                            $value = $attribute->getValue();

                            $values = explode(',', $value);

                            if ($value != '') {
                                $model->$valueRelationMethod()->sync($values);
                            } else {
                                $model->$valueRelationMethod()->detach();
                            }
                        });
                        break;
                    case Attribute::ValueFromDocuments:
                        if (! is_a($attribute, Documents::class)) {
                            throw new \RuntimeException('The attribute with value reference "'.$reference.'" in section that uses the "'.get_class($this).'" service must be a "'.Documents::class.'" but was a "'.get_class($attribute).'"');
                        }

                        /** @var Documents $attribute */
                        $this->documentService->processUploadedDocumentsForModel($model, $attribute);
                        break;
                    case Attribute::ValueFromComponents:
                        $componentAreaService = $this->componentAreaService;
                        $this->doAfterSaveTranslations(function ($model) use ($reference, $attribute, $sectionTabItem, $componentAreaService) {
                            if (! is_a($attribute, ComponentArea::class)) {
                                throw new \RuntimeException('The attribute with value reference "'.$reference.'" in section that uses the "'.get_class($this).'" service must be a "'.ComponentArea::class.'" but was a "'.get_class($attribute).'"');
                            }
                            /** @var ComponentArea $attribute */
                            $componentAreaService->saveOrUpdateComponentAreaForAttribute($model, $attribute);
                        });
                        break;
                }
            }
        );
        $model->save();
        $this->executeAfterSaveClosures($model);
        $this->saveModelTranslations($model);
        $this->executeAfterSaveTranslationClosures($model);
        CatalogService::manageSearchabilityForGivenOrAllTranslatablesWhere($model, 'active', 0);

        return $model;
    }

    /**
     * Save the translations of the given AbstractTranslatableModel if they contain some data.
     * Also links translations and translatable model with eachother
     *
     * @param Model $model
     * @return Model
     */
    protected function saveModelTranslations(Model $model): Model
    {
        if (! is_a($model, AbstractTranslatableModel::class)) {
            return $model;
        }

        $model->translations->each(function (AbstractTranslationModel $abstractTranslationModel) use ($model) {
            if (! $abstractTranslationModel->isEmpty()) {
                if ($model->exists()) {
                    $abstractTranslationModel->translatable()->associate($model);
                }
                $abstractTranslationModel->save();
            }
        });

        return $model;
    }

    /**
     * Queue an action to run after the model is saved.
     *
     * @param \Closure $closure
     */
    protected function doAfterSave(\Closure $closure)
    {
        $this->afterSaveQueue->push($closure);
    }

    /**
     * Queue an action to run after the model is saved.
     *
     * @param \Closure $closure
     */
    protected function doAfterSaveTranslations(\Closure $closure)
    {
        $this->afterSaveQueueTranslations->push($closure);
    }

    /**
     * Executes the closures that where queued with the doAfterSave method
     * @param Model $model
     */
    private function executeAfterSaveClosures(Model $model)
    {
        $this->afterSaveQueue->each(function (\Closure $closure) use ($model) {
            $closure->call($this, $model);
        });
    }

    /**
     * Executes the closures that where queued with the doAfterSave method
     * @param Model $model
     */
    private function executeAfterSaveTranslationClosures(Model $model)
    {
        $this->afterSaveQueueTranslations->each(function (\Closure $closure) use ($model) {
            $closure->call($this, $model);
        });
    }

    /**
     * Uses a model that must have a translations method, creates a new translation for it if it does not exists and associates it with the language given.
     * Returns the translation
     *
     * @param AbstractTranslatableModel $model
     * @param Language $language
     * @return AbstractTranslationModel|null The translation
     */
    public function getTranslationModelForModelByLanguage(AbstractTranslatableModel &$model, Language $language): ?AbstractTranslationModel
    {
        $translation = $model->translations->filter(function (AbstractTranslationModel $translation) use ($language) {
            return $translation->language_id == $language->id;
        })->first();

        return $translation;
    }

    /**
     * Returns true when the section has a sortable items list to the left side of the screen or false if not.
     *
     * @return bool
     */
    public function getSortable()
    {
        return $this->sortable;
    }

    /**
     * @return BaseCollection containing SelectOptionInterface
     */
    public function getOptionsForSelect($allowNullableSelectOption = false): BaseCollection
    {
        $selectOptions = collect();

        if ($this->sortable == false) {
            if ($allowNullableSelectOption) {
                $selectOption = (App::make(SelectOptionInterface::class))
                    ->setContent(__('kms/global.none'))
                    ->setHtmlContent(__('kms/global.none'))
                    ->setValue(null);
                $selectOptions->push($selectOption);
            }

            /** @var $sidebarListItems SidebarListItem[] */
            $sidebarListItems = $this->getModelsForSideBar();

            foreach ($sidebarListItems as $sidebarListItem) {
                $model = $this->forModelName::find($sidebarListItem->getId());
                if (! $model) {
                    continue;
                }

                /** @var SelectOptionInterface $selectOption */
                $selectOption = (App::make(SelectOptionInterface::class))
                    ->setContent($sidebarListItem->getName())
                    ->setHtmlContent($sidebarListItem->getName())
                    ->setValue($model->id);

                $selectOptions->push($selectOption);
            }

            return $selectOptions;
        } else {
            /** @var TreeModelInterface $tree */
            $model = $this->getRootModelForTree();
            if (! $model) {
                return collect();
            }

            $rootSelectOption = $this->convertTreeModelToSelectOption($model);

            $modelWithAllChildren = $model->findChildren();

            $selectOptions = $this->convertAllModelsToSelectOptionModels(collect($modelWithAllChildren));
            $selectOptions->prepend($rootSelectOption);

            return $selectOptions;
        }
    }

    /**
     * Fills attributes with data from a model in this way:
     *
     * 1. First it looks if it needs to get the value from a translationModel.
     *    If so, gets it, fills the attribute and fills the next attribute if any
     * 2. Then it looks if it needs to get the value from it's model.
     *    If so, gets it, fills the attribute and fills the next attribute if any
     * 3. Then it looks if it needs to get the value from a hasMany relation.
     *    If so, gets it, fills the attribute and fills the next attribute if any
     * 4. Then it looks if it needs to get the value from the documents associated with it.
     *    If so, gets it, fills the attribute and fills the next attribute if any
     * Please notice that if cases 1, 2 and 3 where true the value you may have set with setValue is overwritten.
     *
     * @param DatabaseCollection $sectionTabItems A collection containing implementations AbstractSectionTabItem's
     * @param Model $model
     * @return DatabaseCollection
     */
    public function fillAttributesWithData(DatabaseCollection $sectionTabItems, Model $model)
    {
        //Validate that each Collection item is an instance of AbstractSectionTabItem
        $sectionTabItems->every(function ($item, $key) {
            if (! is_a($item, AbstractSectionTabItem::class)) {
                throw new \InvalidArgumentException('The attributes passed must be a Collection of AbstractSectionTabItem instances but was not');
            }
        });

        $filledAttributes = new DatabaseCollection();
        if (is_a($model, AbstractTranslatableModel::class)) {
            $model->load('translations');
        }

        $sectionTabItems->each(
            function ($sectionTabItem, $key) use ($model, $filledAttributes) {
                /** @var $sectionTabItem SectionTabItem */
                if (! is_a($sectionTabItem->getAttribute(), Attribute::class)) {
                    throw new \InvalidArgumentException('One of the attributes in a AbstractSectionTabItem instance is not but must be an child instance of Attribute.');
                }

//                if(is_a($sectionTabItem->getAttribute(), OnOff::class)) dd($sectionTabItem->getAttribute());
//                if(is_a($sectionTabItem->getAttribute(), Select::class)) dd($sectionTabItem->getAttribute()->getValue());

                $value = null;

                $valueReference = $sectionTabItem->getAttribute()->getsValueFromReference();
                switch($sectionTabItem->getAttribute()->getsValueFrom()) {
                    //Case 1. Check if the translation of the model has a value.
                    case Attribute::ValueFromTranslationModel:
                        if ($sectionTabItem->getAttribute()->hasAssociatedLanguage()) {
                            if (! is_a($model, AbstractTranslatableModel::class)) {
                                throw new \RuntimeException("The model '".get_class($model)."' is not a model that is translatable (It does not implement the TranslatableModelInterface). Please check the section configuration (Attribute with reference '".$sectionTabItem->getAttribute()->getsValueFromReference()."') or make the model implement the interface.");
                            }

                            $translation = $model->translations->where('language_id', $sectionTabItem->getAttribute()->getAssociatedLanguage()->id)->first();
//                            \Log::info("Filling attribute of class: '".get_class($sectionTabItem->getAttribute())."' with a value that comes from a translation.");

                            if (! $translation) {
//                                \Log::warning("Skipping filling attribute of class '".get_class($sectionTabItem->getAttribute())."' because it did not have a translation");
                                return;
                            } //Skip filling the value if the model did not have a translation

                            if (method_exists($model, 'translations') === false) {
                                throw new \InvalidArgumentException('The model given did not have a translation while its attribute states that it references a value of that model.');
                            }
                            if (array_key_exists($valueReference, $translation->attributesToArray()) === false) {
                                throw new \RuntimeException("The translation value reference field name ('".$valueReference."') does not exist on the translations from the model with class name: ".(get_class($model)).'. Please check the attribute configuration in the section.');
                            }

//                            \Log::info("Filled attribute with key '".$sectionTabItem->getAttribute()->getKey()."' with a value that comes from the '".$valueReference."' field of the translation model. Value: ".$translation->$valueReference);
                            $value = $translation->$valueReference;
                        }
                        break;
                        //Case 2 check if the model directly has a value
                    case Attribute::ValueFromModel:
                        $keys = array_keys($model->getAttributes());
                        if (! array_key_exists($valueReference, $keys) === false) {
                            throw new \RuntimeException("The value reference field name ('".$valueReference."') does not exist on the model with class name: ".(get_class($model)).'. Please check the attribute configuration in the section. All available field names: '.implode(', ', $keys));
                        }

                        $value = $model->$valueReference;

                        break;
                        //Case 3
                    case Attribute::ValueFromModelHasManyRelation:
                        if (! method_exists($model, $valueReference) === false) {
                            throw new \RuntimeException("The value reference method name ('".$valueReference."') does not exist or is not a HasMany relation on the model with class name: ".(get_class($model)).'. Please check the attribute configuration in the section.');
                        }
                        $data = explode('|', $valueReference);
                        if (count($data) != 2) {
                            throw new \InvalidArgumentException("The value reference from attribute '".get_class($sectionTabItem->getAttribute())."' must be a pipe seperated relation|relationAttribute pair. But now it contains more pipes and that is not allowed.");
                        }

                        $relationMethod = $data[0];
                        $relationFieldId = $data[1];

                        $collection = $model->$relationMethod()->get();
                        $valueToProcess = [];

                        $self = $this;
                        $collection->each(function ($item, $key) use (&$valueToProcess, $relationFieldId, $sectionTabItem, $relationMethod, $self) {
                            if (strpos($item->$relationFieldId, ',') !== false) {
                                throw new \RuntimeException("The relation method '".$relationMethod."' returned a value with a , character in it which is not allowed. See Attribute configuration of attribute '".get_class($sectionTabItem->getAttribute())."' in section '".get_class($self)."'");
                            }
                            $valueToProcess[] = $item->$relationFieldId;
                        });

                        $value = implode(',', $valueToProcess);
                        break;
                    case Attribute::ValueFromDocuments:
                        //Case 4 check if the model has documents.
                        if (! is_a($model, DocumentableInterface::class)) {
                            throw new InvalidArgumentException("The model '".get_class($model)."' must implement the interface '".DocumentableInterface::class."' but did not");
                        }
                        /** @var $model DocumentableInterface */
                        $value = json_encode($model->documents()->where('key', '=', (string) $sectionTabItem->getAttribute()->getKey())->get());
                        break;
                    case Attribute::ValueFromComponents:
                        $value = $this->componentAreaService->getComponentsSaveStateDataAsJsonFromModel($model, $sectionTabItem->getAttribute()); //Don't worry about type checking here. It is done in the service itself
                        break;

                    case Attribute::ValueFromItself:
                        if ($sectionTabItem->getAttribute()->getsValueFromReference() == 'last_modified') {
                            if ($sectionTabItem->getAttribute()->hasAssociatedLanguage()) {
                                $attribute = $sectionTabItem->getAttribute();
                                $language = $attribute->getAssociatedLanguage();
                                $translation = $this->getTranslationModelForModelByLanguage($model, $language);

                                if (isset($translation)) {
                                    $value = $translation->updated_at;
                                } else {
                                    $value = null;
                                }
                            }
                        }
                        break;
                }

                //Finally when we have a value we will set it on the attribute and then process the next one.
                if ($value !== null) {
                    $sectionTabItem->getAttribute()->setValue((string) $value);
                }

                $filledAttributes->push($sectionTabItem);
            }
        );

//        dd($filledAttributes);
        return $filledAttributes;
    }

    /**
     * This method will remove an TranslatableModelInterface instance
     *
     * @param $model
     * @throws \Exception
     */
    public function destroyModel(Model $model)
    {
        //delete the images
        if (is_a($model, DocumentableInterface::class)) {
            $this->documentService->deleteDocumentsForModel($model);
        }

        if (is_a($model, AbstractTranslatableModel::class)) {
            foreach ($model->translations()->get() as $translation) {
                //Delete the route of the translation
                if (is_a($model, HasRoutesInterface::class) && is_a($model, PageTranslation::class)) {
                    $translation->routes()->delete();
                }

                //Delete the translation
                $translation->delete();
            }
        }
        $model->delete();
    }

    /**
     * Generate an unique slug for an Abstract translatable model
     *
     * @param AbstractTranslationModel $translation
     * @param string $name
     * @param int $uniquifier
     * @return string
     */
    public function createOrGetUniqueSlug(AbstractTranslationModel $translation, string $name, int $uniquifier = 0): string
    {
        if ($uniquifier == 0) {
            $slug = str_slug($name);
        } else {
            $slug = str_slug($name).'-'.$uniquifier;
        }

        $results = $translation::where('slug', $slug)
            ->where('id', '!=', $translation->id)
            ->where('language_id', $translation->language_id)
            ->get();

        if ($results->count() == 0) {
            return $slug;
        } else {
            $slug = $this->createOrGetUniqueSlug($translation, $name, ($uniquifier + 1));

            return $slug;
        }
    }

    /**
     * Generate an unique slug for an Abstract translatable model
     *
     * @param Model $model
     * @param string $name
     * @param int $uniquifier
     * @return string
     */
    public function createOrGetUniqueSlugForModel(Model $model, string $name, int $uniquifier = 0): string
    {
        if ($uniquifier == 0) {
            $slug = str_slug($name);
        } else {
            $slug = str_slug($name).'-'.$uniquifier;
        }

        $results = $model::where('slug', $slug)
            ->where('id', '!=', $model->id)
            ->get();

        if ($results->count() == 0) {
            return $slug;
        } else {
            $slug = $this->createOrGetUniqueSlugForModel($model, $name, ($uniquifier + 1));

            return $slug;
        }
    }
}