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/zelfverkopen.komma.pro/app/KommaApp/Kms/Core/Sections/Section.php
<?php
namespace App\KommaApp\Kms\Core\Sections;

use App\KommaApp\Images\ImageService;
use App\KommaApp\Kms\Core\Attributes\Attribute;
use App\KommaApp\Kms\Core\Attributes\ImageableAttribute;
use App\KommaApp\Kms\Core\Attributes\Traits\LabelTrait;
use App\KommaApp\Kms\Core\KmsInterface;
use App\KommaApp\Languages\Models\Language;
use App\KommaApp\Routes\RouteService;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\ViewErrorBag;

/**
 * Class Section. Will replace KmsSection
 * @package App\KommaApp\Kms\Core\Sections
 */
abstract class Section
{
    protected $kms;

    /**
     * @var SectionService $sectionService
     */
    protected $sectionService;

    /** @var RouteService $routeService */
    protected $routeService;

    protected $eventListeners = [];

    /**
     * @var SectionTabsCollectionInterface
     */
    protected $sectionTabDirector;

    /** @var Collection The AbstractSectionTabItem instances that belong to a sectionTab. Todo: this should be the sectionTab instead. If we edit this like i described then we must modify related code too */
    protected $sectionTabItems;

    private $processedAttributes = [];

    /** @var Model $model */
    private $model = null;
    protected $models = [];

    //True if the models uses an eav
    protected $eav = false;

    protected $errors = [];

    protected $title = "";
    protected $subTitle = "";
    protected $slug = "";

    public $submitButtonLabel = 'save';
//    public $submitRoute = null;

    public $type = 'model';

    public $keyAsArray = false;
    public $keyArrayKey = '';

    /** @var bool whether or not the RouteService should manage routes for this section using the models translationmodels name attribute*/
    protected $hasRoutes = false;

    /*
     * Protected actions
     * options:
     * all - can be accessed by everybody
     * admin - can be accessed only by super admins
     * none - can be accessed by nobody
     */
    public $showSave = 'all';
    public $showDelete = 'all';
    public $showCreate = 'all';
    public $deleteButtonText = '';

    //When this var is true, the right pane will be generated
    public $showEntity = true;

    function __construct (SectionService $sectionService, RouteService $routeService, AbstractSectionTabsDirector $sectionTabDirector)
    {
        $this->submitButtonLabel = __('kms/global.save');
        $this->kms = \App::make(KmsInterface::class);
        $this->sectionService = $sectionService;
        $this->routeService = $routeService;
        $this->sectionTabDirector = $sectionTabDirector;

        //localize
        $this->submitButtonLabel = \Lang::get('kms/global.save');
    }

    public function getSectionService()
    {
        return $this->sectionService;
    }

    public function getTitle()
    {
        return $this->title;
    }

    public function getSubTitle()
    {
        return $this->subTitle;
    }
    public function setSubTitle($string)
    {
        $this->subTitle = $string;
    }

    public function getSlug()
    {
        return $this->slug;
    }

    public function getEntities()
    {
        return $this->models;
    }
//
//    public function setKeyAsArray($keyAsArray)
//    {
//        $this->keyAsArray = $keyAsArray;
//    }
//
//    public function setKeyArrayKey($keyArrayKey)
//    {
//        $this->keyArrayKey = $keyArrayKey;
//
//    }


    /**
     * This method will generate the list of models.
     * These are used in the sidebar.
     *
     * @return mixed
     */
    public function loadModels()
    {
        //If getSortable() returns true, dont load the models, they are loaded via Ajax
        if ($this->getSortable()) return null;

        return $this->models = $this->sectionService->getModelsForSideBar();
    }

    /**
     * This method returns the thumbnail for the top bar
     *
     * @return string
     */
    public function getThumbnail()
    {
        //Check if a thumbnail is set
        if (isset($this->model->thumbnail)) {
            //If there is an image_url set, return with image tags
            if (isset($this->model->thumbnail['image_url'])) {
                return '<img src="' . $this->model->thumbnail['image_url'] . '"/>';
            }
            //If there is a string set as array, return only the string
            elseif (isset($this->model->thumbnail['string'])) {
                return $this->model->thumbnail['string'];
            }
            //return thumbnail
            else return '';
        }
        //Return empty string
        return '';
    }


    /**
     * This method is called when an model is called
     * eg. for the edit form via the controller
     *
     * @param null $idOrModel
     * @return null
     */
    public function loadModel($idOrModel = null)
    {
//        \Log::debug("KmsSection:191 loadModel");

        if(is_numeric($idOrModel) || is_null($idOrModel)) {
            $this->model = $this->sectionService->getModel($idOrModel);
        }
        elseif(gettype($idOrModel) == "object" && is_a($idOrModel, Model::class))
        {
            $this->model = $idOrModel;
            $this->sectionService->setThumbnail($idOrModel);
        } else {
            throw new \InvalidArgumentException("KmsSection loadModel expected a variable of type int or numeric string or eloquent model but got a variable of type: ".gettype($idOrModel));
        }

        return $this->model;
    }

    public function setOrCreateModel($model)
    {
        if ($model == null) {
            $this->model = $this->sectionService->newModel();
        } else {
            $this->model = $model;
        }

        return $this->model;
    }

    /**
     * This method returns the model id.
     * When there is no model it returns false.
     *
     * @return mixed False, model id
     */
    public function getModelId()
    {
        //Check if there is an model, return false
        if (!$this->model) return false;
        //Return the model id
        return $this->model->id;
    }

    /**
     * Gets the model name
     */
    public function getModelName($stripModelSuffix = true)
    {
        if(!$this->model) return '';

        $reflect = new \ReflectionClass($this->model);

        $className = $reflect->getShortName();

        if($stripModelSuffix) $className = str_ireplace('model', '', $className);

        return $className;
    }

    public function getModel()
    {
//        if($this->model) \Log::debug("KmsSection:250 getModel (exists)"); else \Log::debug("KmsSection:250 (does not exist)");
        return $this->model;
    }


    /**
     * This method returns the title of the model
     * It will check if there is an model title
     * or returns new {type}
     *
     * @return string
     */
    public function getModelTitle()
    {
        //If there is an title set use this
        if ($this->model && $this->model->exists && ($this->model->title || $this->model->title != "")) {
            return $this->model->title;
        }

        //No model title return new {type}
        return trans('kms/' . $this->slug . '.new'); 
    }

    /**
     * Generates the attributes for this section. They all must extend the App\KommaApp\Kms\Core\Attributes\Attribute class
     *
     * @return Collection A collection of SectionTabItems
     */
    abstract protected function generateAttributes(): Collection;


    /**
     * Generates the attributes for the section using the sections generatesAttributes method
     * and adds them to the appropriate tabs
     *
     * @return void
     */
    public function generateAttributesAndAddThemToTabs()
    {
        $this->sectionTabItems = $this->generateAttributes();
        $this->addAttributesToTabs();
    }

    /**K
     * Returns a rendered view
     *
     * @return \Illuminate\Contracts\View\View
     */
    public function render()
    {
//        \Log::debug("KmsSection:300 render");
        if($this->showEntity) $this->generateAttributesAndAddThemToTabs();

        if(!$this->getModel()) return $this->makeView(); //Return a view since we can't fill it with data because the model is not set.

        $this->getSectionService()->fillAttributesWithData($this->sectionTabItems, $this->getModel());

        return $this->makeView();
    }


//    private function routes()
//    {
//        $modelId = $this->getModelId();                 //43 for example
//        $slug = $this->getSlug();                       //Pages for example
//        $siteSlug = $this->kms->getSiteSlug();   //default for example
//
//
//        //If we have a modelId, then we need to update (PUT) the model. Else we need to store (POST) a new model.
//        $saveRoute = ($modelId) ? strtolower($this->slug).'.update' : strtolower($this->slug).'.store';
//        $saveMethod = ($modelId) ? 'PUT': 'POST';
//
//        //Set route parameters
//        $routeParameters = [];
//        if($siteSlug) $routeParameters['siteSlug'] = $siteSlug;
//        if($siteSlug) $routeParameters[$this->sectionService->] = $siteSlug;
//
//
//        route($saveRoute, $routeParameters);
//    }

    /**
     * Makes the view and returns it
     *
     * @return \Illuminate\Contracts\View\View
     */
    protected function makeView(): \Illuminate\Contracts\View\View
    {
        $saveRoute = $this->routeService->getSaveRoute($this->getSlug(), $this->getModelId());

        return \View::make('kms/section.index', [
            'kms' => $this->kms,
            'section' => $this,
            'saveRoute' => $saveRoute
        ]);
    }

    /**
     * This method will trigger the save method
     *
     * @param $id
     * @throws \Exception
     */
    public function update($id)
    {
        $this->generateAttributesAndAddThemToTabs();
        $this->save($id);
    }

    /**
     * This method creates or updates a model.
     * Based on the input data of the form.
     *
     * @param Model $model
     * @param bool $forCreate
     * @return Model|mixed $id
     * @throws \Exception
     */
    public function save(Model $model = null)
    {
        $this->fillAttributesFromInput();

        $model = $this->sectionService->saveModel($model, $this->sectionTabItems);
        $this->processImageableAttributesFromInput($model);

        $this->createOrUpdateRoutesForModelIfChanged($model);

        //Fire the event saved method
        \Event::fire('kms.entity.saved.' . $this->slug, $this->model);

        //Return the id
        return $model;
    }

    private function createOrUpdateRoutesForModelIfChanged(Model $model)
    {
        $this->routeService->createOrUpdateRoutesForModelIfChanged($model);

        return null;
    }

    /**
     * This method calls the destroyModel method
     * from the give repository class.
     *
     * @param Model $model
     * @throws \Exception
     */
    public function destroy(Model $model)
    {
        if($this->hasRoutes) $this->routeService->deleteRoutesForModel($model);
        $this->sectionService->destroyModel($model);
    }

    /**
     * This method creates a error list / attribute
     *
     * @param ViewErrorBag|null $errors
     */
    public function setErrors(ViewErrorBag $errors = null)
    {
        //If errors are empty; return
        if (!$errors) return;
        //Get the errors from the error
        $this->errors = $errors->getMessages();

        if(!$this->sectionTabItems) return;

        $this->sectionTabItems->each(function($sectionTabItem, $key) {
            /** @var SectionTabItem $sectionTabItem */
            $attribute = $sectionTabItem->getAttribute();

            if (isset($errors)) {
                $errors = $this->errors[$attribute->getKey().''];
                $attribute->getValidationSet()->setErrors($errors);
            }
        });
    }

    /**
     * Iterates over all sectionTabItem instances and builds a Validator
     * from the rules and messages in them.
     *
     * @param array $input The input to validate. The function wil retrieve the input itself if you don't specify this.
     * If you specify it must be an associative array containing input names as keys and values as values from
     * those inputs.
     *
     * @return \Validator
     */
    public function validateInputAndReturnValidator(array $input = [])
    {
        $this->extendValidator();

        if(empty($input)) $input = \Input::all();

        $rules = [];
        $messages = [];

        /** @var AbstractSectionTabItem $sectionTabItem */
        foreach($this->sectionTabItems as $sectionTabItem)
        {
            $attribute = $sectionTabItem->getAttribute();
            $set = $attribute->getValidationSet();

            if(!$set->hasRulesAndMessages()) continue;

            //prepend the attribute key in front of each message rule followed by a dot to make sure the message is unique for each attribute.
            $set->prefixMessageRulesWith((string)$attribute->getKey());

            //And replace :attribute with the label text of the attribute
            if(in_array(LabelTrait::class, class_uses($attribute))) $set->modifyMessages(":attribute", $attribute->getLabelText());

            $rules = array_merge($rules, [(string)$attribute->getKey() => $set->getRules()]);
            $messages = array_merge($messages, $set->getMessages());
        }

//        dd($rules);
        $validator = \Validator::make($input, $rules, $messages);

        return $validator;
    }


    /**
     * This method is used to show/hide the create button
     * Based on the user and the settings it will return true or false
     *
     * @return bool
     */
    public function showCreate()
    {
        if (is_array($this->showCreate)) {
            return in_array(\Auth::user()->role->id, $this->showCreate);
        }

        return $this->showCreate == 'all' || ($this->showCreate == 'admin' && \Auth::user()->role->id == 1);
    }

    /**
     * This method is used to show/hide the delete button (delete possible)
     * Based on the user and the settings it will return true or false
     *
     * @return bool
     */
    public function  showDelete()
    {
        if (is_array($this->showDelete)) {
            return in_array(\Auth::user()->role->id, $this->showDelete);
        }

        return $this->showDelete == 'all' || ($this->showDelete == 'admin' && \Auth::user()->role->id == 1);
    }

    public function getDeleteButtonText()
    {
        return $this->deleteButtonText != '' ? $this->deleteButtonText : '';
    }

    /**
     * This method is used to show/hide the save button (update possible)
     * Based on the user and the settings it will return true or false
     *
     * @return bool
     */
    public function showSave()
    {
        if (is_array($this->showSave)) {
            return in_array(\Auth::user()->role->id, $this->showSave);
        }

        return $this->showSave == 'all' || ($this->showSave == 'admin' && \Auth::user()->role->id == 1);
    }

    /**
     * For which model this section works for.
     *
     * @param string $forModel Referencing a eloquent Model
     */
    public function setForModelName(string $forModel)
    {
        $this->forModelName = $forModel;
        $this->routeService->setForModelName($forModel);
    }

//    /**
//     * Create a list with KmsAttribute Objects, based on $this->modelAttributes
//     * The data is from the modelSection file
//     *
//     * @return array
//     */
//    protected function parseModelAttributesData($test = false)
//    {
//        //TODO JULES: REBUILD THIS OOP. THIS METHOD PROBABLE CAN BE DELETED IN THE FUTURE
//
//        //Create  empty attributes
//        $this->sectionTabItems = [];
//
//
//        foreach ($this->sectionTabItems as $attributeKey => $attribute) {
//            if(is_object($attribute)) {
//                //Detected a new type of object! This branch is used for transitioning from not so OOP objects to OOP and will become obsolete in the future
//
//                $this->sectionTabItems[] = $attribute;
////                $this->tabDirector->addItem($attribute, \Config::get('kms.main.defaultTabName')); TODO: This is now done in the section constructor
//            } else {
//                if (isset($attribute['role']) && !in_array(\Auth::user()->role->id, $attribute['role'] )) continue;
//
//                //Set the specific type of foreach
//                $forEach = isset($attribute['forEach']) ? $attribute['forEach'] : null;
//
//                //TODO JULES MAYBE WE SHOULD CREATE FACTORYS / BUIDERS FOR THIS STUFF BELOW
//                // create attributes
//                switch ($forEach) {
//                    case "CurrentSiteLanguages":
////                        $this->createAttributeForCurrentSiteLanguages($attributeKey, $attribute);
//                        $this->createAttributeForCurrentSiteLanguages($attributeKey, $attribute);
//                        break;
//                    case "AllLanguages":
//                        $this->createAttributeForAllLanguages($attributeKey, $attribute);
//                        break;
//                    case "AllSites":
//                        $this->createAttributeForAllSites($attributeKey, $attribute);
//                        break;
//                    case "AllSitesAsSub":
//                        $this->createAttributeForAllSites($attributeKey, $attribute, true);
//                        break;
//                    case "AllSitesAndLanguages":
//                        $this->createAttributeForAllSitesAndLanguages($attributeKey, $attribute);
//                        break;
//                    case'AllForeachModels':
//                        $this->createAttributeForAllForeachModel($attributeKey, $attribute);
//                        break;
//                    case'AllForeachModelsForAllSites':
//                        $this->createAttributeForAllForeachModel($attributeKey, $attribute, 'AllSites');
//                        break;
//                    case 'AllForeachModelsForAllSitesAndLanguages':
//                        $this->createAttributeForAllForeachModel($attributeKey, $attribute, 'AllSitesAndLanguages');
//                        break;
//                    default:
//                        $this->createAttribute($attributeKey, $attribute);
//                        break;
//                }
//            }
//        }
//
////        dd($this->attributes);
//        return $this->sectionTabItems;
//    }

    /**
     * Creates copies of the $baseAttribute for each language given.
     * Also retrieves the translation that belongs to the model based on each language that is being processed.
     * The translation is stored inside if it exists the attribute to later on check at which tab in a section it belongs.
     *
     * @param Attribute $baseAttribute
     * @param $languages
     * @return array with the created attributes
     */
    private function createAttributesFromExistingAttributeForTheGivenLanguages(Attribute $baseAttribute, $languages)
    {
        $createdAttributes = [];

        //Loop trough each language of the current site
        foreach ($languages as $language) {
            //Get the translation of the model

            $clone = clone $baseAttribute;

            if($language) $clone->setAssociatedLanguage($language);

            $createdAttributes[] = $clone;
        }

        return $createdAttributes;
    }


    /**
     * This method create copies of the attributes you pass it for all current site languages and
     * injects translations via another method
     *
     * @param $baseAttributes
     * @return array
     */
    protected function createAttributesFromExistingAttributeForCurrentSiteLanguages(Array $baseAttributes)
    {
        //Get the languages
        $languages = $this->kms->getSiteLanguages();

        $attributes = [];
        foreach($baseAttributes as $baseAttribute) {
            $attributesCreatedFromBaseAttribute = $this->createAttributesFromExistingAttributeForTheGivenLanguages($baseAttribute, $languages);
            $attributes = array_merge($attributes, $attributesCreatedFromBaseAttribute);
        }
        return $attributes;
    }

    /**
     * This method create copies of the attributes you pass it for all used site languages and
     * injects translations via another method.
     *
     * @param $baseAttributes
     * @return array
     */
    protected function createAttributesFromExistingAttributeForAllUsedLanguagesBySites(Array $baseAttributes)
    {
        // Get all used languages from the site_languages table
        $languages = Language::has('Sites')->get();

        $attributes = [];
        foreach($baseAttributes as $baseAttribute) {
            $attributesCreatedFromBaseAttribute = $this->createAttributesFromExistingAttributeForTheGivenLanguages($baseAttribute, $languages);
            $attributes = array_merge($attributes, $attributesCreatedFromBaseAttribute);
        }
        return $attributes;
    }


    /**
     * This method will create the fields for all the Languages
     *
     * @param $key
     * @param $attributeData
     * @return null
     */
    protected function createAttributeForAllLanguages($key, $attributeData)
    {
        //Get the languages
        $languages = $this->kms->getSiteLanguages();
        $this->createAttributeForLanguages($key, $attributeData, $languages);
    }

    /**
     * This method will create the fields for all the Languages
     *
     * @param array $baseAttributes
     * @return null
     */
    protected function createAttributeFromExistingAttributeForAllLanguages(Array $baseAttributes)
    {
        //Get the languages
        $languages = $this->kms->getSiteLanguages();

        $attributes = [];
        foreach($baseAttributes as $baseAttribute) {
            $attributesCreatedFromBaseAttribute = $this->createAttributesFromExistingAttributeForTheGivenLanguages($baseAttribute, $languages);
            $attributes = array_merge($attributes, $attributesCreatedFromBaseAttribute);
        }
        return $attributes;
    }


    /**
     * This method creates the fields for all the languages in all the sites
     *
     * @param $key
     * @param $attributeData
     * @deprecated
     */
    protected function createAttributeForAllSitesAndLanguages($key, $attributeData)
    {
        //Loop trough the sites
        foreach ($this->kms->getSites() as $site) {


            //Check if There is an sub models set, and load this.
            if (!isset($attributeData['model']) || !$subModel = $this->getSubModel($this->model, $attributeData['model'], $attributeData, 'site_id', $site->id)) {
                //Set the default model to submodel
                $subModel = $this->model;
            }

            //Loop trough the site languages
            foreach ($site->languages as $language) {

                //Load the ModelTranslation
                if (!$modelTranslation = $this->getModelTranslation($subModel, $language->id)) {
                    //set the submodel to the ModelTranslation
                    $modelTranslation = $subModel;
                }

                //Set the value
                $value = isset($modelTranslation->$key) ? $modelTranslation->$key : null;

                if ($relatedAttribute = $this->getRelatedAttribute($key, $attributeData, $site->id, $language->id)) $value = $relatedAttribute;

                //Parse the options
                $options = isset($attributeData['options']) ? $this->parseOptions($attributeData['options'], $language->id, $site->id) : [];
                //Create the site tab
                $tab = $site->short_name;
                //Create the sub tab based on the language
                $subTab = $language->iso_2;
                //Set the key to $key_language_id_site_id
                $currentKey = $key . '_' . $language->id . '_' . $site->id;
                //Create a new attributes
                $item = $this->sectionTabItems[] = new $attributeData['type']($currentKey, $value, $options, [], $site->id, $language->id, $this);
                //Add the item to the tab
//                $this->tabDirector->addItem($item, $tab, $subTab);
            }
        }
    }

    /**
     * This method creates the fields for all the sites
     *
     * @param $key
     * @param $attributeData
     */
    protected function createAttributeForAllSites($key, $attributeData, $subTab = false)
    {
        //NOTE: METHOD IS DEPRECATED. Don't use it anymore

        if ($key == 'site_label') return;

        //Loop trough the site
        foreach ($this->kms->getSites() as $site) {

            $value = isset($this->model) && isset($this->model->$key) ? $this->model->$key : null;

            //GEt the value by the related attribute
            if ($relatedAttribute = $this->getRelatedAttribute($key, $attributeData, $site->id)) $value = $relatedAttribute;
            //Check if there is an model set, load the model sorted by the site_id
            elseif (isset($attributeData['model']) && $subModel = $this->getSubModel($this->model, $attributeData['model'], $attributeData, 'site_id', $site->id)) {
                $value = (isset($subModel->$key) ? $subModel->$key : $value);
            }


            //parse the options
            $tab = $site->short_name;
            $options = isset($attributeData['options']) ? $this->parseOptions($attributeData['options'], null, $site->id) : [];
            //Create the site tab
            //Create the ket from $key and site id
            $currentKey = $key . '_' . $site->id;

            //Check if there is an attribute foreach, set this to $foreach
            if (isset($attributeData['forEach'])) $foreach = $attributeData['forEach'];
            //Create a new attributes
            $item = $this->sectionTabItems[] =
                new $attributeData['type']($currentKey, $value, $options, [], $site->id, null, $this, $foreach);
            //Add the item to the tab

            if ($subTab) {
//                $this->tabDirector->addItem($item, null, $tab);
            } else {
//                $this->tabDirector->addItem($item, $tab);

            }
        }
    }

    /**
     * This method creates the fields for all the instances of the given model.
     *
     * @param $key
     * @param $attributeData
     */
    public function createAttributeForAllForeachModel($key, $attributeData, $repeat = null)
    {
        //NOTE: METHOD IS DEPRECATED. Don't use it anymore


        //Check if the foreachmodel is set, if not abort 500, with the corresponding error.
        if (!isset($attributeData['forEachModel']))
            return \App::abort(500, 'ForeachModel for ' . $key . ' not set!');

        //Set the foreach model to an array
        $foreachModel = $attributeData['forEachModel'];

        //Check if the model has the releatedForeachModel, if not abort 500, with the corresponding error.
        if (!$foreachModels = $this->model->$foreachModel)
            return \App::abort(500, $foreachModel . ' not found on ' . get_class($this->model) . '!');

        //Check if the relatedModel count is 0
        if ($foreachModels->count() == 0) {
            //Get the foreachModel als relation
            $modelRelation = $this->model->$foreachModel();
            //Set the empty related model (the foreachModel) to the foreachModels
            $foreachModels[] = $modelRelation->getRelated();
        }


        //We got foreachModels, let start to work
        foreach ($foreachModels as $loopKey => $model) {

            //Count  = $loopkey +1
            $count = $loopKey++;

            //Set the current key with Key and the Count
            $currentKey = $key . '_' . $count;

            $foreach = null;


            $tabName = isset($attributeData['tab']) ? $attributeData['tab'] : \lang::get('kms/global.foreach_name.' . $foreachModel);

            if (isset($attributeData['forEachTab']) && $attributeData['forEachTab'] === false) {
                $tabName = $tabName;
            } elseif (isset($attributeData['forEachTabKey']) && $model->$attributeData['forEachTabKey'] != null) {
                $tabName .= ' ' . $model->$attributeData['forEachTabKey'];
            } else {
                $tabName .= ' ' . $count;
            }


            switch ($repeat) {
                case 'AllSites':
                    $this->forEachModelAllSites($key, $attributeData, $model, $count, $tabName);
                    break;
                case 'AllSitesAndLanguages':
                    $this->forEachModelAllSitesAndLanguages($key, $attributeData, $model, $count, $tabName);
                    break;
                default:
                    $options = isset($attributeData['options']) ? $this->parseOptions($attributeData['options'], null, null, $loopKey, $model->id) : [];

                    //Get the value for the given key
                    $value = $this->getItemValue($key, $attributeData, null, null, $model);
                    $item = $this->sectionTabItems[] =
                        new $attributeData['type']($currentKey, $model, $options, [], $model->id, null, $this, $loopKey);
//                    $this->tabDirector->addItem($item, $tabName);
            }

        }
    }

    public function forEachModelAllSites($key, $attributeData, $model, $count, $tabName)
    {
        //NOTE: METHOD IS DEPRECATED. Don't use it anymore


        foreach ($this->kms->getSites() as $site) {
            $currentKey = $key . '_' . $count . '_' . $site->id;
            $value = $this->getRelatedAttribute($key, $attributeData, $site->id, null, $model);
            $options = isset($attributeData['options']) ? $this->parseOptions($attributeData['options'], null, $site->id, $count, $model->id) : [];


            $item = $this->sectionTabItems[] =
                new $attributeData['type']($currentKey, $value, $options, [], $site->id, null, $this, null);
//            $this->tabDirector->addItem($item, $tabName, $site->short_name);
        }
    }

    public function forEachModelAllSitesAndLanguages($key, $attributeData, $model, $count, $tabName)
    {
        //NOTE: METHOD IS DEPRECATED. Don't use it anymore


        foreach ($this->kms->getSites() as $site) {
            foreach ($this->kms->getSiteLanguages($site->id) as $language) {
                $currentKey = $key . '_' . $count . '_' . $site->id . '_' . $language->id;
                $value = $this->getRelatedAttribute($key, $attributeData, $site->id, $language->id, $model);
                $options = isset($attributeData['options']) ? $this->parseOptions($attributeData['options'], $language->id, $site->id, $count, $model->id) : [];


                $item = $this->sectionTabItems[] =
                    new $attributeData['type']($currentKey, $value, $options, [], $site->id, $language->id, $this, null);
//                $this->tabDirector->addItem($item, $tabName, $site->short_name, $language->iso_2);
            }
        }
    }


    public function getItemValue($key, $attributeData, $siteId = null, $languageId = null, $model = null)
    {

        //Set the value in the most basic way
        if (isset($model->$key)) return $model->$key;
        //Check if there is an relatedAttribute
        if ($value = $this->getRelatedAttribute($key, $attributeData, $siteId, $languageId, $model)) return $value;

        //Return the model when nothing is found
        return $model;
    }


//    TODO: We should not need this. We may implement related attribtutes as embeded child attributes in attributes themselves.
//    public function getRelatedAttribute($key, $attributeData, $siteId = null, $languageId = null, $model = null)
//    {
//        //Check if it has the parameter relatedAttribute and is true
//        if (!isset($attributeData['relatedAttribute']) || !$attributeData['relatedAttribute']) return null;
//
//        //Set the $this->model to the model
//        if ($model == null) $model = $this->model;
//
//        //Check if there is an submodel and it exist
//        if (isset($attributeData['model'])) {
//            $models = explode('->', $attributeData['model']);
//            foreach ($models as $countKey => $deeperLevel) {
//
//                $keyId = (isset($attributeData['relatedAttributeOptions'][$countKey]['keyId']) ? $this->replaceModelStrings($attributeData['relatedAttributeOptions'][$countKey]['keyId'], $siteId, $languageId, $model) : null);
//                $keyBy = (isset($attributeData['relatedAttributeOptions'][$countKey]['keyBy']) ? $attributeData['relatedAttributeOptions'][$countKey]['keyBy'] : null);
//                //Check if it is an method
//                if (preg_match('/([a-z0-9_]+)\((.*)\)/i', $deeperLevel, $matches)) {
//                    //If the method doesn't exist exit to a 500 page
//                    if (!method_exists($model, $matches[1])) return \App::abort(500, 'Method ' . $matches[1] . ' doesn\'t exist on section-key: ' . $key);
//
//                    $parameters = (!empty($matches[2]) ? explode(',', $matches[2]) : []);
//
//                    $model = call_user_func_array([$model, $matches[1]], $parameters);
//
//                } else {
//                    $model = $this->getSubModel($model, $deeperLevel, $attributeData, $keyBy, $keyId);
//                }
//
//            }
//            if (!$model) return false;
//
//            //if KeyBy is is not set, return the current model (or collection)
//            if (!isset($keyBy)) return $model;
//        }
//
//        //Check if the model exists, if not return false
//        if (!$model) return false;
//
//        //Get the value of the model
//        return $model->getValue($key, $siteId, $languageId);
//
//    }

//    TODO: We should not need this. We may implement related attribtutes as embeded child attributes in attributes themselves.
//    public function getSubModelValue($key, $attributeData, $siteId = null, $languageId = null, $model = null)
//    {
//
//        $model = $this->getSubModelExtended($key, $attributeData, $siteId, $languageId, $model);
//
//        if (is_string($model)) return $model;
//
//        if ($model && $model->$key) return $model->$key;
//
//        return $model;
//    }

//    TODO: We should not need this. We may implement related attribtutes as embeded child attributes in attributes themselves.
//    public function getSubModelExtended($key, $attributeData, $siteId = null, $languageId = null, $model = null)
//    {
//        //Set the $this->model to the model
//        if ($model == null) $model = $this->model;
//
//        //Check if there is an submodel and it exist
//        if (!isset($attributeData['model'])) return false;
//
//        //Split the model by ->
//        $models = explode('->', $attributeData['model']);
//        //loop trough the models and keep the dept of the level ($countKey)
//
//        foreach ($models as $countKey => $deeperLevel) {
//
//            //Set the KeyId if exist
//            $keyId = (isset($attributeData['relatedAttributeOptions'][$countKey]['keyId']) ? $this->replaceModelStrings($attributeData['relatedAttributeOptions'][$countKey]['keyId'], $siteId, $languageId, $model) : null);
//            //Set the KeyBy if exist
//            $keyBy = (isset($attributeData['relatedAttributeOptions'][$countKey]['keyBy']) ? $attributeData['relatedAttributeOptions'][$countKey]['keyBy'] : null);
//
//            //Check if the "model" is an method
//            if (preg_match('/([a-z0-9_]+)\((.*)\)/i', $deeperLevel, $matches)) {
//
//                //If the method doesn't exist exit to a 500 page
//                if (!method_exists($model, $matches[1])) return \App::abort(500, 'Method ' . $matches[1] . ' doesn\'t exist on section-key: ' . $key);
//
//                //  Explode the found $parameters, if they exist. or an empty []
//                $parameters = (!empty($matches[2]) ? explode(',', $matches[2]) : []);
//                //Call the method with call_user_func_array, so we can add the parameters as an array
//
//                $model = call_user_func_array([$model, $matches[1]], $parameters);
//            } else {
//                //No method, check if it is an subModel (or value)
//                $orgModel = $model;
//
//                $model = $this->getSubModel($model, $deeperLevel, $attributeData, $keyBy, $keyId);
//                if(!$model && $orgModel) {
//                     //throw new \RuntimeException("Could not get subModel '".$deeperLevel."' from ".get_class($orgModel));
//                }
//            }
//
//        }
//
////        if(!$model) throw new \RuntimeException("Model wasn't found for key: ".$key." Models: ".$attributeData['model']);
//
//        return $model;
//    }


    /**
     * This method loads the subModel from the $this->model colleciton.
     * It uses the modelName and an sort field and the specific key.
     *
     * @param $attributeData
     * @param $keyBy | Db field that is used to map the array
     * @param $keyId | the id of the key we want
     * @return mixed
     */
    public function getSubModel($model, $subModelName, $attributeData, $keyBy = 'id', $keyId = null)
    {
        if (!$keyId) $keyId = $this->model->id;


        if (isset($attributeData['relatedAttributeOptions'])) {
            $keyBy = (isset($attributeData['relatedAttributeOptions']['keyBy']) ? $attributeData['relatedAttributeOptions']['keyBy'] : $keyBy);
            $keyId = (isset($attributeData['relatedAttributeOptions']['keyId']) ? $this->replaceModelStrings($attributeData['relatedAttributeOptions']['keyId']) : $keyId);
        }

        //Check if the model exists, if not return false
        if (!$model) return false;
        //Check if the model has the given model, if not return false

        if (!$subModelCollection = $model->$subModelName) return false;

        //If $keyBy is not set. return the entire collection
        if (!isset($keyBy)) {
            return $model->$subModelName;
        }

        //Sort the collection by the given keyBy parameter
        $subModelCollection = $model->$subModelName->keyBy($keyBy);

        //Return the wanted collection model based on the keyId, or false
        $collection = $subModelCollection->get($keyId);

//        if($subModelName == "routes") { dd($subModelCollection); }

        return $collection;
    }

    /**
     * This method loads the translation from a given model colletion
     *
     * @param        $model | The model were we want to get the translation from
     * @param        $language_id | the language id of the translation we want
     * @param string $translationModel | the translation model default {translations}
     * @param string $keyBy | Db field that is used to map the array defaukt {language_id}
     * @return bool
     */
    public function getModelTranslation($model, $language_id, $translationModel = 'translations', $keyBy = 'language_id')
    {
        //Check if the model has the translationModel, if not return false
        if (!$modelTranslations = $model->$translationModel) return false;
        //Key the modelTranslations by the given key_by parameter
        $modelTranslations = $modelTranslations->keyBy($keyBy);
        //Get the wanted language model, or false
        return $modelTranslations->get($language_id);
    }


    /**
     * Set the section attributes by $this->currentEntities
     * @param array /object $data
     */
    protected function populateAttributes()
    {

        //mooi('dit is niet gebruikt denk ik');
        foreach ($this->sectionTabItems as $attribute) {
            if ($attribute->getOption('populate') === false) continue;
            $key = $attribute->getKey();
            $value = $attribute->getValue();

            $attribute->setValue($value);
        }
        $this->processAttributes();
    }

    /**
     * Fills the model with the processed values form the attributes array.
     * Processing happens in each attributes by $attribute->getValue()
     * Eg. Hash::make in the Password attribute
     */
    protected function processAttributes()
    {
        foreach ($this->sectionTabItems as $key => $attribute) {
            $attribute->process();
            $this->processedAttributes[$attribute->getKey()] = $attribute;
        }
    }


//  //TODO Jules: We don't need this mess anymore because we handle this stuff in attributes them self via getters and setters in a more clear and concise way
//    /**
//     * Parse the options
//     *
//     * @param      $options
//     * @param null $languageId
//     * @param null $siteId
//     * @return mixed
//     */
//    private function parseOptions($options, $languageId = null, $siteId = null, $foreachCounter = null, $foreachModelId = null)
//    {
//
//        if ($siteId) $site = Site::find($siteId);
//        else {
//            $site = $this->kms->getSite();
//
//        }
//        if ($languageId) $language = Language::find($languageId);
//        foreach ($options as $key => $option) {
//
//            // Call recursively if the option is an array.
//            if (is_array($option)) $options[$key] = $this->parseOptions($option, $languageId, $siteId, $foreachCounter, $foreachModelId);
//
//            // Skip if option if isn't a string.
//            if (!is_string($option)) continue;
//
//            //Parse static strings to the value
//            if (isset($this->model->id)) {
//
//                //Replace the [[modelId]] to the model id
//                $options[$key] = $option = $this->model->id ?
//                    str_replace('[[modelId]]', $this->model->id, $option) : str_replace('[[modelId]]', '', $option);
//            }
//
//            if (isset($site)) {
//                //Replace [[siteName]] to the site name
//                $options[$key] = $option = str_replace('[[siteName]]', $site->name, $option);
//
//                //Replace [[siteId]] to the site id
//                $options[$key] = $option = str_replace('[[siteId]]', $site->id, $option);
//            }
//            if (isset($language)) {
//                //replace the [[languageId]] to the current language id
//                $options[$key] = $option = str_replace('[[languageId]]', $language->id, $option);
//                //replace the [[LanguageIso2]] to the current language id
//                $options[$key] = $option = str_replace('[[LanguageName]]', \Lang::get('languages.' . $language->iso_2), $option);
//                //replace the [[LanguageIso2]] to the current language iso
//                $options[$key] = $option = str_replace('[[languageIso]]', $language->iso_2, $option);
//            }
//            if (isset($foreachModelId)) {
//                //replace the [[foreachModelId]] to the current foreachModel id
//                $options[$key] = $option = str_replace('[[foreachModelId]]', $foreachModelId, $option);
//            }
//            if (isset($foreachCounter)) {
//                //replace the [[foreachModelId]] to the current foreachModel id
//                $options[$key] = $option = str_replace('[[foreachCounter]]', $foreachCounter, $option);
//            }
//
//            //Replace [[status]] with the status if it exist
//            if (isset($this->model->id) && $this->model->status) {
//                $options[$key] = $option = str_replace('[[status]]', $this->model->status, $option);
//            }
//
//            $options[$key] = $option = str_replace('[[key]]', $key, $option);
//
//            if (isset($this->model->id)) {
//
//                if(isset($this->model->translations)){
//                    // Replace routable id in validation rule
//                    $translations = $this->model->translations->keyBy('language_id');
//                }
//
//                if(isset($translations[$languageId]))
//                {
//                    $translationId = $translations[$languageId]->id;
//                    $options[$key] = $option = str_replace('[[routeableId]]', $translationId, $option);
//                }
//
//                foreach (get_class_methods($this->model) as $methodName) {
//                    // check if the method name is in the template to prevent automatic method calls
//                    $pos = strpos($option, '[[' . $methodName . ']]');
//                    if ($pos !== false) {
//                        $options[$key] = $option = str_replace('[[' . $methodName . ']]', $this->model->$methodName($siteId, $languageId), $option);
//                    }
//                }
//
//                // replace with a property in the model
//                foreach (get_object_vars($this->model) as $property => $value)
//                {
//                    if (is_string($value)) {
//                        $options[$key] = $option = str_replace('[[' . $property . ']]', $value, $option);
//                    }
//                }
//            }
//
//            //If the option starts with lang_get:, call the \Lang::get method with the option as parameter
//            if (preg_match('/^lang_get:([a-z0-9\/._\-]+)#?(.*)/i', $option, $match)) {
//
//                $options[$key] = $option = \Lang::get($match[1], ['value' => $match[2]]);
//            }
//            if (preg_match('/^config_get:([a-z0-9\/._\-]+)#?(.*)/i', $option, $match)) {
//
//                $options[$key] = $option = \Config::get($match[1], ['value' => $match[2]]);
//            }
//        }
//
//        return $options;
//    }


    /**
     * Fills all attributes with values from the forms / session
     *
     * @return void
     * @throws \Exception
     */
    public function fillAttributesFromInput()
    {
//        $imageService = new ImageService();

        /** @var AbstractSectionTabItem $sectionTabItem */
        foreach ($this->sectionTabItems as $sectionTabItem) {
            //Insert the the appropriate value in the attribute
            $attribute = $sectionTabItem->getAttribute();
            $input = \Input::get((string)$attribute->getKey());

            if($input) {
                $attribute->setValue($input);
            }

            //Insert the associated language (if any) into the attribute
            if($attribute->getKey()->getTranslationIso2() != '')
            {
                //get the language associated with the attribute]
                $language = Language::where('iso_2', $attribute->getKey()->getTranslationIso2())->first();

                $attribute->setAssociatedLanguage($language);
            }
        }
    }

    /**
     * Iterates over all attributes and checks if they are have Imageable interfaces.
     * If they do, it checks if the input contains an image for them and adds them to
     *
     * @param Model $model
     * @throws \Exception
     */
    public function processImageableAttributesFromInput(Model $model)
    {
        $imageService = new ImageService();

        /** @var AbstractSectionTabItem $sectionTabItem */
        foreach ($this->sectionTabItems as $sectionTabItem) {

            $attribute = $sectionTabItem->getAttribute();
            $input = \Input::get((string)$attribute->getKey());

            if(is_a($attribute, ImageableAttribute::class)) {
                $imageService->setSubFolder($attribute->getSubfolder());
                $imageAndStates = $imageService->uploadAndDeleteImagesFromInput($input, $attribute->getImageSizes());
                    if($imageAndStates) $imageService->updateImageReferencesInDatabase($imageAndStates, $model);
            }
        }
    }


    /*
     * This method returns the validation from the loaded section
     *
     * @return array
     */
    public function getValidation($id = null, $update = false, $model = null)
    {


        //set empty validation array
        $validation = ['messages' => [], 'rules' => []];

        //Loop trough the attributes (these are modelAttributesData parsed)
        foreach ($this->sectionTabItems as $attribute) {
            //If options doest't exist, continue
            if (!isset($attribute->options)) continue;
            //If the validation doesn't exist, continue
            if (!isset($attribute->options['validation'])) continue;
            //If the rules exist add these for the current key
            if (isset($attribute->options['validation']['rules'])) $validation['rules'][$attribute->key] = $attribute->options['validation']['rules'];
            //If the messages exist add these for the current key
            if (!isset($attribute->options['validation']['messages'])) continue;

            foreach ($attribute->options['validation']['messages'] as $key => $message) {
                $validation['messages'][$attribute->key . '.' . $key] = $message;
            }

        }

        return $validation;
    }


    /**
     * This method will check if the repository is sortable
     *
     * @return bool
     */
    public function getSortable()
    {
        if (method_exists($this->sectionService, 'getSortable')) return $this->sectionService->getSortable();

        return False;
    }

    /**
     * Returns the data_type from the given key
     * When it doesn't exist returns false
     *
     * @param $key
     * @return bool
     */
    public function getDataType($key)
    {
        if (!isset($this->sectionTabItems[$key]['data_type'])) return false;
        return $this->sectionTabItems[$key]['data_type'];
    }

    public function replaceModelStrings($string, $siteId = null, $languageId = null, $model = null)
    {


        $string = preg_replace('#\[\[modelId\]\]#', $this->model->id, $string);
        if ($model) $string = preg_replace('#\[\[selfId\]\]#', $model->id, $string);

        if ($languageId) $string = preg_replace('#\[\[languageId\]\]#', $languageId, $string);

        return $string;
    }


    public function extendValidator()
    {

        //Checks if the input can be json decoded
        \Validator::extend('isJson', function ($key, $value, $options = null) {
            //If it is a, return true
            if ($prices = json_decode($value)) return true;
            return false;
        });
        //Check if the price in the json is set and greater than 0
        \Validator::extend('priceNotNull', function ($key, $value, $options = null) {
            if (!$prices = json_decode($value)) return false;

            foreach ($prices as $price) {
                //If price_inc is not set
                if (!isset($price->price_inc)) return false;
                //If price is empty
                if (empty($price->price_inc)) return false;
                //If price is 0 or smaller
                if ($price->price_inc <= 0) return false;
            }
            return true;
        });
        //Check if the minimum amount in the json is set and greater than 0
        \Validator::extend('minimumNotNull', function ($key, $value, $options = null) {
            if (!$prices = json_decode($value)) return false;

            foreach ($prices as $price) {
                //If price_inc is not set
                if (!isset($price->min_amount)) return false;
                //If price is empty
                if (empty($price->min_amount)) return false;
                //If price is 0 or smaller
                if ($price->min_amount <= 0) return false;
            }
            return true;
        });
        //Check if the price in the Json is a number
        \Validator::extend('priceAsNumber', function ($key, $value, $options = null) {
            if (!$prices = json_decode($value)) return false;
            foreach ($prices as $price) {
                if (!is_numeric($price->price_inc)) return false;
            }
            return true;
        });
        //Check if the minimum amount in the Json is a number
        \Validator::extend('minimumAsNumber', function ($key, $value, $options = null) {
            if (!$prices = json_decode($value)) return false;
            foreach ($prices as $price) {
                if (!is_numeric($price->min_amount)) return false;
            }
            return true;
        });
        //Check if the minimum amount in the Json is not repeated
        \Validator::extend('minimumRepeat', function ($key, $value, $options = null) {
            if (!$prices = json_decode($value)) return false;
            $min_amounts = [];
            foreach ($prices as $price) {
                if (in_array($price->min_amount, $min_amounts)) return false;
                $min_amounts[] = $price->min_amount;
            }
            return true;
        });
        //Check if the given field
        \Validator::extend('uniqueAttribute', function ($key, $value, $options = null) {
            $eavService = new EavService();
            //$options[0] = model_type
            //$options[1] = attribute_type
            //$options[2] = exclude_id
            //$options[2] = exclude_field
            $values = $eavService->getAllValuesForModelAndType($options[0], $options[1], $options[2], $options[3]);
            //Check if the value  exist, if so return false
            if (in_array($value, $values)) return false;

            return true;

        });


        //Check if the value is not when an other field has a certain value
        \Validator::extend('isNotIf', function ($key, $value, $options) {
            //options[0] value that the key can't be
            //options[1] the key for the if check
            //options[2] the value for the if check
            if (!\Input::has($options[1])) return true;

            if (\Input::get($options[1]) != $options[2]) return true;
            if (\Input::get($key) == $options[0]) return false;
            return true;
        });

        /**
         * Use extendImplicit when the validation also should work when the value is empty
         * Check if the model is new
         * If so, check if it is empty
         */
        \Validator::extendImplicit('required_if_new', function ($key, $value, $options) {

            //Check of the modelId is not null. If true, return true
            if ($options[0] !== null) return true;

            //New Item

            //Check if the value is not empty. return true
            if (!empty($value)) return true;

            //New model, and empty value, return false
            return false;

        });
    }

    public function generateKey($key)
    {
        if (is_array($key)) $key = implode('_', $key);

        if ($this->keyAsArray) $key = $this->keyAsArray . '_' . $key . '[' . $this->keyArrayKey . ']';

        return $key;
    }

    ///Getters
    public function getKms()
    {
        return $this->kms;
    }

    /**
     * Returns an array containing SectionTabInterfaces representing Tabs for sections
     *
     * @return SectionTabInterface[]|array
     */
    public function getTabs()
    {
        if(!$this->sectionTabDirector->getTabsCollection()) return [];
        return $this->sectionTabDirector->getTabsCollection()->getTabs();
    }

    /**
     * Adds attributes to their appropriate tabs.
     *
     * @return void
     */
    protected function addAttributesToTabs()
    {
        $tabsCollection = $this->getSectionTabDirector()->getTabsCollection();

        $tabs = $tabsCollection->getTabs();
        foreach($tabs as $tab) {
            if($tab->getGroup() == SectionTabGroups::General) {
                $this->sectionTabItems->each(function($sectionTabItem, $key) use($tab) {
                    /** @var AbstractSectionTabItem $sectionTabItem */
                    if($sectionTabItem->getGroup() == SectionTabGroups::General) $tab->addItem($sectionTabItem->getAttribute());
                });
            }
            elseif ($tab->getGroup() == SectionTabGroups::Languages)
            {
                $this->sectionTabItems->each(function($sectionTabItem, $key) use($tab) {
                    /** @var AbstractSectionTabItem $sectionTabItem */

                    if($sectionTabItem->getGroup() != SectionTabGroups::Languages) return; //Item does not belong to a language section tab. Most likely to the general tab. Skip it.
                    $attribute = $sectionTabItem->getAttribute();

                    if(!$attribute->hasAssociatedLanguage()) {
                        return; //attribute does not have a translation. Skip it
                    }

                    if(strtolower($attribute->getAssociatedLanguage()->iso_2) != $tab->getName()) return; //Attribute does belong to another language tab, not this one. Skip it.

                    $tab->addItem($attribute);
                });
            }
        }
    }

    /**
     * Returns the sectiontab director which manages the creation of tabs via a builder
     *
     * @return AbstractSectionTabsDirector
     */
    public function getSectionTabDirector(): AbstractSectionTabsDirector
    {
        return $this->sectionTabDirector;
    }


}