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/farmfun.komma.pro/app/Komma/Kms/Core/TranslationService.php
<?php

declare(strict_types=1);

namespace App\Komma\Kms\Core;

use App\Komma\Globalization\Languages\Kms\LanguageService;
use App\Komma\Globalization\Languages\Models\Language;
use App\Komma\Kms\Core\Attributes\Attribute;
use App\Komma\Routes\HasSlugInterface;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Query\Builder;
use Illuminate\Support\Collection;

class TranslationService extends AbstractModelHandler implements TranslationServiceInterface
{
    /** @var string */
    protected $modelClassName = null;

    /**
     * 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): Model
    {
        if (! is_a($model, AbstractTranslatableModel::class)) {
            return $model;
        }

        $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->modelClassName();

                //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 Builder
     */
    public function translationModels(): Builder
    {
        if (! $this->modelClassName) {
            return null;
        }

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

    /**
     * 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
     * @return Model
     */
    public function saveModelTranslations(Model $model): Model
    {
        $self = $this;
        if (! is_a($model, AbstractTranslatableModel::class)) {
            return $model;
        }

        /** @var $model AbstractTranslatableModel */
        $model->translations->each(function (AbstractTranslationModel $abstractTranslationModel) use ($model, $self) {
            if (! $abstractTranslationModel->isEmpty()) {
                if ($model->exists()) {
                    $abstractTranslationModel->translatable()->associate($model);
                }
                $abstractTranslationModel->save();
            }
        });

        return $model;
    }

    public function save(Model $model, Collection $attributes = null): Model
    {
        if ($attributes == null) {
            return $model;
        }

        $this->checkContainsAttributes($attributes);
        if (! is_a($model, AbstractTranslatableModel::class)) {
            return $model;
        } //Only handle abstractTranslatable models. Since only those can have translations.
        /** @var $model AbstractTranslatableModel */
        $attributes->each((function (Attribute $attribute) use (&$model) {
            $valueFrom = $attribute->getsValueFrom();
            $valueReference = $attribute->getsValueFromReference();
            if ($valueFrom !== Attribute::ValueFromTranslationModel) {
                return;
            }
            $value = $attribute->getValue();

            /** @var Language $language */
            $language = $attribute->getAssociatedLanguage();
            $translation = $this->getTranslationModelForModelByLanguage($model, $language);

            // Save value on the model
            $translation->$valueReference = $value;
            $model = $this->saveModelTranslations($model);
        })->bindTo($this));

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

        return $model;
    }

    public function load(Model $model, Collection $attributes = null): Collection
    {
        if ($attributes === null) {
            return new Collection();
        }
        if (! is_a($model, AbstractTranslatableModel::class)) {
            return $attributes;
        } //Only handle abstractTranslatable models. Since only those can have translations.

        return $attributes->map(function (Attribute $attribute) use ($model) {
            $valueFrom = $attribute->getsValueFrom();
            $valueReference = $attribute->getsValueFromReference();
            if ($valueFrom !== Attribute::ValueFromTranslationModel) {
                return $attribute;
            }
            $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));
            }

            if ($attribute->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 '".$attribute->getsValueFromReference()."') or make the model implement the interface.");
                }
                /** @var AbstractTranslatableModel $model */
                /** @var AbstractTranslationModel $translation */
                $translation = $model->translations->where('language_id', $attribute->getAssociatedLanguage()->id)->first();
                if (! $translation) {
                    return $attribute;
                } //Skip filling the value if the model did not have a translation
                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.');
                }
                $value = $translation->$valueReference;
                if ($value && ! is_int($value)) {
                    $attribute->setValue($value);
                }
            }

            return $attribute;
        });
    }

    /**
     * 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;
    }

    /**
     * Destroys the appropriate related models for a given model.
     * Those related models must be the responsibility of this service
     *
     * @param Model|Collection $model
     * @return Model|Collection
     */
    public function destroyForModel(Model $model)
    {
        if (is_a($model, AbstractTranslatableModel::class)) {
            /** @var AbstractTranslatableModel $model */
            $model->translations()->delete();
        }

        return $model;
    }

    /**
     * @param string $modelClassName
     */
    public function setModelClassName(string $modelClassName): void
    {
        $this->modelClassName = $modelClassName;
    }

    /**
     * Make the slug for the translations model if translation models has the HasSlugInterface
     *
     * @param Model $model
     * @return Model
     */
    protected function makeSlugForTranslationsIfNeeded(Model $model)
    {
        // Check for interface on translation model
        if (is_a(new $this->modelClassName, HasSlugInterface::class)) {
            foreach ($model->translations as $translation) {
                // Make the desired slug
                $slug = $translation->makeSlug();

                // Check if the slug isn't empty
                if (empty($slug)) {
                    continue;
                }

                // TODO: We need to expand this so it will also work with multiple site.
                // TODO: Because then we only need the Translation Models of the Models that are within a Site

                // We already try to filter as much models here
                // Only grab the translation models that also starts with the same slug
                $similarSluggedTranslationModels = $this->modelClassName::where('id', '!=', $translation->id)
                    ->where('language_id', $translation->language_id)
                    ->where('slug', 'LIKE', $slug.'%')
                    ->get();

                // If the are similar slugged translation models then we need to make an unique slug
                if ($similarSluggedTranslationModels->count() != 0) {
                    $slug = create_unique_slug($similarSluggedTranslationModels, $slug);
                }

                $translation->slug = $slug;
                $translation->save();
            }
        }

        return $model;
    }
}