l10n_server-2.x-dev/l10n_community/src/Form/TranslateForm.php

l10n_community/src/Form/TranslateForm.php
<?php

namespace Drupal\l10n_community\Form;

use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Messenger\MessengerInterface;
use Drupal\Core\Render\Element;
use Drupal\Core\Render\RendererInterface;
use Drupal\Core\Routing\RouteMatch;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Security\TrustedCallbackInterface;
use Drupal\Core\Session\AccountProxyInterface;
use Drupal\Core\Url;
use Drupal\l10n_community\L10nAccess;
use Drupal\l10n_community\L10nTranslator;
use Drupal\l10n_server\L10nPo;
use Drupal\locale\PluralFormulaInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\RequestStack;

/**
 * Translation form.
 */
class TranslateForm extends FormBase implements TrustedCallbackInterface {

  /**
   * Entity type manager service.
   *
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
   */
  protected $entityTypeManager;

  /**
   * Translator service (l10n_community).
   *
   * @var \Drupal\l10n_community\L10nTranslator
   */
  protected $translator;

  /**
   * L10N access manager.
   *
   * @var \Drupal\l10n_community\L10nAccess
   */
  protected $accessManager;

  /**
   * Messenger service.
   *
   * @var \Drupal\Core\Messenger\MessengerInterface
   */
  protected $messenger;

  /**
   * Current user.
   *
   * @var \Drupal\Core\Session\AccountProxyInterface
   */
  protected $currentUser;

  /**
   * Plural formula service.
   *
   * @var \Drupal\locale\PluralFormulaInterface
   */
  protected $pluralFormula;

  /**
   * Current route match service.
   *
   * @var \Drupal\Core\Routing\RouteMatchInterface
   */
  protected $currentRouteMatch;

  /**
   * Renderer service.
   *
   * @var \Drupal\Core\Render\RendererInterface
   */
  protected $renderer;

  /**
   * L10n helper.
   *
   * @var \Drupal\l10n_server\L10nPo
   */
  protected $l10nPo;

  /**
   * Current request object.
   *
   * @var \Symfony\Component\HttpFoundation\Request
   */
  protected $currentRequest;

  /**
   * Constructor.
   */
  public function __construct(EntityTypeManagerInterface $entity_type_manager, L10nTranslator $translator, L10nAccess $access_manager, MessengerInterface $messenger, AccountProxyInterface $current_user, PluralFormulaInterface $plural_formula, RouteMatchInterface $current_route_match, RendererInterface $renderer, L10nPo $l10n_po, RequestStack $request_stack) {
    $this->entityTypeManager = $entity_type_manager;
    $this->translator = $translator;
    $this->accessManager = $access_manager;
    $this->messenger = $messenger;
    $this->currentUser = $current_user;
    $this->pluralFormula = $plural_formula;
    $this->currentRouteMatch = $current_route_match;
    $this->renderer = $renderer;
    $this->l10nPo = $l10n_po;
    $this->currentRequest = $request_stack->getCurrentRequest();
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    return new static(
      $container->get('entity_type.manager'),
      $container->get('l10n_community.translator'),
      $container->get('l10n_community.access'),
      $container->get('messenger'),
      $container->get('current_user'),
      $container->get('locale.plural.formula'),
      $container->get('current_route_match'),
      $container->get('renderer'),
      $container->get('l10n_server.po'),
      $container->get('request_stack')
    );
  }

  /**
   * {@inheritdoc}
   */
  public function getFormId() {
    return 'l10n_community_translate';
  }

  /**
   * Form callback: List translations and suggestions.
   *
   * @param array $form
   *   Form.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The form state object.
   * @param \Drupal\Core\Language\LanguageInterface $language
   *   A language object.
   * @param array $filters
   *   An array of filters applied to the strings.
   * @param array $strings
   *   An array of string records from database.
   *
   * @return array
   *   Form render array.
   */
  public function buildForm(array $form, FormStateInterface $form_state, ?LanguageInterface $language = NULL, array $filters = [], array $strings = []) {
    $langcode = $language->getId();

    // The form will show but the submit buttons will only appear if the user
    // has permissions to submit suggestions. This allows the use of this form
    // to review strings in the database.
    $form = [
      'langcode' => [
        '#type' => 'value',
        '#value' => $langcode,
      ],
      'pager_top' => [
        '#weight' => -10,
        '#type' => 'pager',
      ],
      'strings' => [
        '#tree' => TRUE,
        '#type' => 'table',
        '#header' => $this->getHeader(),
        '#attributes' => ['class' => ['l10n-table']],
        '#weight' => 0,
      ],
      'submit' => [
        '#type' => 'submit',
        '#value' => t('Save changes'),
        '#access' => $this->accessManager->check('submit suggestions'),
      ],
      'pager_bottom' => [
        '#weight' => 10,
        '#type' => 'pager',
      ],
    ];

    $this->setRows($form, $form_state, $strings, $language, $filters);

    // Useful during validation.
    $form_state->set('source_strings', $this->extractSourceStrings($strings));

    return $form;
  }

  /**
   * Get source strings.
   *
   * @param array $strings
   *   An array of string records from database.
   *
   * @return array
   *   Source strings, keyed by IDs.
   */
  protected function extractSourceStrings(array $strings) {
    $source_strings = [];

    foreach ($strings as $string) {
      $source_strings[$string->sid] = $string->value[0];
    }

    return $source_strings;
  }

  /**
   * Get header for translation form.
   *
   * @return array
   *   Header.
   */
  protected function getHeader() {
    return [
      [
        'data' => $this->t('Source text'),
        'colspan' => 2,
      ],
      $this->t('Translations'),
    ];
  }

  /**
   * Generate rows for translation form.
   *
   * @param array $form
   *   Form.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The form state object.
   * @param array $strings
   *   Source strings retrieved from the database.
   * @param \Drupal\Core\Language\LanguageInterface $language
   *   A language object.
   * @param array $filters
   *   An array of filters applied to the strings.
   *
   * @return array
   *   Rows.
   */
  protected function setRows(array &$form, FormStateInterface &$form_state, array $strings, LanguageInterface $language, array $filters) {
    $rows = [];

    // Fill in string values for the editor.
    foreach ($strings as $string) {
      $form_item = $this->formItem($form_state, $string, $language, $filters);
      $sid = $form_item['#string']->sid;
      $form['strings'][$sid] = [
        'sid' => [
          '#type' => 'link',
          '#title' => '#',
          '#url' => Url::fromRoute('l10n_community.language.translate.translate',
          ['group' => $this->currentRouteMatch->getRawParameter('group')],
          [
            'query' => ['sid' => $sid],
            'attributes' => [
              'title' => $this->t('Direct and permanent link to this string.'),
            ],
          ]
          ),
          '#wrapper_attributes' => ['class' => ['sid']],
        ],
        'source' => [
          'actions' => $this->translateActions($form_item['source'], TRUE),
          'string' => [
            '#type' => 'html_tag',
            '#tag' => 'label',
            '#attributes' => [
              'class' => [
                'l10n-string',
                $form_item['filters_match']['#value'] ? 'filter-match' : 'filter-no-match',
              ],
            ],
            'inner' => $form_item['source']['string'],
          ],
          'context' => empty($form_item['#string']->context) ? [] : [
            '#type' => 'html_tag',
            '#tag' => 'div',
            '#value' => $this->t('in context: @context', ['@context' => $form_item['#string']->context]),
            '#attributes' => [
              'class' => ['string-context'],
            ],
          ],
          'usage' => [
            '#type' => 'container',
            '#attributes' => ['class' => ['l10n-usage']],
            'link' => [
              '#type' => 'link',
              '#title' => $this->t('Show related projects'),
              '#url' => Url::fromRoute('l10n_community.source_details', ['string' => $sid], [
                'attributes' => [
                  'class' => ['l10n-more-link'],
                  'title' => $this->t('Show list of projects and releases where this text is used.'),
                ],
              ]),
            ],
            'more_info' => [
              '#type' => 'container',
              '#attributes' => ['class' => ['l10n-more-info']],
            ],
          ],
          '#wrapper_attributes' => ['class' => ['source']],
        ],
        'translation' => $this->translationList($form_item) + [
          '#wrapper_attributes' => ['class' => ['translation']],
          '#pre_render' => [[$this, 'renderList']],
        ],
      ];
    }

    return $rows;
  }

  /**
   * Creates the form fragment for a source string.
   *
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The form state object.
   * @param object $source
   *   Source string retrieved from the database.
   * @param \Drupal\Core\Language\LanguageInterface $language
   *   A language object.
   * @param array $filters
   *   An array of filters applied to the strings.
   *
   * @return array
   *   Form item.
   */
  protected function formItem(FormStateInterface &$form_state, $source, LanguageInterface $language, array $filters) {
    $langcode = $language->getId();

    if (!$source->translation) {
      // If there is no picked translation yet, simulate with a visible
      // placeholder.
      $source->tid = '0';
      $source->translation = [$this->t('(not translated)')];
      $source->status = '1';
      $source->suggestion = '0';
    }
    else {
      $source->translation = $this->unpackString($source->translation);
    }
    $source_unpacked = $source->value;
    $source->value = $this->unpackString($source->value);

    $form = [
      '#string' => $source,
      '#langcode' => $langcode,
      'filters_match' => [
        '#type' => 'value',
        '#value' => ($source_match = (!empty($filters['search']) && (stripos($source_unpacked, $filters['search']) !== FALSE))),
      ],
      'source' => [
        'string' => $this->renderTextarray($source->value),
      ],
    ];

    if ($this->accessManager->check('submit suggestions')) {
      $form['source']['edit'] = [
        '#type' => 'html_tag',
        '#tag' => 'label',
        '#value' => $this->t('Translate'),
        '#attributes' => [
          'title' => $this->t('Translate'),
        ],
      ];
    }

    // Add the current string (either as approved translation or a mock object
    // for the "untranslated" string).
    $form[$source->tid] = $this->translateTranslation($form_state, $source, $source, $filters, $source_match);

    // Format suggestions.
    if (!empty($source->suggestions)) {
      foreach ($source->suggestions as $suggestion) {
        $suggestion->translation = $this->unpackString($suggestion->translation);
        // Add the suggestion to the list.
        $form[$suggestion->tid] = $this->translateTranslation($form_state, $suggestion, $source, $filters, $source_match);
      }
    }

    // If the user may add new suggestions, display a textarea.
    if ($this->accessManager->check('submit suggestions')) {
      $nplurals = $this->pluralFormula->getNumberOfPlurals($langcode);
      $textarea = $this->translateTranslationTextarea($source, $nplurals);
      $form[$textarea->tid] = $this->translateTranslation($form_state, $textarea, $source, $filters, $source_match);
    }
    return $form;
  }

  /**
   * Appropriate actions for the given string element.
   *
   * @param array $source
   *   Source string data.
   * @param bool $theme_as_list
   *   Whether to theme the actions as an item list.
   *
   * @return array
   *   Render array
   */
  protected function translateActions(array $source, bool $theme_as_list = FALSE) {
    $actions = [];
    foreach (['declined', 'edit'] as $type) {
      if (isset($source[$type])) {
        $actions[] = $source[$type] + [
          '#wrapper_attributes' => [
            'class' => [$type],
          ],
        ];
      }
    }

    if (!empty($actions)) {
      if ($theme_as_list) {
        $actions = [
          '#theme' => 'item_list',
          '#items' => $actions,
        ];
      }
      $actions['#attributes']['class'] = ['actions'];
    }

    return $actions;
  }

  /**
   * Generate a list of suggestions for a string.
   *
   * @param array $form_item
   *   Form item generated by self::formItem().
   *
   * @return array
   *   Render array.
   */
  protected function translationList(array $form_item) {
    $items = [];

    foreach (Element::children($form_item) as $child) {
      if (is_numeric($child) || $child == 'new') {
        $items[$child] = $this->translationListItem($form_item[$child]);
      }
    }

    return $items;
  }

  /**
   * Unpacks a string as retrieved from the database.
   *
   * Creates an array out of the string. If it was a single string, the array
   * will have one item. If the string was a plural string, the array will have
   * as many items as the language requires (two for source strings).
   *
   * @param string $string
   *   The string with optional separation markers (NULL bytes)
   *
   * @return array
   *   An array of strings with one element for each plural form in case of
   *   a plural string, or one element in case of a regular string. This
   *   is called a $textarray elsewhere.
   */
  protected function unpackString($string) {
    return explode("\0", $string);
  }

  /**
   * Packs a string for storage in the database.
   *
   * @param array $strings
   *   An array of strings.
   *
   * @return string
   *   A packed string with NULL bytes separating each string.
   */
  protected function packString(array $strings) {
    return implode("\0", $strings);
  }

  /**
   * Generate markup for an unpacked string.
   *
   * @param array $textarray
   *   An array of strings as generated by unpacktring().
   * @param string $empty
   *   string Specific data to include as the data to use when empty.
   *
   * @return array
   *   A render array.
   */
  protected function renderTextarray(array $textarray, $empty = '') {
    $return = [];

    foreach ($textarray as $index => $value) {
      $span = [
        '#type' => 'html_tag',
        '#tag' => 'span',
        '#value' => $value,
      ];
      // data-empty is a proprietary attribute used in editor.css to be
      // displayed when starting to submit a new suggestion.
      if (!empty($empty)) {
        $span['#attributes']['data-empty'] = $empty;
      }
      if ($index) {
        // Do not add a <br> tag before the first element.
        $return[] = [
          '#type' => 'html_tag',
          '#tag' => 'br',
        ];
      }
      $return[] = $span;
    }

    return $return;
  }

  /**
   * Creates the form fragment for a translated string.
   *
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The form state object.
   * @param object $string
   *   Suggestion when there is one.
   * @param object $source
   *   Source string retrieved from the database.
   * @param array $filters
   *   An array of filters applied to the strings.
   * @param bool $source_match
   *   Whether the strings matches with what?
   *
   * @return array
   *   Form fragment.
   */
  protected function translateTranslation(FormStateInterface &$form_state, $string, $source, array $filters, $source_match) {
    $is_own = $this->currentUser->id() == $string->uid;
    $is_active = $string->status && !$string->suggestion;
    $is_new = $string->tid == 'new';
    $may_moderate = ($is_own ? $this->accessManager->check('moderate own suggestions') : $this->accessManager->check('moderate suggestions from others'));
    $may_moderate_suggestions = ($is_own ? $this->currentUser->hasPermission('decline own suggestions') : $this->currentUser->hasPermission('moderate suggestions from others'));

    // This string is a match if it was data from the database and was entered
    // by the searched user or its text included the searched portion.
    $filters_matched = [];
    $filters_to_match = 0;
    if ((int) $string->tid > 0) {
      // If we had a real tid for this, it has a uid and translation
      // loaded.
      if (!empty($filters['author'])) {
        $filters_matched[] = $string->uid == $filters['author']->id();
        $filters_to_match++;
      }
      if (!empty($filters['search'])) {
        // Search is matched if the source matched the search and the
        // translation matched the user or the translation matched both.
        $filters_matched[] = (!empty($filters['author']) && $source_match) || (stripos($this->packString($string->translation), $filters['search']) !== FALSE);
        $filters_to_match++;
      }
      $filters_matched = array_filter($filters_matched);
    }

    $form = [
      'original' => [
        '#type' => 'value',
        '#value' => $string,
      ],
      'filters_match' => [
        '#type' => 'value',
        '#value' => ($filters_to_match > 0) && (count($filters_matched) == $filters_to_match),
      ],
    ];

    // Active radio box is used to pick the approved translation.
    $form['active'] = [
      '#type' => 'radio',
      // #title does not support render arrays...
      '#title' => $this->render($this->renderTextarray($string->translation, $is_new ? $this->t('(empty)') : FALSE)),
      '#return_value' => $string->tid,
      '#default_value' => $is_active ? $string->tid : NULL,
      '#parents' => ['strings', $string->sid, 'active'],
      // Let a moderator roll back to the current translation even if they
      // would otherwise not have permission to approve such a string.
      '#disabled' => !$may_moderate && !$is_active,
      '#attributes' => ['class' => ['selector']],
      '#label_attributes' => ['class' => ['l10n-string']],
      '#title_display' => $is_new ? 'invisible' : 'before',
    ];

    if ($string->tid) {
      if (($may_moderate || $may_moderate_suggestions) && $string->tid != 'new') {
        $form['declined'] = [
          '#type' => 'checkbox',
          '#title' => t('Decline'),
          '#default_value' => !($string->status || $string->suggestion),
        ];
      }
      if ($string->tid == 'new') {
        // Fill in with as many textareas as required to enter translation
        // for this string.
        $form['value'] = array_fill(0, count($string->translation), [
          '#type' => 'textarea',
          '#cols' => 60,
          '#rows' => 3,
          '#default_value' => $this->t('<New translation>'),
        ]);
      }
      else {
        if ($this->accessManager->check('submit suggestions')) {
          $form['edit'] = [
            '#type' => 'html_tag',
            '#tag' => 'label',
            '#value' => $this->t('Edit a copy'),
            '#attributes' => ['title' => $this->t('Edit a copy')],
          ];
        }
        if (isset($string->username)) {
          $title = $this->l10nPo->translateByline($string->username, $string->uid, $string->created, -1, -1, FALSE);

          $form['author'] = [
            '#type' => 'container',
            '#attributes' => [
              'class' => ['l10n-byline'],
            ],
            'link' => [
              '#type' => 'link',
              '#title' => $title,
              '#url' => Url::fromRoute('l10n_community.translation_details', ['translation' => $string->tid], [
                'attributes' => [
                  'class' => ['l10n-more-link'],
                  'title' => $this->t('Show full history for translation.'),
                ],
              ]),
            ],
            'more_info' => [
              '#type' => 'container',
              '#attributes' => [
                'class' => ['l10n-more-info'],
              ],
            ],
          ];
        }
      }
    }
    return $form;
  }

  /**
   * Format a suggestion/translation.
   *
   * @param array $element
   *   Form item to format.
   *
   * @return array
   *   Render array
   */
  protected function translationListItem(array $element) {
    $element['#attributes']['class'][] = 'translation';

    // Add is-selectable and is-declinable helpers for JS.
    if (!$element['active']['#disabled']) {
      $element['#attributes']['class'][] = 'is-selectable';
    }
    if (isset($element['declined'])) {
      $element['#attributes']['class'][] = 'is-declinable';
    }

    // Add information on whether this matched the filter.
    if ((int) $element['active']['#return_value'] > 0) {
      $element['#attributes']['class'][] = ($element['filters_match']['#value']) ? 'filter-match' : 'filter-no-match';
    }

    switch ($element['active']['#return_value']) {
      case 'new':
        $element['#attributes']['class'][] = 'new-translation';
        break;

      case '0':
        $element['#attributes']['class'][] = 'no-translation';
        // Fallthrough.
      default:
        if (!empty($element['active']['#value'])) {
          $element['#attributes']['class'][] = 'is-active default';
        }
    }

    return [
      '#wrapper_attributes' => $element['#attributes'],
      'actions' => $this->translateActions($element),
      'original' => $element['original'] ?? [],
      // Add the radio box to pick the active translation.
      'active' => $element['active'],
      'author' => empty($element['author']) ? [] : [
        '#type' => 'container',
        '#attributes' => ['class' => ['author']],
        'inner' => $element['author'],
      ],
      'value' => empty($element['value']) ? [] : $element['value'],
    ];
  }

  /**
   * Build mock object for new textarea.
   *
   * @param object $source
   *   Source string ?
   * @param int $nplurals
   *   Number of plurals.
   *
   * @return object
   *   String object.
   */
  protected function translateTranslationTextarea($source, $nplurals) {
    return (object) [
      'sid' => $source->sid,
      'tid' => 'new',
      // Fill in with as many items as required. If the source was plural, we
      // need to fill in with a number adequate for this language.
      'translation' => array_fill(0, count($source->value) > 1 ? $nplurals : 1, ''),
      'status' => '1',
      'suggestion' => '1',
      'uid' => $this->currentUser->id(),
    ];
  }

  /**
   * {@inheritdoc}
   */
  public static function trustedCallbacks() {
    return ['renderList'];
  }

  /**
   * Render items as an unordered list.
   *
   * @param array $element
   *   Part of render array to be formatted as a list.
   *
   * @return array
   *   Array with formatted element.
   */
  public static function renderList(array $element) {
    $items = [];

    foreach (Element::children($element) as $child) {
      $items[] = $element[$child];
      unset($element[$child]);
    }

    $element['rendered_list'] = [
      '#theme' => 'item_list',
      '#items' => $items,
    ];

    return $element;
  }

  /**
   * Helper function when rendering cannot be delayed.
   *
   * Sometimes a render array is not accepted (e.g. #title for a form).
   *
   * @param array $element
   *   Element to be rendered.
   *
   * @return string
   *   Rendered element.
   */
  protected function render(array $element) {
    return $this->renderer->renderPlain($element);
  }

  /**
   * {@inheritdoc}
   */
  public function validateForm(array &$form, FormStateInterface $form_state) {
    $strings = $form_state->getValue('strings');

    // Original strings.
    $originals = $form_state->get('source_strings');

    // Iterate outer structure built in l10n_community_translate_form().
    $pattern = '/([!@%:]|<(ins|del)>[!@%:]<\/(ins|del)>)([\w-]+|<(ins|del)>[\w-]+<\/(ins|del)>)/';
    foreach ($strings as $sid => $string) {
      $matches = [];

      // If there are variables in the original string, check in new
      // suggestions. We need to explode the strings as they may contain plurals
      // and check the corresponding plural.
      $originals_plurals = $this->unpackString($originals[$sid]);
      foreach ($originals_plurals as $sourceid => $sourcevalue) {
        if (preg_match_all($pattern, $sourcevalue, $matches)) {
          foreach ($string['translation'] as $tid => $options) {
            // Only process new suggestions.
            if (isset($options['value']) && is_array($options['value']) && $tid === 'new') {
              $not_found = [];
              $value = $options['value'][$sourceid];
              if ($value !== (string) t('<New translation>')) {
                foreach ($matches[0] as $match) {
                  if (!strstr($value, $match)) {
                    $not_found[] = $match;
                  }
                }
              }
              if ($not_found) {
                $form_state->setErrorByName('strings][' . $sid . '][translation][new][value][0', $this->t('Missing variable(s) @variables', ['@variables' => implode(', ', $not_found)]));
              }
            }
          }

        }
      }
    }
  }

  /**
   * {@inheritdoc}
   */
  public function submitForm(array &$form, FormStateInterface $form_state) {
    $langcode = $form_state->getValue('langcode');

    // Iterate outer structure built in self::setRows().
    foreach ($form_state->getValue('strings') as $sid => $string) {

      // Iterate inner structure built in self::formItem().
      // Form items have numeric $tid values and other keys here.
      foreach ($string['translation'] as $tid => $options) {

        // Store new suggestion.
        $all_values = 0;
        $empty_values = 0;
        // $options['value'] is the result of (a series of) textareas.
        if (isset($options['value']) && is_array($options['value'])) {
          foreach ($options['value'] as $key => $value) {
            $all_values++;
            if ($value === (string) t('<New translation>')) {
              $options['value'] = '';
              $empty_values++;
            }
          }
          // If we had value in any of the textareas, add new suggestion.
          if ($tid === 'new' && $all_values > $empty_values) {
            $tid = $this->l10nPo->addSuggestion($sid, $this->packString($options['value']), $langcode, $this->currentUser->id(), $this->currentUser->id(), L10N_SERVER_MEDIUM_WEB);
            if ($tid) {
              if ($string['active'] === 'new') {
                // This new string was selected to be approved, so remember $tid
                // for later, so we can save this as an approved translation.
                $string['active'] = $tid;
                $this->l10nPo->counter(L10N_COMMUNITY_COUNT_ADDED);
              }
              else {
                $this->l10nPo->counter(L10N_COMMUNITY_COUNT_SUGGESTED);
              }
            }
            elseif ($tid === FALSE) {
              // We found this as an active string already in the DB.
              $this->l10nPo->counter(L10N_COMMUNITY_COUNT_DUPLICATE);
            }
          }
        }

        if (is_numeric($tid) && $tid > 0) {
          if ($tid == $string['active']) {
            if ($options['original']->suggestion) {
              // $tid is a suggestion that was made active.
              $this->l10nPo->approveString($langcode, $sid, $tid);
              $this->l10nPo->counter(L10N_COMMUNITY_COUNT_APPROVED);
            }
          }
          elseif (!empty($options['declined'])) {
            // The decline checkbox for this suggestion was checked.
            $this->l10nPo->counter($options['original']->suggestion ? L10N_COMMUNITY_COUNT_SUGGESTION_DECLINED : L10N_COMMUNITY_COUNT_DECLINED);
            $this->l10nPo->declineString($langcode, $sid, $tid, $user->uid);
          }
        }
      }
    }

    // Tell the user what happened.
    $this->l10nPo->updateMessage();

    // Keep existing filters and other query arguments on form submission.
    $route_match = RouteMatch::createFromRequest($this->currentRequest);
    $form_state->setRedirect('l10n_community.language.translate.translate', ['group' => $route_match->getRawParameter('group')], ['query' => $this->currentRequest->query->all()]);
  }

}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc