vendor/pimcore/pimcore/bundles/AdminBundle/Controller/Admin/TranslationController.php line 361

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\Bundle\AdminBundle\Controller\Admin;
  15. use Doctrine\DBAL\Exception\SyntaxErrorException;
  16. use Doctrine\DBAL\Query\QueryBuilder as DoctrineQueryBuilder;
  17. use Pimcore\Bundle\AdminBundle\Controller\AdminAbstractController;
  18. use Pimcore\File;
  19. use Pimcore\Localization\LocaleServiceInterface;
  20. use Pimcore\Logger;
  21. use Pimcore\Model\DataObject;
  22. use Pimcore\Model\Document;
  23. use Pimcore\Model\Element;
  24. use Pimcore\Model\Translation;
  25. use Pimcore\Tool;
  26. use Pimcore\Tool\Session;
  27. use Pimcore\Translation\ExportService\Exporter\ExporterInterface;
  28. use Pimcore\Translation\ExportService\ExportServiceInterface;
  29. use Pimcore\Translation\ImportDataExtractor\ImportDataExtractorInterface;
  30. use Pimcore\Translation\ImporterService\ImporterServiceInterface;
  31. use Pimcore\Translation\TranslationItemCollection\TranslationItemCollection;
  32. use Pimcore\Translation\Translator;
  33. use Symfony\Component\HttpFoundation\BinaryFileResponse;
  34. use Symfony\Component\HttpFoundation\JsonResponse;
  35. use Symfony\Component\HttpFoundation\Request;
  36. use Symfony\Component\HttpFoundation\Response;
  37. use Symfony\Component\HttpFoundation\ResponseHeaderBag;
  38. use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBagInterface;
  39. use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
  40. use Symfony\Component\Routing\Annotation\Route;
  41. use Symfony\Contracts\Translation\TranslatorInterface;
  42. /**
  43.  * @Route("/translation")
  44.  *
  45.  * @internal
  46.  */
  47. class TranslationController extends AdminAbstractController
  48. {
  49.     protected const PLACEHOLDER_NAME 'placeHolder';
  50.     /**
  51.      * @Route("/import", name="pimcore_admin_translation_import", methods={"POST"})
  52.      *
  53.      * @param Request $request
  54.      * @param LocaleServiceInterface $localeService
  55.      *
  56.      * @return JsonResponse
  57.      */
  58.     public function importAction(Request $requestLocaleServiceInterface $localeService)
  59.     {
  60.         $domain $request->get('domain'Translation::DOMAIN_DEFAULT);
  61.         $admin $domain == Translation::DOMAIN_ADMIN;
  62.         $dialect $request->get('csvSettings'null);
  63.         $session Session::get('pimcore_importconfig');
  64.         $tmpFile $session->get('translation_import_file');
  65.         if ($dialect) {
  66.             $dialect json_decode($dialect);
  67.         }
  68.         $this->checkPermission(($admin 'admin_' '') . 'translations');
  69.         $merge $request->get('merge');
  70.         $overwrite = !$merge;
  71.         $allowedLanguages $this->getAdminUser()->getAllowedLanguagesForEditingWebsiteTranslations();
  72.         if ($admin) {
  73.             $allowedLanguages Tool\Admin::getLanguages();
  74.         }
  75.         $delta Translation::importTranslationsFromFile(
  76.             $tmpFile,
  77.             $domain,
  78.             $overwrite,
  79.             $allowedLanguages,
  80.             $dialect
  81.         );
  82.         if (is_file($tmpFile)) {
  83.             @unlink($tmpFile);
  84.         }
  85.         $result = [
  86.             'success' => true,
  87.         ];
  88.         if ($merge) {
  89.             $enrichedDelta = [];
  90.             foreach ($delta as $item) {
  91.                 $lg $item['lg'];
  92.                 $currentLocale $localeService->findLocale();
  93.                 $item['lgname'] = \Locale::getDisplayLanguage($lg$currentLocale);
  94.                 $item['icon'] = $this->generateUrl('pimcore_admin_misc_getlanguageflag', ['language' => $lg]);
  95.                 $item['current'] = $item['text'];
  96.                 $enrichedDelta[] = $item;
  97.             }
  98.             $result['delta'] = base64_encode(json_encode($enrichedDelta));
  99.         }
  100.         $response $this->adminJson($result);
  101.         // set content-type to text/html, otherwise (when application/json is sent) chrome will complain in
  102.         // Ext.form.Action.Submit and mark the submission as failed
  103.         $response->headers->set('Content-Type''text/html');
  104.         return $response;
  105.     }
  106.     /**
  107.      * @Route("/upload-import", name="pimcore_admin_translation_uploadimportfile", methods={"POST"})
  108.      *
  109.      * @param Request $request
  110.      *
  111.      * @return JsonResponse
  112.      */
  113.     public function uploadImportFileAction(Request $request)
  114.     {
  115.         $tmpData file_get_contents($_FILES['Filedata']['tmp_name']);
  116.         //store data for further usage
  117.         $filename uniqid('import_translations-');
  118.         $importFile PIMCORE_SYSTEM_TEMP_DIRECTORY '/' $filename;
  119.         File::put($importFile$tmpData);
  120.         Session::useSession(function (AttributeBagInterface $session) use ($importFile) {
  121.             $session->set('translation_import_file'$importFile);
  122.         }, 'pimcore_importconfig');
  123.         // determine csv settings
  124.         $dialect Tool\Admin::determineCsvDialect($importFile);
  125.         //ignore if line terminator is already hex otherwise generate hex for string
  126.         if (!empty($dialect->lineterminator) && empty(preg_match('/[a-f0-9]{2}/i'$dialect->lineterminator))) {
  127.             $dialect->lineterminator bin2hex($dialect->lineterminator);
  128.         }
  129.         return $this->adminJson([
  130.             'success' => true,
  131.             'config' => [
  132.                 'csvSettings' => $dialect,
  133.             ],
  134.         ]);
  135.     }
  136.     /**
  137.      * @Route("/export", name="pimcore_admin_translation_export", methods={"GET"})
  138.      *
  139.      * @param Request $request
  140.      *
  141.      * @return Response
  142.      */
  143.     public function exportAction(Request $request)
  144.     {
  145.         $domain $request->get('domain'Translation::DOMAIN_DEFAULT);
  146.         $admin $domain == Translation::DOMAIN_ADMIN;
  147.         $this->checkPermission(($admin 'admin_' '') . 'translations');
  148.         $translation = new Translation();
  149.         $translation->setDomain($domain);
  150.         $tableName $translation->getDao()->getDatabaseTableName();
  151.         // clear translation cache
  152.         Translation::clearDependentCache();
  153.         $list = new Translation\Listing();
  154.         $list->setDomain($domain);
  155.         $joins = [];
  156.         $list->setOrder('asc');
  157.         $list->setOrderKey($tableName '.key'false);
  158.         $conditions $this->getGridFilterCondition($request$tableNamefalse$admin);
  159.         if (!empty($conditions)) {
  160.             $list->setCondition($conditions['condition'], $conditions['params']);
  161.         }
  162.         $filters $this->getGridFilterCondition($request$tableNametrue$admin);
  163.         if ($filters) {
  164.             $joins array_merge($joins$filters['joins']);
  165.         }
  166.         $this->extendTranslationQuery($joins$list$tableName$filters);
  167.         try {
  168.             $list->load();
  169.         } catch (SyntaxErrorException $syntaxErrorException) {
  170.             throw new \InvalidArgumentException('Check your arguments.');
  171.         }
  172.         $translations = [];
  173.         $translationObjects $list->getTranslations();
  174.         // fill with one dummy translation if the store is empty
  175.         if (empty($translationObjects)) {
  176.             if ($admin) {
  177.                 $t = new Translation();
  178.                 $t->setDomain(Translation::DOMAIN_ADMIN);
  179.                 $languages Tool\Admin::getLanguages();
  180.             } else {
  181.                 $t = new Translation();
  182.                 $languages $this->getAdminUser()->getAllowedLanguagesForViewingWebsiteTranslations();
  183.             }
  184.             foreach ($languages as $language) {
  185.                 $t->addTranslation($language'');
  186.             }
  187.             $translationObjects[] = $t;
  188.         }
  189.         foreach ($translationObjects as $t) {
  190.             $row $t->getTranslations();
  191.             $row Element\Service::escapeCsvRecord($row);
  192.             $translations[] = array_merge(
  193.                 ['key' => $t->getKey(),
  194.                     'creationDate' => $t->getCreationDate(),
  195.                     'modificationDate' => $t->getModificationDate(),
  196.                 ],
  197.                 $row
  198.             );
  199.         }
  200.         //header column
  201.         $columns array_keys($translations[0]);
  202.         if ($admin) {
  203.             $languages Tool\Admin::getLanguages();
  204.         } else {
  205.             $languages $this->getAdminUser()->getAllowedLanguagesForViewingWebsiteTranslations();
  206.         }
  207.         //add language columns which have no translations yet
  208.         foreach ($languages as $l) {
  209.             if (!in_array($l$columns)) {
  210.                 $columns[] = $l;
  211.             }
  212.         }
  213.         //remove invalid languages
  214.         foreach ($columns as $key => $column) {
  215.             if (strtolower(trim($column)) != 'key' && !in_array($column$languages)) {
  216.                 unset($columns[$key]);
  217.             }
  218.         }
  219.         $columns array_values($columns);
  220.         $headerRow = [];
  221.         foreach ($columns as $key => $value) {
  222.             $headerRow[] = '"' $value '"';
  223.         }
  224.         $csv implode(';'$headerRow) . "\r\n";
  225.         foreach ($translations as $t) {
  226.             $tempRow = [];
  227.             foreach ($columns as $key) {
  228.                 $value $t[$key] ?? null;
  229.                 //clean value of evil stuff such as " and linebreaks
  230.                 if (is_string($value)) {
  231.                     $value Tool\Text::removeLineBreaks($value);
  232.                     $value str_replace('"''&quot;'$value);
  233.                     $tempRow[$key] = '"' $value '"';
  234.                 } else {
  235.                     $tempRow[$key] = $value;
  236.                 }
  237.             }
  238.             $csv .= implode(';'$tempRow) . "\r\n";
  239.         }
  240.         $response = new Response("\xEF\xBB\xBF" $csv);
  241.         $response->headers->set('Content-Encoding''UTF-8');
  242.         $response->headers->set('Content-Type''text/csv; charset=UTF-8');
  243.         $response->headers->set('Content-Disposition''attachment; filename="export_' $domain '_translations.csv"');
  244.         ini_set('display_errors''0'); //to prevent warning messages in csv
  245.         return $response;
  246.     }
  247.     /**
  248.      * @Route("/add-admin-translation-keys", name="pimcore_admin_translation_addadmintranslationkeys", methods={"POST"})
  249.      *
  250.      * @param Request $request
  251.      *
  252.      * @return JsonResponse
  253.      */
  254.     public function addAdminTranslationKeysAction(Request $request)
  255.     {
  256.         $keys $request->get('keys');
  257.         if ($keys) {
  258.             $availableLanguages Tool\Admin::getLanguages();
  259.             $data $this->decodeJson($keys);
  260.             foreach ($data as $translationData) {
  261.                 $t null// reset
  262.                 try {
  263.                     $t Translation::getByKey($translationDataTranslation::DOMAIN_ADMIN);
  264.                 } catch (\Exception $e) {
  265.                     Logger::log((string) $e);
  266.                 }
  267.                 if (!$t instanceof Translation) {
  268.                     $t = new Translation();
  269.                     $t->setDomain(Translation::DOMAIN_ADMIN);
  270.                     $t->setKey($translationData);
  271.                     $t->setCreationDate(time());
  272.                     $t->setModificationDate(time());
  273.                     foreach ($availableLanguages as $lang) {
  274.                         $t->addTranslation($lang'');
  275.                     }
  276.                     try {
  277.                         $t->save();
  278.                     } catch (\Exception $e) {
  279.                         Logger::log((string) $e);
  280.                     }
  281.                 }
  282.             }
  283.         }
  284.         return $this->adminJson(null);
  285.     }
  286.     /**
  287.      * @Route("/translations", name="pimcore_admin_translation_translations", methods={"POST"})
  288.      *
  289.      * @param Request $request
  290.      * @param Translator $translator
  291.      *
  292.      * @return JsonResponse
  293.      */
  294.     public function translationsAction(Request $requestTranslatorInterface $translator)
  295.     {
  296.         $domain $request->get('domain'Translation::DOMAIN_DEFAULT);
  297.         $admin $domain === Translation::DOMAIN_ADMIN;
  298.         $this->checkPermission(($admin 'admin_' '') . 'translations');
  299.         $translation = new Translation();
  300.         $translation->setDomain($domain);
  301.         $tableName $translation->getDao()->getDatabaseTableName();
  302.         // clear translation cache
  303.         Translation::clearDependentCache();
  304.         if ($request->get('data')) {
  305.             $data $this->decodeJson($request->get('data'));
  306.             if ($request->get('xaction') == 'destroy') {
  307.                 $t Translation::getByKey($data['key'], $domain);
  308.                 if ($t instanceof Translation) {
  309.                     $t->delete();
  310.                 }
  311.                 return $this->adminJson(['success' => true'data' => []]);
  312.             } elseif ($request->get('xaction') == 'update') {
  313.                 $t Translation::getByKey($data['key'], $domain);
  314.                 foreach ($data as $key => $value) {
  315.                     $key preg_replace('/^_/'''$key1);
  316.                     if (!in_array($key, ['key''type'])) {
  317.                         $t->addTranslation($key$value);
  318.                     }
  319.                 }
  320.                 if ($data['key']) {
  321.                     $t->setKey($data['key']);
  322.                 }
  323.                 if ($data['type']) {
  324.                     $t->setType($data['type']);
  325.                 }
  326.                 $t->setModificationDate(time());
  327.                 $t->save();
  328.                 $return array_merge(
  329.                     [
  330.                         'key' => $t->getKey(),
  331.                         'creationDate' => $t->getCreationDate(),
  332.                         'modificationDate' => $t->getModificationDate(),
  333.                         'type' => $t->getType(),
  334.                     ],
  335.                     $this->prefixTranslations($t->getTranslations())
  336.                 );
  337.                 return $this->adminJson(['data' => $return'success' => true]);
  338.             } elseif ($request->get('xaction') == 'create') {
  339.                 $t Translation::getByKey($data['key'], $domain);
  340.                 if ($t) {
  341.                     return $this->adminJson([
  342.                         'message' => 'identifier_already_exists',
  343.                         'success' => false,
  344.                     ]);
  345.                 }
  346.                 $t = new Translation();
  347.                 $t->setDomain($domain);
  348.                 $t->setKey($data['key']);
  349.                 $t->setCreationDate(time());
  350.                 $t->setModificationDate(time());
  351.                 $t->setType($data['type'] ?? null);
  352.                 foreach (Tool::getValidLanguages() as $lang) {
  353.                     $t->addTranslation($lang'');
  354.                 }
  355.                 $t->save();
  356.                 $return array_merge(
  357.                     [
  358.                         'key' => $t->getKey(),
  359.                         'creationDate' => $t->getCreationDate(),
  360.                         'modificationDate' => $t->getModificationDate(),
  361.                         'type' => $t->getType(),
  362.                     ],
  363.                     $this->prefixTranslations($t->getTranslations())
  364.                 );
  365.                 return $this->adminJson(['data' => $return'success' => true]);
  366.             }
  367.         } else {
  368.             // get list of types
  369.             $list = new Translation\Listing();
  370.             $list->setDomain($domain);
  371.             $validLanguages $admin Tool\Admin::getLanguages() : $this->getAdminUser()->getAllowedLanguagesForViewingWebsiteTranslations();
  372.             $list->setOrder('asc');
  373.             $list->setOrderKey($tableName '.key'false);
  374.             $list->setLanguages($validLanguages);
  375.             $sortingSettings \Pimcore\Bundle\AdminBundle\Helper\QueryParams::extractSortingSettings(
  376.                 array_merge($request->request->all(), $request->query->all())
  377.             );
  378.             $joins = [];
  379.             if ($orderKey $sortingSettings['orderKey']) {
  380.                 if (in_array(trim($orderKey'_'), $validLanguages)) {
  381.                     $orderKey trim($orderKey'_');
  382.                     $joins[] = [
  383.                         'language' => $orderKey,
  384.                     ];
  385.                     $list->setOrderKey($orderKey);
  386.                 } elseif ($list->isValidOrderKey($sortingSettings['orderKey'])) {
  387.                     $list->setOrderKey($tableName '.' $sortingSettings['orderKey'], false);
  388.                 }
  389.             }
  390.             if ($sortingSettings['order']) {
  391.                 $list->setOrder($sortingSettings['order']);
  392.             }
  393.             $list->setLimit($request->get('limit'));
  394.             $list->setOffset($request->get('start'));
  395.             $conditions $this->getGridFilterCondition($request$tableNamefalse$admin);
  396.             $filters $this->getGridFilterCondition($request$tableNametrue$admin);
  397.             if ($filters) {
  398.                 $joins array_merge($joins$filters['joins']);
  399.             }
  400.             if (!empty($conditions)) {
  401.                 $list->setCondition($conditions['condition'], $conditions['params']);
  402.             }
  403.             $this->extendTranslationQuery($joins$list$tableName$filters);
  404.             try {
  405.                 $list->load();
  406.             } catch (SyntaxErrorException $syntaxErrorException) {
  407.                 throw new \InvalidArgumentException('Check your arguments.');
  408.             }
  409.             $translations = [];
  410.             $searchString $request->get('searchString');
  411.             foreach ($list->getTranslations() as $t) {
  412.                 //Reload translation to get complete data,
  413.                 //if translation fetched based on the text not key
  414.                 if ($searchString && !strpos($searchString$t->getKey())) {
  415.                     if (!$t Translation::getByKey($t->getKey(), $domain)) {
  416.                         continue;
  417.                     }
  418.                 }
  419.                 $translations[] = array_merge(
  420.                     $this->prefixTranslations($t->getTranslations()),
  421.                     [
  422.                         'key' => $t->getKey(),
  423.                         'creationDate' => $t->getCreationDate(),
  424.                         'modificationDate' => $t->getModificationDate(),
  425.                         'type' => $t->getType(),
  426.                     ]
  427.                 );
  428.             }
  429.             return $this->adminJson(['data' => $translations'success' => true'total' => $list->getTotalCount()]);
  430.         }
  431.         return $this->adminJson(['success' => false]);
  432.     }
  433.     /**
  434.      * @param array $translations
  435.      *
  436.      * @return array
  437.      */
  438.     protected function prefixTranslations($translations)
  439.     {
  440.         if (!is_array($translations)) {
  441.             return $translations;
  442.         }
  443.         $prefixedTranslations = [];
  444.         foreach ($translations as $lang => $trans) {
  445.             $prefixedTranslations['_' $lang] = $trans;
  446.         }
  447.         return $prefixedTranslations;
  448.     }
  449.     /**
  450.      * @param array $joins
  451.      * @param Translation\Listing $list
  452.      * @param string $tableName
  453.      * @param array $filters
  454.      */
  455.     protected function extendTranslationQuery($joins$list$tableName$filters)
  456.     {
  457.         if ($joins) {
  458.             $list->onCreateQueryBuilder(
  459.                 function (DoctrineQueryBuilder $select) use (
  460.                     $joins,
  461.                     $tableName,
  462.                     $filters
  463.                 ) {
  464.                     $db \Pimcore\Db::get();
  465.                     $alreadyJoined = [];
  466.                     foreach ($joins as $join) {
  467.                         $fieldname $join['language'];
  468.                         if (isset($alreadyJoined[$fieldname])) {
  469.                             continue;
  470.                         }
  471.                         $alreadyJoined[$fieldname] = 1;
  472.                         $select->addSelect($fieldname '.text AS ' $fieldname);
  473.                         $select->leftJoin(
  474.                             $tableName,
  475.                             $tableName,
  476.                             $fieldname,
  477.                             '('
  478.                             $fieldname '.key = ' $tableName '.key'
  479.                             ' and ' $fieldname '.language = ' $db->quote($fieldname)
  480.                             . ')'
  481.                         );
  482.                     }
  483.                     $havings $filters['conditions'];
  484.                     if ($havings) {
  485.                         $havings implode(' AND '$havings);
  486.                         $select->having($havings);
  487.                     }
  488.                 }
  489.             );
  490.         }
  491.     }
  492.     /**
  493.      * @param Request $request
  494.      * @param string $tableName
  495.      * @param bool $languageMode
  496.      * @param bool $admin
  497.      *
  498.      * @return array
  499.      */
  500.     protected function getGridFilterCondition(Request $request$tableName$languageMode false$admin false)
  501.     {
  502.         $placeHolderCount 0;
  503.         $joins = [];
  504.         $conditions = [];
  505.         $validLanguages $admin Tool\Admin::getLanguages() : $this->getAdminUser()->getAllowedLanguagesForViewingWebsiteTranslations();
  506.         $db \Pimcore\Db::get();
  507.         $conditionFilters = [];
  508.         $filterJson $request->get('filter');
  509.         if ($filterJson) {
  510.             $propertyField 'property';
  511.             $operatorField 'operator';
  512.             $filters $this->decodeJson($filterJson);
  513.             foreach ($filters as $filter) {
  514.                 $operator '=';
  515.                 $field null;
  516.                 $value null;
  517.                 $fieldname $filter[$propertyField];
  518.                 if (in_array(ltrim($fieldname'_'), $validLanguages)) {
  519.                     $fieldname ltrim($fieldname'_');
  520.                 }
  521.                 $fieldname str_replace('--'''$fieldname);
  522.                 if (!$languageMode && in_array($fieldname$validLanguages)
  523.                     || $languageMode && !in_array($fieldname$validLanguages)) {
  524.                     continue;
  525.                 }
  526.                 if (!$languageMode) {
  527.                     $fieldname $tableName '.' $fieldname;
  528.                 }
  529.                 if (!empty($filter['value'])) {
  530.                     if ($filter['type'] == 'string') {
  531.                         $operator 'LIKE';
  532.                         $field $fieldname;
  533.                         $value '%' $filter['value'] . '%';
  534.                     } elseif ($filter['type'] == 'date' ||
  535.                         (in_array($fieldname, ['modificationDate''creationDate']))) {
  536.                         if ($filter[$operatorField] == 'lt') {
  537.                             $operator '<';
  538.                         } elseif ($filter[$operatorField] == 'gt') {
  539.                             $operator '>';
  540.                         } elseif ($filter[$operatorField] == 'eq') {
  541.                             $operator '=';
  542.                             $fieldname "UNIX_TIMESTAMP(DATE(FROM_UNIXTIME({$fieldname})))";
  543.                         }
  544.                         $filter['value'] = strtotime($filter['value']);
  545.                         $field $fieldname;
  546.                         $value $filter['value'];
  547.                     }
  548.                 }
  549.                 if ($field && $value) {
  550.                     $condition $db->quoteIdentifier($field) . ' ' $operator ' ' $db->quote($value);
  551.                     if ($languageMode) {
  552.                         $conditions[$fieldname] = $condition;
  553.                         $joins[] = [
  554.                             'language' => $fieldname,
  555.                         ];
  556.                     } else {
  557.                         $placeHolderName self::PLACEHOLDER_NAME $placeHolderCount;
  558.                         $placeHolderCount++;
  559.                         $conditionFilters[] = [
  560.                             'condition' => $field ' ' $operator ' :' $placeHolderName,
  561.                             'field' => $placeHolderName,
  562.                             'value' => $value,
  563.                         ];
  564.                     }
  565.                 }
  566.             }
  567.         }
  568.         if ($request->get('searchString')) {
  569.             $conditionFilters[] = [
  570.                 'condition' => '(lower(' $tableName '.key) LIKE :filterTerm OR lower(' $tableName '.text) LIKE :filterTerm)',
  571.                 'field' => 'filterTerm',
  572.                 'value' => '%' mb_strtolower($request->get('searchString')) . '%',
  573.             ];
  574.         }
  575.         if ($languageMode) {
  576.             return [
  577.                 'joins' => $joins,
  578.                 'conditions' => $conditions,
  579.             ];
  580.         }
  581.         if(!empty($conditionFilters)) {
  582.             $conditions = [];
  583.             $params = [];
  584.             foreach($conditionFilters as $conditionFilter) {
  585.                 $conditions[] = $conditionFilter['condition'];
  586.                 $params[$conditionFilter['field']] = $conditionFilter['value'];
  587.             }
  588.             $conditionFilters = [
  589.                 'condition' => implode(' AND '$conditions),
  590.                 'params' => $params,
  591.             ];
  592.         }
  593.         return $conditionFilters;
  594.     }
  595.     /**
  596.      * @Route("/cleanup", name="pimcore_admin_translation_cleanup", methods={"DELETE"})
  597.      *
  598.      * @param Request $request
  599.      *
  600.      * @return JsonResponse
  601.      */
  602.     public function cleanupAction(Request $request)
  603.     {
  604.         $domain $request->get('domain'Translation::DOMAIN_DEFAULT);
  605.         $list = new Translation\Listing();
  606.         $list->setDomain($domain);
  607.         $list->cleanup();
  608.         \Pimcore\Cache::clearTags(['translator''translate']);
  609.         return $this->adminJson(['success' => true]);
  610.     }
  611.     /**
  612.      * -----------------------------------------------------------------------------------
  613.      * THE FOLLOWING ISN'T RELATED TO THE SHARED TRANSLATIONS OR ADMIN-TRANSLATIONS
  614.      * XLIFF CONTENT-EXPORT & MS WORD CONTENT-EXPORT
  615.      * -----------------------------------------------------------------------------------
  616.      */
  617.     /**
  618.      * @Route("/content-export-jobs", name="pimcore_admin_translation_contentexportjobs", methods={"POST"})
  619.      *
  620.      * @param Request $request
  621.      *
  622.      * @return JsonResponse
  623.      */
  624.     public function contentExportJobsAction(Request $request)
  625.     {
  626.         $data $this->decodeJson($request->get('data'));
  627.         $elements = [];
  628.         $jobs = [];
  629.         $exportId uniqid();
  630.         $source $request->get('source');
  631.         $target $request->get('target');
  632.         $type $request->get('type');
  633.         $source str_replace('_''-'$source);
  634.         $target str_replace('_''-'$target);
  635.         if ($data && is_array($data)) {
  636.             foreach ($data as $element) {
  637.                 $elements[$element['type'] . '_' $element['id']] = [
  638.                     'id' => $element['id'],
  639.                     'type' => $element['type'],
  640.                 ];
  641.                 $el null;
  642.                 if ($element['children']) {
  643.                     $el Element\Service::getElementById($element['type'], $element['id']);
  644.                     $baseClass ELement\Service::getBaseClassNameForElement($element['type']);
  645.                     $listClass '\\Pimcore\\Model\\' $baseClass '\\Listing';
  646.                     $list = new $listClass();
  647.                     $list->setUnpublished(true);
  648.                     if ($el instanceof DataObject\AbstractObject) {
  649.                         // inlcude variants
  650.                         $list->setObjectTypes(
  651.                             [DataObject::OBJECT_TYPE_VARIANT,
  652.                                 DataObject::OBJECT_TYPE_OBJECT,
  653.                                 DataObject::OBJECT_TYPE_FOLDER, ]
  654.                         );
  655.                     }
  656.                     $list->setCondition(
  657.                         ($el instanceof DataObject 'o_' '') . 'path LIKE ?',
  658.                         [$list->escapeLike($el->getRealFullPath() . ($el->getRealFullPath() != '/' '/' '')) . '%']
  659.                     );
  660.                     $childs $list->load();
  661.                     foreach ($childs as $child) {
  662.                         $childId $child->getId();
  663.                         $elements[$element['type'] . '_' $childId] = [
  664.                             'id' => $childId,
  665.                             'type' => $element['type'],
  666.                         ];
  667.                         if (isset($element['relations']) && $element['relations']) {
  668.                             $childDependencies $child->getDependencies()->getRequires();
  669.                             foreach ($childDependencies as $cd) {
  670.                                 if ($cd['type'] == 'object' || $cd['type'] == 'document') {
  671.                                     $elements[$cd['type'] . '_' $cd['id']] = $cd;
  672.                                 }
  673.                             }
  674.                         }
  675.                     }
  676.                 }
  677.                 if (isset($element['relations']) && $element['relations']) {
  678.                     if (!$el instanceof Element\ElementInterface) {
  679.                         $el Element\Service::getElementById($element['type'], $element['id']);
  680.                     }
  681.                     $dependencies $el->getDependencies()->getRequires();
  682.                     foreach ($dependencies as $dependency) {
  683.                         if ($dependency['type'] == 'object' || $dependency['type'] == 'document') {
  684.                             $elements[$dependency['type'] . '_' $dependency['id']] = $dependency;
  685.                         }
  686.                     }
  687.                 }
  688.             }
  689.         }
  690.         $elements array_values($elements);
  691.         $elementsPerJob 10;
  692.         if ($type == 'word') {
  693.             // the word export can only handle one document per request
  694.             // the problem is Document\Service::render(), ... in the action can be a $this->redirect() or exit;
  695.             // nobody knows what's happening in an action ;-) So we need to isolate them in isolated processes
  696.             // so that the export doesn't stop completely after a "redirect" or any other unexpected behavior of an action
  697.             $elementsPerJob 1;
  698.         }
  699.         // one job = X elements
  700.         $elements array_chunk($elements$elementsPerJob);
  701.         foreach ($elements as $chunk) {
  702.             $jobs[] = [[
  703.                 'url' => $request->getBaseUrl() . '/admin/translation/' $type '-export',
  704.                 'method' => 'POST',
  705.                 'params' => [
  706.                     'id' => $exportId,
  707.                     'source' => $source,
  708.                     'target' => $target,
  709.                     'data' => $this->encodeJson($chunk),
  710.                 ],
  711.             ]];
  712.         }
  713.         return $this->adminJson(
  714.             [
  715.                 'success' => true,
  716.                 'jobs' => $jobs,
  717.                 'id' => $exportId,
  718.             ]
  719.         );
  720.     }
  721.     /**
  722.      * @Route("/xliff-export", name="pimcore_admin_translation_xliffexport", methods={"POST"})
  723.      *
  724.      * @param Request $request
  725.      * @param ExportServiceInterface $exportService
  726.      *
  727.      * @return JsonResponse
  728.      *
  729.      * @throws \Exception
  730.      */
  731.     public function xliffExportAction(Request $requestExportServiceInterface $exportService)
  732.     {
  733.         $id $request->get('id');
  734.         $data $this->decodeJson($request->get('data'));
  735.         $source $request->get('source');
  736.         $target $request->get('target');
  737.         $translationItems = new TranslationItemCollection();
  738.         foreach ($data as $el) {
  739.             $element Element\Service::getElementById($el['type'], $el['id']);
  740.             $translationItems->addPimcoreElement($element);
  741.         }
  742.         $exportService->exportTranslationItems($translationItems$source, [$target], $id);
  743.         return $this->adminJson([
  744.             'success' => true,
  745.         ]);
  746.     }
  747.     /**
  748.      * @Route("/xliff-export-download", name="pimcore_admin_translation_xliffexportdownload", methods={"GET"})
  749.      *
  750.      * @param Request $request
  751.      * @param ExporterInterface $translationExporter
  752.      *
  753.      * @return BinaryFileResponse
  754.      */
  755.     public function xliffExportDownloadAction(Request $requestExporterInterface $translationExporterExportServiceInterface $exportService)
  756.     {
  757.         $id $request->get('id');
  758.         $exportFile $exportService->getTranslationExporter()->getExportFilePath($id);
  759.         $response = new BinaryFileResponse($exportFile);
  760.         $response->headers->set('Content-Type'$translationExporter->getContentType());
  761.         $response->setContentDisposition(ResponseHeaderBag::DISPOSITION_ATTACHMENTbasename($exportFile));
  762.         $response->deleteFileAfterSend(true);
  763.         return $response;
  764.     }
  765.     /**
  766.      * @Route("/xliff-import-upload", name="pimcore_admin_translation_xliffimportupload", methods={"POST"})
  767.      *
  768.      * @param Request $request
  769.      * @param ImportDataExtractorInterface $importDataExtractor
  770.      *
  771.      * @return JsonResponse
  772.      *
  773.      * @throws \Exception
  774.      */
  775.     public function xliffImportUploadAction(Request $requestImportDataExtractorInterface $importDataExtractor)
  776.     {
  777.         $jobs = [];
  778.         $id uniqid();
  779.         $importFile $importDataExtractor->getImportFilePath($id);
  780.         copy($_FILES['file']['tmp_name'], $importFile);
  781.         $steps $importDataExtractor->countSteps($id);
  782.         for ($i 0$i $steps$i++) {
  783.             $jobs[] = [[
  784.                 'url' => $this->generateUrl('pimcore_admin_translation_xliffimportelement'),
  785.                 'method' => 'POST',
  786.                 'params' => [
  787.                     'id' => $id,
  788.                     'step' => $i,
  789.                 ],
  790.             ]];
  791.         }
  792.         $response $this->adminJson([
  793.             'success' => true,
  794.             'jobs' => $jobs,
  795.             'id' => $id,
  796.         ]);
  797.         // set content-type to text/html, otherwise (when application/json is sent) chrome will complain in
  798.         // Ext.form.Action.Submit and mark the submission as failed
  799.         $response->headers->set('Content-Type''text/html');
  800.         return $response;
  801.     }
  802.     /**
  803.      * @Route("/xliff-import-element", name="pimcore_admin_translation_xliffimportelement", methods={"POST"})
  804.      *
  805.      * @param Request $request
  806.      * @param ImportDataExtractorInterface $importDataExtractor
  807.      * @param ImporterServiceInterface $importerService
  808.      *
  809.      * @return JsonResponse
  810.      *
  811.      * @throws \Exception
  812.      */
  813.     public function xliffImportElementAction(Request $requestImportDataExtractorInterface $importDataExtractorImporterServiceInterface $importerService)
  814.     {
  815.         $id $request->get('id');
  816.         $step $request->get('step');
  817.         try {
  818.             $attributeSet $importDataExtractor->extractElement($id$step);
  819.             if ($attributeSet) {
  820.                 $importerService->import($attributeSet);
  821.             } else {
  822.                 Logger::warning(sprintf('Could not resolve element %s'$id));
  823.             }
  824.         } catch (\Exception $e) {
  825.             Logger::err($e->getMessage());
  826.             return $this->adminJson([
  827.                 'success' => false,
  828.                 'message' => $e->getMessage(),
  829.             ]);
  830.         }
  831.         return $this->adminJson([
  832.             'success' => true,
  833.         ]);
  834.     }
  835.     /**
  836.      * @Route("/word-export", name="pimcore_admin_translation_wordexport", methods={"POST"})
  837.      *
  838.      * @param Request $request
  839.      *
  840.      * @return JsonResponse
  841.      */
  842.     public function wordExportAction(Request $request)
  843.     {
  844.         ini_set('display_errors''off');
  845.         $id $this->sanitzeExportId((string)$request->get('id'));
  846.         $exportFile $this->getExportFilePath($idfalse);
  847.         $data $this->decodeJson($request->get('data'));
  848.         $source $request->get('source');
  849.         if (!is_file($exportFile)) {
  850.             File::put($exportFile'');
  851.         }
  852.         foreach ($data as $el) {
  853.             try {
  854.                 $element Element\Service::getElementById($el['type'], $el['id']);
  855.                 $output '';
  856.                 // check supported types (subtypes)
  857.                 if (!in_array($element->getType(), ['page''snippet''email''object'])) {
  858.                     continue;
  859.                 }
  860.                 if ($element instanceof Element\ElementInterface) {
  861.                     $output .= '<h1 class="element-headline">' ucfirst(
  862.                         $element->getType()
  863.                     ) . ' - ' $element->getRealFullPath() . ' (ID: ' $element->getId() . ')</h1>';
  864.                 }
  865.                 if ($element instanceof Document\PageSnippet) {
  866.                     if ($element instanceof Document\Page) {
  867.                         $structuredDataEmpty true;
  868.                         $structuredData '
  869.                             <table border="1" cellspacing="0" cellpadding="5">
  870.                                 <tr>
  871.                                     <td colspan="2"><span style="color:#cc2929;font-weight: bold;">Structured Data</span></td>
  872.                                 </tr>
  873.                         ';
  874.                         if ($element->getTitle()) {
  875.                             $structuredData .= '<tr>
  876.                                     <td><span style="color:#cc2929;">Title</span></td>
  877.                                     <td>' $element->getTitle() . '&nbsp;</td>
  878.                                 </tr>';
  879.                             $structuredDataEmpty false;
  880.                         }
  881.                         if ($element->getDescription()) {
  882.                             $structuredData .= '<tr>
  883.                                     <td><span style="color:#cc2929;">Description</span></td>
  884.                                     <td>' $element->getDescription() . '&nbsp;</td>
  885.                                 </tr>';
  886.                             $structuredDataEmpty false;
  887.                         }
  888.                         if ($element->getProperty('navigation_name')) {
  889.                             $structuredData .= '<tr>
  890.                                     <td><span style="color:#cc2929;">Navigation</span></td>
  891.                                     <td>' $element->getProperty('navigation_name') . '&nbsp;</td>
  892.                                 </tr>';
  893.                             $structuredDataEmpty false;
  894.                         }
  895.                         $structuredData .= '</table>';
  896.                         if (!$structuredDataEmpty) {
  897.                             $output .= $structuredData;
  898.                         }
  899.                     }
  900.                     // we need to set the parameter "pimcore_admin" here to be able to render unpublished documents
  901.                     $html Document\Service::render($element, [], false, ['pimcore_admin' => true]);
  902.                     $html preg_replace(
  903.                         '@</?(img|meta|div|section|aside|article|body|bdi|bdo|canvas|embed|footer|head|header|html)([^>]+)?>@',
  904.                         '',
  905.                         $html
  906.                     );
  907.                     $html preg_replace('/<!--(.*)-->/Uis'''$html);
  908.                     $dom = new Tool\DomCrawler($html);
  909.                     // remove containers including their contents
  910.                     $elements $dom->filter('form, script, style, noframes, noscript, object, area, mapm, video, audio, iframe, textarea, input, select, button');
  911.                     foreach ($elements as $element) {
  912.                         $element->parentNode->removeChild($element);
  913.                     }
  914.                     $clearText = function ($string) {
  915.                         $string str_replace("\r\n"''$string);
  916.                         $string str_replace("\n"''$string);
  917.                         $string str_replace("\r"''$string);
  918.                         $string str_replace("\t"''$string);
  919.                         $string preg_replace('/&[a-zA-Z0-9]+;/'''$string); // remove html entities
  920.                         $string preg_replace('#[ ]+#'''$string);
  921.                         return $string;
  922.                     };
  923.                     // remove empty tags (where it matters)
  924.                     // replace links => links get [Linktext]
  925.                     $elements $dom->filter('a');
  926.                     foreach ($elements as $element) {
  927.                         $string $clearText($element->textContent);
  928.                         if (!empty($string)) {
  929.                             $newNode $element->ownerDocument->createTextNode('[' $element->textContent ']');
  930.                             $element->parentNode->replaceChild($newNode$element);
  931.                         } else {
  932.                             $element->ownerDocument->textContent '';
  933.                         }
  934.                     }
  935.                     if ($dom->count() > 0) {
  936.                         $html $dom->html();
  937.                     }
  938.                     $dom->clear();
  939.                     unset($dom);
  940.                     // force closing tags
  941.                     $doc = new \DOMDocument();
  942.                     libxml_use_internal_errors(true);
  943.                     $doc->loadHTML('<?xml encoding="UTF-8"><article>' $html '</article>');
  944.                     libxml_clear_errors();
  945.                     $html $doc->saveHTML();
  946.                     $bodyStart strpos($html'<body>');
  947.                     $bodyEnd strpos($html'</body>');
  948.                     if ($bodyStart && $bodyEnd) {
  949.                         $html substr($html$bodyStart 6$bodyEnd $bodyStart);
  950.                     }
  951.                     $output .= $html;
  952.                 } elseif ($element instanceof DataObject\Concrete) {
  953.                     $hasContent false;
  954.                     /** @var DataObject\ClassDefinition\Data\Localizedfields|null $fd */
  955.                     $fd $element->getClass()->getFieldDefinition('localizedfields');
  956.                     if ($fd) {
  957.                         $definitions $fd->getFieldDefinitions();
  958.                         $locale str_replace('-''_'$source);
  959.                         if (!Tool::isValidLanguage($locale)) {
  960.                             $locale \Locale::getPrimaryLanguage($locale);
  961.                         }
  962.                         $output .= '
  963.                             <table border="1" cellspacing="0" cellpadding="2">
  964.                                 <tr>
  965.                                     <td colspan="2"><span style="color:#cc2929;font-weight: bold;">Localized Data</span></td>
  966.                                 </tr>
  967.                         ';
  968.                         foreach ($definitions as $definition) {
  969.                             // check allowed datatypes
  970.                             if (!in_array($definition->getFieldtype(), ['input''textarea''wysiwyg'])) {
  971.                                 continue;
  972.                             }
  973.                             $content $element->{'get' ucfirst($definition->getName())}($locale);
  974.                             if (!empty($content)) {
  975.                                 $output .= '
  976.                                 <tr>
  977.                                     <td><span style="color:#cc2929;">' $definition->getTitle() . ' (' $definition->getName() . ')<span></td>
  978.                                     <td>' $content '&nbsp;</td>
  979.                                 </tr>
  980.                                 ';
  981.                                 $hasContent true;
  982.                             }
  983.                         }
  984.                         $output .= '</table>';
  985.                     }
  986.                     if (!$hasContent) {
  987.                         $output ''// there's no content in the object, so reset all contents and do not inclide it in the export
  988.                     }
  989.                 }
  990.                 // append contents
  991.                 if (!empty($output)) {
  992.                     $f fopen($exportFile'a+');
  993.                     fwrite($f$output);
  994.                     fclose($f);
  995.                 }
  996.             } catch (\Exception $e) {
  997.                 Logger::error('Word Export: ' $e);
  998.                 throw $e;
  999.             }
  1000.         }
  1001.         return $this->adminJson(
  1002.             [
  1003.                 'success' => true,
  1004.             ]
  1005.         );
  1006.     }
  1007.     /**
  1008.      * @Route("/word-export-download", name="pimcore_admin_translation_wordexportdownload", methods={"GET"})
  1009.      *
  1010.      * @param Request $request
  1011.      *
  1012.      * @return Response
  1013.      */
  1014.     public function wordExportDownloadAction(Request $request)
  1015.     {
  1016.         $id $this->sanitzeExportId((string)$request->get('id'));
  1017.         $exportFile $this->getExportFilePath($idtrue);
  1018.         // no conversion, output html file, works fine with MS Word and LibreOffice
  1019.         $content file_get_contents($exportFile);
  1020.         @unlink($exportFile);
  1021.         // replace <script> and <link>
  1022.         $content preg_replace('/<link[^>]+>/im''$1'$content);
  1023.         $content preg_replace("/<script[^>]+>(.*)?<\/script>/im"'$1'$content);
  1024.         $content =
  1025.             "<html>\n" .
  1026.             "<head>\n" .
  1027.             '<style type="text/css">' "\n" .
  1028.             file_get_contents(PIMCORE_WEB_ROOT '/bundles/pimcoreadmin/css/word-export.css') .
  1029.             "</style>\n" .
  1030.             "</head>\n\n" .
  1031.             "<body>\n" .
  1032.             $content .
  1033.             "\n\n</body>\n" .
  1034.             "</html>\n";
  1035.         $response = new Response($content);
  1036.         $response->headers->set('Content-Type''text/html');
  1037.         $response->headers->set(
  1038.             'Content-Disposition',
  1039.             'attachment; filename="word-export-' date('Ymd') . '_' uniqid() . '.htm"'
  1040.         );
  1041.         return $response;
  1042.     }
  1043.     private function sanitzeExportId(string $id): string
  1044.     {
  1045.         if (empty($id) || !preg_match('/^[a-z0-9]+$/'$id)) {
  1046.             throw new BadRequestHttpException('Invalid export ID format');
  1047.         }
  1048.         return $id;
  1049.     }
  1050.     private function getExportFilePath(string $idbool $checkExistence true): string
  1051.     {
  1052.         // no need to check for path traversals here as sanitizeExportId restricted the ID parameter
  1053.         $exportFile PIMCORE_SYSTEM_TEMP_DIRECTORY DIRECTORY_SEPARATOR $id '.html';
  1054.         if ($checkExistence && !file_exists($exportFile)) {
  1055.             throw $this->createNotFoundException(sprintf('Export file does not exist at path %s'$exportFile));
  1056.         }
  1057.         return $exportFile;
  1058.     }
  1059.     /**
  1060.      * @Route("/merge-item", name="pimcore_admin_translation_mergeitem", methods={"PUT"})
  1061.      *
  1062.      * @param Request $request
  1063.      *
  1064.      * @return JsonResponse
  1065.      */
  1066.     public function mergeItemAction(Request $request)
  1067.     {
  1068.         $domain $request->get('domain'Translation::DOMAIN_DEFAULT);
  1069.         $dataList json_decode($request->get('data'), true);
  1070.         foreach ($dataList as $data) {
  1071.             $t Translation::getByKey($data['key'], $domaintrue);
  1072.             $newValue htmlspecialchars_decode($data['current']);
  1073.             $t->addTranslation($data['lg'], $newValue);
  1074.             $t->setModificationDate(time());
  1075.             $t->save();
  1076.         }
  1077.         return $this->adminJson(
  1078.             [
  1079.                 'success' => true,
  1080.             ]
  1081.         );
  1082.     }
  1083.     /**
  1084.      * @Route("/get-website-translation-languages", name="pimcore_admin_translation_getwebsitetranslationlanguages", methods={"GET"})
  1085.      *
  1086.      * @param Request $request
  1087.      *
  1088.      * @return JsonResponse
  1089.      */
  1090.     public function getWebsiteTranslationLanguagesAction(Request $request)
  1091.     {
  1092.         return $this->adminJson(
  1093.             [
  1094.                 'view' => $this->getAdminUser()->getAllowedLanguagesForViewingWebsiteTranslations(),
  1095.                 //when no view language is defined, all languages are editable. if one view language is defined, it
  1096.                 //may be possible that no edit language is set intentionally
  1097.                 'edit' => $this->getAdminUser()->getAllowedLanguagesForEditingWebsiteTranslations(),
  1098.             ]
  1099.         );
  1100.     }
  1101.     /**
  1102.      * @Route("/get-translation-domains", name="pimcore_admin_translation_gettranslationdomains", methods={"GET"})
  1103.      *
  1104.      * @param Request $request
  1105.      *
  1106.      * @return JsonResponse
  1107.      */
  1108.     public function getTranslationDomainsAction(Request $request)
  1109.     {
  1110.         $translation = new Translation();
  1111.         $domains array_map(
  1112.             fn ($domain) => ['name' => $domain],
  1113.             $translation->getDao()->getAvailableDomains(),
  1114.         );
  1115.         return $this->adminJson(['domains' => $domains]);
  1116.     }
  1117. }