1<?php
2
3/**
4 * @file
5 * Install, update and uninstall functions for the locale module.
6 */
7
8/**
9 * Implements hook_install().
10 */
11function locale_install() {
12  // locales_source.source and locales_target.target are not used as binary
13  // fields; non-MySQL database servers need to ensure the field type is text
14  // and that LIKE produces a case-sensitive comparison.
15
16  db_insert('languages')
17    ->fields(array(
18      'language' => 'en',
19      'name' => 'English',
20      'native' => 'English',
21      'direction' => 0,
22      'enabled' => 1,
23      'weight' => 0,
24      'javascript' => '',
25    ))
26    ->execute();
27}
28
29/**
30 * @addtogroup updates-6.x-to-7.x
31 * @{
32 */
33
34/**
35 * Add context field index and allow longer location.
36 */
37function locale_update_7000() {
38  db_drop_index('locales_source', 'source');
39  db_add_index('locales_source', 'source_context', array(array('source', 30), 'context'));
40
41  // Also drop the 'textgroup_location' index added by the i18nstrings module
42  // of the i18n project, which prevents the below schema update from running.
43  if (db_index_exists('locales_source', 'textgroup_location')) {
44    db_drop_index('locales_source', 'textgroup_location');
45  }
46
47  db_change_field('locales_source', 'location', 'location', array(
48    'type' => 'text',
49    'not null' => FALSE,
50    'size' => 'big',
51    'description' => 'Drupal path in case of online discovered translations or file path in case of imported strings.',
52  ));
53}
54
55/**
56 * Upgrade language negotiation settings.
57 */
58function locale_update_7001() {
59  require_once DRUPAL_ROOT . '/includes/language.inc';
60  require_once DRUPAL_ROOT . '/includes/locale.inc';
61  require_once DRUPAL_ROOT . '/modules/locale/locale.module';
62
63  switch (variable_get('language_negotiation', 0)) {
64    // LANGUAGE_NEGOTIATION_NONE.
65    case 0:
66      $negotiation = array();
67      break;
68
69    // LANGUAGE_NEGOTIATION_PATH_DEFAULT.
70    case 1:
71      $negotiation = array(LOCALE_LANGUAGE_NEGOTIATION_URL);
72      // In Drupal 6 path prefixes are shown for the default language only when
73      // language negotiation is set to LANGUAGE_NEGOTIATION_PATH, while in
74      // Drupal 7 path prefixes are always shown if not empty. Hence we need to
75      // ensure that the default language has an empty prefix to avoid breaking
76      // the site URLs with a prefix that previously was missing.
77      $default = language_default();
78      $default->prefix = '';
79      variable_set('language_default', $default);
80      db_update('languages')
81        ->fields(array('prefix' => $default->prefix))
82        ->condition('language', $default->language)
83        ->execute();
84      break;
85
86    // LANGUAGE_NEGOTIATION_PATH.
87    case 2:
88      $negotiation = array(LOCALE_LANGUAGE_NEGOTIATION_URL, LOCALE_LANGUAGE_NEGOTIATION_USER, LOCALE_LANGUAGE_NEGOTIATION_BROWSER);
89      break;
90
91    // LANGUAGE_NEGOTIATION_DOMAIN.
92    case 3:
93      variable_set('locale_language_negotiation_url_part', LOCALE_LANGUAGE_NEGOTIATION_URL_DOMAIN);
94      $negotiation = array(LOCALE_LANGUAGE_NEGOTIATION_URL);
95      break;
96  }
97
98  // Save the new language negotiation options.
99  language_negotiation_set(LANGUAGE_TYPE_INTERFACE, array_flip($negotiation));
100  language_negotiation_set(LANGUAGE_TYPE_CONTENT, array(LOCALE_LANGUAGE_NEGOTIATION_INTERFACE => 0));
101  language_negotiation_set(LANGUAGE_TYPE_URL, array(LOCALE_LANGUAGE_NEGOTIATION_URL => 0));
102
103  // Save admininstration UI settings.
104  $type = LANGUAGE_TYPE_INTERFACE;
105  $provider_weights = array_flip(array_keys(locale_language_negotiation_info()));
106  variable_set("locale_language_providers_weight_$type", $provider_weights);
107
108  // Unset the old language negotiation system variable.
109  variable_del('language_negotiation');
110
111  return array();
112}
113
114/**
115 * Updates URL language negotiation by adding the URL fallback detection method.
116 */
117function locale_update_7002() {
118  // language.inc may not have been included during bootstrap if there is not
119  // more than one language currently enabled.
120  require_once DRUPAL_ROOT . '/includes/language.inc';
121  $language_types_info = language_types_info();
122  $info = $language_types_info[LANGUAGE_TYPE_URL];
123  if (isset($info['fixed'])) {
124    language_negotiation_set(LANGUAGE_TYPE_URL, array_flip($info['fixed']));
125  }
126}
127
128/**
129 * @} End of "addtogroup updates-6.x-to-7.x".
130 */
131
132/**
133 * @addtogroup updates-7.x-extra
134 * @{
135 */
136
137/**
138 * Update "language_count" variable.
139 */
140function locale_update_7003() {
141  $languages = language_list('enabled');
142  variable_set('language_count', count($languages[1]));
143}
144
145/**
146 * Remove duplicates in {locales_source}.
147 */
148function locale_update_7004() {
149  // Look up all duplicates. For each set of duplicates, we select the row
150  // with the lowest lid as the "master" that will be preserved.
151  $result_source = db_query("SELECT MIN(lid) AS lid, source, context FROM {locales_source} WHERE textgroup = 'default' GROUP BY source, context HAVING COUNT(*) > 1");
152
153  $conflict = FALSE;
154  foreach ($result_source as $source) {
155    // Find all rows in {locales_target} that are translations of the same
156    // string (incl. context).
157    $result_target = db_query("SELECT t.lid, t.language, t.plural, t.translation FROM {locales_source} s JOIN {locales_target} t ON s.lid = t.lid WHERE s.source = :source AND s.context = :context AND s.textgroup = 'default' ORDER BY lid", array(
158      ':source' => $source->source,
159      ':context' => $source->context,
160    ));
161
162    $translations = array();
163    $keep_lids = array($source->lid);
164    foreach ($result_target as $target) {
165      if (!isset($translations[$target->language])) {
166        $translations[$target->language] = $target->translation;
167        if ($target->lid != $source->lid) {
168          // Move translation to the master lid.
169          db_query('UPDATE {locales_target} SET lid = :new_lid WHERE lid = :old_lid', array(
170            ':new_lid' => $source->lid,
171            ':old_lid' => $target->lid));
172        }
173      }
174      elseif ($translations[$target->language] == $target->translation) {
175        // Delete duplicate translation.
176        db_query('DELETE FROM {locales_target} WHERE lid = :lid AND language = :language', array(
177          ':lid' => $target->lid,
178          ':language' => $target->language));
179      }
180      else {
181        // The same string is translated into several different strings in one
182        // language. We do not know which is the preferred, so we keep them all.
183        $keep_lids[] = $target->lid;
184        $conflict = TRUE;
185      }
186    }
187
188    // Delete rows in {locales_source} that are no longer referenced from
189    // {locales_target}.
190    db_delete('locales_source')
191      ->condition('source', $source->source)
192      ->condition('context', $source->context)
193      ->condition('textgroup', 'default')
194      ->condition('lid', $keep_lids, 'NOT IN')
195      ->execute();
196  }
197
198  if ($conflict) {
199    $url = 'http://drupal.org/node/746240';
200    drupal_set_message('Your {locales_source} table contains duplicates that could not be removed automatically. See <a href="' . $url .'" target="_blank">' . $url . '</a> for more information.', 'warning');
201  }
202}
203
204/**
205 * Increase {locales_languages}.formula column's length.
206 */
207function locale_update_7005() {
208  db_change_field('languages', 'formula', 'formula', array(
209    'type' => 'varchar',
210    'length' => 255,
211    'not null' => TRUE,
212    'default' => '',
213    'description' => 'Plural formula in PHP code to evaluate to get plural indexes.',
214  ));
215}
216
217/**
218 * @} End of "addtogroup updates-7.x-extra".
219 */
220
221/**
222 * Implements hook_uninstall().
223 */
224function locale_uninstall() {
225  // Delete all JavaScript translation files.
226  $locale_js_directory = 'public://' . variable_get('locale_js_directory', 'languages');
227
228  if (is_dir($locale_js_directory)) {
229    $files = db_query('SELECT language, javascript FROM {languages}');
230    foreach ($files as $file) {
231      if (!empty($file->javascript)) {
232        file_unmanaged_delete($locale_js_directory . '/' . $file->language . '_' . $file->javascript . '.js');
233      }
234    }
235    // Delete the JavaScript translations directory if empty.
236    if (!file_scan_directory($locale_js_directory, '/.*/')) {
237      drupal_rmdir($locale_js_directory);
238    }
239  }
240
241  // Clear variables.
242  variable_del('language_default');
243  variable_del('language_count');
244  variable_del('language_types');
245  variable_del('locale_language_negotiation_url_part');
246  variable_del('locale_language_negotiation_session_param');
247  variable_del('language_content_type_default');
248  variable_del('language_content_type_negotiation');
249  variable_del('locale_cache_strings');
250  variable_del('locale_js_directory');
251  variable_del('javascript_parsed');
252  variable_del('locale_field_language_fallback');
253  variable_del('locale_cache_length');
254
255  foreach (language_types() as $type) {
256    variable_del("language_negotiation_$type");
257    variable_del("locale_language_providers_weight_$type");
258  }
259
260  foreach (node_type_get_types() as $type => $content_type) {
261    $setting = variable_del("language_content_type_$type");
262  }
263
264  // Switch back to English: with a $language->language value different from 'en'
265  // successive calls of t() might result in calling locale(), which in turn might
266  // try to query the unexisting {locales_source} and {locales_target} tables.
267  drupal_language_initialize();
268
269}
270
271/**
272 * Implements hook_schema().
273 */
274function locale_schema() {
275  $schema['languages'] = array(
276    'description' => 'List of all available languages in the system.',
277    'fields' => array(
278      'language' => array(
279        'type' => 'varchar',
280        'length' => 12,
281        'not null' => TRUE,
282        'default' => '',
283        'description' => "Language code, e.g. 'de' or 'en-US'.",
284      ),
285      'name' => array(
286        'type' => 'varchar',
287        'length' => 64,
288        'not null' => TRUE,
289        'default' => '',
290        'description' => 'Language name in English.',
291      ),
292      'native' => array(
293        'type' => 'varchar',
294        'length' => 64,
295        'not null' => TRUE,
296        'default' => '',
297        'description' => 'Native language name.',
298      ),
299      'direction' => array(
300        'type' => 'int',
301        'not null' => TRUE,
302        'default' => 0,
303        'description' => 'Direction of language (Left-to-Right = 0, Right-to-Left = 1).',
304      ),
305      'enabled' => array(
306        'type' => 'int',
307        'not null' => TRUE,
308        'default' => 0,
309        'description' => 'Enabled flag (1 = Enabled, 0 = Disabled).',
310      ),
311      'plurals' => array(
312        'type' => 'int',
313        'not null' => TRUE,
314        'default' => 0,
315        'description' => 'Number of plural indexes in this language.',
316      ),
317      'formula' => array(
318        'type' => 'varchar',
319        'length' => 255,
320        'not null' => TRUE,
321        'default' => '',
322        'description' => 'Plural formula in PHP code to evaluate to get plural indexes.',
323      ),
324      'domain' => array(
325        'type' => 'varchar',
326        'length' => 128,
327        'not null' => TRUE,
328        'default' => '',
329        'description' => 'Domain to use for this language.',
330      ),
331      'prefix' => array(
332        'type' => 'varchar',
333        'length' => 128,
334        'not null' => TRUE,
335        'default' => '',
336        'description' => 'Path prefix to use for this language.',
337      ),
338      'weight' => array(
339        'type' => 'int',
340        'not null' => TRUE,
341        'default' => 0,
342        'description' => 'Weight, used in lists of languages.',
343      ),
344      'javascript' => array(
345        'type' => 'varchar',
346        'length' => 64,
347        'not null' => TRUE,
348        'default' => '',
349        'description' => 'Location of JavaScript translation file.',
350      ),
351    ),
352    'primary key' => array('language'),
353    'indexes' => array(
354      'list' => array('weight', 'name'),
355    ),
356  );
357
358  $schema['locales_source'] = array(
359    'description' => 'List of English source strings.',
360    'fields' => array(
361      'lid' => array(
362        'type' => 'serial',
363        'not null' => TRUE,
364        'description' => 'Unique identifier of this string.',
365      ),
366      'location' => array(
367        'type' => 'text',
368        'not null' => FALSE,
369        'size' => 'big',
370        'description' => 'Drupal path in case of online discovered translations or file path in case of imported strings.',
371      ),
372      'textgroup' => array(
373        'type' => 'varchar',
374        'length' => 255,
375        'not null' => TRUE,
376        'default' => 'default',
377        'description' => 'A module defined group of translations, see hook_locale().',
378      ),
379      'source' => array(
380        'type' => 'text',
381        'mysql_type' => 'blob',
382        'not null' => TRUE,
383        'description' => 'The original string in English.',
384      ),
385      'context' => array(
386        'type' => 'varchar',
387        'length' => 255,
388        'not null' => TRUE,
389        'default' => '',
390        'description' => 'The context this string applies to.',
391      ),
392      'version' => array(
393        'type' => 'varchar',
394        'length' => 20,
395        'not null' => TRUE,
396        'default' => 'none',
397        'description' => 'Version of Drupal, where the string was last used (for locales optimization).',
398      ),
399    ),
400    'primary key' => array('lid'),
401    'indexes' => array(
402      'source_context' => array(array('source', 30), 'context'),
403    ),
404  );
405
406  $schema['locales_target'] = array(
407    'description' => 'Stores translated versions of strings.',
408    'fields' => array(
409      'lid' => array(
410        'type' => 'int',
411        'not null' => TRUE,
412        'default' => 0,
413        'description' => 'Source string ID. References {locales_source}.lid.',
414      ),
415      'translation' => array(
416        'type' => 'text',
417        'mysql_type' => 'blob',
418        'not null' => TRUE,
419        'description' => 'Translation string value in this language.',
420      ),
421      'language' => array(
422        'type' => 'varchar',
423        'length' => 12,
424        'not null' => TRUE,
425        'default' => '',
426        'description' => 'Language code. References {languages}.language.',
427      ),
428      'plid' => array(
429        'type' => 'int',
430        'not null' => TRUE, // This should be NULL for no referenced string, not zero.
431        'default' => 0,
432        'description' => 'Parent lid (lid of the previous string in the plural chain) in case of plural strings. References {locales_source}.lid.',
433      ),
434      'plural' => array(
435        'type' => 'int',
436        'not null' => TRUE,
437        'default' => 0,
438        'description' => 'Plural index number in case of plural strings.',
439      ),
440    ),
441    'primary key' => array('language', 'lid', 'plural'),
442    'foreign keys' => array(
443      'locales_source' => array(
444        'table' => 'locales_source',
445        'columns' => array('lid' => 'lid'),
446      ),
447    ),
448    'indexes' => array(
449      'lid'      => array('lid'),
450      'plid'     => array('plid'),
451      'plural'   => array('plural'),
452    ),
453  );
454
455  return $schema;
456}
457
458