vendor/pimcore/pimcore/models/Translation.php line 434

Open in your IDE?
  1. <?php
  2. /**
  3.  * Pimcore
  4.  *
  5.  * This source file is available under two different licenses:
  6.  * - GNU General Public License version 3 (GPLv3)
  7.  * - Pimcore Commercial License (PCL)
  8.  * Full copyright and license information is available in
  9.  * LICENSE.md which is distributed with this source code.
  10.  *
  11.  *  @copyright  Copyright (c) Pimcore GmbH (http://www.pimcore.org)
  12.  *  @license    http://www.pimcore.org/license     GPLv3 and PCL
  13.  */
  14. namespace Pimcore\Model;
  15. use Doctrine\DBAL\Exception\TableNotFoundException;
  16. use Pimcore\Cache;
  17. use Pimcore\Cache\RuntimeCache;
  18. use Pimcore\Event\Model\TranslationEvent;
  19. use Pimcore\Event\Traits\RecursionBlockingEventDispatchHelperTrait;
  20. use Pimcore\Event\TranslationEvents;
  21. use Pimcore\File;
  22. use Pimcore\Localization\LocaleServiceInterface;
  23. use Pimcore\Model\Element\Service;
  24. use Pimcore\Tool;
  25. use Pimcore\Translation\TranslationEntriesDumper;
  26. use Symfony\Component\Translation\Exception\NotFoundResourceException;
  27. /**
  28.  * @method \Pimcore\Model\Translation\Dao getDao()
  29.  */
  30. final class Translation extends AbstractModel
  31. {
  32.     use RecursionBlockingEventDispatchHelperTrait;
  33.     const DOMAIN_DEFAULT 'messages';
  34.     const DOMAIN_ADMIN 'admin';
  35.     /**
  36.      * @var string|null
  37.      */
  38.     protected $key;
  39.     /**
  40.      * @var string[]
  41.      */
  42.     protected $translations = [];
  43.     /**
  44.      * @var int|null
  45.      */
  46.     protected $creationDate;
  47.     /**
  48.      * @var int|null
  49.      */
  50.     protected $modificationDate;
  51.     /**
  52.      * @var string
  53.      */
  54.     protected $domain self::DOMAIN_DEFAULT;
  55.     /**
  56.      * @var string|null
  57.      */
  58.     protected $type 'simple';
  59.     /**
  60.      * ID of the owner user
  61.      *
  62.      * @var int|null
  63.      */
  64.     protected ?int $userOwner null;
  65.     /**
  66.      * ID of the user who make the latest changes
  67.      *
  68.      * @var int|null
  69.      */
  70.     protected ?int $userModification null;
  71.     /**
  72.      * @return string
  73.      */
  74.     public function getType()
  75.     {
  76.         return $this->type ?: 'simple';
  77.     }
  78.     /**
  79.      * @param string|null $type
  80.      */
  81.     public function setType($type): void
  82.     {
  83.         $this->type $type;
  84.     }
  85.     /**
  86.      * {@inheritdoc}
  87.      */
  88.     public static function IsAValidLanguage(string $domainstring $locale): bool
  89.     {
  90.         return in_array($locale, (array)static::getValidLanguages($domain));
  91.     }
  92.     /**
  93.      * @return string|null
  94.      */
  95.     public function getKey()
  96.     {
  97.         return $this->key;
  98.     }
  99.     /**
  100.      * @param string $key
  101.      *
  102.      * @return $this
  103.      */
  104.     public function setKey($key)
  105.     {
  106.         $this->key $key;
  107.         return $this;
  108.     }
  109.     /**
  110.      * @return string[]
  111.      */
  112.     public function getTranslations()
  113.     {
  114.         return $this->translations;
  115.     }
  116.     /**
  117.      * @param string[] $translations
  118.      *
  119.      * @return $this
  120.      */
  121.     public function setTranslations($translations)
  122.     {
  123.         $this->translations $translations;
  124.         return $this;
  125.     }
  126.     /**
  127.      * @param int $date
  128.      *
  129.      * @return $this
  130.      */
  131.     public function setDate($date)
  132.     {
  133.         $this->setModificationDate($date);
  134.         return $this;
  135.     }
  136.     /**
  137.      * @return int|null
  138.      */
  139.     public function getCreationDate()
  140.     {
  141.         return $this->creationDate;
  142.     }
  143.     /**
  144.      * @param int $date
  145.      *
  146.      * @return $this
  147.      */
  148.     public function setCreationDate($date)
  149.     {
  150.         $this->creationDate = (int) $date;
  151.         return $this;
  152.     }
  153.     /**
  154.      * @return int|null
  155.      */
  156.     public function getModificationDate()
  157.     {
  158.         return $this->modificationDate;
  159.     }
  160.     /**
  161.      * @param int $date
  162.      *
  163.      * @return $this
  164.      */
  165.     public function setModificationDate($date)
  166.     {
  167.         $this->modificationDate = (int) $date;
  168.         return $this;
  169.     }
  170.     /**
  171.      * @return string
  172.      */
  173.     public function getDomain(): string
  174.     {
  175.         return $this->domain;
  176.     }
  177.     /**
  178.      * @param string $domain
  179.      */
  180.     public function setDomain(string $domain): void
  181.     {
  182.         $this->domain = !empty($domain) ? $domain self::DOMAIN_DEFAULT;
  183.     }
  184.     /**
  185.      * @return int|null
  186.      */
  187.     public function getUserOwner(): ?int
  188.     {
  189.         return $this->userOwner;
  190.     }
  191.     /**
  192.      * @param int|null $userOwner
  193.      */
  194.     public function setUserOwner(?int $userOwner): void
  195.     {
  196.         $this->userOwner $userOwner;
  197.     }
  198.     /**
  199.      * @return int|null
  200.      */
  201.     public function getUserModification(): ?int
  202.     {
  203.         return $this->userModification;
  204.     }
  205.     /**
  206.      * @param int|null $userModification
  207.      */
  208.     public function setUserModification(?int $userModification): void
  209.     {
  210.         $this->userModification $userModification;
  211.     }
  212.     /**
  213.      * @internal
  214.      *
  215.      * @param string $domain
  216.      *
  217.      * @return array
  218.      */
  219.     public static function getValidLanguages(string $domain self::DOMAIN_DEFAULT): array
  220.     {
  221.         if ($domain == self::DOMAIN_ADMIN) {
  222.             return \Pimcore\Tool\Admin::getLanguages();
  223.         }
  224.         return Tool::getValidLanguages();
  225.     }
  226.     /**
  227.      * @param string $language
  228.      * @param string $text
  229.      */
  230.     public function addTranslation($language$text)
  231.     {
  232.         $this->translations[$language] = $text;
  233.     }
  234.     /**
  235.      * @param string $language
  236.      *
  237.      * @return string
  238.      */
  239.     public function getTranslation($language)
  240.     {
  241.         return $this->translations[$language];
  242.     }
  243.     /**
  244.      * @param string $language
  245.      *
  246.      * @return bool
  247.      */
  248.     public function hasTranslation($language)
  249.     {
  250.         return isset($this->translations[$language]);
  251.     }
  252.     /**
  253.      * @internal
  254.      */
  255.     public static function clearDependentCache()
  256.     {
  257.         Cache::clearTags(['translator''translate']);
  258.     }
  259.     /**
  260.      * @param string $id
  261.      * @param string $domain
  262.      * @param bool $create
  263.      * @param bool $returnIdIfEmpty
  264.      * @param array|null $languages
  265.      *
  266.      * @return static|null
  267.      *
  268.      * @throws \Exception
  269.      */
  270.     public static function getByKey(string $id$domain self::DOMAIN_DEFAULT$create false$returnIdIfEmpty false$languages null)
  271.     {
  272.         $cacheKey 'translation_' $id '_' $domain;
  273.         if (is_array($languages)) {
  274.             $cacheKey .= '_' implode('-'$languages);
  275.         }
  276.         if (RuntimeCache::isRegistered($cacheKey)) {
  277.             return RuntimeCache::get($cacheKey);
  278.         }
  279.         $translation = new static();
  280.         $translation->setDomain($domain);
  281.         $idOriginal $id;
  282.         $languages $languages array_intersect(static::getValidLanguages($domain), $languages) : static::getValidLanguages($domain);
  283.         try {
  284.             $translation->getDao()->getByKey($id$languages);
  285.         } catch (\Exception $e) {
  286.             if (!$create && !$returnIdIfEmpty) {
  287.                 return null;
  288.             }
  289.             $translation->setKey($id);
  290.             $translation->setCreationDate(time());
  291.             $translation->setModificationDate(time());
  292.             if ($create && ($e instanceof NotFoundResourceException || $e instanceof TableNotFoundException)) {
  293.                 $translations = [];
  294.                 foreach ($languages as $lang) {
  295.                     $translations[$lang] = '';
  296.                 }
  297.                 $translation->setTranslations($translations);
  298.                 TranslationEntriesDumper::addToSaveQueue($translation);
  299.             }
  300.         }
  301.         if ($returnIdIfEmpty) {
  302.             $translations $translation->getTranslations();
  303.             foreach ($languages as $language) {
  304.                 if (!array_key_exists($language$translations) || empty($translations[$language])) {
  305.                     $translations[$language] = $idOriginal;
  306.                 }
  307.             }
  308.             $translation->setTranslations($translations);
  309.         }
  310.         // add to key cache
  311.         RuntimeCache::set($cacheKey$translation);
  312.         return $translation;
  313.     }
  314.     /**
  315.      * @param string $id
  316.      * @param string $domain
  317.      * @param bool $create - creates an empty translation entry if the key doesn't exists
  318.      * @param bool $returnIdIfEmpty - returns $id if no translation is available
  319.      * @param string|null $language
  320.      *
  321.      * @return string|null
  322.      *
  323.      * @throws \Exception
  324.      */
  325.     public static function getByKeyLocalized(string $id$domain self::DOMAIN_DEFAULT$create false$returnIdIfEmpty false$language null)
  326.     {
  327.         $args func_get_args();
  328.         $domain $args[1] ?? self::DOMAIN_DEFAULT;
  329.         $create $args[2] ?? false;
  330.         $returnIdIfEmpty $args[3] ?? false;
  331.         $language $args[4] ?? null;
  332.         if ($domain == self::DOMAIN_ADMIN) {
  333.             if ($user Tool\Admin::getCurrentUser()) {
  334.                 $language $user->getLanguage();
  335.             } elseif ($user Tool\Authentication::authenticateSession()) {
  336.                 $language $user->getLanguage();
  337.             }
  338.             if (!$language) {
  339.                 $language \Pimcore::getContainer()->get('pimcore.locale')->findLocale();
  340.             }
  341.             if (!in_array($languageTool\Admin::getLanguages())) {
  342.                 $config \Pimcore\Config::getSystemConfiguration('general');
  343.                 $language $config['language'] ?? null;
  344.             }
  345.         }
  346.         if (!$language) {
  347.             $language \Pimcore::getContainer()->get(LocaleServiceInterface::class)->findLocale();
  348.             if (!$language) {
  349.                 return null;
  350.             }
  351.         }
  352.         $translationItem self::getByKey($id$domain$create$returnIdIfEmpty);
  353.         if ($translationItem instanceof self) {
  354.             return $translationItem->getTranslation($language);
  355.         }
  356.         return null;
  357.     }
  358.     /**
  359.      * @param string $domain
  360.      *
  361.      * @return bool
  362.      */
  363.     public static function isAValidDomain(string $domain): bool
  364.     {
  365.         $translation = new static();
  366.         return $translation->getDao()->isAValidDomain($domain);
  367.     }
  368.     public function save()
  369.     {
  370.         $this->dispatchEvent(new TranslationEvent($this), TranslationEvents::PRE_SAVE);
  371.         $this->getDao()->save();
  372.         $this->dispatchEvent(new TranslationEvent($this), TranslationEvents::POST_SAVE);
  373.         self::clearDependentCache();
  374.     }
  375.     public function delete()
  376.     {
  377.         $this->dispatchEvent(new TranslationEvent($this), TranslationEvents::PRE_DELETE);
  378.         $this->getDao()->delete();
  379.         self::clearDependentCache();
  380.         $this->dispatchEvent(new TranslationEvent($this), TranslationEvents::POST_DELETE);
  381.     }
  382.     /**
  383.      * Imports translations from a csv file
  384.      * The CSV file has to have the same format as an Pimcore translation-export-file
  385.      *
  386.      * @internal
  387.      *
  388.      * @param string $file - path to the csv file
  389.      * @param string $domain
  390.      * @param bool $replaceExistingTranslations
  391.      * @param array|null $languages
  392.      * @param array|null $dialect
  393.      *
  394.      * @return array
  395.      *
  396.      * @throws \Exception
  397.      */
  398.     public static function importTranslationsFromFile(string $file$domain self::DOMAIN_DEFAULT$replaceExistingTranslations true$languages null$dialect null)
  399.     {
  400.         $delta = [];
  401.         if (is_readable($file)) {
  402.             if (!$languages || !is_array($languages)) {
  403.                 $languages = static::getValidLanguages($domain);
  404.             }
  405.             //read import data
  406.             $tmpData file_get_contents($file);
  407.             //replace magic excel bytes
  408.             $tmpData str_replace("\xEF\xBB\xBF"''$tmpData);
  409.             //convert to utf-8 if needed
  410.             $tmpData Tool\Text::convertToUTF8($tmpData);
  411.             //store data for further usage
  412.             $importFile PIMCORE_SYSTEM_TEMP_DIRECTORY '/import_translations';
  413.             File::put($importFile$tmpData);
  414.             $importFileOriginal PIMCORE_SYSTEM_TEMP_DIRECTORY '/import_translations_original';
  415.             File::put($importFileOriginal$tmpData);
  416.             // determine csv type if not set
  417.             if (empty($dialect)) {
  418.                 $dialect Tool\Admin::determineCsvDialect(PIMCORE_SYSTEM_TEMP_DIRECTORY '/import_translations_original');
  419.             }
  420.             //read data
  421.             $data = [];
  422.             if (($handle fopen(PIMCORE_SYSTEM_TEMP_DIRECTORY '/import_translations''r')) !== false) {
  423.                 while (($rowData fgetcsv($handle0$dialect->delimiter$dialect->quotechar$dialect->escapechar)) !== false) {
  424.                     $data[] = $rowData;
  425.                 }
  426.                 fclose($handle);
  427.             }
  428.             //process translations
  429.             if (is_array($data) && count($data) > 1) {
  430.                 $keys $data[0];
  431.                 // remove wrong quotes in some export/import constellations
  432.                 $keys array_map(function ($value) {
  433.                     return trim($value'""');
  434.                 }, $keys);
  435.                 $data array_slice($data1);
  436.                 foreach ($data as $row) {
  437.                     $keyValueArray = [];
  438.                     for ($counter 0$counter count($row); $counter++) {
  439.                         $rd str_replace('&quot;''"'$row[$counter]);
  440.                         $keyValueArray[$keys[$counter]] = $rd;
  441.                     }
  442.                     $textKey $keyValueArray['key'] ?? null;
  443.                     if ($textKey) {
  444.                         $t = static::getByKey($textKey$domaintrue);
  445.                         $dirty false;
  446.                         foreach ($keyValueArray as $key => $value) {
  447.                             $value Service::unEscapeCsvField($value);
  448.                             if (in_array($key$languages)) {
  449.                                 $currentTranslation $t->hasTranslation($key) ? $t->getTranslation($key) : null;
  450.                                 if ($replaceExistingTranslations) {
  451.                                     $t->addTranslation($key$value);
  452.                                     if ($currentTranslation != $value) {
  453.                                         $dirty true;
  454.                                     }
  455.                                 } else {
  456.                                     if (!$currentTranslation) {
  457.                                         $t->addTranslation($key$value);
  458.                                         if ($currentTranslation != $value) {
  459.                                             $dirty true;
  460.                                         }
  461.                                     } elseif ($currentTranslation != $value && $value) {
  462.                                         $delta[] =
  463.                                             [
  464.                                                 'lg' => $key,
  465.                                                 'key' => $textKey,
  466.                                                 'text' => $t->getTranslation($key),
  467.                                                 'csv' => $value,
  468.                                             ];
  469.                                     }
  470.                                 }
  471.                             }
  472.                         }
  473.                         if ($dirty) {
  474.                             if (array_key_exists('creationDate'$keyValueArray) && $keyValueArray['creationDate']) {
  475.                                 $t->setCreationDate((int) $keyValueArray['creationDate']);
  476.                             }
  477.                             $t->setModificationDate(time()); //ignore modificationDate from file
  478.                             $t->save();
  479.                         }
  480.                     }
  481.                     // call the garbage collector if memory consumption is > 100MB
  482.                     if (memory_get_usage() > 100_000_000) {
  483.                         \Pimcore::collectGarbage();
  484.                     }
  485.                 }
  486.                 static::clearDependentCache();
  487.             } else {
  488.                 throw new \Exception('less than 2 rows of data - nothing to import');
  489.             }
  490.         } else {
  491.             throw new \Exception("$file is not readable");
  492.         }
  493.         return $delta;
  494.     }
  495. }