[ Index ]

PHP Cross Reference of Drupal 6 (yi-drupal)

title

Body

[close]

/modules/translation/ -> translation.module (source)

   1  <?php
   2  
   3  /**
   4   * @file
   5   *   Manages content translations.
   6   *
   7   *   Translations are managed in sets of posts, which represent the same
   8   *   information in different languages. Only content types for which the
   9   *   administrator explicitly enabled translations could have translations
  10   *   associated. Translations are managed in sets with exactly one source
  11   *   post per set. The source post is used to translate to different
  12   *   languages, so if the source post is significantly updated, the
  13   *   editor can decide to mark all translations outdated.
  14   *
  15   *   The node table stores the values used by this module:
  16   *    - 'tnid' is the translation set id, which equals the node id
  17   *      of the source post.
  18   *    - 'translate' is a flag, either indicating that the translation
  19   *      is up to date (0) or needs to be updated (1).
  20   */
  21  
  22  /**
  23   * Identifies a content type which has translation support enabled.
  24   */
  25  define('TRANSLATION_ENABLED', 2);
  26  
  27  /**
  28   * Implementation of hook_help().
  29   */
  30  function translation_help($path, $arg) {
  31    switch ($path) {
  32      case 'admin/help#translation':
  33        $output = '<p>'. t('The content translation module allows content to be translated into different languages. Working with the <a href="@locale">locale module</a> (which manages enabled languages and provides translation for the site interface), the content translation module is key to creating and maintaining translated site content.', array('@locale' => url('admin/help/locale'))) .'</p>';
  34        $output .= '<p>'. t('Configuring content translation and translation-enabled content types:') .'</p>';
  35        $output .= '<ul><li>'. t('Assign the <em>translate content</em> permission to the appropriate user roles at the <a href="@permissions">Permissions configuration page</a>.', array('@permissions' => url('admin/user/permissions'))) .'</li>';
  36        $output .= '<li>'. t('Add and enable desired languages at the <a href="@languages">Languages configuration page</a>.', array('@languages' => url('admin/settings/language'))) .'</li>';
  37        $output .= '<li>'. t('Determine which <a href="@content-types">content types</a> should support translation features. To enable translation support for a content type, edit the type and at the <em>Multilingual support</em> drop down, select <em>Enabled, with translation</em>. (<em>Multilingual support</em> is located within <em>Workflow settings</em>.) Be sure to save each content type after enabling multilingual support.', array('@content-types' => url('admin/content/types'))) .'</li></ul>';
  38        $output .= '<p>'. t('Working with translation-enabled content types:') .'</p>';
  39        $output .= '<ul><li>'. t('Use the <em>Language</em> drop down to select the appropriate language when creating or editing posts.') .'</li>';
  40        $output .= '<li>'. t('Provide new or edit current translations for existing posts via the <em>Translation</em> tab. Only visible while viewing a post as a user with the <em>translate content</em> permission, this tab allows translations to be added or edited using a specialized editing form that also displays the content being translated.') .'</li>';
  41        $output .= '<li>'. t('Update translations as needed, so that they accurately reflect changes in the content of the original post. The translation status flag provides a simple method for tracking outdated translations. After editing a post, for example, select the <em>Flag translations as outdated</em> check box to mark all of its translations as outdated and in need of revision. Individual translations may be marked for revision by selecting the <em>This translation needs to be updated</em> check box on the translation editing form.') .'</li>';
  42        $output .= '<li>'. t('The <a href="@content-node">Content management administration page</a> displays the language of each post, and also allows filtering by language or translation status.', array('@content-node' => url('admin/content/node'))) .'</li></ul>';
  43        $output .= '<p>'. t('Use the <a href="@blocks">language switcher block</a> provided by locale module to allow users to select a language. If available, both the site interface and site content are presented in the language selected.', array('@blocks' => url('admin/build/block'))) .'</p>';
  44        $output .= '<p>'. t('For more information, see the online handbook entry for <a href="@translation">Translation module</a>.', array('@translation' => 'http://drupal.org/handbook/modules/translation/')) .'</p>';
  45        return $output;
  46      case 'node/%/translate':
  47        $output = '<p>'. t('Translations of a piece of content are managed with translation sets. Each translation set has one source post and any number of translations in any of the <a href="!languages">enabled languages</a>. All translations are tracked to be up to date or outdated based on whether the source post was modified significantly.', array('!languages' => url('admin/settings/language'))) .'</p>';
  48        return $output;
  49    }
  50  }
  51  
  52  /**
  53   * Implementation of hook_menu().
  54   */
  55  function translation_menu() {
  56    $items = array();
  57    $items['node/%node/translate'] = array(
  58      'title' => 'Translate',
  59      'page callback' => 'translation_node_overview',
  60      'page arguments' => array(1),
  61      'access callback' => '_translation_tab_access',
  62      'access arguments' => array(1),
  63      'type' => MENU_LOCAL_TASK,
  64      'weight' => 2,
  65      'file' => 'translation.pages.inc',
  66    );
  67    return $items;
  68  }
  69  
  70  /**
  71   * Menu access callback.
  72   *
  73   * Only display translation tab for node types, which have translation enabled
  74   * and where the current node is not language neutral (which should span
  75   * all languages).
  76   */
  77  function _translation_tab_access($node) {
  78    return !empty($node->language) && translation_supported_type($node->type) && node_access('view', $node) && user_access('translate content');
  79  }
  80  
  81  /**
  82   * Implementation of hook_perm().
  83   */
  84  function translation_perm() {
  85    return array('translate content');
  86  }
  87  
  88  /**
  89   * Implementation of hook_form_alter().
  90   *
  91   * - Add translation option to content type form.
  92   * - Alters language fields on node forms when a translation
  93   *   is about to be created.
  94   */
  95  function translation_form_alter(&$form, $form_state, $form_id) {
  96    if ($form_id == 'node_type_form') {
  97      // Add translation option to content type form.
  98      $form['workflow']['language_content_type']['#options'][TRANSLATION_ENABLED] = t('Enabled, with translation');
  99      // Description based on text from locale.module.
 100      $form['workflow']['language_content_type']['#description'] = t('Enable multilingual support for this content type. If enabled, a language selection field will be added to the editing form, allowing you to select from one of the <a href="!languages">enabled languages</a>. You can also turn on translation for this content type, which lets you have content translated to any of the enabled languages. If disabled, new posts are saved with the default language. Existing content will not be affected by changing this option.', array('!languages' => url('admin/settings/language')));
 101    }
 102    elseif (isset($form['#id']) && $form['#id'] == 'node-form' && translation_supported_type($form['#node']->type)) {
 103      $node = $form['#node'];
 104      if (!empty($node->translation_source)) {
 105        // We are creating a translation. Add values and lock language field.
 106        $form['translation_source'] = array('#type' => 'value', '#value' => $node->translation_source);
 107        $form['language']['#disabled'] = TRUE;
 108      }
 109      elseif (!empty($node->nid) && !empty($node->tnid)) {
 110        // Disable languages for existing translations, so it is not possible to switch this
 111        // node to some language which is already in the translation set. Also remove the
 112        // language neutral option.
 113        unset($form['language']['#options']['']);
 114        foreach (translation_node_get_translations($node->tnid) as $translation) {
 115          if ($translation->nid != $node->nid) {
 116            unset($form['language']['#options'][$translation->language]);
 117          }
 118        }
 119        // Add translation values and workflow options.
 120        $form['tnid'] = array('#type' => 'value', '#value' => $node->tnid);
 121        $form['translation'] = array(
 122          '#type' => 'fieldset',
 123          '#title' => t('Translation settings'),
 124          '#access' => user_access('translate content'),
 125          '#collapsible' => TRUE,
 126          '#collapsed' => !$node->translate,
 127          '#tree' => TRUE,
 128          '#weight' => 30,
 129        );
 130        if ($node->tnid == $node->nid) {
 131          // This is the source node of the translation
 132          $form['translation']['retranslate'] = array(
 133            '#type' => 'checkbox',
 134            '#title' => t('Flag translations as outdated'),
 135            '#default_value' => 0,
 136            '#description' => t('If you made a significant change, which means translations should be updated, you can flag all translations of this post as outdated. This will not change any other property of those posts, like whether they are published or not.'),
 137          );
 138          $form['translation']['status'] = array('#type' => 'value', '#value' => 0);
 139        }
 140        else {
 141          $form['translation']['status'] = array(
 142            '#type' => 'checkbox',
 143            '#title' => t('This translation needs to be updated'),
 144            '#default_value' => $node->translate,
 145            '#description' => t('When this option is checked, this translation needs to be updated because the source post has changed. Uncheck when the translation is up to date again.'),
 146          );
 147        }
 148      }
 149    }
 150  }
 151  
 152  /**
 153   * Implementation of hook_link().
 154   *
 155   * Display translation links with native language names, if this node
 156   * is part of a translation set.
 157   */
 158  function translation_link($type, $node = NULL, $teaser = FALSE) {
 159    $links = array();
 160    if ($type == 'node' && ($node->tnid) && $translations = translation_node_get_translations($node->tnid)) {
 161      // Do not show link to the same node.
 162      unset($translations[$node->language]);
 163      $languages = language_list();
 164      foreach ($languages as $langcode => $language) {
 165        if (isset($translations[$langcode]) && $translations[$langcode]->status) {
 166          $links["node_translation_$langcode"] = array(
 167            'title' => $language->native,
 168            'href' => 'node/'. $translations[$langcode]->nid,
 169            'language' => $language,
 170            'attributes' => array('title' => $translations[$langcode]->title, 'class' => 'translation-link')
 171          );
 172        }
 173      }
 174    }
 175    return $links;
 176  }
 177  
 178  /**
 179   * Implementation of hook_nodeapi().
 180   *
 181   * Manages translation information for nodes.
 182   */
 183  function translation_nodeapi(&$node, $op, $teaser = NULL, $page = NULL) {
 184    // Only act if we are dealing with a content type supporting translations.
 185    if (!translation_supported_type($node->type)) {
 186      return;
 187    }
 188  
 189    switch ($op) {
 190      case 'prepare':
 191        if (empty($node->nid) && user_access('translate content') && isset($_GET['translation']) && isset($_GET['language']) && is_numeric($_GET['translation'])) {
 192          $translation_source = node_load($_GET['translation']);
 193          if (empty($translation_source) || !node_access('view', $translation_source)) {
 194            // Source node not found or no access to view. We should not check
 195            // for edit access, since the translator might not have permissions
 196            // to edit the source node but should still be able to translate.
 197            return;
 198          }
 199          $language_list = language_list();
 200          if (!isset($language_list[$_GET['language']]) || ($translation_source->language == $_GET['language'])) {
 201            // If not supported language, or same language as source node, break.
 202            return;
 203          }
 204          // Populate fields based on source node.
 205          $node->language = $_GET['language'];
 206          $node->translation_source = $translation_source;
 207          $node->title = $translation_source->title;
 208          // If user has no access to the filter used for the body, Drupal core
 209          // does not let the edit form to appear, so we should avoid exposing
 210          // the source text here too.
 211          $node->body = filter_access($translation_source->format) ? $translation_source->body : '';
 212          // Let every module add custom translated fields.
 213          node_invoke_nodeapi($node, 'prepare translation');
 214        }
 215        break;
 216  
 217      case 'insert':
 218        if (!empty($node->translation_source)) {
 219          if ($node->translation_source->tnid) {
 220            // Add node to existing translation set.
 221            $tnid = $node->translation_source->tnid;
 222          }
 223          else {
 224            // Create new translation set, using nid from the source node.
 225            $tnid = $node->translation_source->nid;
 226            db_query("UPDATE {node} SET tnid = %d, translate = %d WHERE nid = %d", $tnid, 0, $node->translation_source->nid);
 227          }
 228          db_query("UPDATE {node} SET tnid = %d, translate = %d WHERE nid = %d", $tnid, 0, $node->nid);
 229          $node->tnid = $tnid;
 230        }
 231        break;
 232  
 233      case 'update':
 234        if (isset($node->translation) && $node->translation && !empty($node->language) && $node->tnid) {
 235          // Update translation information.
 236          db_query("UPDATE {node} SET tnid = %d, translate = %d WHERE nid = %d", $node->tnid, $node->translation['status'], $node->nid);
 237          if (!empty($node->translation['retranslate'])) {
 238            // This is the source node, asking to mark all translations outdated.
 239            db_query("UPDATE {node} SET translate = 1 WHERE tnid = %d AND nid != %d", $node->tnid, $node->nid);
 240          }
 241        }
 242        break;
 243  
 244      case 'delete':
 245        translation_remove_from_set($node);
 246        break;
 247    }
 248  }
 249  
 250  /**
 251   * Remove a node from its translation set (if any)
 252   * and update the set accordingly.
 253   */
 254  function translation_remove_from_set($node) {
 255    if (isset($node->tnid)) {
 256      if (db_result(db_query('SELECT COUNT(*) FROM {node} WHERE tnid = %d', $node->tnid)) == 1) {
 257        // There is only one node left in the set: remove the set altogether.
 258        db_query('UPDATE {node} SET tnid = 0, translate = 0 WHERE tnid = %d', $node->tnid);
 259      }
 260      else {
 261        db_query('UPDATE {node} SET tnid = 0, translate = 0 WHERE nid = %d', $node->nid);
 262  
 263        // If the node being removed was the source of the translation set,
 264        // we pick a new source - preferably one that is up to date.
 265        if ($node->tnid == $node->nid) {
 266          $new_tnid = db_result(db_query('SELECT nid FROM {node} WHERE tnid = %d ORDER BY translate ASC, nid ASC', $node->tnid));
 267          db_query('UPDATE {node} SET tnid = %d WHERE tnid = %d', $new_tnid, $node->tnid);
 268        }
 269      }
 270    }
 271  }
 272  
 273  /**
 274   * Get all nodes in a translation set, represented by $tnid.
 275   *
 276   * @param $tnid
 277   *   The translation source nid of the translation set, the identifier
 278   *   of the node used to derive all translations in the set.
 279   * @return
 280   *   Array of partial node objects (nid, title, language) representing
 281   *   all nodes in the translation set, in effect all translations
 282   *   of node $tnid, including node $tnid itself. Because these are
 283   *   partial nodes, you need to node_load() the full node, if you
 284   *   need more properties. The array is indexed by language code.
 285   */
 286  function translation_node_get_translations($tnid) {
 287    static $translations = array();
 288  
 289    if (is_numeric($tnid) && $tnid) {
 290      if (!isset($translations[$tnid])) {
 291        $translations[$tnid] = array();
 292        $result = db_query(db_rewrite_sql('SELECT n.nid, n.type, n.uid, n.status, n.title, n.language FROM {node} n WHERE n.tnid = %d'), $tnid);
 293        while ($node = db_fetch_object($result)) {
 294          $translations[$tnid][$node->language] = $node;
 295        }
 296      }
 297      return $translations[$tnid];
 298    }
 299  }
 300  
 301  /**
 302   * Returns whether the given content type has support for translations.
 303   *
 304   * @return
 305   *   Boolean value.
 306   */
 307  function translation_supported_type($type) {
 308    return variable_get('language_content_type_'. $type, 0) == TRANSLATION_ENABLED;
 309  }
 310  
 311  /**
 312   * Return paths of all translations of a node, based on
 313   * its Drupal path.
 314   *
 315   * @param $path
 316   *   A Drupal path, for example node/432.
 317   * @return
 318   *   An array of paths of translations of the node accessible
 319   *   to the current user keyed with language codes.
 320   */
 321  function translation_path_get_translations($path) {
 322    $paths = array();
 323    // Check for a node related path, and for its translations.
 324    if ((preg_match("!^node/([0-9]+)(/.+|)$!", $path, $matches)) && ($node = node_load((int)$matches[1])) && !empty($node->tnid)) {
 325      foreach (translation_node_get_translations($node->tnid) as $language => $translation_node) {
 326        $paths[$language] = 'node/'. $translation_node->nid . $matches[2];
 327      }
 328    }
 329    return $paths;
 330  }
 331  
 332  /**
 333   * Implementation of hook_translation_link_alter().
 334   *
 335   * Replaces links with pointers to translated versions of the content.
 336   */
 337  function translation_translation_link_alter(&$links, $path) {
 338    if ($paths = translation_path_get_translations($path)) {
 339      // Path can only start with "node/$nid" or "node/$nid/" here.
 340      $path = explode('/', $path);
 341      $node = node_load($path[1]);
 342      $translations = translation_node_get_translations($node->tnid);  
 343      foreach ($links as $langcode => $link) {
 344        if (isset($paths[$langcode]) && $translations[$langcode]->status) {
 345          // Translation in a different node.
 346          $links[$langcode]['href'] = $paths[$langcode];
 347        }
 348        else {
 349          // No translation in this language, or no permission to view.
 350          unset($links[$langcode]);
 351        }
 352      }
 353    }
 354  }


Generated: Mon Jul 9 18:01:44 2012 Cross-referenced by PHPXref 0.7