users()->detach(); $tag->organizations()->detach(); $tag->banks()->detach(); // Delete locale tied directly to the tag $tag->locale()->delete(); // Clean up any orphaned contexts (with no related tag models left) $unusedLocaleContexts = TaggableLocaleContext::doesntHave('tags')->get(); $unusedLocaleContexts->each(fn ($localeContext) => $localeContext->delete()); $unusedContexts = TaggableContext::doesntHave('tags')->get(); $unusedContexts->each(fn ($context) => $context->delete()); }); } /** * Get the index name for the model. * * @return string */ public function searchableAs() { return 'tags_index'; } /** * Get the value used to index the model. */ public function getScoutKey(): mixed { return $this->tag_id; } /** * Get the key name used to index the model. */ public function getScoutKeyName(): mixed { return 'tag_id'; } public function users() { return $this->morphedByMany(User::class, 'taggable', 'taggable_taggables', 'tag_id', 'taggable_id'); } public function organizations() { return $this->morphedByMany(Organization::class, 'taggable', 'taggable_taggables', 'tag_id', 'taggable_id'); } public function banks() { return $this->morphedByMany(Bank::class, 'taggable', 'taggable_taggables', 'tag_id', 'taggable_id'); } public function locale() { return $this->hasOne(TaggableLocale::class, 'taggable_tag_id'); } public function localeCode() { return $this->hasOne(TaggableLocale::class, 'taggable_tag_id')->select('locale'); } public function contexts() { return $this->belongsToMany(TaggableContext::class, 'taggable_locale_context', 'tag_id', 'context_id'); } public function localeContext() { return $this->hasOne(TaggableLocaleContext::class, 'tag_id'); } /** * Scope to find tags by name. * * @param \Illuminate\Database\Eloquent\Builder $query * @param $value * * @return \Illuminate\Database\Eloquent\Builder */ public function scopeByLocalName(Builder $query, string $value): Builder { $normalized = app(TagService::class)->normalize($value); return $query->where('normalized', $normalized); } /** * Get all related tags with their locales using a raw query. * Note: this is not a relationship definition! * * @return \Illuminate\Support\Collection */ public function translations() { return DB::table('taggable_tags as tt') ->join('taggable_locale_context as tlc', 'tt.tag_id', '=', 'tlc.tag_id') ->join('taggable_locales as tl', 'tt.tag_id', '=', 'tl.taggable_tag_id') ->select('tt.*', 'tl.*', 'tlc.context_id') ->whereIn('tlc.context_id', function ($query) { $query->select('context_id') ->from('taggable_locale_context') ->where('tag_id', $this->tag_id); }) ->distinct() ->get(); } public function getLocalesAttribute() { // Get context_ids for current tag $contextIds = DB::table('taggable_locale_context') ->where('tag_id', $this->tag_id) ->pluck('context_id'); // Get tag_ids that share these context_ids $relatedTagIds = DB::table('taggable_locale_context') ->whereIn('context_id', $contextIds) ->pluck('tag_id'); // Get translations for these tags return DB::table('taggable_tags as tt') ->join('taggable_locales as tl', 'tt.tag_id', '=', 'tl.taggable_tag_id') ->whereIn('tt.tag_id', $relatedTagIds) ->select('tt.tag_id', 'tt.name', 'tl.*') ->get(); } public function getCategoriesAttribute() { // Get context_ids for current tag $contextIds = DB::table('taggable_locale_context') ->where('tag_id', $this->tag_id) ->pluck('context_id'); // Get categories for these contexts $categoryId = DB::table('taggable_contexts as tc') ->join('categories as c', 'tc.category_id', '=', 'c.id') ->whereIn('tc.id', $contextIds) ->pluck('c.id'); // Get the first category ID $firstCategoryId = $categoryId->first(); // Find the category by its ID $category = Category::find($firstCategoryId); $categoryWithRelated = $category ? $category->related() : null; return $categoryWithRelated; } /** * Get the translation attribute for the tag. * * This method attempts to retrieve the translation for the tag * in the current locale. If a translation in the current locale is * not found, it falls back to the base locale defined in the timebank-cc config. * * @return \App\Models\Translation|null The translation object for the category in the current or base locale, or null if not found. */ public function getTranslationAttribute() { $baseLocale = timebank_config('base_language'); // Retrieve all translations using the translations() method $translations = $this->translations(); // Attempt to get the translation in the current locale $translation = $translations->firstWhere('locale', App::getLocale()); // Fallback to base locale if translation not found if (!$translation) { $translation = $translations->firstWhere('locale', $baseLocale); // Fallback to only available locale if no translation in base locale is not found if (!$translation) { return $translations->first(); //We can do first() as tags can only be translated to the base locale. So there are no other translations. } } return $translation; } }