You are here

function lingotek_grid_get_rows in Lingotek Translation 7.4

Same name and namespace in other branches
  1. 7.7 lingotek.bulk_grid.inc \lingotek_grid_get_rows()
  2. 7.5 lingotek.bulk_grid.inc \lingotek_grid_get_rows()
  3. 7.6 lingotek.bulk_grid.inc \lingotek_grid_get_rows()

Dynamic query processing function for the grid Since the header defines which columns are shown, this query gets all possible values and refines the header using the columns selected in the UI The filters are also processed here

Return value

array $table_data Returns array of rows Populates The Grid

1 call to lingotek_grid_get_rows()
lingotek_bulk_grid_form in ./lingotek.bulk_grid.inc
@file Bulk Grid form

File

./lingotek.bulk_grid.inc, line 1002
Bulk Grid form

Code

function lingotek_grid_get_rows($form, &$form_state) {
  $table_data = array();
  $limit = isset($_SESSION['limit_select']) ? $_SESSION['limit_select'] : 10;
  $columns = isset($form_state['values']['columns']) ? $form_state['values']['columns'] : array();
  $source = isset($form_state['values']['source']) ? $form_state['values']['source'] : TRUE;
  $header = array(
    // Define the tentative source header
    'nid' => array(
      'data' => t('Node ID'),
      'field' => 'n.nid',
    ),
    'title' => array(
      'data' => t('Title'),
      'field' => 'n.title',
    ),
    'content_type' => array(
      'data' => t('Content Type'),
      'field' => 'n.type',
    ),
    'language' => array(
      'data' => t('Source Uploaded'),
      'field' => 'upload_status',
    ),
    'translations' => array(
      'data' => t('Translations'),
      'field' => 't_current_c',
    ),
    'configuration' => array(
      'data' => t('Profile'),
    ),
    'workflow' => array(
      'data' => t('Workflow'),
      'field' => 'workflow',
    ),
    'document_id' => array(
      'data' => t('Doc ID'),
      'field' => 'document_id',
    ),
    'changed' => array(
      'data' => t('Last Modified'),
      'field' => 'changed',
      'sort' => 'desc',
    ),
    'last_uploaded' => array(
      'data' => t('Last Uploaded'),
      'field' => 'last_uploaded',
    ),
    //      'translation_progress_percent' => array('data' => t('Workflow Progress'), 'field' => 'translation_progress_percent'),
    'actions' => array(
      'data' => t('Actions'),
    ),
    'translation_status' => array(
      'data' => t('Translation Status'),
    ),
    'translation_progress' => array(
      'data' => t('Translation Progress'),
    ),
    'locale_progress_percent' => array(
      'data' => t('Target Progress'),
      'field' => 'locale_progress_percent',
    ),
    'progress_updated' => array(
      'data' => t('Progress Updated'),
      'field' => 'progress_updated',
    ),
    'last_downloaded' => array(
      'data' => t('Last Downloaded'),
      'field' => 'last_downloaded',
    ),
    'translate_link' => array(
      'data' => t('Translate'),
    ),
  );
  foreach ($header as $title => $data) {

    // Refine the source header using the selected columns
    if (array_key_exists($title, $columns) && $columns[$title]) {
      $form_state['values']['grid_header'][$title] = $data;
    }
  }

  // Initialize Query and extend paginator and tablesort
  $query = db_select('node', 'n')
    ->extend('PagerDefault')
    ->extend('TableSort');
  $query
    ->innerJoin('node', 'node2', '(n.nid = node2.nid) AND (node2.tnid = 0 OR node2.tnid = node2.nid)');
  $query
    ->limit($limit);
  $query
    ->orderByHeader($form_state['values']['grid_header']);

  // Node Title and Name of Content Type (type)
  $query
    ->fields('n', array(
    'nid',
    'title',
    'type',
    'language',
    'changed',
  ));
  $query
    ->addExpression("(SELECT COUNT(nid) FROM lingotek WHERE nid=n.nid AND lingokey LIKE 'target_sync_status_%' AND lingovalue='CURRENT')", 't_current_c');
  $query
    ->addExpression("(SELECT GROUP_CONCAT(SUBSTRING(lingokey, 20, 10)) FROM lingotek WHERE nid=n.nid AND lingokey LIKE 'target_sync_status_%' AND lingovalue='PENDING')", 't_pending');
  $query
    ->addExpression("(SELECT GROUP_CONCAT(SUBSTRING(lingokey, 20, 10)) FROM lingotek WHERE nid=n.nid AND lingokey LIKE 'target_sync_status_%' AND lingovalue='READY')", 't_ready');
  $query
    ->addExpression("(SELECT GROUP_CONCAT(SUBSTRING(lingokey, 20, 10)) FROM lingotek WHERE nid=n.nid AND lingokey LIKE 'target_sync_status_%' AND lingovalue='CURRENT')", 't_current');
  $query
    ->addExpression("(SELECT GROUP_CONCAT(SUBSTRING(lingokey, 20, 10)) FROM lingotek WHERE nid=n.nid AND lingokey LIKE 'target_sync_status_%' AND lingovalue='EDITED')", 't_edited');
  $filters = isset($_SESSION['grid_filters']) ? $_SESSION['grid_filters'] : array();
  global $language;
  if (language_default()->language != $language->language) {
    $query
      ->leftJoin('field_data_title_field', 't_title', 't_title.entity_id = n.nid and t_title.entity_type = \'node\' and t_title.language=\'' . $language->language . '\'');
    $query
      ->addField('t_title', 'title_field_value', 'localized_title');
  }

  // left joins are necessary here because some lingotek table keys might not exist
  // Lingotek Document ID
  $query
    ->leftJoin('lingotek', 'lingo_document_id', 'lingo_document_id.nid = n.nid and lingo_document_id.lingokey = \'document_id\'');
  $query
    ->addField('lingo_document_id', 'lingovalue', 'document_id');

  // Node Upload Status
  $query
    ->leftJoin('lingotek', 'lingo_upload_status', 'lingo_upload_status.nid = n.nid and lingo_upload_status.lingokey = \'node_sync_status\' and lingo_upload_status.lingovalue <> \'' . LingotekSync::STATUS_TARGET . '\'');

  //$query->leftJoin('lingotek', 'lingo_upload_status', 'lingo_upload_status.nid = n.nid and lingo_upload_status.lingokey = \'node_sync_status\'');

  //$query->condition('lingo_upload_status.lingovalue', LingotekSync::STATUS_TARGET, '<>');
  $query
    ->addField('lingo_upload_status', 'lingovalue', 'upload_status');

  // Profile Settings
  $query
    ->leftJoin('lingotek', 'lingo_profile', 'lingo_profile.nid = n.nid and lingo_profile.lingokey = \'profile\'');
  $query
    ->addField('lingo_profile', 'lingovalue', 'profile');

  // Last Uploaded Timestamp
  $query
    ->leftJoin('lingotek', 'lingo_last_uploaded', 'lingo_last_uploaded.nid = n.nid and lingo_last_uploaded.lingokey = \'last_uploaded\'');
  $query
    ->addField('lingo_last_uploaded', 'lingovalue', 'last_uploaded');

  // Translation Progress Percent (Average of all target workflow progresses)
  //    $query->leftJoin('lingotek', 'lingo_translation_progress_percent', 'lingo_translation_progress_percent.nid = n.nid and lingo_translation_progress_percent.lingokey = \'translation_progress\'');
  //      $query->addField('lingo_translation_progress_percent', 'lingovalue', 'translation_progress_percent');
  // Add translation status to the query
  //    $query->leftJoin('lingotek', 'lingo_translation_status', 'n.nid = lingo_translation_status.nid and lingo_translation_status.lingokey LIKE \'target_sync_status_' . $language_filter . '\'');
  //      $query->addField('lingo_translation_status', 'lingovalue', 'translation_status');
  // Add workflow to the query
  $query
    ->leftJoin('lingotek', 'lingo_workflow', 'n.nid = lingo_workflow.nid and lingo_workflow.lingokey = \'workflow_id\'');
  $query
    ->addField('lingo_workflow', 'lingovalue', 'workflow');

  //    // Target Workflow Progress
  //    $query->leftJoin('lingotek', 'lingo_locale_progress_percent', 'n.nid = lingo_locale_progress_percent.nid and lingo_locale_progress_percent.lingokey LIKE \'target_sync_progress_' . $language_filter . '\'');
  //      $query->addField('lingo_locale_progress_percent', 'lingovalue', 'locale_progress_percent');
  //
  //    // Target Progress Last Updated Timestamp
  //    $query->leftJoin('lingotek', 'lingo_progress_updated', 'n.nid = lingo_progress_updated.nid and lingo_progress_updated.lingokey LIKE \'target_sync_last_progress_updated_' . $language_filter . '\'');
  //      $query->addField('lingo_progress_updated', 'lingovalue', 'progress_updated');
  //
  //    // Last Downloaded Timestamp
  //    $query->leftJoin('lingotek', 'lingo_last_downloaded', 'n.nid = lingo_last_downloaded.nid and lingo_last_downloaded.lingokey LIKE \'target_last_downloaded_' . $language_filter . '\'');
  //      $query->addField('lingo_last_downloaded', 'lingovalue', 'last_downloaded');
  if (isset($filters['search_type']) && $filters['search_type'] == 'all') {
    $filters['title'] = $filters['body'] = $filters['search'];
  }
  if (isset($filters['title'])) {
    $title_query = db_select('field_data_title_field', 'tf')
      ->distinct()
      ->fields('tf', array(
      'entity_id',
    ))
      ->condition('tf.title_field_value', '%' . $filters['title'] . '%', 'LIKE');
  }
  if (isset($filters['body'])) {
    $body_query = db_select('field_data_body', 'tb')
      ->distinct()
      ->fields('tb', array(
      'entity_id',
    ))
      ->condition('tb.body_value', '%' . $filters['body'] . '%', 'LIKE');
  }

  // START FILTERS
  //  Search
  if (isset($filters['search_type']) && $filters['search_type'] == 'all' && isset($filters['search']) && strlen($filters['search'])) {
    $or = db_or();
    $or
      ->condition('n.title', '%' . $filters['search'] . '%', 'LIKE');

    // is this redundant when the following line is added?
    $or
      ->condition('n.nid', $title_query, 'IN');
    $or
      ->condition('n.nid', $body_query, 'IN');
    $query
      ->condition($or);
  }
  else {

    //  Title Field
    if (isset($filters['title']) && $filters['title'] != '') {
      $query
        ->condition('n.nid', $title_query, 'IN');
    }

    // Body Field
    if (isset($filters['body']) && $filters['body'] != '') {
      $query
        ->condition('n.nid', $body_query, 'IN');
    }
  }

  //  Node ID
  if (isset($filters['nid']) && $filters['nid'] != '') {
    $query
      ->condition('n.nid', $filters['nid']);
  }

  // Lingotek Document ID
  if (isset($filters['document_id']) && $filters['document_id'] != '') {
    if ($filters['document_id'] == 'None') {
      $query
        ->condition('lingo_document_id.lingovalue', NULL);
    }
    else {
      $query
        ->condition('lingo_document_id.lingovalue', $filters['document_id']);
    }
  }
  $array_fix = array(
    'upload_status',
    'content_type',
    'auto_upload',
    'auto_download',
    'crowdsourcing',
    'url_alias',
    'translation_status',
    'locale_progress_percent',
  );
  foreach ($array_fix as $value) {
    if (isset($filters[$value]) && !is_array($filters[$value])) {
      $filters[$value] = array(
        $filters[$value],
      );
    }
  }

  // Source Language
  if (isset($filters['source_language']) && $filters['source_language'] != 'all') {
    $query
      ->condition('n.language', $filters['source_language']);
  }

  // Upload Status
  if (isset($filters['upload_status']) && !in_array('all', $filters['upload_status'])) {
    $query
      ->condition('lingo_upload_status.lingovalue', $filters['upload_status'], 'IN');
  }

  //  Content Type
  if (isset($filters['content_type']) && !in_array('all', $filters['content_type'])) {
    $query
      ->condition('n.type', $filters['content_type'], 'IN');
  }
  if (isset($filters['profile']) && $filters['profile'] != 'all') {
    $or = lingotek_profile_condition('n', 'lingo_auto_upload', 'lingo_profile', $filters['profile']);
    $query
      ->condition($or);
  }

  // Last Uploaded
  if (isset($filters['last_uploaded']) && $filters['last_uploaded'] != 'all') {
    if ($filters['last_uploaded'] == '1 day') {
      $query
        ->condition('lingo_last_uploaded.lingovalue', strToTime($filters['last_uploaded']), '<');
    }
    elseif ($filters['last_uploaded'] == 'unknown') {
      $query
        ->condition('lingo_last_uploaded.lingovalue', NULL);
    }
    else {
      $params = explode(' ', $filters['last_uploaded'], 2);

      // string formatted like '< 1 week', so explode with a limit of two gives us array(0 => '<', 1 => '1 week')
      $query
        ->condition('lingo_last_uploaded.lingovalue', strToTime($params[1]), $params[0]);
    }
  }

  // Translation Progress Percentage
  //      if (isset($columns['translation_progress_percent']) && $columns['translation_progress_percent'] && isset($filters['translation_progress_percent']) && !in_array('all', $filters['translation_progress_percent'])) {
  //        $or = db_or();
  //        foreach ($filters['translation_progress_percent'] as $percent) {
  //          if ($percent == 0) {
  //            $or->condition('lingo_translation_progress_percent.lingovalue', $percent);
  //            $or->condition('lingo_translation_progress_percent.lingovalue', NULL);
  //          }
  //          else {
  //            $range = array((int)$percent - 24, (int)$percent);
  //            $or->where('CAST(lingo_translation_progress_percent.lingovalue as UNSIGNED) BETWEEN ' . $range[0] . '  AND ' . $range[1]);
  //          }
  //        }
  //        $query->condition($or);
  //      }
  // Translation Status
  //      if (isset($filters['translation_status']) && !empty($filters['translation_status']) && !in_array('all', $filters['translation_status'])) {
  //        $or = db_or();
  //        if (in_array('out_of_sync', $filters['translation_status'])) {
  //          $or->condition('lingo_upload_status.lingovalue', LingotekSync::STATUS_EDITED);
  //          $or->condition('lingo_translation_status.lingovalue', LingotekSync::STATUS_PENDING);
  //          $or->condition('lingo_translation_status.lingovalue', LingotekSync::STATUS_READY);
  //        }
  //        if (in_array('in_sync', $filters['translation_status'])) {
  //          $or->condition('lingo_translation_status.lingovalue', LingotekSync::STATUS_CURRENT);
  //        }
  //        if (in_array('disabled', $filters['translation_status'])) {
  //          $or->condition('lingo_translation_status.lingovalue', LingotekSync::STATUS_DISABLED);
  //        }
  //        $query->condition($or);
  //      }
  // Translation Progress
  //      if (isset($filters['translation_progress']) && !empty($filters['translation_progress']) && !in_array('all', $filters['translation_progress'])) {
  //        $or = db_or();
  //        if (in_array('needs_upload', $filters['translation_progress'])) {
  //          $or->condition('lingo_upload_status.lingovalue', LingotekSync::STATUS_EDITED);
  //        }
  //        if (in_array('in_progress', $filters['translation_progress'])) {
  //          $or->condition('lingo_translation_status.lingovalue', LingotekSync::STATUS_PENDING);
  //        }
  //        if (in_array('download', $filters['translation_progress'])) {
  //          $or->condition('lingo_translation_status.lingovalue', LingotekSync::STATUS_READY);
  //        }
  //        if (in_array('complete', $filters['translation_progress'])) {
  //          $or->condition('lingo_translation_status.lingovalue', LingotekSync::STATUS_CURRENT);
  //        }
  //        if (in_array('disabled', $filters['translation_progress'])) {
  //          $or->condition('lingo_translation_status.lingovalue', LingotekSync::STATUS_DISABLED);
  //        }
  //        $query->condition($or);
  //      }
  // Target Progress Percentage
  //      if (isset($filters['locale_progress_percent']) && !in_array('all', $filters['locale_progress_percent'])) {
  //        $or = db_or();
  //        foreach ($filters['locale_progress_percent'] as $percent) {
  //          if ($percent == 0) {
  //            $or->condition('lingo_locale_progress_percent.lingovalue', $percent);
  //            $or->condition('lingo_locale_progress_percent.lingovalue', NULL);
  //          }
  //          else {
  //            $range = array((int)$percent - 24, (int)$percent);
  //            $or->where('CAST(lingo_locale_progress_percent.lingovalue as UNSIGNED) BETWEEN ' . $range[0] . '  AND ' . $range[1]);
  //          }
  //        }
  //        $query->condition($or);
  //      }
  // Progress Last Updated
  if (isset($filters['progress_updated']) && $filters['progress_updated'] != 'all') {
    if ($filters['progress_updated'] == '1 day') {
      $query
        ->condition('lingo_progress_updated.lingovalue', strToTime($filters['progress_updated']), '<');
    }
    elseif ($filters['progress_updated'] == 'unknown') {
      $query
        ->condition('lingo_progress_updated.lingovalue', NULL);
    }
    else {
      $params = explode(' ', $filters['progress_updated'], 2);

      // string formatted like '< 1 week', so explode with a limit of two gives us array(0 => '<', 1 => '1 week')
      $query
        ->condition('lingo_progress_updated.lingovalue', strToTime($params[1]), $params[0]);
    }
  }

  // Last Downloaded
  if (isset($filters['last_downloaded']) && $filters['last_downloaded'] != 'all') {
    if ($filters['last_downloaded'] == '1 day') {
      $query
        ->condition('lingo_last_downloaded.lingovalue', strToTime($filters['last_downloaded']), '<');
    }
    elseif ($filters['last_downloaded'] == 'unknown') {
      $query
        ->condition('lingo_last_downloaded.lingovalue', NULL);
    }
    else {
      $params = explode(' ', $filters['last_downloaded'], 2);

      // string formatted like '< 1 week', so explode with a limit of two gives us array(0 => '<', 1 => '1 week')
      $query
        ->condition('lingo_last_downloaded.lingovalue', strToTime($params[1]), $params[0]);
    }
  }

  // END FILTERS
  // Execute the query
  $table_data_raw = $query
    ->execute()
    ->fetchAllAssoc('nid');
  lingotek_node_load($table_data_raw, array());
  $languages = language_list();
  $profiles = lingotek_get_profiles();
  $api = LingotekApi::instance();
  $workflows = $api
    ->listWorkflows();
  $types = _node_types_build()->types;

  // Parse returned objects and make them arrays keyed by the Node ID for clean use in The Grid.
  foreach ($table_data_raw as $row) {

    // RENAMING
    $title_truncate_length = 55;
    if (strlen($row->title) > $title_truncate_length) {

      // very long title names make The Grid look messy, so we truncate them.
      $dots = '...';
      $dots_length = strlen($dots);
      $row->title = substr($row->title, 0, $title_truncate_length - $dots_length) . $dots;
    }
    $icon = '';
    if ($row->lingotek['profile'] == LingotekSync::PROFILE_DISABLED) {
      $row->upload_status = LingotekSync::STATUS_DISABLED;
    }
    switch ($row->upload_status) {
      case LingotekSync::STATUS_EDITED:
        $icon = '<i class="fa fa-square-o" title="Needs to be uploaded"></i>';
        break;
      case LingotekSync::STATUS_CURRENT:
        $icon = '<i class="fa fa-check-square" title="Uploaded to Lingotek"></i>';
        break;
      case LingotekSync::STATUS_DISABLED:
        $icon = '<i class="fa fa-minus-square" style="color: #999;" title="Lingotek is disabled"></i>';
        break;
      default:
        $icon = '<i class="fa fa-minus-square" style="color: #999;" title="Unknown upload status"></i>';
        break;
    }
    $list_statuses = array(
      'pending',
      'ready',
      'current',
      'edited',
    );
    $locales = array();
    $statuses = array();
    foreach ($list_statuses as $status) {
      $key = 't_' . $status;
      foreach (explode(',', $row->{$key}) as $l) {
        if (!empty($l)) {
          $locales[] = $l;
          $statuses[] = $status;
        }
      }
    }
    array_multisort($locales, SORT_ASC, $statuses);
    $translations = lingotek_lang_icons($languages, $locales, $statuses, $row->nid, TRUE);
    $configuration = '';
    $disabled = $row->upload_status == LingotekSync::STATUS_DISABLED ? ' lingotek-disabled' : '';
    if ($row->lingotek['create_lingotek_document']) {
      $configuration .= '<i class="fa fa-arrow-up' . $disabled . '" title="Automatic Upload"></i> ';
    }
    if ($row->lingotek['sync_method']) {
      $configuration .= '<i class="fa fa-arrow-down' . $disabled . '" title="Automatic Download"></i> ';
    }
    if ($row->lingotek['allow_community_translation']) {
      $configuration .= '<i class="fa fa-globe' . $disabled . '" title="Crowdsourcing Enabled"></i> ';
    }
    if ($row->lingotek['profile'] == LingotekSync::PROFILE_CUSTOM) {
      $configuration = t('Custom') . ' <span class="node-configuration">' . $configuration . '</span>';
    }
    elseif ($row->lingotek['profile'] == LingotekSync::PROFILE_DISABLED) {
      $configuration = t('Disabled');
    }
    else {
      $profile_key = $row->lingotek['profile'];

      //dpm($profile_key); dpm($profiles);
      if (array_key_exists($profile_key, $profiles)) {
        $configuration = $profiles[$profile_key]['name'];
      }
      else {
        $configuration = t('Unknown');

        // profile id does not exist in profiles (this state should be unobtainable)
      }
    }
    $source = '<span class="lingotek-language-source" style="white-space: nowrap;">' . $icon . ' ' . ($row->language == 'und' ? t('None') : $languages[$row->language]->name) . '</span>';
    if (empty($row->upload_status) || $row->upload_status == LingotekSync::STATUS_EDITED) {
      $source = '<span title="Upload Now" class="ltk-upload-button lingotek-language-source" onclick="lingotek_perform_action(' . $row->nid . ',\'upload\')">' . $source . '</span>';
    }
    $pm_icon = ' <i title="' . t('View Translations') . '" class="fa fa-tasks' . $disabled . '"></i>';
    $pm_link = ' ' . l($pm_icon, 'node/' . $row->nid . '/lingotek_pm', array(
      'html' => TRUE,
      'attributes' => array(
        'target' => '_blank',
      ),
    ));
    $actions = '';
    $actions .= l('<i title="' . t('Edit Node') . '" class="fa fa-edit"></i>', 'node/' . $row->nid . '/edit', array(
      'html' => TRUE,
      'attributes' => array(
        'target' => '_blank',
      ),
    ));
    $actions .= ' ' . l('<i title="' . t('Set Lingotek Settings') . '" class="fa fa-gear"></i>', '', array(
      'html' => TRUE,
      'attributes' => array(
        'onclick' => 'lingotek_perform_action(' . $row->nid . ',"edit"); return false;',
      ),
    ));
    $actions .= empty($disabled) ? $pm_link : $pm_icon;
    $no_localized_title = language_default()->language != $language->language && $row->localized_title == '';

    // Build the data to be output for this row
    $data = array(
      'nid' => $row->nid ?: t('??'),
      'title' => $row->nid ? ($no_localized_title ? '<span class="no-localized-title">' : '') . l(isset($row->localized_title) ? $row->localized_title : $row->title, 'node/' . $row->nid, array(
        'attributes' => array(
          'target' => '_blank',
        ),
      )) . ($no_localized_title ? '</span>' : '') : t('N/A'),
      'language' => $source,
      'translations' => $translations,
      'configuration' => $configuration,
      'document_id' => $row->document_id ?: t('N/A'),
      'content_type' => $types[$row->type]->name ?: t('??'),
      //        'translation_status' => $row->upload_status ?: t('N/A'),
      //        'translation_progress' => $translation_progress ?: t('N/A'),
      'changed' => $row->changed ? t('@time ago', array(
        '@time' => lingotek_human_readable_timestamp($row->changed),
      )) : t('Never'),
      'last_uploaded' => $row->last_uploaded ? t('@time ago', array(
        '@time' => lingotek_human_readable_timestamp($row->last_uploaded),
      )) : t('Never'),
      //        'locale_progress_percent' => $row->locale_progress_percent ? lingotek_grid_create_progress_bar($row->locale_progress_percent) : lingotek_grid_create_progress_bar(0),
      //        'progress_updated' => $row->progress_updated ? t('@time ago', array('@time' => lingotek_human_readable_timestamp($row->progress_updated))) : t('Never'),
      //        'last_downloaded' => $row->last_downloaded ? t('@time ago', array('@time' => lingotek_human_readable_timestamp($row->last_downloaded))) : t('Never'),
      'workflow' => $row->workflow ? check_plain($workflows[$row->workflow]) : '',
      'actions' => '<span class="lingotek-node-actions">' . $actions . '</span>',
    );
    $table_data[$row->nid] = $data;
  }
  return $table_data;
}