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); } 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', 'core'); 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 = $args = array(); if (!$_SESSION['translation_overview_filter']) { $_SESSION['translation_overview_filter'] = array(); } foreach ($_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; } $args[] = $value; } // Make sure we limit it to translation enabled types. if (!isset($where['type'])) { $types = array_keys(translation_overview_node_types()); $where[] = 'n.type IN ('. db_placeholders($types, 'varchar') .')'; $args = array_merge($args, $types); } return array('where' => $where, 'join' => $join, 'args' => $args); } /** * Translation overview page. * * Current assumptions: * - translated node's status is synchronized via the i18n_sync module. */ function translation_overview_overview_page() { drupal_add_css(drupal_get_path('module', 'translation_overview') .'/translation_overview.css'); $rows_per_page = 30; // Bail if there are no translatable nodes if (count(translation_overview_node_types()) == 0) { drupal_set_message(t('There are no translateable node types on this site.'), 'error'); return ''; } // Get a list of the enabled languages $languages = array(); foreach (language_list() as $key => $language) { if ($language->enabled) { $languages[$key] = $language->language; } } // Sort the list so the order matches the menu items. asort($languages); $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 $key => $language_name) { $header[] = array('data' => str_replace('-', '
', $language_name), 'class' => 'translation-overview-lang'); } $query = translation_overview_build_filter_query(); $sql = "SELECT n.nid, n.title, n.type, n.tnid, n.status, n.created, n.language, n.translate 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['args']); while ($node = db_fetch_object($result)) { // Shorten down the title so the table isn't too wide. $title = $node->title; if (drupal_strlen($title) > 20) { $title = drupal_substr($title, 0, 15) .'...'; } $row = array( array('data' => l($title, 'node/'. $node->nid, array('attributes' => array('title' => $node->title)))), 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 $language) { $translation_nid = empty($translations[$language]->nid) ? NULL : $translations[$language]->nid; $row[$language] = array( 'data' => translation_overview_translation_link($node, $translation_nid, $language, FALSE), 'class' => 'translation-overview-status', ); } $rows[] = $row; } return drupal_get_form('translation_overview_filter_form') . theme('table', $header, $rows) . theme('pager', NULL, $rows_per_page); } function translation_overview_language_page($language) { $rows_per_page = 30; $types = array_keys(translation_overview_node_types()); // Bail if there are no translatable nodes if (count($types) == 0) { drupal_set_message(t('There are no translateable node types on this site.'), 'error'); return ''; } $header = array( array('field' => 'n.title', 'data' => t('Title')), array('field' => 'n.type', 'data' => t('Type')), array('field' => 'n.status', 'data' => t('Status')), array('field' => 'n.language', 'data' => t('Language')), array('field' => 'n.created', 'data' => t('Created')), array('field' => 'translation_status', 'data' => t('Translated'), 'sort' => 'asc'), ); // We want to sort the nodes by the status so we have to resort to this SQL // CASE statement. // NOTE: the '0 AS i18n' is a little hack to prevent i18n from re-writing our // query. $rows = array(); $query = translation_overview_build_filter_query(); $sql = "SELECT n.nid, n.title, n.type, n.language, n.status, n.created, n.tnid, nt.nid AS t_nid, CASE WHEN n.language = '%s' THEN '4 LOCAL' WHEN nt.translate = 0 THEN '3 DONE' WHEN nt.tnid IS NULL THEN '2 MISS' WHEN n.nid = n.tnid THEN '1 OLD' ELSE 'unknown' END AS translation_status, 0 AS i18n FROM {node} n LEFT JOIN {node} nt ON n.nid = nt.tnid AND nt.language = '%s' ". 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); // We need to put the language that we use as an argument early in the query // at the beginning of the arguments list. array_unshift($query['args'], $language); array_unshift($query['args'], $language); $rows = array(); $result = pager_query(db_rewrite_sql($sql), $rows_per_page, 0, NULL, $query['args']); while ($node = db_fetch_object($result)) { // Shorten down the title $title = $node->title; if (drupal_strlen($title) > 25) { $title = drupal_substr($title, 0, 20) .'...'; } $rows[] = array( l($title, 'node/'. $node->nid, array('attributes' => array('title' => $node->title))), check_plain($node->type), empty($node->status) ? t('Unpublished') : t('Published'), $node->language, format_date($node->created, 'custom', 'j M Y'), translation_overview_translation_link($node, $node->t_nid, $language, TRUE), ); } return drupal_get_form('translation_overview_filter_form') . theme('table', $header, $rows) . theme('pager', NULL, $rows_per_page); }