content_translation.admin.inc 14.9 KB
Newer Older
1 2 3 4
<?php

/**
 * @file
5
 * The content translation administration forms.
6 7
 */

8
use Drupal\Component\Utility\String;
9
use Drupal\Core\Field\FieldDefinitionInterface;
10
use Drupal\Core\Language\Language;
11
use Drupal\Core\Render\Element;
12
use Drupal\field\Entity\FieldConfig;
13
use Drupal\field\FieldInstanceConfigInterface;
14

15
/**
16
 * Returns a form element to configure field synchronization.
17
 *
18
 * @param \Drupal\Core\Field\FieldDefinitionInterface $field
19
 *   A field definition object.
20 21 22 23
 *
 * @return array
 *   A form element to configure field synchronization.
 */
24
function content_translation_field_sync_widget(FieldDefinitionInterface $field) {
25 26
  $element = array();

27 28
  $definition = \Drupal::service('plugin.manager.field.field_type')->getDefinition($field->getType());
  $column_groups = $definition['column_groups'];
29
  if (!empty($column_groups) && count($column_groups) > 1) {
30 31 32
    $options = array();
    $default = array();

33
    foreach ($column_groups as $group => $info) {
34 35 36 37
      $options[$group] = $info['label'];
      $default[$group] = !empty($info['translatable']) ? $group : FALSE;
    }

38 39
    $settings = array('dependent_selectors' => array('instance[settings][translation_sync]' => array('file')));

40
    $translation_sync = $field->getSetting('translation_sync');
41 42 43 44
    $element = array(
      '#type' => 'checkboxes',
      '#title' => t('Translatable elements'),
      '#options' => $options,
45
      '#default_value' => !empty($translation_sync) ? $translation_sync : $default,
46
      '#attached' => array(
47
        'library' => array(
48
          'content_translation/drupal.content_translation.admin',
49
        ),
50
        'js' => array(
51
          array('data' => array('contentTranslationDependentOptions' => $settings), 'type' => 'setting'),
52 53
        ),
      ),
54 55 56 57 58 59 60 61
    );
  }

  return $element;
}

/**
 * (proxied) Implements hook_form_FORM_ID_alter().
62
 */
63
function _content_translation_form_language_content_settings_form_alter(array &$form, array &$form_state) {
64 65
  // Inject into the content language settings the translation settings if the
  // user has the required permission.
66
  if (!user_access('administer content translation')) {
67 68 69 70
    return;
  }

  $default = $form['entity_types']['#default_value'];
71 72
  foreach ($default as $entity_type_id => $enabled) {
    $default[$entity_type_id] = $enabled || content_translation_enabled($entity_type_id) ? $entity_type_id : FALSE;
73 74 75
  }
  $form['entity_types']['#default_value'] = $default;

76
  $form['#attached']['library'][] = 'content_translation/drupal.content_translation.admin';
77
  $form['#attached']['js'][] = array('data' => drupal_get_path('module', 'content_translation') . '/content_translation.admin.js', 'type' => 'file');
78

79
  $dependent_options_settings = array();
80
  $entity_manager = Drupal::entityManager();
81 82 83
  foreach ($form['#labels'] as $entity_type_id => $label) {
    $entity_type = \Drupal::entityManager()->getDefinition($entity_type_id);
    foreach (entity_get_bundles($entity_type_id) as $bundle => $bundle_info) {
84 85 86
      // Here we do not want the widget to be altered and hold also the "Enable
      // translation" checkbox, which would be redundant. Hence we add this key
      // to be able to skip alterations.
87
      $form['settings'][$entity_type_id][$bundle]['settings']['language']['#content_translation_skip_alter'] = TRUE;
88

89 90
      // Only show the checkbox to enable translation if the bundles in the
      // entity might have fields and if there are fields to translate.
91 92
      if ($entity_type->isFieldable()) {
        $fields = $entity_manager->getFieldDefinitions($entity_type_id, $bundle);
93
        if ($fields) {
94
          $form['settings'][$entity_type_id][$bundle]['translatable'] = array(
95
            '#type' => 'checkbox',
96
            '#default_value' => content_translation_enabled($entity_type_id, $bundle),
97 98
          );

99
          $field_settings = content_translation_get_config($entity_type_id, $bundle, 'fields');
100 101 102 103 104 105 106
          foreach ($fields as $field_name => $definition) {
            $translatable = !empty($field_settings[$field_name]);

            // We special case Field API fields as they always natively support
            // translation.
            // @todo Remove this special casing as soon as configurable and
            //   base field definitions are "unified".
107
            if ($definition instanceof FieldInstanceConfigInterface) {
108
              $form['settings'][$entity_type_id][$bundle]['fields'][$field_name] = array(
109
                '#label' => $definition->getLabel(),
110 111 112
                '#type' => 'checkbox',
                '#default_value' => $translatable,
              );
113
              $column_element = content_translation_field_sync_widget($definition);
114
              if ($column_element) {
115
                $form['settings'][$entity_type_id][$bundle]['columns'][$field_name] = $column_element;
116 117 118

                // @todo This should not concern only files.
                if (isset($column_element['#options']['file'])) {
119
                  $dependent_options_settings["settings[{$entity_type_id}][{$bundle}][columns][{$field_name}]"] = array('file');
120
                }
121 122
              }
            }
123 124 125 126
            // Instead we need to rely on field definitions to determine whether
            // fields support translation. Whether they are actually enabled is
            // determined through our settings. As a consequence only fields
            // that support translation can be enabled or disabled.
127
            elseif (isset($field_settings[$field_name]) || $definition->isTranslatable()) {
128
              $form['settings'][$entity_type_id][$bundle]['fields'][$field_name] = array(
129
                '#label' => $definition->getLabel(),
130 131 132 133
                '#type' => 'checkbox',
                '#default_value' => $translatable,
              );
            }
134
          }
135
        }
136 137 138
      }
    }
  }
139
  $settings = array('dependent_selectors' => $dependent_options_settings);
140 141 142
  $form['#attached']['js'][] = array('data' => array('contentTranslationDependentOptions' => $settings), 'type' => 'setting');
  $form['#validate'][] = 'content_translation_form_language_content_settings_validate';
  $form['#submit'][] = 'content_translation_form_language_content_settings_submit';
143 144
}

145 146 147
/**
 * (proxied) Implements hook_preprocess_HOOK();
 */
148
function _content_translation_preprocess_language_content_settings_table(&$variables) {
149 150
  // Alter the 'build' variable injecting the translation settings if the user
  // has the required permission.
151
  if (!user_access('administer content translation')) {
152 153 154 155 156 157 158 159 160
    return;
  }

  $element = $variables['element'];
  $build = &$variables['build'];

  array_unshift($build['#header'], array('data' => t('Translatable'), 'class' => array('translatable')));
  $rows = array();

161 162
  foreach (Element::children($element) as $bundle) {
    $field_names = !empty($element[$bundle]['fields']) ? Element::children($element[$bundle]['fields']) : array();
163 164 165
    if (!empty($element[$bundle]['translatable'])) {
      $checkbox_id = $element[$bundle]['translatable']['#id'];
    }
166 167
    $rows[$bundle] = $build['#rows'][$bundle];

168 169 170 171 172 173
    if (!empty($element[$bundle]['translatable'])) {
      $translatable = array(
        'data' => $element[$bundle]['translatable'],
        'class' => array('translatable'),
      );
      array_unshift($rows[$bundle]['data'], $translatable);
174

175 176 177 178 179 180 181 182
      $rows[$bundle]['data'][1]['data']['#prefix'] = '<label for="' . $checkbox_id . '">';
    }
    else {
      $translatable = array(
        'class' => array('untranslatable'),
      );
      array_unshift($rows[$bundle]['data'], $translatable);
    }
183 184 185 186 187 188 189 190 191 192 193 194 195 196

    foreach ($field_names as $field_name) {
      $field_element = &$element[$bundle]['fields'][$field_name];
      $rows[] = array(
        'data' => array(
          array(
            'data' => drupal_render($field_element),
            'class' => array('translatable'),
          ),
          array(
            'data' => array(
              '#prefix' => '<label for="' . $field_element['#id'] . '">',
              '#suffix' => '</label>',
              'bundle' => array(
197
                '#prefix' => '<span class="visually-hidden">',
198
                '#suffix' => '</span> ',
199
                '#markup' => String::checkPlain($element[$bundle]['settings']['#label']),
200 201
              ),
              'field' => array(
202
                '#markup' => String::checkPlain($field_element['#label']),
203 204 205 206 207 208 209 210 211 212 213 214 215 216
              ),
            ),
            'class' => array('field'),
          ),
          array(
            'data' => '',
            'class' => array('operations'),
          ),
        ),
        'class' => array('field-settings'),
      );

      if (!empty($element[$bundle]['columns'][$field_name])) {
        $column_element = &$element[$bundle]['columns'][$field_name];
217
        foreach (Element::children($column_element) as $key) {
218 219 220 221 222 223 224 225 226 227 228 229 230
          $column_label = $column_element[$key]['#title'];
          unset($column_element[$key]['#title']);
          $rows[] = array(
            'data' => array(
              array(
                'data' => drupal_render($column_element[$key]),
                'class' => array('translatable'),
              ),
              array(
                'data' => array(
                  '#prefix' => '<label for="' . $column_element[$key]['#id'] . '">',
                  '#suffix' => '</label>',
                  'bundle' => array(
231
                    '#prefix' => '<span class="visually-hidden">',
232
                    '#suffix' => '</span> ',
233
                    '#markup' => String::checkPlain($element[$bundle]['settings']['#label']),
234 235
                  ),
                  'field' => array(
236
                    '#prefix' => '<span class="visually-hidden">',
237
                    '#suffix' => '</span> ',
238
                    '#markup' => String::checkPlain($field_element['#label']),
239 240
                  ),
                  'columns' => array(
241
                    '#markup' => String::checkPlain($column_label),
242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260
                  ),
                ),
                'class' => array('column'),
              ),
              array(
                'data' => '',
                'class' => array('operations'),
              ),
            ),
            'class' => array('column-settings'),
          );
        }
      }
    }
  }

  $build['#rows'] = $rows;
}

261
/**
262
 * Form validation handler for content_translation_admin_settings_form().
263
 *
264
 * @see content_translation_admin_settings_form_submit()
265
 */
266
function content_translation_form_language_content_settings_validate(array $form, array &$form_state) {
267 268 269 270 271 272 273 274 275
  $settings = &$form_state['values']['settings'];
  foreach ($settings as $entity_type => $entity_settings) {
    foreach ($entity_settings as $bundle => $bundle_settings) {
      if (!empty($bundle_settings['translatable'])) {
        $name = "settings][$entity_type][$bundle][translatable";

        $translatable_fields = isset($settings[$entity_type][$bundle]['fields']) ? array_filter($settings[$entity_type][$bundle]['fields']) : FALSE;
        if (empty($translatable_fields)) {
          $t_args = array('%bundle' => $form['settings'][$entity_type][$bundle]['settings']['#label']);
276
          form_set_error($name, $form_state, t('At least one field needs to be translatable to enable %bundle for translation.', $t_args));
277 278 279
        }

        $values = $bundle_settings['settings']['language'];
280
        if (empty($values['language_show']) && \Drupal::languageManager()->isLanguageLocked($values['langcode'])) {
281
          foreach (\Drupal::languageManager()->getLanguages(Language::STATE_LOCKED) as $language) {
282 283
            $locked_languages[] = $language->name;
          }
284
          form_set_error($name, $form_state, t('Translation is not supported if language is always one of: @locked_languages', array('@locked_languages' => implode(', ', $locked_languages))));
285 286 287 288 289 290 291
        }
      }
    }
  }
}

/**
292
 * Form submission handler for content_translation_admin_settings_form().
293
 *
294
 * @see content_translation_admin_settings_form_validate()
295
 */
296
function content_translation_form_language_content_settings_submit(array $form, array &$form_state) {
297 298 299 300 301 302 303
  $entity_types = $form_state['values']['entity_types'];
  $settings = &$form_state['values']['settings'];

  // If an entity type is not translatable all its bundles and fields must be
  // marked as non-translatable. Similarly, if a bundle is made non-translatable
  // all of its fields will be not translatable.
  foreach ($settings as $entity_type => &$entity_settings) {
304
    foreach ($entity_settings as &$bundle_settings) {
305 306 307
      if (!empty($bundle_settings['translatable'])) {
        $bundle_settings['translatable'] = $bundle_settings['translatable'] && $entity_types[$entity_type];
      }
308 309 310
      if (!empty($bundle_settings['fields'])) {
        foreach ($bundle_settings['fields'] as $field_name => $translatable) {
          $bundle_settings['fields'][$field_name] = $translatable && $bundle_settings['translatable'];
311 312 313 314 315
          // If we have column settings and no column is translatable, no point
          // in making the field translatable.
          if (isset($bundle_settings['columns'][$field_name]) && !array_filter($bundle_settings['columns'][$field_name])) {
            $bundle_settings['fields'][$field_name] = FALSE;
          }
316 317 318 319 320
        }
      }
    }
  }

321
  _content_translation_update_field_translatability($settings);
322 323 324 325
  drupal_set_message(t('Settings successfully updated.'));
}

/**
326
 * Stores content translation settings.
327 328 329 330 331 332 333 334 335 336
 *
 * @param array $settings
 *   An associative array of settings keyed by entity type and bundle. At bundle
 *   level the following keys are available:
 *   - translatable: The bundle translatability status, which is a bool.
 *   - settings: An array of language configuration settings as defined by
 *     language_save_default_configuration().
 *   - fields: An associative array with field names as keys and a boolean as
 *     value, indicating field translatability.
 */
337
function _content_translation_update_field_translatability($settings) {
338 339 340
  // Update translatability for configurable fields.
  // @todo Remove this once configurable fields rely on entity field info to
  //   track translatability. See https://drupal.org/node/2018685.
341
  foreach ($settings as $entity_type => $entity_settings) {
342
    $fields = array();
343
    foreach ($entity_settings as $bundle_settings) {
344 345 346 347 348
      // Collapse field settings since here we have per instance settings, but
      // translatability has per-field scope. We assume that all the field
      // instances have the same value.
      if (!empty($bundle_settings['fields'])) {
        foreach ($bundle_settings['fields'] as $field_name => $translatable) {
349 350
          // If translatability changes for at least one field instance we need
          // to switch field translatability.
351
          $field = FieldConfig::loadByName($entity_type, $field_name);
352
          if ($field && $field->isTranslatable() !== $translatable) {
353
            $fields[$field_name] = $translatable;
354
          }
355 356 357
        }
      }
    }
358 359
    // Store updated fields.
    foreach ($fields as $field_name => $translatable) {
360
      $field = FieldConfig::loadByName($entity_type, $field_name);
361 362 363
      $field->translatable = $translatable;
      $field->save();
    }
364 365
  }

366
  content_translation_save_settings($settings);
367
}