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/stafa/werkenbijstafa.nl/app/Komma/Shop/Properties/Kms/PropertyService.php
<?php
namespace App\Komma\Shop\Properties\Kms;
use App\Helpers\KommaHelpers;
use App\Komma\Kms\Core\Attributes\Attribute;
use App\Komma\Kms\Core\Attributes\Currency;
use App\Komma\Kms\Core\Attributes\Models\SelectOption;
use App\Komma\Kms\Core\Attributes\Models\SelectOptionInterface;
use App\Komma\Kms\Core\Attributes\Models\Traits\HasThumbnailInterface;
use App\Komma\Kms\Core\Attributes\TextField;
use App\Komma\Kms\Core\Sections\SectionService;
use App\Komma\Kms\Core\Sections\SectionTabItem;
use App\Komma\Kms\Core\Sections\SideBarListItem;
use App\Komma\Globalization\Languages\Models\Language;
use App\Komma\Shop\Properties\Models\PropertizableInterface;
use App\Komma\Shop\Properties\Models\Property;
use App\Komma\Shop\Properties\Models\PropertyKey;
use App\Komma\Shop\Properties\Models\PropertyKeyTranslation;
use App\Komma\Shop\Properties\Models\PropertyValue;
use App\Komma\Shop\Properties\Models\PropertyValueTranslation;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Builder;

/**
 * Class PropertyService
 * @package App\Komma\Shop\Properties
 */
class PropertyService extends SectionService
{
    protected $sortable = false;

    /** @var PropertyKeyService $propertyKeyService */
    protected $propertyKeyService;

    /** @var PropertyValueService $propertyValueService */
    protected $propertyValueService;

    function __construct()
    {
        $this->forModelName = Property::class;
        $this->propertyKeyService = app(PropertyKeyService::class);
        $this->propertyValueService = app(PropertyValueService::class);
        parent::__construct();
    }

    /**
     * This method will save an property.
     *
     * @param $model Model or null
     * @param Collection $sectionTabItems These must be filled with data. This is something you need to do yourself.
     *
     * @return mixed
     */
    public function saveModel(Model $model = null, Collection $sectionTabItems): Model
    {
        $sectionTabItems->each(function($sectionTabItem) use($model, $sectionTabItems) {
            /** @var SectionTabItem $sectionTabItem */

            $attribute = $sectionTabItem->getAttribute();

            $reference = $attribute->getsValueFromReference();
            $value = $attribute->getValue();

            switch ($attribute->getsValueFrom())
            {
                case Attribute::ValueFromItself:
                    if($reference == 'key') {
                        /** @var Property $model */
                        $propertyKey = $this->getOrCreatePropertyKeyWithTranslationValue(trim($value), $attribute->getAssociatedLanguage(), $model);

                        //Check if there is a translation that we can update or check if we need to create a new translation
                        /** @var PropertyKeyTranslation $keyTranslation */
                        $keyTranslation = $this->propertyKeyService->getTranslationModelForModelByLanguage($propertyKey, $attribute->getAssociatedLanguage());
                        $keyTranslation->property_key_id = $propertyKey->id;
                        $keyTranslation->value = $value;
                        $keyTranslation->save();
                    }
                    else if($reference == 'value') {
                        $propertyValue = $this->getOrCreatePropertyValueWithTranslationValue(trim($value), $attribute->getAssociatedLanguage(), $model);

                        //Check if there is a translation that we can update or check if we need to create a new translation
                        /** @var PropertyValueTranslation $valueTranslation */
                        $valueTranslation = $this->propertyValueService->getTranslationModelForModelByLanguage($propertyValue, $attribute->getAssociatedLanguage());
                        $valueTranslation->property_value_id = $propertyValue->id;

                        $valueTranslation->value = $value;
                        $valueTranslation->save();
                    }
                    break;
            }
        });

        $model->save();

        $model = parent::saveModel($model, $sectionTabItems); //First make sure we have a model and save the attributes in them from the SectionTabItem attributes

        //Return the page
        return $model;
    }

    /**
     * Get or create a PropertyKey with a given translation value for a specific language and optionally associate it to a Property
     * Notice: When you want to associate it to a Property you need to save the property yourself.
     *
     * @param string $value
     * @param Language $language
     * @param Property $property
     * @return PropertyKey
     */
    public function getOrCreatePropertyKeyWithTranslationValue(string $value, Language $language, Property &$property = null):PropertyKey
    {
        /** @var PropertyKeyTranslation $keyTranslation */
        $keyTranslation = PropertyKeyTranslation::firstOrNew([
            ['language_id', '=', $language->id],
            ['value', '=', strtolower($value)]
        ]);


        /** @var PropertyKey $propertyKey */
        if($keyTranslation->exists) {
            $propertyKey = $keyTranslation->translatable;
        } else {
            $propertyKey = $this->propertyKeyService->newModel();
            $propertyKey->save();
            $keyTranslation->translatable()->associate($propertyKey);
            $keyTranslation->save();
        }


        if($property) $property->key()->associate($propertyKey);

        return $propertyKey;
    }

    /**
     * Get or create a PropertyValue with a given translation value for a specific language and optionally associate it to a Property
     * Notice: When you want to associate it to a Property you need to save the property yourself.
     *
     * @param string $value
     * @param Language $language
     * @param Property $property
     * @return PropertyValue
     */
    public function getOrCreatePropertyValueWithTranslationValue(string $value, Language $language, Property &$property = null):PropertyValue
    {
        /** @var PropertyValueTranslation $valueTranslation */
        $valueTranslation = PropertyValueTranslation::firstOrNew([
            ['language_id', '=', $language->id],
            ['value', '=', strtolower($value)]
        ]);


        /** @var PropertyValue $propertyValue */
        if($valueTranslation->exists) {
            $propertyValue = $valueTranslation->translatable;
        } else {
            $propertyValue = $this->propertyValueService->newModel();
            $propertyValue->save();
            $valueTranslation->translatable()->associate($propertyValue);
            $valueTranslation->save();
        }


        if($property) $property->value()->associate($propertyValue);

        return $propertyValue;
    }

    /**
     * Fills non-product specific attributes. Product specific attributes are processed in the parent
     *
     * @param Collection $sectionTabItems A collection containing implementations AbstractSectionTabItem's
     * @param Model $model
     * @return Collection
     */
    public function fillAttributesWithData(Collection $sectionTabItems, Model $model)
    {
        /** @var $model Property */

        $filledAttributesCollection = parent::fillAttributesWithData($sectionTabItems, $model);

        /** @var TextField $quantityDiscountAttribute */
        $quantityDiscountAttribute = null;
        /** @var Currency $quantityPriceAttribute */
        $quantityPriceAttribute = null;

        $sectionTabItems->each(
            function ($sectionTabItem, $key) use ($model, $filledAttributesCollection, &$quantityDiscountAttribute, &$quantityPriceAttribute) {
                /** @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.");

                $attribute = $sectionTabItem->getAttribute();
                $valueReference = $sectionTabItem->getAttribute()->getsValueFromReference();

                switch ($valueReference) {
                    case 'key':
                        $key = $model->key()->first();
                        if(!$key) break;
                        $translation = $this->propertyKeyService->getTranslationModelForModelByLanguage($key, $attribute->getAssociatedLanguage());
                        $attribute->setValue(($translation) ? $translation->value : '');
                        break;
                    case 'value':
                        $propertyValue = $model->value()->with(['translations' => function(HasMany $query) use($attribute) {
                            $query->where('language_id', '=', $attribute->getAssociatedLanguage()->id)->get();
                        }])->first();

                        if(!$propertyValue || !$propertyValue->relationLoaded('translations') || !$propertyValue->translations->first()) break;

                        /** @var PropertyValue $propertyValue */
                        $attribute->setValue($propertyValue->translations->first()->value);
                }
            }
        );

        return $filledAttributesCollection;
    }

    /**
     * Links a propertizable to properties
     * The properties id is a comma separated string
     *
     * @param PropertizableInterface $model
     * @param string $propertyIds
     */
    public function linkToProperties(PropertizableInterface $model, string $propertyIds):void
    {
        if(!$model->id) return;

        if($propertyIds !== "") {
            $propertyIds = array_unique(explode(',', $propertyIds));
        } else {
            $propertyIds = [];
        }

        $properties = $this->forModelName::whereIn('id', $propertyIds)->get();

        if(!$properties) return;

        $modelShortClassName = KommaHelpers::getShortNameFromClass($model, true);
        $relationName = str_plural($modelShortClassName);

        if(!method_exists((new $this->forModelName), $relationName)) {
            throw new \RuntimeException('The class "'.$this->forModelName.'" must have, but did not have a MorphToMany relation method called "'.$relationName.'". Without it we cannot make the "'.get_class($model).'" a child of "'.$this->forModelName.'"');
        }

        $model->properties()->sync($propertyIds);
    }


    /**
     * Create an array of Select options for a PropertyValueSelect
     *
     * @param Property $property
     * @param Language $language
     * @return array
     */
    public function getOptionModelsForPropertyValueSelect(Property $property, Language $language):array
    {
        //Get all the PropertyValues in a collection where it has a translation with associated with the language. And include the translations
        $values = $property->value()->whereHas('translations', function(Builder $query) use($language) {
            $query->where('language_id', '=', $language->id);
        })->with(['translations' => function(HasMany $query) use ($language) {
            $query->where('language_id', '=', $language->id);
        }])->get();

        $optionsCollection = $values->map(function(PropertyValue $propertyValue, $index) {
            if($propertyValue->translations->count() == 0) return null;
            $translation = $propertyValue->translations->first();

            /** @var SelectOptionInterface $option */
            $option = app(SelectOptionInterface::class);
            $option->setValue((string) $translation->value);
            $option->setHtmlContent($translation->value);
            $option->setContent($translation->value);

            return $option;
        });

        /** @var SelectOptionInterface $empty */
        $empty = app(SelectOptionInterface::class);
        $empty->setValue(null);
        $empty->setHtmlContent('&nbsp;');
        $empty->setContent('');

        $options = $optionsCollection->toArray();
        array_unshift($options,$empty); //add element to beginning

        return $options;
    }

    /**
     * Returns the category ids as a comma separated string for the CategorizableInterface implementation
     *
     * @param PropertizableInterface $model
     * @return string Category ids, comma separated
     */
    public function getPropertyIdsForModel(PropertizableInterface $model): ?string
    {
        if(!$model->id) return null;

        $idsCollection = $model->properties()->get(['property_id'])->map(function(Property $property ) {
            return $property->property_id;
        });
        $idString = implode(',', $idsCollection->toArray());

        if($idString == "") return null;

        return $idString;
    }

    /**
     * Creates or deletes a property depending on if the PropertyValueSelect has a numeric value or not.
     * Links a valueTranslation with the id that is the same as the attributes value to the property and
     * links it to the given model. The property will also get a predefined key name suffixed with the given nameSuffix
     *
     * @param PropertizableInterface $model
     * @param PropertyValueSelect $attribute
     * @param string $nameSuffix
     * @throws \Exception
     */
    public function createOrDeletePropertyUsingPropertyValueSelectAttributeAndLinkItToModel(PropertizableInterface $model, PropertyValueSelect $attribute, string $nameSuffix)
    {
        $keyTranslationValue = ($this->getPropertyKeyTranslationValueForModel($model, $nameSuffix));

        //Get the value translation to link to the property
        /** @var PropertyValueTranslation $valueTranslation */
        $valueTranslation = PropertyValueTranslation::where('value', '=', $attribute->getValue())->first();
        /** @var Language $language */
        $language = $attribute->getAssociatedLanguage();

        if($valueTranslation) {
            if($this->propertyWithKeyAndValueTranslationValuesExists($keyTranslationValue, $valueTranslation->value, $language)) return;

            $alreadyExists = $this->createPropertyForPropertizableModelIfItNotExists($language, $keyTranslationValue, $valueTranslation->value, $model);
            if($alreadyExists) $this->deletePropertyForPropertizableModel($keyTranslationValue, $language); //Already exists but with a new value. That's why we delete the old one.
            $this->createPropertyForPropertizableModelIfItNotExists($language, $keyTranslationValue, $valueTranslation->value, $model);
        } else {
            $this->deletePropertyForPropertizableModel($keyTranslationValue, $language);
        }
    }

    /**
     * Returns the id of a property if there is a property with the specified key and value translation values. false if not.
     *
     * @param string $propertyKeyTranslationValue
     * @param string $properyValueTranslationValue
     * @param Language $language
     * @return bool
     */
    public function propertyWithKeyAndValueTranslationValuesExists(string $propertyKeyTranslationValue, string $properyValueTranslationValue, Language $language = null)
    {
        /** @var PropertyKeyTranslation $keyTranslation */
        $keyTranslationQueryBuilder = PropertyKeyTranslation::where('value', '=', $propertyKeyTranslationValue);
        $keyTranslation = ($language) ? $keyTranslationQueryBuilder->where('language_id', '=', $language->id)->first() : $keyTranslationQueryBuilder->first();
        if(!$keyTranslation) return false;

        /** @var Collection $propertyValueTranslations */
        $valueTranslationQueryBuilder = PropertyValueTranslation::where('value', '=', $properyValueTranslationValue);
        $propertyValueTranslations = ($language) ? $valueTranslationQueryBuilder->where('language_id', '=', $language->id)->get() : $valueTranslationQueryBuilder->get();
        if(!$propertyValueTranslations) return false;

        /** @var PropertyKey $propertyKey */
        $propertyKey = $keyTranslation->translatable()->first();
        if(!$propertyKey) return false;

        /** @var Property $propertyFromKey */
        $propertyFromKey = $propertyKey->property()->first();
        if(!$propertyFromKey) return false;

        $keyPropertyId = $propertyFromKey->id;

        $existingPropertyIdOrFalse = false;
        $propertyValueTranslations->each(function(PropertyValueTranslation $propertyValueTranslation) use ($keyPropertyId, &$existingPropertyIdOrFalse) {
            /** @var PropertyValue $propertyValue */
            $propertyValue = $propertyValueTranslation->translatable()->first();
            if(!$propertyValue) return null; //Continue

            $valuePropertyId = $propertyValue->property_id;
            if($keyPropertyId == $valuePropertyId) {
                $existingPropertyIdOrFalse = $keyPropertyId;
                return false; //Break
            }
        });

        return $existingPropertyIdOrFalse;
    }


    /**
     * Creates a new property with a key and a value (both with translations), sets the key and value translation values to the given ones, and links it to the given model.
     * All on one condition: A property must not exist with the given key translation value. Returns false if the property was created. true if it was not created because it already exists
     *
     * @param Language $language
     * @param string $propertyKeyTranslationValue
     * @param string $propertyValueTranslationValue
     * @param PropertizableInterface $model
     * @return bool alreadyExists
     * @throws \Throwable
     */
    public function createPropertyForPropertizableModelIfItNotExists(Language $language, string $propertyKeyTranslationValue, string $propertyValueTranslationValue, PropertizableInterface $model):bool
    {
        if(PropertyKeyTranslation::where('value', '=', $propertyKeyTranslationValue)->first()) return true;

        \DB::transaction(function () use($propertyKeyTranslationValue, $language, $propertyValueTranslationValue, $model) {
            //Create the key
            $key = new PropertyKey();
            $key->save();
            $keyTranslation = new PropertyKeyTranslation();
            $keyTranslation->language()->associate($language);
            $keyTranslation->translatable()->associate($key);
            $keyTranslation->value = $propertyKeyTranslationValue;
            $keyTranslation->save();

            //Create the property
            $property = new Property();
            $property->key()->associate($key);
            $property->code_name = str_slug($keyTranslation->value);
            $property->save();

            //Link the value translation to the property by linking its value to the property //TODO this could be a improvement. A value CAN belong to multiple properties (key_values). The only problem it gives is when you delete a property linked to that value you cant do a cascade delete because it can break other properties
            $value = new PropertyValue();
            $value->property()->associate($property);
            $value->save();

            $newValueTranslation = new PropertyValueTranslation();
            $newValueTranslation->value = $propertyValueTranslationValue;
            $newValueTranslation->language()->associate($language);
            $newValueTranslation->translatable()->associate($value);
            $newValueTranslation->save();

            $model->properties()->save($property);
        });

        return false;
    }

    /**
     * Deletes the property and its key and value by a keys translation value for a certain language
     *
     * @param string $keyTranslationValue
     * @param Language $keysTranslationLanguage
     * @throws \Throwable
     */
    public function deletePropertyForPropertizableModel(string $keyTranslationValue, Language $keysTranslationLanguage)
    {
        \DB::transaction(function () use($keyTranslationValue, $keysTranslationLanguage) {
            //The value does not exist, so that means we need to delete the current property if it exists.
            /** @var PropertyKeyTranslation|null $keyTranslation */
            $keyTranslation = PropertyKeyTranslation::where([
                ['value', '=', $keyTranslationValue],
                ['language_id', '=', $keysTranslationLanguage->id]
            ])->first();
            if (!$keyTranslation) {
                return;
            }

            /** @var PropertyKey|null $key */
            $key = $keyTranslation->translatable()->first();
            if (!$key) {
                return;
            }

            /** @var Property $property */
            $property = $key->property()->first();
            if (!$property) throw new \RuntimeException('PropertyKey with id: "' . $key->id . '"" did not have a property while it should');

            /** @var PropertyValue $value */
            $value = $property->value()->first();
            if($value) {
                $value->translations()->get()->each(function(PropertyValueTranslation $propertyValueTranslation) {
                    $propertyValueTranslation->delete();
                });
            }
            $value->delete();

            $key->delete();

            $property->delete();
        });
    }

    /**
     * This method will remove an TranslatableModelInterface instance
     *
     * @param $model
     * @throws \Exception
     * @throws \Throwable
     */
    public function destroyModel(Model $model)
    {
        \DB::transaction(function () use($model) {
            /** @var Property $model */
            /** @var Collection $value */
            $value = $model->value()->with(['translations'])->first();
            $key = $model->key()->with(['translations'])->first();

            $valueTranslationIdsToDelete = [];
            $keyTranslationIdsToDelete = [];

            $model->delete();

            //Only delete the value if it does exist and does not belong to another property
            if($value && Property::where('property_value_id', '=', $value->id)->count() == 0) {
                $value->translations->each(function (PropertyValueTranslation $propertyValueTranslation) use (&$valueTranslationIdsToDelete) {
                    $valueTranslationIdsToDelete[] = $propertyValueTranslation->id;
                });
                PropertyValueTranslation::destroy($valueTranslationIdsToDelete);
                $value->delete();
            }

            //Only delete the key if it does exist and does not belong to another property
            if($key && Property::where('property_key_id', '=', $key->id)->count() == 0) {
                $key->translations->each(function (PropertyKeyTranslation $propertyValueTranslation) use (&$keyTranslationIdsToDelete) {
                    $keyTranslationIdsToDelete[] = $propertyValueTranslation->id;
                });
                PropertyKeyTranslation::destroy($keyTranslationIdsToDelete);
                $key->delete();
            }
        });
    }

    /**
     * Gets all properties for the specified property key translation value for the language the keyValue is in OR
     * for all languages if you specify the boolean as false
     *
     * @param string $keyTranslationValue
     * @return Builder|null
     */
    public function getPropertiesForPropertyKeyValue(string $keyTranslationValue):Builder
    {
        /** @var PropertyKeyTranslation|null $keyTranslation */
        $keyTranslation = PropertyKeyTranslation::where('value', '=', strtolower($keyTranslationValue))->first();
        if(!$keyTranslation) return null;

        /** @var PropertyKey|null $key */
        $key = $keyTranslation->translatable()->first();
        if(!$key) return null;

        /** @var  $property */
        $properties = Property::where('property_key_id', '=', $key->id)->with(['value' => function(BelongsTo $query) {
            $query->with(['translations']);
        }]);

        return $properties;
    }

    /**
     * Set the attributes value with the id of the properties translation of which it has a key with a translation value like the getPropertyNameForModel method returns
     *
     * @param PropertizableInterface $model
     * @param PropertyValueSelect $attribute
     * @param string $nameSuffix
     */
    public function setPropertyValueSelectValueForModelIfPossible(PropertizableInterface $model, PropertyValueSelect $attribute, string $nameSuffix)
    {
        $name = $this->getPropertyKeyTranslationValueForModel($model, $nameSuffix);

        /** @var PropertyKeyTranslation $keyTranslation */
        $keyTranslation = PropertyKeyTranslation::where('value', '=', $name)->first();
        if(!$keyTranslation) return;

        /** @var PropertyKey $key */
        $key = $keyTranslation->translatable()->first();
        if(!$key) return;

        /** @var Property $property */
        $property = $key->property()->first();
        if(!$property) return;

        /** @var PropertyValue $value */
        $value = $property->value()->first();
        if(!$value) return;

        /** @var PropertyValueTranslation $propertyValueTranslation */
        $propertyValueTranslation = $value->translations()->where('language_id', '=', $attribute->getAssociatedLanguage()->id)->first();
        if(!$propertyValueTranslation) return;

        $attribute->setValue((string) $propertyValueTranslation->value);
    }

    /**
     * Creates a property name for a model using a model and a suffix
     *
     * @param Model $model
     * @param string $nameSuffix
     * @return string
     */
    private function getPropertyKeyTranslationValueForModel(Model $model, string $nameSuffix)
    {
        return KommaHelpers::getShortNameFromClass($model).' '.$model->id.' '.$nameSuffix;
    }

    /**
     * Returns all models for the sidebar menu in the backend
     *
     * @return array
     */
    public function getModelsForSideBar(): array
    {
        $models = $this->forModelName::with([
            'key' => function(BelongsTo $query) {
                $query->with('translations');
            },
            'value' => function(BelongsTo $query) {
                $query->with('translations');
            }
        ])->get();

        $sidebarList = [];
        foreach ($models as $model) {
            $sidebarListItem = new SidebarListItem();

            /** @var HasThumbnailInterface $model */
            $model->generateThumbnail();

            //Set the values for the sidebar
            $sidebarListItem->setId($model->id);
            $sidebarListItem->setStatus(true);
            $title = KommaHelpers::str_limit_full_word($model->title, 75);
            $sidebarListItem->setName($title);

            $sidebarList[] = $sidebarListItem;
        }

        return $sidebarList;
    }


}