l10n_server-2.x-dev/l10n_community/src/Form/ExportForm.php
l10n_community/src/Form/ExportForm.php
<?php declare(strict_types=1); namespace Drupal\l10n_community\Form; use Drupal\Core\Entity\Element\EntityAutocomplete; use Drupal\Core\Entity\EntityStorageInterface; use Drupal\Core\Form\FormBase; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Language\LanguageManagerInterface; use Drupal\l10n_community\L10nExporter; use Symfony\Component\DependencyInjection\ContainerInterface; /** * Translation export form. */ class ExportForm extends FormBase { /** * The project entity storage. */ protected EntityStorageInterface $projectStorage; /** * The release entity storage. */ protected EntityStorageInterface $releaseStorage; /** * The language manager serviec. */ protected LanguageManagerInterface $languageManager; /** * The exporter service. */ protected L10nExporter $communityExporter; /** * Constructs a ExportForm object. * * @param \Drupal\Core\Entity\EntityStorageInterface $project_storage * The current route match. * @param \Drupal\Core\Entity\EntityStorageInterface $release_storage * The current route match. * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager * The language manager serviec. * @param \Drupal\l10n_community\L10nExporter $community_exporter * The exporter service. */ public function __construct( EntityStorageInterface $project_storage, EntityStorageInterface $release_storage, LanguageManagerInterface $language_manager, L10nExporter $community_exporter, ) { $this->projectStorage = $project_storage; $this->releaseStorage = $release_storage; $this->languageManager = $language_manager; $this->communityExporter = $community_exporter; } /** * {@inheritdoc} */ public static function create(ContainerInterface $container) { return new static( $container->get('entity_type.manager')->getStorage('l10n_server_project'), $container->get('entity_type.manager')->getStorage('l10n_server_release'), $container->get('language_manager'), $container->get('l10n_community.exporter'), ); } /** * {@inheritdoc} */ public function getFormId() { return 'l10n_community_export'; } /** * Form declaration. * * This form can be displayed in two different use cases. * 1st: you access to the export page of a given project. The project ID * will be forwarded as an URL query argument. * 2nd: You can access only to the export page of a group (language). In * this use case, you will first need to select a project to export strings * from. * * @param array $form * Form declaration. * @param \Drupal\Core\Form\FormStateInterface $form_state * FormState object. * * @return array * Form definition to show. */ public function buildForm(array $form, FormStateInterface $form_state) : array { $preselected_project = FALSE; // First, make sure that the group language is set and valid. if (empty($form_state->get('export_language'))) { $langcode = \Drupal::routeMatch()->getParameter('group')->field_translation_language->target_id; if ($language = $this->languageManager->getLanguage($langcode)) { $form_state->set('export_language', $language); } else { $this->messenger()->addError($this->t('There is no language matching the provided "@langcode" langcode. Please warn an administrator about this issue.', ['@langcode' => $langcode])); $form['language_unavailable'] = [ '#type' => 'html_tag', '#tag' => 'strong', '#value' => $this->t('The group language "@langcode" is missing.', ['@langcode' => $langcode]), ]; return $form; } } $project_id = $this->computeProjectId(); // Determine if the project was preset or it has just been populated through // AJAX interactions. if (!empty($project_id) && ( !isset($form_state->getUserInput()['_drupal_ajax']) || (isset($form_state->getUserInput()['_drupal_ajax']) && $form_state->getUserInput()['_drupal_ajax'] != '1') )) { $preselected_project = TRUE; } // When project was preset from URL query, disable the project selector. if ($preselected_project) { $form['project'] = [ '#type' => 'value', '#value' => $project_id, ]; $project = $this->projectStorage->load($project_id); $form['project_name'] = [ '#type' => 'html_tag', '#tag' => 'h3', '#value' => $this->t('@project project', ['@project' => $project->label()]), ]; } else { $form['project'] = [ '#title' => t('Project'), '#required' => TRUE, '#default_value' => $project_id ?? NULL, '#ajax' => [ 'callback' => [$this, 'ajaxReleases'], 'event' => 'change', 'wrapper' => 'l10n-server-releases', 'effect' => 'fade', 'progress' => [ 'type' => 'throbber', 'message' => $this->t('Getting releases...'), ], ], ]; $project_count = $this->projectStorage ->getQuery() ->accessCheck(TRUE) ->condition('status', 1) ->count() ->accessCheck(FALSE) ->execute(); if ($project_count <= 30) { // Radio box widget for as much as 5 projects, select widget for 5-30 // projects. $form['project']['#type'] = ($project_count <= 5 ? 'radios' : 'select'); $form['project']['#options'] = []; $projects = $this->projectStorage->loadMultiple(); foreach ($projects as $project) { // Title used to conform to the autocomplete behavior. $form['project']['#options'][$project->id()] = $project->label(); } } else { // Autocomplete field for more than 30 projects. $form['project'] += [ '#type' => 'entity_autocomplete', '#target_type' => 'l10n_server_project', ]; } } $release_options = []; if (!empty($project_id)) { $releases = $this->releaseStorage->loadByProperties(['pid' => $project_id]); foreach ($releases as $release) { $release_options[$release->id()] = t('@version only', [ '@version' => $release->getVersion(), ]); } // Sort by semver most recent releases on top. uasort($release_options, 'version_compare'); $release_options = array_reverse($release_options, TRUE); } $release_options = ['all' => t('All releases merged')] + $release_options; $release_id = $this->computeReleaseId(); $form['release'] = [ '#title' => $this->t('Release'), '#required' => TRUE, '#type' => 'select', '#options' => $release_options, '#default_value' => $release_id ?? 'all', '#prefix' => '<div id="l10n-server-releases">', '#suffix' => '</div>', ]; $form['download_type'] = [ '#title' => $this->t('Would you like to download the full strings available or limit to translations?'), '#type' => 'radios', '#options' => [ $this->t('Download untranslated and translated strings'), $this->t('Download only translated strings'), ], '#description' => $this->t("Untranslated strings are useful to work on the file translation."), '#default_value' => 1, ]; $form['suggestions'] = [ '#title' => $this->t('Inject @language suggestions?', ['@language' => $form_state->get('export_language')->getName()]), '#type' => 'checkbox', '#description' => $this->t('If checked, extra translations will be added. The first suggestion will be exported as a fuzzy translation. All other suggestions are exported in comments.'), ]; $form['compact'] = [ '#title' => $this->t('Inject metadata?'), '#type' => 'radios', '#options' => [$this->t('No metadata'), $this->t('Include metadata')], '#description' => $this->t('Metadata are useful to understand string context usages (it includes files and line numbers where used). It is only useful for translators.'), '#default_value' => 0, ]; $form['actions']['#type'] = 'actions'; $form['actions']['submit'] = [ '#type' => 'submit', '#value' => $this->t('Export Gettext file'), ]; return $form; } /** * {@inheritdoc} */ public function validateForm(array &$form, FormStateInterface $form_state) { $project = FALSE; if ($selected_project = $form_state->getValue('project')) { $project = $this->projectStorage->load($selected_project); } if (empty($project)) { $form_state->setError($form['project'], $this->t('This project can not be loaded. Is its ID valid?')); } else { // Store the project for the submit callback. $form_state->set('export_project', $project); if ($form_state->getValue('release') != 'all') { $releases = $this->releaseStorage->loadByProperties([ 'pid' => $project->id(), 'rid' => $form_state->getValue('release'), ]); if (!$releases) { $form_state->setError($form['release'], $this->t('Invalid release chosen.')); } else { $form_state->set('export_release', array_shift($releases)); } } // If all the release should be exported, set the release value to NULL // since NULL represent a placeholder value in internal APIs. // This should be updated to a more explicit value at some point. elseif ($form_state->getValue('release') == 'all') { $form_state->set('export_release', NULL); } } } /** * {@inheritdoc} */ public function submitForm(array &$form, FormStateInterface $form_state) { // Generate tarball or PO file and get file name. $export_result = $this->communityExporter->export( $form_state->get('export_project'), $form_state->get('export_language'), $form_state->get('export_release'), empty($form_state->getValue('download_type')), empty($form_state->getValue('compact')), FALSE, !empty($form_state->getValue('suggestions')) ); if (isset($export_result) && is_array($export_result)) { // If we got an array back from the export build, tear that into pieces. [$mime_type, $file_name, $serve_name, $sid_count] = $export_result; header('Content-Disposition: attachment; filename=' . $serve_name); header('Content-Type: ' . $mime_type); echo file_get_contents($file_name); unlink($file_name); die(); } } /** * Ajax entry point to get the releases of the selected project. * * @param array $form * The form array. * @param \Drupal\Core\Form\FormStateInterface $form_state * The form_state object. * * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException */ public function ajaxReleases(array $form, FormStateInterface $form_state) { return $form['release']; } /** * Helper to extract project ID. * * Extract project ID from Request or Autocomplete response if available. * Careful, since our form is AJAXified, the buildForm() method can be * executed several times and Request attributes availability might change * between calls. * * @return string|null * The project ID. */ protected function computeProjectId(): ?string { $project_id = NULL; if (!empty($_REQUEST['project']) && preg_match('`^[\d]+$`', $_REQUEST['project'])) { $project_id = $_REQUEST['project']; } elseif (!empty($_REQUEST['project'])) { $project_id = EntityAutocomplete::extractEntityIdFromAutocompleteInput($_REQUEST['project']); } return $project_id; } /** * Helper to extract release ID. * * @return string|null * The release ID. */ protected function computeReleaseId(): ?string { $release_id = NULL; if (!empty($_REQUEST['release']) && preg_match('`^[\d]+$`', $_REQUEST['release'])) { $release_id = $_REQUEST['release']; } return $release_id; } }