$node->title))); return drupal_get_form('translation_overview_node_form', $node); } /** * Overview page for a node's translations. * * Based on the translation.module's translation_node_overview(). * * @param $node * Node object. */ function translation_overview_node_form(&$form_state, $node) { if ($node->tnid) { // Already part of a set, grab that set. $tnid = $node->tnid; $translations = translation_node_get_translations($node->tnid); } else { // We have no translation source nid, this could be a new set, emulate that. $tnid = $node->nid; $translations = array($node->language => $node); } $priorities = translation_overview_get_node_priority($node); $form['languages']['#tree'] = TRUE; foreach (language_list() as $lang_code => $language) { // Skip over any disabled languages. if (!$language->enabled) { continue; } $form['languages'][$lang_code]['priority'] = array( '#type' => 'radios', '#default_value' => $priorities[$lang_code], '#options' => array( TRANSLATION_OVERVIEW_HIGH => t('High'), TRANSLATION_OVERVIEW_NORMAL => t('Normal'), TRANSLATION_OVERVIEW_IGNORE => t('Ignore'), ), // If there's no manager role then everyone gets a chance. Otherwise // check that they're a manager for this language. '#access' => translation_overview_is_manager($lang_code), ); if (isset($translations[$lang_code])) { // Existing translation in the translation set: display status. // We load the full node to check whether the user can edit it. $translation_node = node_load($translations[$lang_code]->nid); if ($translation_node->nid == $tnid) { // Original shouldn't have a priority. $form['languages'][$lang_code]['priority']['#access'] = FALSE; $form['languages'][$lang_code]['language'] = array( '#value' => '' . $language->name . ' (source)', ); } else { $form['languages'][$lang_code]['language'] = array( '#value' => $language->name, ); } $form['languages'][$lang_code]['title'] = array( '#value' => l($translation_node->title, 'node/' . $translation_node->nid), ); $form['languages'][$lang_code]['status'] = array( '#value' => translation_overview_translation_link($node, $translation_node, $lang_code, TRUE), ); } else { // No such translation in the set yet: help user to create it. $form['languages'][$lang_code]['language'] = array( '#value' => $language->name, ); $form['languages'][$lang_code]['title'] = array( '#value' => t('n/a'), ); $form['languages'][$lang_code]['status'] = array( '#value' => translation_overview_translation_link($node, NULL, $lang_code, TRUE), ); } } $form['tnid'] = array( '#type' => 'value', '#value' => $tnid, ); $form['submit'] = array( '#type' => 'submit', '#value' => t('Submit'), ); return $form; } function translation_overview_node_form_submit($form, &$form_state) { $row = array('tnid' => (int) $form_state['values']['tnid']); foreach ($form_state['values']['languages'] as $language => $values) { $row[translation_overview_field_name($language)] = $values['priority']; } // We can assume that there will alwyas be a record with the given tnid // because translation_overview_get_node_priority() has been called and it // creates records if one is not present. if (drupal_write_record('translation_overview_priority', $row, 'tnid') !== FALSE) { drupal_set_message(t('The translation priorities have been saved.')); } else { drupal_set_message(t('There was a problem saving the translation priorities.'), 'error'); } } function theme_translation_overview_node_form($form) { drupal_add_css(drupal_get_path('module', 'translation_overview') . '/translation_overview.css'); $is_manager = translation_overview_is_manager(); $header = array( array('data' => t('Language')), array('data' => t('Title')), array('data' => t('Status')), ); if ($is_manager) { $header[] = array('data' => t('Priority'), 'colspan' => 3); } $rows = array(); foreach (element_children($form['languages']) as $key) { $row = array(); $row[] = array('data' => drupal_render($form['languages'][$key]['language'])); $row[] = array('data' => drupal_render($form['languages'][$key]['title'])); $row[] = array('data' => drupal_render($form['languages'][$key]['status']), 'class' => 'status'); if ($is_manager) { foreach (element_children($form['languages'][$key]['priority']) as $priority) { $row[] = array('data' => drupal_render($form['languages'][$key]['priority'][$priority])); } } $rows[] = $row; } return theme('table', array('header' => $header, 'rows' => $rows, 'attributes' => array('class' => 'trov'))) . drupal_render($form); } /** * List node administration filters that can be applied. * * This is a fork of node_filters(). */ function translation_overview_node_filters() { // Regular filters $filters['status'] = array( 'title' => t('Status'), 'options' => array( 'status-1' => t('Published'), 'status-0' => t('Not published'), 'promote-1' => t('Promoted'), 'promote-0' => t('Not promoted'), 'sticky-1' => t('Sticky'), 'sticky-0' => t('Not sticky'), 'translate-0' => t('Up-to-date translation'), 'translate-1' => t('Outdated translation'), ), ); $filters['type'] = array( 'title' => t('type'), 'options' => translation_overview_node_types(), ); // The taxonomy filter if ($taxonomy = module_invoke('taxonomy', 'form_all', 1)) { $filters['category'] = array('title' => t('category'), 'options' => $taxonomy); } //The domain access filter : list domains managed by the Domain Access module if (module_exists('domain')) { $domains = domain_domains(); if (count($domains) > 1) { $filters['domain'] = array('title' => t('Domain'), 'options' => array()); foreach ($domains as $domain) { $filters['domain']['options'][$domain['domain_id']] = $domain['sitename']; } } } return $filters; } /** * Return form for node administration filters. * * This is a fork of node_filter_form(). */ function translation_overview_filter_form() { // We reuse a bunch of the node.module's stuff. module_load_include('inc', 'node', 'node.admin'); $session = &$_SESSION['translation_overview_filter']; $session = is_array($session) ? $session : array(); $filters = translation_overview_node_filters(); $i = 0; $form['filters'] = array( '#type' => 'fieldset', '#title' => t('Show only items where'), '#theme' => 'node_filters', ); $form['#submit'][] = 'translation_overview_filter_form_submit'; foreach ($session as $filter) { list($type, $value) = $filter; if ($type == 'category') { // Load term name from DB rather than search and parse options array. $value = module_invoke('taxonomy', 'get_term', $value); $value = $value->name; } else { $value = $filters[$type]['options'][$value]; } if ($i++) { $form['filters']['current'][] = array('#value' => t('and where %a is %b', array('%a' => $filters[$type]['title'], '%b' => $value))); } else { $form['filters']['current'][] = array('#value' => t('%a is %b', array('%a' => $filters[$type]['title'], '%b' => $value))); } if (in_array($type, array('type', 'language'))) { // Remove the option if it is already being filtered on. unset($filters[$type]); } } foreach ($filters as $key => $filter) { $names[$key] = $filter['title']; $form['filters']['status'][$key] = array('#type' => 'select', '#options' => $filter['options']); } $form['filters']['filter'] = array('#type' => 'radios', '#options' => $names, '#default_value' => 'status'); $form['filters']['buttons']['submit'] = array('#type' => 'submit', '#value' => (count($session) ? t('Refine') : t('Filter'))); if (count($session)) { $form['filters']['buttons']['undo'] = array('#type' => 'submit', '#value' => t('Undo')); $form['filters']['buttons']['reset'] = array('#type' => 'submit', '#value' => t('Reset')); } drupal_add_js('misc/form.js', array('type' => 'file', 'weight' => JS_LIBRARY)); return $form; } /** * Process result from node administration filter form. * * This is a fork of node_filter_form(). */ function translation_overview_filter_form_submit($form, &$form_state) { $filters = translation_overview_node_filters(); switch ($form_state['values']['op']) { case t('Filter'): case t('Refine'): if (isset($form_state['values']['filter'])) { $filter = $form_state['values']['filter']; // Flatten the options array to accommodate hierarchical/nested options. $flat_options = form_options_flatten($filters[$filter]['options']); if (isset($flat_options[$form_state['values'][$filter]])) { $_SESSION['translation_overview_filter'][] = array($filter, $form_state['values'][$filter]); } } break; case t('Undo'): array_pop($_SESSION['translation_overview_filter']); break; case t('Reset'): $_SESSION['translation_overview_filter'] = array(); break; } } /** * Build the where clause for a filtered query. * * This is a fork of node_filter_form_submit(). */ function translation_overview_build_filter_query() { $filters = translation_overview_node_filters(); // Build query $join = $where = $where_args = array(); if (isset($_SESSION['translation_overview_filter'])) { foreach ((array) $_SESSION['translation_overview_filter'] as $index => $filter) { list($key, $value) = $filter; switch ($key) { case 'status': // Note: no exploitable hole as $key/$value have already been checked when submitted list($key, $value) = explode('-', $value, 2); $where['status'] = 'n.' . $key . ' = %d'; break; case 'category': $table = "tn$index"; $where[$table] = "$table.tid = %d"; $join[$table] = "INNER JOIN {term_node} $table ON n.nid = $table.nid "; break; case 'type': $where['type'] = "n.type = '%s'"; break; case 'domain': // Perform query only on the selected domain managed by the Domain // Access module. $table = "da$index"; $where['domain'] = "$table.gid = '%d'"; $join[$table] = "INNER JOIN {domain_access} $table ON n.nid = $table.nid "; break; } $where_args[] = $value; } } // Make sure we limit it to translation enabled types. if (empty($where['type'])) { $types = array_keys(translation_overview_node_types()); $where[] = 'n.type IN (' . db_placeholders($types, 'varchar') . ')'; $where_args = array_merge($where_args, $types); } return array('join' => $join, 'where' => $where, 'where_args' => $where_args); } /** * Translation overview page. */ function translation_overview_manager_page() { drupal_add_css(drupal_get_path('module', 'translation_overview') . '/translation_overview.css'); $rows_per_page = 30; // Get a list of the enabled languages that this user manages. $languages = array(); foreach (language_list() as $key => $language) { if ($language->enabled && translation_overview_is_manager($language->language)) { $languages[$key] = $language->name; } } // Bail if there are no translatable nodes if (count(translation_overview_node_types()) == 0) { drupal_set_message(t('There are no translatable node types on this site.'), 'error'); return ''; } $header = array( array('field' => 'n.title', 'data' => t('Title'), 'sort' => 'asc'), array('field' => 'n.type', 'data' => t('Type')), array('field' => 'n.created', 'data' => t('Created')), ); foreach ($languages as $lang_code => $lang_name) { $header[] = array('data' => str_replace('-', '-
', $lang_code), 'class' => 'trov-lang', 'title' => $lang_name); } $query = translation_overview_build_filter_query(); $sql = "SELECT n.nid, n.title, n.type, n.created FROM {node} n " . implode(' ', $query['join']) . " WHERE (n.nid = n.tnid OR n.tnid = 0) AND n.language <> '' AND n.language IS NOT NULL AND " . implode(' AND ', $query['where']) . tablesort_sql($header); $rows = array(); $result = pager_query(db_rewrite_sql($sql), $rows_per_page, 0, NULL, $query['where_args']); while ($node = db_fetch_object($result)) { $node = node_load($node->nid); $row = array( array('data' => l(translation_overview_trimmed_title($node), 'node/' . $node->nid . '/translate', array('attributes' => array('title' => $node->title), 'query' => array('destination' => $_GET['q'])))), array('data' => check_plain($node->type)), array('data' => format_date($node->created, 'custom', 'j M Y')), ); // Load the node's translations and then fill in the table with the status. $translations = (array) translation_node_get_translations($node->tnid); foreach ($languages as $lang_code => $lang_name) { $translation = empty($translations[$lang_code]->nid) ? NULL : node_load($translations[$lang_code]->nid); $row[$lang_code] = array( 'data' => translation_overview_translation_link($node, $translation, $lang_code, FALSE), 'class' => 'status', ); } $rows[] = $row; } return drupal_get_form('translation_overview_filter_form') . theme('table', array('header' => $header, 'rows' => $rows, 'attributes' => array('class' => 'trov-all'))) . theme('pager', array('tags' => NULL)); } /** * Show a listing of the languages that translators can look at. */ function translation_overview_assignment_lang_page() { $items = array(); foreach (language_list() as $language) { if ($language->enabled) { $items[] = l($language->name, 'admin/content/translation_overview_assignments/' . $language->language); } } return theme('item_list', array('items' => $items)); } function translation_overview_assignment_page($language) { drupal_add_css(drupal_get_path('module', 'translation_overview') . '/translation_overview.css'); $rows_per_page = 30; $node_types = node_type_get_names(); $languages = language_list(); // Bail if there are no translatable nodes if (count(translation_overview_node_types()) == 0) { drupal_set_message(t('There are no translatable node types on this site.'), 'error'); return ''; } $header = array( array('field' => 'n.title', 'data' => t('Title')), array('field' => 'translation_status', 'data' => t('Status'), 'sort' => 'desc'), array('field' => 'n.type', 'data' => t('Type')), array('field' => 'n.language', 'data' => t('Source language')), array('field' => 'n.created', 'data' => t('Created')), ); // We want to sort the nodes by the status so we have to resort to this SQL // CASE statement. $rows = array(); $query = translation_overview_build_filter_query(); $language_field = translation_overview_field_name($language); $sql = "SELECT n.nid, n.title, n.type, n.status, n.language, n.created, nt.nid AS translation_nid, CASE WHEN n.language = '%s' " /* ORIGINAL */ . " THEN 0 WHEN nt.tnid IS NULL " /* MISSING */ . " THEN 3 + 2 * top.$language_field WHEN nt.translate = 0 " /* COMPLETE */ . " THEN 1 WHEN n.nid = n.tnid " /* OUTDATED */ . " THEN 2 + 2 * top.$language_field ELSE -1 END AS translation_status FROM {node} n LEFT JOIN {node} nt ON n.nid = nt.tnid AND nt.language = '%s' INNER JOIN {translation_overview_priority} top ON n.nid = top.tnid AND top.$language_field <> %d " . implode(' ', $query['join']) . " WHERE (n.nid = n.tnid OR n.tnid = 0) AND n.language NOT IN ('', '%s') AND n.language IS NOT NULL AND " . implode(' AND ', $query['where']) . tablesort_sql($header); // The %s in the CASE causes pager_query() to screw up the count query so build it by hand and eat the extra %s. $count_sql = "SELECT COUNT(n.nid), '%s' AS junk FROM {node} n LEFT JOIN {node} nt ON n.nid = nt.tnid AND nt.language = '%s' INNER JOIN {translation_overview_priority} top ON n.nid = top.tnid AND top.$language_field <> %d " . implode(' ', $query['join']) . " WHERE (n.nid = n.tnid OR n.tnid = 0) AND n.language NOT IN ('', '%s') AND n.language IS NOT NULL AND " . implode(' AND ', $query['where']); // We need to put the priority and language that we use as an argument early // in the query at the beginning of the arguments list. $args = array_merge(array($language, $language, TRANSLATION_OVERVIEW_IGNORE, $language), $query['where_args']); $rows = array(); $result = pager_query(db_rewrite_sql($sql), $rows_per_page, 0, db_rewrite_sql($count_sql), $args); while ($n = db_fetch_object($result)) { $node = node_load($n->nid); $translation = empty($n->translation_nid) ? NULL : node_load($n->translation_nid); $rows[] = array( array('data' => node_access('view', $node) ? l(translation_overview_trimmed_title($node, 30), 'node/' . $node->nid, array('attributes' => array('title' => $node->title))) : translation_overview_trimmed_title($node, 30)), array('data' => translation_overview_translation_link($node, $translation, $language, TRUE), 'class' => 'status'), array('data' => isset($node_types[$node->type]) ? $node_types[$node->type] : check_plain($node->type)), array('data' => isset($languages[$node->language]) ? $languages[$node->language]->name : check_plain($node->language)), array('data' => format_date($node->created, 'custom', 'j M Y')), ); } return drupal_get_form('translation_overview_filter_form') . theme('table', array('header' => $header, 'rows' => $rows, 'attributes' => array('class' => 'trov'))) . theme('pager', array('tags' => NULL)); }