File: D:/HostingSpaces/SBogers10/boldt.komma.pro/app/Komma/Kms/Core/Sections/SectionService.php
<?php
namespace App\Komma\Kms\Core\Sections;
use App\Helpers\KommaHelpers;
use App\Komma\Components\ComponentArea\ComponentAreaService;
use App\Komma\Components\ComponentArea\ComponentAreaServiceInterface;
use App\Komma\Kms\Core\Attributes\ComponentArea;
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\Documents\Kms\DocumentableInterface;
use App\Komma\Documents\Kms\DocumentServiceInterface;
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\Documents;
use App\Komma\Kms\Core\Attributes\Models\SelectOptionInterface;
use App\Komma\Kms\Core\HasRoutesInterface;
use App\Komma\Kms\Core\Tree\NestedSets\Nodes\TreeModelInterface;
use App\Komma\Kms\Core\Tree\NestedSets\Nodes\AbstractTranslatableTreeModel;
use App\Komma\Globalization\Languages\Kms\LanguageService;
use App\Komma\Globalization\Languages\Models\Language;
use App\Komma\Pages\Models\PageTranslation;
use App\Komma\Sites\HasSitesInterface;
use App\Komma\Sites\SiteServiceInterface;
use Illuminate\Database\Eloquent\Collection as DatabaseCollection;
use Illuminate\Support\Collection as BaseCollection;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\App;
use InvalidArgumentException;
/**
* @see PageRepository Based of the deprecated PageRepository and KmsRepository
*
* Class SectionService
*/
class SectionService implements SectionServiceInterface
{
/** @var bool $sortable */
protected $sortable = true;
/**
* @var string $forModelName
*/
protected $forModelName;
/** @var AbstractTranslatableModel $forModel */
protected $forModel;
/**
* @var AbstractTranslationModel $forTranslationModel
*/
protected $forTranslationModel;
/**
* @var string $forTranslationModelName
*/
protected $forTranslationModelName;
/**
* @var DocumentServiceInterface
*/
protected $documentService;
/**
* @var string $orderBy
*/
protected $orderBy;
/**
* @var bool $orderReverse
*/
protected $orderByDisplayName = false;
/**
* @var bool $orderReverse
*/
protected $orderReverse = false;
/** @var SiteServiceInterface $siteService */
protected $siteService;
/** @var ComponentAreaService $componentAreaService */
private $componentAreaService;
protected $isSiteService = false; //Only set to true by the site service to prevent an infinite loop
private $afterSaveQueue;
private $afterSaveQueueTranslations;
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 */
if (!is_a($model, HasThumbnailInterface::class)) {
throw new \RuntimeException('The class "' . get_class($model) . '" is expected to implement the "' . HasThumbnailInterface::class . '" but did not');
}
$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();
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('translations', '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 = KommaHelpers::str_limit_full_word($model->getSidebarName(), 75);
$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) {
// the $parents array does not allow loading translations, so we get the children with a where so we can preload the translations
// if we don't do this, down the line the translations are fetched in a foreach loop, what we want to prevent
if (is_a($parent, AbstractTranslatableModel::class) && $indents == 0) {
$childrenArray = $parent::where('lft', '>', $parent->lft)->where('rgt', '<',
$parent->rgt)->with('translations')->get();
} else {
$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(' ', $indents);
/** @var SelectOptionInterface $selectOption */
$selectOption = app(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');
}
//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);
$componentAreasWithData = [];
$editedModel = false;
$sectionTabItems->each(
function ($sectionTabItem, $key) use (
$model,
&$editedModel,
$shortModelClassName,
&$componentAreasWithData
) {
/** @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::ValueFromComponentArea:
if ($attribute->getValue() !== '') {
$componentAreasWithData[] = $attribute;
}
$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,
function (AbstractTranslationModel $abstractTranslationModel) use ($componentAreasWithData) {
//This callback forces saving a translation even if it is empty. As long as it has a component area with a value (components).
foreach ($componentAreasWithData as $componentAreaWithData) {
/** @var ComponentArea $componentAreaWithData */
if ($componentAreaWithData->getKey()->getTranslationIso2() === $abstractTranslationModel->getLanguageIso()) {
return true;
} //Force save because the translation has a component area with components
}
return false;
});
$this->executeAfterSaveTranslationClosures($model);
return $model;
}
/**
* @param Model $model
* @param DatabaseCollection $sectionTabItems
* @param $componentAreaAttributeKeyFrom
* @param $componentAreaAttributeKeyTo
* @return mixed|void
*/
public function copyComponentArea(
Model $model,
DatabaseCollection $sectionTabItems,
string $componentAreaAttributeKeyFrom,
string $componentAreaAttributeKeyTo
) {
/** @var ComponentArea|null $fromComponentArea */
$fromComponentArea = null;
/** @var ComponentArea|null $toComponentArea */
$toComponentArea = null;
//Retrieve the component areas which mach the string keys.
$sectionTabItems->each(
function ($sectionTabItem, $key) use (
$model,
$componentAreaAttributeKeyFrom,
$componentAreaAttributeKeyTo,
&$fromComponentArea,
&$toComponentArea
) {
/** @var SectionTabItem $sectionTabItem */
$attribute = $sectionTabItem->getAttribute();
if ((string)$attribute->getKey() == $componentAreaAttributeKeyFrom) {
$fromComponentArea = $attribute;
} else {
if ((string)$attribute->getKey() == $componentAreaAttributeKeyTo) {
$toComponentArea = $attribute;
}
}
}
);
if (!$fromComponentArea || !$toComponentArea) {
return;
}
//Make sure the translation of the toComponentArea is saved. Because the componentArea has a relation to the translation
$this->saveModelTranslations($model,
function (AbstractTranslationModel $abstractTranslationModel) use ($toComponentArea) {
if ($abstractTranslationModel->getLanguageIso() === $toComponentArea->getAssociatedLanguage()->iso_2) {
return true;
}
return false;
});
//Copy the component area to the other one
$this->componentAreaService->copyComponentArea($model, $fromComponentArea, $toComponentArea);
}
/**
* Save the translations of the given AbstractTranslatableModel if they contain some data.
* Also links translations and translatable model with eachother
* If the force save callback returns true the translation will be saved, even if it does not have data.
* This because something else depends on the translation existing.
*
* @param Model $model
* @param \Closure $forceSaveCallback
* @return Model
*/
protected function saveModelTranslations(Model $model, \Closure $forceSaveCallback = null): Model
{
$self = $this;
if (!is_a($model, AbstractTranslatableModel::class)) {
return $model;
}
$model->translations->each(function (AbstractTranslationModel $abstractTranslationModel) use (
$model,
$forceSaveCallback,
$self
) {
$forceSave = false;
if ($forceSaveCallback) {
if ($forceSaveCallback->call($self, $abstractTranslationModel) === true) {
$forceSave = true;
}
}
if (!$abstractTranslationModel->isEmpty() || $forceSave) {
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 boolean
*/
public function getSortable()
{
return $this->sortable;
}
/**
* @param bool $allowNullableSelectOption
* @return BaseCollection containing SelectOptionInterface
*/
public function getOptionsForSelect(bool $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();
$models = $this->forModelName::all();
foreach ($sidebarListItems as $sidebarListItem) {
$model = $models->where('id', $sidebarListItem->getId())->first();
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");
}
$key = KommaHelpers::getShortNameFromClass($sectionTabItem->getAttribute()) . '-' . $sectionTabItem->getAttribute()->getKey()->getValuePart();
/** @var $model DocumentableInterface */
$value = json_encode($model->documents()->where('key', '=', $key)->get());
break;
case Attribute::ValueFromComponentArea:
$value = $this->componentAreaService->getComponentsSaveStateDataAsJsonFromModel($model,
$sectionTabItem->getAttribute()); //Don't worry about type checking here. It is done in the service itself
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;
}
}
}