ActorLanguage.php 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. <?php
  2. declare(strict_types = 1);
  3. // {{{ License
  4. // This file is part of GNU social - https://www.gnu.org/software/social
  5. //
  6. // GNU social is free software: you can redistribute it and/or modify
  7. // it under the terms of the GNU Affero General Public License as published by
  8. // the Free Software Foundation, either version 3 of the License, or
  9. // (at your option) any later version.
  10. //
  11. // GNU social is distributed in the hope that it will be useful,
  12. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. // GNU Affero General Public License for more details.
  15. //
  16. // You should have received a copy of the GNU Affero General Public License
  17. // along with GNU social. If not, see <http://www.gnu.org/licenses/>.
  18. // }}}
  19. namespace Component\Language\Entity;
  20. use App\Core\Cache;
  21. use App\Core\DB\DB;
  22. use App\Core\Entity;
  23. use App\Entity\Actor;
  24. use App\Entity\LocalUser;
  25. use App\Util\Common;
  26. use Functional as F;
  27. /**
  28. * Entity for actor languages
  29. *
  30. * @category DB
  31. * @package GNUsocial
  32. *
  33. * @author Hugo Sales <hugo@hsal.es>
  34. * @copyright 2021 Free Software Foundation, Inc http://www.fsf.org
  35. * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
  36. */
  37. class ActorLanguage extends Entity
  38. {
  39. // {{{ Autocode
  40. // @codeCoverageIgnoreStart
  41. private int $actor_id;
  42. private int $language_id;
  43. private int $ordering;
  44. public function setActorId(int $actor_id): self
  45. {
  46. $this->actor_id = $actor_id;
  47. return $this;
  48. }
  49. public function getActorId(): int
  50. {
  51. return $this->actor_id;
  52. }
  53. public function setLanguageId(int $language_id): self
  54. {
  55. $this->language_id = $language_id;
  56. return $this;
  57. }
  58. public function getLanguageId(): int
  59. {
  60. return $this->language_id;
  61. }
  62. public function setOrdering(int $ordering): self
  63. {
  64. $this->ordering = $ordering;
  65. return $this;
  66. }
  67. public function getOrdering(): int
  68. {
  69. return $this->ordering;
  70. }
  71. // @codeCoverageIgnoreEnd
  72. // }}} Autocode
  73. public static function cacheKeys(LocalUser|Actor|int $actor, ?Actor $context = null): array
  74. {
  75. $actor_id = \is_int($actor) ? $actor : $actor->getId();
  76. return [
  77. 'related-ids' => "actor-{$actor_id}-lang-related-ids",
  78. 'actor-langs' => "actor-{$actor_id}-langs" . (!\is_null($context) ? "-cxt-{$context->getId()}" : ''),
  79. ];
  80. }
  81. public static function normalizeOrdering(LocalUser|Actor $actor)
  82. {
  83. $langs = DB::dql('select l.locale, al.ordering, l.id from language l join actor_language al with l.id = al.language_id where al.actor_id = :id order by al.ordering ASC', ['id' => $actor->getId()]);
  84. usort($langs, fn ($l, $r) => [$l['ordering'], $l['locale']] <=> [$r['ordering'], $r['locale']]);
  85. foreach ($langs as $order => $l) {
  86. $actor_lang = DB::getReference('actor_language', ['actor_id' => $actor->getId(), 'language_id' => $l['id']]);
  87. $actor_lang->setOrdering($order + 1);
  88. }
  89. }
  90. /**
  91. * @return Language[]
  92. */
  93. public static function getActorLanguages(LocalUser|Actor $actor, ?Actor $context = null): array
  94. {
  95. $id = $context?->getId() ?? $actor->getId();
  96. return Cache::getList(
  97. self::cacheKeys($actor, context: $context)['actor-langs'],
  98. fn () => DB::dql(
  99. 'select l from actor_language al join language l with al.language_id = l.id where al.actor_id = :id order by al.ordering ASC',
  100. ['id' => $id],
  101. ),
  102. ) ?: [Language::getByLocale(Common::config('site', 'language'))];
  103. }
  104. public static function getActorRelatedLanguagesIds(Actor $actor): array
  105. {
  106. return Cache::getList(
  107. self::cacheKeys($actor)['related-ids'],
  108. function () use ($actor) {
  109. return F\map(
  110. F\flat_map(
  111. self::getActorLanguages($actor),
  112. function ($language) {
  113. if (str_contains($language->getLocale(), '_')) {
  114. // Actor selected a language with a country, so don't attempt to provide alternatives
  115. return $language;
  116. } else {
  117. // Actor selected a language without a country, so find all variants of the language
  118. return DB::dql('select l from language l where l.locale like :locale', ['locale' => $language->getLocale() . '%']);
  119. }
  120. },
  121. ),
  122. fn ($l) => $l->getId(),
  123. );
  124. },
  125. );
  126. }
  127. public static function schemaDef(): array
  128. {
  129. return [
  130. 'name' => 'actor_language',
  131. 'description' => 'join table where one actor can have many languages',
  132. 'fields' => [
  133. 'actor_id' => ['type' => 'int', 'not null' => true, 'foreign key' => true, 'target' => 'Actor.id', 'multiplicity' => 'one to many', 'description' => 'the actor this language entry refers to'],
  134. 'language_id' => ['type' => 'int', 'not null' => true, 'foreign key' => true, 'target' => 'Language.id', 'multiplicity' => 'many to many', 'description' => 'the language this entry refers to'],
  135. 'ordering' => ['type' => 'int', 'not null' => true, 'description' => 'the order in which a user\'s language options should be displayed'],
  136. ],
  137. 'primary key' => ['actor_id', 'language_id'],
  138. 'indexes' => [
  139. 'actor_idx' => ['actor_id'],
  140. ],
  141. ];
  142. }
  143. }