HEX
Server: Microsoft-IIS/8.5
System: Windows NT YDAWBH120 6.3 build 9600 (Windows Server 2012 R2 Standard Edition) AMD64
User: tentjecom_web (0)
PHP: 7.4.14
Disabled: NONE
Upload Files
File: D:/HostingSpaces/Neopoints/momsecurity.be/vendor/komma/kms/src/Core/SectionController.php
<?php
namespace Komma\KMS\Core;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Routing\Controller;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Redirect;
use Illuminate\Support\Facades\Session;
use Illuminate\Support\MessageBag;
use Illuminate\View\View;
use Komma\KMS\Components\ComponentArea\ComponentAreaService;
use Komma\KMS\Components\ComponentArea\ComponentAreaServiceInterface;
use Komma\KMS\Core\Attributes\ComponentArea;
use Komma\KMS\Core\Attributes\Documents;
use Komma\KMS\Core\Sections\SectionService;
use Komma\KMS\Core\Tree\NestedSets\Nodes\TreeModelInterface;
use Komma\KMS\Helpers\KommaHelpers;
use Komma\KMS\Documents\Kms\DocumentService;
use Komma\KMS\Core\Attributes\Attribute;
use Komma\KMS\Core\Attributes\Models\Traits\HasThumbnailInterface;
use Komma\KMS\Core\Entities\DisplayNameInterface;
use Komma\KMS\Core\Sections\Section;
use Komma\KMS\Core\Tree\TreeServiceInterface;
use Komma\KMS\Sites\SiteServiceInterface;

abstract class SectionController extends Controller
{
    use AuthorizesRequests;

    /** @var $forModelInstance Model|HasThumbnailInterface|DisplayNameInterface */
    protected $forModelInstance;
    protected string $slug;
    protected string $classModelName;
    protected bool $showEntity = true;
    protected ?string $forTranslationModelName = null;

    protected Section $section;

    protected DocumentService $documentService;
    protected SiteServiceInterface $siteService;
    protected TreeServiceInterface $treeService;
    protected ComponentAreaService $componentAreaService;
    protected ModelServiceInterface $modelService;
    protected TranslationServiceInterface $translationService;
    protected AttributeDataServiceInterface $dataService;

    private RelatedModelServiceInterface $relatedModelService;
    private SectionService $sectionService;

    public function __construct(Section $section)
    {
        $this->validateSetup();

        $this->section = $section;
        $this->dataService = app(AttributeDataServiceInterface::class);
        $this->modelService = app(ModelServiceInterface::class);
        $this->modelService->setModelClassName($this->classModelName);
        $this->forModelInstance = $this->modelService->getForModelFromRoute($this->slug);
        $this->treeService = app(TreeServiceInterface::class);
        $this->treeService->setClassModelName($this->classModelName);
        if($this->forTranslationModelName) {
            $this->translationService = app(TranslationServiceInterface::class);
            $this->translationService->setModelClassName($this->forTranslationModelName);
        }
        $this->documentService = new DocumentService();
        $this->siteService = app(SiteServiceInterface::class);
        $this->sectionService = new SectionService($section, $this->siteService);
        $this->sectionService->setModelClassName($this->classModelName);

        $this->relatedModelService = app(RelatedModelServiceInterface::class);
        $this->relatedModelService->setModelClassName($this->classModelName);
        $this->componentAreaService = app(ComponentAreaServiceInterface::class);
    }

    /**
     * This method is called on the overview page.
     * It will render the section and view.
     *
     * @return mixed
     * @throws \Illuminate\Auth\Access\AuthorizationException
     */
    public function index()
    {
        $this->authorize('index', $this->classModelName);

        //Create a root model if the modelClassName instance implements the treeInterface
        $this->modelService->createRootTreeModelIfNeeded();

        //Disable the right pane
        $this->showEntity = false;

        return $this->render(null);
    }

    /**
     * This method is called when a item is selected.
     * By default it will generate an edit form.
     *
     * @param Model $model
     * @return mixed
     * @throws \Illuminate\Auth\Access\AuthorizationException
     */
    public function show($model)
    {
        $this->authorize('show', $model);

        //Create a root model if the model
        //ClassName instance implements the treeInterface
        $this->modelService->createRootTreeModelIfNeeded();

        //Call the edit method
//        $this->section->loadModels(); //we need to do this here because this won't work in section constructors since there the authenticated user is not available in laravel 5.3
        return $this->edit($model);
    }

    /**
     * This method is called when we want to edit an item.
     * It is called from the $this->show method.
     *
     * @param Model $model
     * @return mixed
     * @throws \Illuminate\Auth\Access\AuthorizationException
     */
    public function edit($model)
    {
        $this->authorize('edit', $model);
        //Create a root model if the modelClassName instance implements the treeInterface
        $this->modelService->createRootTreeModelIfNeeded();

        return $this->render($model);
    }



    /**
     * This method will generate a new item form
     *
     * @return mixed
     * @throws \Illuminate\Auth\Access\AuthorizationException
     */
    public function create()
    {
        $this->authorize('create', $this->classModelName);

        //Create a root model if the modelClassName instance implements the treeInterface
        $this->modelService->createRootTreeModelIfNeeded();

        //Render the form
        return $this->render();
    }


    /**
     * This method will validate and save the model
     * It is called on create form submit,
     * and it is called on the form edit.
     *
     * @return mixed
     * @throws \Illuminate\Auth\Access\AuthorizationException
     * @throws \Throwable
     */
    public function store()
    {
        $this->authorize('store', $this->classModelName);
        $this->modelService->createRootTreeModelIfNeeded();

        //Prepare the new model, and attributes
        $model = $this->modelService->newModel();
        if($this->forTranslationModelName) $model = $this->translationService->makeAndInjectEmptyTranslationsIntoTranslatableIfNeeded($model);
        $this->sectionService->generateAttributesAndAddThemToTabs($model);
        $attributes = $this->section->getAttributes();

        //Validate the form data and fill the attributes from input
        $validator = $this->dataService->validateInputAndReturnValidator($attributes);
        if ($validator->fails()) return redirect()->back()->withInput()->withErrors($validator);
        $attributes = $this->dataService->fillAttributesFromInput($attributes);

        $byValueFrom = $attributes->mapToGroups(function($attribute, $index) {
            /** @var Attribute $attribute */
            return [$attribute->getsValueFrom() => $attribute];
        });

        $model = $this->save($model, $byValueFrom);

        //Set the siteSlug variable for routes that need the siteSlug variable in their route
        $routeParameters = [];
        $site = $this->siteService->getCurrentSite();
        if($site->exists)
            $routeParameters['siteSlug'] = $site->slug;

        $routeParameters[strtolower(KommaHelpers::getShortNameFromClass($model, true))] = $model;

        $sessionData = [
            'tabslug' => request()->get('tabslug'),
            'success' => __('KMS::global.saved')
        ];

        return Redirect::action('\\'.get_class($this) . '@show', $routeParameters)->with($sessionData);
    }

    /**
     * This method handles the update functionality.
     * And is called by the edit form.
     *
     * @param $model Model
     * @return mixed
     * @throws \Exception
     * @throws \Throwable
     */
    public function update($model)
    {
        $this->authorize('update', $model);
        $this->modelService->createRootTreeModelIfNeeded();

        //Prepare the model and the attributes
        if($this->forTranslationModelName) $model = $this->translationService->makeAndInjectEmptyTranslationsIntoTranslatableIfNeeded($model);
        $this->sectionService->generateAttributesAndAddThemToTabs($model);
        $attributes = $this->section->getAttributes();

        //Validate the form data and fill the attributes from input
        $validator = $this->dataService->validateInputAndReturnValidator($attributes);

        if ($validator->fails()) return redirect()->back()->withInput()->withErrors($validator);
        $attributes = $this->dataService->fillAttributesFromInput($attributes);

        $byValueFrom = $attributes->mapToGroups(function($attribute, $index) {
            /** @var Attribute $attribute */
            return [$attribute->getsValueFrom() => $attribute];
        });

        $model = $this->save($model, $byValueFrom);

        //Set the siteSlug variable for routes that need the siteSlug variable in their route
        $routeParameters = [];
        $site = $this->siteService->getCurrentSite();
        if($site->exists)
            $routeParameters['siteSlug'] = $site->slug;
        $routeParameters[strtolower(KommaHelpers::getShortNameFromClass($model, true))] = $model;

        $sessionData = [
            'tabslug' => request()->get('tabslug'),
            'success' => __('KMS::global.saved')
        ];

        //Redirect the users to the "show" page.
//        return \Redirect::action('\\'.get_class($this) . '@show', $routeParameters)->with($sessionData);
        return Redirect::action('\\'.get_class($this) . '@show', $routeParameters)->with($sessionData);
    }

    /**
     * This method is called when a item will be deleted
     *
     * @param Model $model
     * @return mixed
     * @throws \Exception
     */
    public function destroy(Model $model)
    {
        $this->authorize('destroy', $model);

        $this->componentAreaService->destroyForModel($model);
        if($this->forTranslationModelName) {
            $model->translations()->get()->each(function (AbstractTranslationModel $translationModel) {
                $this->componentAreaService->destroyForModel($translationModel);
            });
        }

        $this->modelService->createRootTreeModelIfNeeded();

        //Set the siteSlug variable for routes that need the siteSlug variable in their route
        $routeParameters = [];
        $site = $this->siteService->getCurrentSite();
        if($site->exists)
            $routeParameters['siteSlug'] = $site->slug;

        /** @var Model $model */
        $model = $this->destroyForModel($model);

        if(is_a($model, TreeModelInterface::class)) {
            /** @var TreeModelInterface $model */
            $model = $this->treeService->destroyForModelAndExecuteCallbackForeach($model, function(TreeModelInterface $childModel) {
                $childModel = $this->destroyForModel($childModel);
            });
            $model->deleteWithChildren();
        } else {
            $model->delete();
        }


        //Return to the item index
        return Redirect::action('\\' . get_class($this) . '@index', $routeParameters)->with('message', __('KMS::global.removed'));
    }

    /**
     * Deletes stuff attached or related to the given model.
     * So that afterwards you can dispose that model.
     *
     * @param Model $model
     * @return Model
     */
    protected function destroyForModel(Model $model) {
        $this->documentService->destroyForModel($model);
        if($this->forTranslationModelName) {
            $model->translations()->get()->each(function(AbstractTranslationModel $translationModel) {
                $this->documentService->destroyForModel($translationModel);
            });
            $model = $this->translationService->destroyForModel($model);
        }
        $model = $this->modelService->destroyForModel($model);

        return $model;
    }

    /**
     * Returns a rendered view
     *
     * @param Model $model
     * @return View
     */
    protected function render(Model $model = null)
    {
        if ($this->showEntity) $this->sectionService->generateAttributesAndAddThemToTabs($model);
        if (!$model) return $this->makeView(); //Return a view since we can't fill it with data because the model is not set

        if($this->showEntity) {
            $byValueFrom = $this->section->getAttributes()->mapToGroups(function($attribute, $index) {
                /** @var Attribute $attribute */
                return [$attribute->getsValueFrom() => $attribute];
            });
            $this->load($model, $byValueFrom);
        }

        return $this->makeView();
    }


    /**
     * Makes the view and returns it
     *
     * @return View
     */
    protected function makeView(): View
    {
        $tabslug = null !== request()->get('tabslug') ? request()->get('tabslug') : Session::get('tabslug', '');
        $sessionData = [
            'tabslug' => $tabslug
        ];


        $modelId = ($this->forModelInstance) ? $this->forModelInstance->id : null;
        $successes = (Session::has('successes')) ? Session::get('successes') : new MessageBag();

        $thumbnail = '';
        if(is_a($this->forModelInstance, HasThumbnailInterface::class)) {
            $thumbnail = $this->forModelInstance->getThumbnail();
        };

        if(is_a($this->forModelInstance, DisplayNameInterface::class)) {
            $displayName = $this->forModelInstance->getDisplayName();
        } else {
            $displayName = $this->section->getSectionNewModel();
        }

        $saveRoute = $this->modelService->getSaveRoute($this->slug, $modelId);
//        $sideBarModels = $this->modelService->getModelsForSideBar();p
        $siteSlug = !$this->siteService->getCurrentSite()->exists ? null : $this->siteService->getCurrentSite()->slug;

        // If model has TreeModelInterface enable the sortable
        $sortable = is_a(new $this->classModelName, TreeModelInterface::class);

        return \View::make('KMS::section.index', [
            'sectionTitle'                 => $this->section->getSectionTitle(),
            'sectionTabs'                  => $this->section->getTabs(),
            'slug'                         => $this->slug,
            'siteSlug'                     => $siteSlug,
            'successes'                    => $successes,
            'models'                       => $this->modelService->getModelsForSideBar(),
            'maxUploadSize'                => KommaHelpers::fileUploadMaxSize(),
            'maxPostSize'                  => KommaHelpers::maxPostSize(),
            'modelClassName'               => $this->classModelName,
            'saveRoute'                    => $saveRoute,
            'sortable'                     => $sortable,
            'showEntity'                   => $this->showEntity,
            'thumbnail'                    => $thumbnail,
            'displayName'                  => $displayName,
            'currentModel'                 => $this->forModelInstance,
            'submitButtonLabel'            => $this->section->getSubmitButtonLabel(),
            'preventNavigationTranslation' => json_encode(__('KMS::prevent_navigation'))
        ])->with($sessionData);
    }

    /**
     * Get the models a a tree for api calls
     *
     * @throws \Illuminate\Auth\Access\AuthorizationException
     */
    public function getStructureAsJson()
    {
        $this->authorize('index', $this->classModelName);
        $rootModel = $this->modelService->getRootModelForTree();
        $structure = $this->treeService->getStructure($rootModel);
        return json_encode($structure);
    }

    /**
     * Change the model tree
     *
     * @return \Illuminate\Http\JsonResponse
     */
    public function setStructureAsJson()
    {
        // Special way to get POST data send by ajax on IIS servers
        $data = json_decode(request()->getContent());
        $tree = json_decode($data->tree, true);

        $this->treeService->setStructure($tree);

        return response()->json($tree);
    }

    private function validateSetup()
    {
        if(!$this->classModelName) throw new \RuntimeException('Make sure you set (override) the $classModelName property in the controller ('.get_class($this).') to the eloquent model the controller does it\'s job for.');
        if(!class_exists($this->classModelName)) throw new \RuntimeException('The modelClassName must be a class but was not ('.$this->classModelName.').');

        if(!$this->slug) throw new \RuntimeException('Make sure you set (override) the $slug property in the controller ('.get_class($this).') to appropriate slug.');
    }

    /**
     * A function that MUST delegate to services to accomplish saving.
     * It MUST NOT act like a service.
     *
     * @param Model $model
     * @param Collection $attributesByValueFrom Keys must be ValueFrom integers. Values must be Attributes that have the ValueFrom integers
     * @return Model
     */
    protected function save(Model $model, Collection $attributesByValueFrom = null): Model
    {
        if($attributesByValueFrom == null) return $model;

        /** @var Attribute $attribute */
        $this->modelService->save($model, $attributesByValueFrom->collapse());
        if(is_a($model, AbstractTranslatableModel::class)) $this->translationService->save($model, $attributesByValueFrom->get(Attribute::ValueFromTranslationModel));

        $documentAttributes = $attributesByValueFrom->get(Attribute::ValueFromDocuments, collect([]));
        //Get document attributes for translatable models
        $translatableDocumentAttributes = $documentAttributes->filter(function(Attribute $attribute) {
            return !$attribute->hasAssociatedLanguage();
        });

        $this->documentService->save($model, $translatableDocumentAttributes);

        //Retrieve attributes whose value's determine if a translation must be saved.
        //We do this because some things like documents and component areas are linked to a translation, and therefore it must exist if there are any.
        $attributesThatCanForceTranslationSave = $attributesByValueFrom->flatten()->filter(function(Attribute $attribute) {
            return is_a($attribute, Documents::class) || is_a($attribute, ComponentArea::class);
        });

        if(is_a($model, AbstractTranslatableModel::class)) {
            //When a documents or componentArea attribute isn't empty, we need to save the translation
            $this->translationService->forceSaveWhenTrue($model, function(AbstractTranslationModel $translation) use($attributesThatCanForceTranslationSave, $model) {
                foreach($attributesThatCanForceTranslationSave as $attributeThatCanForceTranslationSave) {
                    if (!$translation->exists && $attributeThatCanForceTranslationSave->getValue() !== '[]') return true;
                }
                return false;
            });

            //Now we can be sure, translations exist and we can save documents and component areas if needed.
            $this->executeCallbackForModelsTranslationsMatchingAttributeLanguage($model, $attributesThatCanForceTranslationSave,
                function (AbstractTranslationModel $translation, $attribute) use ($model) {
                    if(is_a($attribute, Documents::class)) $this->documentService->save($translation, collect([$attribute]));
                    elseif(is_a($attribute, ComponentArea::class)) $this->componentAreaService->saveAttribute($translation, $attribute);
                }
            );
        }

        $this->siteService->save($model, $attributesByValueFrom->collapse()->filter(function (Attribute $attribute) use ($model) {
            return $attribute->getsValueFromReference() == 'site_id';
        }));

        $this->relatedModelService->save($model, $attributesByValueFrom->get(Attribute::ValueFromModelHasManyRelation));

        return $model;
    }

    /**
     * A function that MUST delegate to services to accomplish loading.
     * It MUST NOT act like a service.
     *
     * @param Model $model
     * @param Collection $attributesByValueFrom Keys must be ValueFrom integers. Values must be Attributes that have the ValueFrom integers
     * @return Collection The same collection as you've passed in. Only "filled".
     */
    protected function load(Model $model, Collection $attributesByValueFrom = null): Collection
    {
        if($attributesByValueFrom === null) return new Collection();

        /** @var Attribute $attribute */
        $this->modelService->load($model, $attributesByValueFrom->collapse());
        if($this->forTranslationModelName) $this->translationService->load($model, $attributesByValueFrom->get(Attribute::ValueFromTranslationModel));

        $documentAttributes = $attributesByValueFrom->get(Attribute::ValueFromDocuments, collect([]));
        //Get document attributes for translatable models
        $translatableDocumentAttributes = $documentAttributes->filter(function(Attribute $attribute) {
            return !$attribute->hasAssociatedLanguage();
        });
        $this->documentService->load($model, $translatableDocumentAttributes);

        //Get document attributes for translation models
        $translationDocumentAttributes = $documentAttributes->filter(function(Attribute $attribute) {
            return $attribute->hasAssociatedLanguage();
        });

        //Get the component area attributes for translation models
        $translationComponentAreaAttributes = $attributesByValueFrom->get(Attribute::ValueFromComponentArea, collect())->filter(function(Attribute $attribute) {
            return $attribute->hasAssociatedLanguage();
        });

        $componentAreaService = $this->componentAreaService;
        if(is_a($model, AbstractTranslatableModel::class)) {
            $this->executeCallbackForModelsTranslationsMatchingAttributeLanguage($model, $translationDocumentAttributes,
                function (AbstractTranslationModel $translationModel, Documents $attribute) use($componentAreaService) {
                    $this->documentService->load($translationModel, collect([$attribute]));
                    $componentAreaService->loadAttribute($translationModel, $attribute);
                });

            $this->executeCallbackForModelsTranslationsMatchingAttributeLanguage($model, $translationComponentAreaAttributes, function(AbstractTranslationModel $translationModel, $attribute) {
                $this->componentAreaService->loadAttribute($translationModel, $attribute);
            });
        }

        $this->siteService->load($model, $attributesByValueFrom->collapse()->filter(function (Attribute $attribute) use ($model) {
            return $attribute->getsValueFromReference() == 'site_id';
        }));
        $this->relatedModelService->load($model, $attributesByValueFrom->get(Attribute::ValueFromModelHasManyRelation));

        return $attributesByValueFrom;
    }

    /**
     * Loops over a collection of attributes, retrieving the translation
     * models of the given model, that match the attributes language.
     * These translations, along with the attribute are passed to a callback function when it's called.
     *
     * @param AbstractTranslatableModel $model
     * @param Collection $attributes
     * @param \Closure $do
     */
    protected function executeCallbackForModelsTranslationsMatchingAttributeLanguage(AbstractTranslatableModel $model, Collection $attributes, \Closure $do)
    {
        if($this->forTranslationModelName && $attributes->count() > 0) {
            if($attributes) {
                $attributes->each(function (Attribute $attribute) use ($model, $do) {
                    if (($language = $attribute->getAssociatedLanguage()) && $model) {
                        $translationModel = $this->translationService->getTranslationModelForModelByLanguage($model, $language);
                        if ($translationModel) $do->call($this, $translationModel, $attribute);
                    }
                });
            }
        }
    }

    /**
     * @param Model   $model
     * @param Request $request
     *
     * @return \Illuminate\Http\JsonResponse
     */
    public function outOfDateCheck(Model $model, Request $request) {
        // Only allow ajax requests
        if(!$request->ajax()) return response()->json('Only Ajax requests are allowed');

        // If the model was deleted before, return an UNPROCESSABLE_ENTITY response
        if(!$model->exists) abort(Response::HTTP_UNPROCESSABLE_ENTITY, "Model doesn't exist");

        // Retrieve the fields that are out of date.
        $outOfDateModelFields = [];
        if(is_a($this->modelService, OutOfDateInterface::class)) $outOfDateModelFields = array_merge($outOfDateModelFields, $this->modelService->outOfDate($model));
        if(is_a($this->translationService, OutOfDateInterface::class)) $outOfDateModelFields = array_merge($outOfDateModelFields, $this->translationService->outOfDate($model));
        if(is_a($this->treeService, OutOfDateInterface::class)) $outOfDateModelFields = array_merge($outOfDateModelFields, $this->treeService->outOfDate($model));
        if(is_a($this->documentService, OutOfDateInterface::class)) $outOfDateModelFields = array_merge($outOfDateModelFields, $this->documentService->outOfDate($model));
        if(is_a($this->relatedModelService, OutOfDateInterface::class)) $outOfDateModelFields = array_merge($outOfDateModelFields, $this->relatedModelService->outOfDate($model));
        if(is_a($this->componentAreaService, OutOfDateInterface::class)) $outOfDateModelFields = array_merge($outOfDateModelFields, $this->componentAreaService->outOfDate($model));

        // If there are fields out of date, return a CONFLICT response containing the fields and values that are out of date.
        if(count($outOfDateModelFields) > 0) return response()->json($outOfDateModelFields, Response::HTTP_CONFLICT);

        // If the model was not out of date, return a success response
        return response()->json(null, Response::HTTP_NO_CONTENT);
    }
}