entity translation compatibility. here ya go

Comments

jannis’s picture

Issue summary: View changes
jannis’s picture

Title: Revisioning Compatibility for D7 » Entity Translation compatibility with D7
Issue summary: View changes
Issue tags: -revisioning +entity translation

I'm using entity translation for my site's content. This module doesn't seem to support entity (field) translation as well as it supports 'content (node) translation'. i modified i18n_access.module a lot and entity_translation.node.inc a little to make this work the way it works for 'content/node translation'

Notes: 1) this will probably completely break i18n_access permissions for content translation.
2) the modification to entity_translation.node.inc was necessary because hook_module_implements_alter() is called by entity translation to make it last in implementation order.

Here are both of my files:

i18n_access.module :
<?php

/**
* @file
* file_description
*/

define('I18N_ACCESS_LANGUAGE_NEUTRAL', 'NEUTRAL');

/**
* Implements hook_user_insert().
*/
function i18n_access_user_insert(&$edit, &$account, $category = NULL) {
if ($category == 'account') {
// see user_admin_perm_submit()
if (isset($edit['i18n_access'])) {
db_delete('i18n_access')
->condition('uid', $account->uid)
->execute();
$edit['i18n_access'] = array_filter($edit['i18n_access']);
if (count($edit['i18n_access'])) {
db_insert('i18n_access')
->fields(array(
'uid' => $account->uid,
'perm' => implode(', ', array_keys($edit['i18n_access'])),
))->execute();
}
unset($edit['i18n_access']);
}
}
}

/**
* Implements hook_user_update().
*/
function i18n_access_user_update(&$edit, &$account, $category = NULL) {
if ($category == 'account') {
// see user_admin_perm_submit()
if (isset($edit['i18n_access'])) {
db_delete('i18n_access')
->condition('uid', $account->uid)
->execute();
$edit['i18n_access'] = array_filter($edit['i18n_access']);
if (count($edit['i18n_access'])) {
db_insert('i18n_access')
->fields(array(
'uid' => $account->uid,
'perm' => implode(', ', array_keys($edit['i18n_access'])),
))->execute();
}
unset($edit['i18n_access']);
}
}
}

/**
* Load the language permissions for a given user
*/
function i18n_access_load_permissions($uid = NULL) {
static $perms = array();

// use the global user id if none is passed
if (!isset($uid)) {
$uid = $GLOBALS['user']->uid;
$account = NULL;
}
else {
$account = user_load($uid);
}

if (!isset($perms[$uid])) {
$perm_string = db_query('SELECT perm FROM {i18n_access} WHERE uid = :uid', array(':uid' => $uid))->fetchField();

if ($perm_string) {
$perms[$uid] = drupal_map_assoc(explode(', ', $perm_string));
}
else {
$perms[$uid] = array();
}
}

// adding the default languages if permission has been granted
if (user_access('access selected languages', $account)) {
$perms[$uid] = array_merge($perms[$uid], drupal_map_assoc(variable_get('i18n_access_languages', array())));
}

return $perms[$uid];
}

/**
* Implements hook_permission().
*/
function i18n_access_permission() {
return array(
'access selected languages' => array(
'title' => t('Access selected languages'),
'description' => t('access selected languages.'),
),
);
}

/**
* Implements hook_form_node_form_alter().
*/
function i18n_access_form_node_form_alter(&$form, &$form_state, $form_id) {

if (isset($form['language']['#options'])) {
// Remove inaccessible languages from the select box
// don't do it for admininstrators
if (!user_access('administer nodes')) {
$perms = i18n_access_load_permissions();
foreach ($form['language']['#options'] as $key => $value) {
$perm_key = ($key == '') ? I18N_ACCESS_LANGUAGE_NEUTRAL : $key;

//JMA - remove english from here, we treate english the same as any language
//if ($key!='en' && empty($perms[$perm_key])) {
if (empty($perms[$perm_key])) {
unset($form['language']['#options']["$key"]);
}
}
}
//unset($form['#after_build']['0']);
}
}

/**
* Implements hook_form_alter().
*/
function i18n_access_form_alter(&$form, &$form_state, $form_id) {

//Configuring translation edit form to limit it to allowed language
if ($form_id == 'i18n_node_select_translation' && !user_access('administer nodes')) {

$perms = i18n_access_load_permissions();

foreach ($form['translations']['nid'] as $language => $translation) {
if (!isset($perms[$language]) && $language != '#tree') {
unset($form['translations']['nid'][$language]);
}
}
foreach ($form['translations']['language'] as $language => $translation) {
if (!isset($perms[$language]) && $language != '#tree') {
unset($form['translations']['language'][$language]);
}
}
foreach ($form['translations']['node'] as $language => $translation) {
if (!isset($perms[$language]) && $language != '#tree') {
unset($form['translations']['node'][$language]);
}
}

}

// Add i18n_access things to user/edit /user/add
if ($form_id == 'user_register_form' || $form_id == 'user_profile_form' ) {

$form['i18n_access'] = array(
'#type' => 'fieldset',
'#title' => t('Translation access'),
'#tree' => 0,
'#access' => user_access('administer users'),
);
$form['i18n_access']['i18n_access'] = array(
'#type' => 'checkboxes',
'#options' => array(I18N_ACCESS_LANGUAGE_NEUTRAL => t('Language neutral')) + locale_language_list('name'),
'#default_value' => i18n_access_load_permissions($form['#user']->uid),
'#description' => t('Select the languages that this user should have permission to create and edit content for.'),
);
}
}

/**
* Wrapper around node_access() with additional checks for language permissions.
*
* @see node_access()
*/

//make it take language langcode as an argument? make it null if not passed
function i18n_access_node_access($node, $op, $account = NULL, $langcode = NULL) {
//JMA big re-work here. discarded entire original function-- replaced with our own

if (is_object($node)) {
// JMA - make sure that site administrators always have access
$permissions = i18n_access_load_permissions($user);
if (user_access('site administrator', $account)) {
return TRUE;
}
// JMA - if langcode is null it means the user is not accessing by translation overview, we throw access deny and allow to hard deny sneaky people and keep unpermitted tabs out of the menu system for the user
elseif ($langcode == NULL) {
global $language;
$langcode = $language->language;

switch($op){
case 'view':
return NODE_ACCESS_ALLOW;
break;
case 'update':
if (empty($permissions[$langcode])) {
return NODE_ACCESS_DENY;
}
else {
return NODE_ACCESS_ALLOW;
}
break;
case 'create':
if (empty($permissions[$langcode])) {
return NODE_ACCESS_DENY;
}
else {
return NODE_ACCESS_ALLOW;
}
break;

}
}
//if they are accessing by translation overview, the language code gets passed by the translation overview, we send true or false here
else {
switch($op){
case 'view':
return TRUE;
break;
case 'update':
if (empty($permissions[$langcode])) {
return FALSE;
}
else {
return TRUE;
}
break;
case 'create':
if (empty($permissions[$langcode])) {
return FALSE;
}
else {
return TRUE;
}
break;
}
}
}
}

/**
* Implements hook_menu_alter().
*/

//make function name i18n_access_node_menu_alter
function i18n_access_node_menu_alter(&$items) {

//JMA - due to hook_module_implementation_alter calling entity translation last, we can't change the callback here, i've done it in entity_translation.node.inc - consider calling it here?
// Replace the translation overview page since we can't hook it.
$items['node/%node/translate']['page callback'] = 'i18n_access_translation_node_overview';

}

function i18n_access_translation_node_overview($node) {

include_once DRUPAL_ROOT . '/includes/language.inc';

//JMA - include functions from i18n_node.pages.inc
include_once DRUPAL_ROOT . '/' . drupal_get_path('module', 'i18n_node') . '/i18n_node.pages.inc';

//JMA - this is the part where this thing sorts out how to build a list of existing translations for this node

//JMA - since we use entity translation, the tnid isn't what we're using to build the translation list. we're using node->translations->data[keys]
$available_translations = $node->translations->data;

//JMA - iterate over each available translation and add its key (which is the 2 letter language code) to the array we call $translations with the node object as the value
foreach($available_translations as $key=>$value) {
$translations[$key] = $node;
}

$type = variable_get('translation_language_type', LANGUAGE_TYPE_INTERFACE);

$header = array(t('Language'), t('Title'), t('Status'), t('Operations'));

//added from i18n/i18n_node/i18n_node.pages.inc function
global $user;
$account = $user;
$perms = i18n_access_load_permissions($account->uid);
//end

// Modes have different allowed languages

foreach (i18n_node_language_list($node) as $langcode => $language_name) {

if ($langcode == LANGUAGE_NONE) {
// Never show language neutral on the overview.
continue;
}
$options = array();
if (isset($translations[$langcode])) {
// 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[$langcode]->nid);
$path = 'node/' . $translation_node->nid;

//this next line doesn't work with title_field module:
$title = i18n_node_translation_link($translation_node->title, $path, $langcode);
//this does:
//JMA - make title point to title_field[language][0][value]
$title = i18n_node_translation_link($translation_node->title_field[$langcode][0]['value'], $path, $langcode);

if (i18n_access_node_access($translation_node, 'update', $user, $langcode)) {
$text = t('edit');
$path = 'node/' . $translation_node->nid . '/edit';
$options[] = i18n_node_translation_link($text, $path, $langcode);
}
$status = $translation_node->status ? t('Published') : t('Not published');
$status .= $translation_node->translate ? ' - ' . t('outdated') . '' : '';
if ($translation_node->nid == $tnid) {
$language_name = t('@language_name (source)', array('@language_name' => $language_name));
}
}
else {
// No such translation in the set yet: help user to create it.
$title = t('n/a');
if (node_access('create', $node)) {
$text = t('add translation');
$path = 'node/add/' . str_replace('_', '-', $node->type);
$query = array('query' => array('translation' => $node->nid, 'target' => $langcode));

//condition added from i18n/i18n_node/i18n_node.pages.inc
if (in_array($langcode, $perms)) {
$options[] = i18n_node_translation_link($text, $path, $langcode, $query);
}
}
$status = t('Not translated');
}
$rows[] = array($language_name, $title, $status, implode(" | ", $options));
}

drupal_set_title(t('Translations of %title', array('%title' => $node->title)), PASS_THROUGH);

$build['translation_node_overview'] = array(
'#theme' => 'table',
'#header' => $header,
'#rows' => $rows,
);

if (user_access('administer content translations')) {
$build['translation_node_select'] = drupal_get_form('i18n_node_select_translation', $node, $translations);
}
return $build;
}

/**
* Implements hook_menu().
*/
function i18n_access_menu() {
$items = array();

$items['admin/settings/language/access'] = array(
'title' => 'Access',
'page callback' => 'drupal_get_form',
'page arguments' => array('i18n_access_admin_settings'),
'access arguments' => array('administer site configuration'),
'type' => MENU_LOCAL_TASK,
'weight' => 10,
);

return $items;
}

/**
* Admin settings form
*/
function i18n_access_admin_settings() {

$form['i18n_access_languages'] = array(
'#title' => t('Select the default access languages'),
'#type' => 'select',
'#multiple' => 'true',
'#options' => array(I18N_ACCESS_LANGUAGE_NEUTRAL => t('Language neutral')) + locale_language_list('name'),
'#default_value' => variable_get('i18n_access_languages', array()),
'#description' => t("This selection of languages will be connected with the 'access selected languages' permission which you can use to grant a role access to these languages at once.")
);

return system_settings_form($form);
}

entity_translation.node.inc :

<?php

/**
* @file
* The node specific translation functions and hook implementations.
*/

/**
* Identifies a content type which has translation support enabled.
*/
define('ENTITY_TRANSLATION_ENABLED', 4);

/**
* Hides translation metadata.
*/
define('ENTITY_TRANSLATION_METADATA_HIDE', 0);

/**
* Adds translation metadata to the original authoring information.
*/
define('ENTITY_TRANSLATION_METADATA_SHOW', 1);

/**
* Replaces the original authoring information with translation metadata.
*/
define('ENTITY_TRANSLATION_METADATA_REPLACE', 2);

/**
* Checks if the given entity has node translation enabled.
*/
function entity_translation_node($entity_type, $node) {
return $entity_type == 'node' && function_exists('translation_supported_type') && translation_supported_type($node->type);
}

/**
* Node-specific menu alterations.
*/
function entity_translation_node_menu_alter(&$items, $backup) {
if (isset($backup['node'])) {
$item = $backup['node'];
// Preserve the menu router item defined by other modules.
$callback['page callback'] = $item['page callback'];
$callback['file'] = $item['file'];
$callback['module'] = $item['module'];
$access_arguments = array_merge(array(1, $item['access callback']), $item['access arguments']);
}
else {
$callback = FALSE;
$access_arguments = array(1);
}

//JMA - point the 'translate' tab to point to the i18n_access version of the translation overview page

//JMA - this line calls back to the entity translation overview function (which doesn't support i18n_access permissions:
//$items['node/%node/translate']['page callback'] = 'entity_translation_overview';
//JMA - but this one callsback to the i18n_access one
$items['node/%node/translate']['page callback'] = 'i18n_access_translation_node_overview';

//JMA - there are 3 page arguments for the entity translation overview, only one fori18n_access:
//$items['node/%node/translate']['page arguments'] = array('node', 1, $callback);
//JMA - let's make it just one, position [1] in the URL (the nid)
$items['node/%node/translate']['page arguments'] = array(1);

//let's examine the i18n_access permissions and jame them in here maybe?
$items['node/%node/translate']['access arguments'] = $access_arguments;
$items['node/%node/translate']['access callback'] = 'entity_translation_node_tab_access';

//JMA - the function is in a different file, i18n_access.module:
//$items['node/%node/translate']['file'] = 'entity_translation.admin.inc';
$items['node/%node/translate']['file'] = 'i18n_access.module';

//JMA - and a different module, i18n_access
//$items['node/%node/translate']['module'] = 'entity_translation';
$items['node/%node/translate']['module'] = 'i18n_access';
}

/**
* Node specific access callback.
*/
function entity_translation_node_tab_access() {
$args = func_get_args();
$node = array_shift($args);
if (entity_translation_node('node', $node)) {
$function = array_shift($args);
return call_user_func_array($function, $args);
}
else {
return entity_translation_tab_access('node', $node);
}
}

/**
* Returns whether the given node type has support for translations.
*
* @return
* Boolean value.
*/
function entity_translation_node_supported_type($type) {
return variable_get('language_content_type_' . $type, 0) == ENTITY_TRANSLATION_ENABLED;
}

/**
* Implements hook_node_view().
*
* Provides content language switcher links to navigate among node translations.
*/
function entity_translation_node_view($node, $build_mode, $langcode) {
if (!empty($node->translations) && drupal_multilingual() && entity_translation_node_supported_type($node->type) && !variable_get("entity_translation_hide_translation_links_{$node->type}", FALSE)) {
$path = 'node/' . $node->nid;
$links = EntityTranslationDefaultHandler::languageSwitchLinks($path);

if (is_object($links) && !empty($links->links)) {
$handler = entity_translation_get_handler('node', $node);
$translations = $handler->getTranslations()->data;

// Remove the link for the current language.
unset($links->links[$langcode]);

// Remove links to unavailable translations.
foreach ($links->links as $langcode => $link) {
if (!isset($translations[$langcode]) || !entity_translation_access('node', $translations[$langcode])) {
unset($links->links[$langcode]);
}
}

$node->content['links']['translation'] = array(
'#theme' => 'links',
'#links' => $links->links,
'#attributes' => array('class' => 'links inline'),
);
}
}
}

/**
* Implements hook_form_FORM_ID_alter().
*
* Provides settings into the node content type form to choose for entity
* translation metadata and comment filtering.
*/
function entity_translation_form_node_type_form_alter(&$form, &$form_state) {
if (entity_translation_enabled('node')) {
$type = $form['#node_type']->type;

$t_args = array('!url' => url('admin/config/regional/entity_translation'));
$form['workflow']['language_content_type']['#options'][ENTITY_TRANSLATION_ENABLED] = t('Enabled, with field translation');
$form['workflow']['language_content_type']['#description'] .= '

' . t('If field translation is selected you can have per-field translation for each available language. You can find more options in the entity translation settings.', $t_args) . '

';

// Hide settings when entity translation is disabled for this content type.
$states = array(
'visible' => array(
':input[name="language_content_type"]' => array('value' => ENTITY_TRANSLATION_ENABLED),
),
);

$form['workflow']['entity_translation_hide_translation_links'] = array(
'#type' => 'checkbox',
'#default_value' => variable_get("entity_translation_hide_translation_links_$type", FALSE),
'#title' => t('Hide content translation links'),
'#description' => t('Hide the links to translations in content body and teasers. If you choose this option, switching language will only be available from the language switcher block.'),
'#states' => $states,
);

$form['display']['entity_translation_node_metadata'] = array(
'#type' => 'radios',
'#title' => t('Translation post information'),
'#description' => t('Whether the translation authoring information should be hidden, shown, or replace the node\'s authoring information.'),
'#default_value' => variable_get("entity_translation_node_metadata_$type", ENTITY_TRANSLATION_METADATA_HIDE),
'#options' => array(t('Hidden'), t('Shown'), t('Replacing post information')),
'#states' => $states,
);

if (isset($form['comment'])) {
$form['comment']['entity_translation_comment_filter'] = array(
'#type' => 'checkbox',
'#title' => t('Filter comments per language'),
'#default_value' => variable_get("entity_translation_comment_filter_$type", FALSE),
'#description' => t('Show only comments whose language matches content language.'),
'#states' => $states,
);
}
}
}

/**
* Implements hook_preprocess_node().
*
* Alters node template variables to show/replace entity translation metadata.
*/
function entity_translation_preprocess_node(&$variables) {
$node = $variables['node'];
$submitted = variable_get("node_submitted_{$node->type}", TRUE);
$mode = variable_get("entity_translation_node_metadata_{$node->type}", ENTITY_TRANSLATION_METADATA_HIDE);

if ($submitted && $mode != ENTITY_TRANSLATION_METADATA_HIDE) {
global $language_content, $user;

$handler = entity_translation_get_handler('node', $node);
$translations = $handler->getTranslations();
$langcode = $language_content->language;

if (isset($translations->data[$langcode]) && $langcode != $translations->original) {
$translation = $translations->data[$langcode];
$date = format_date($translation['created']);
$name = FALSE;

if ($node->uid != $translation['uid']) {
$account = $user->uid != $translation['uid'] ? user_load($translation['uid']) : $user;
$name = theme('username', array('account' => $account));
}

switch ($mode) {
case ENTITY_TRANSLATION_METADATA_SHOW:
$variables['date'] .= ' (' . t('translated on !date', array('!date' => $date)) . ')';
if ($name) {
$variables['name'] .= ' (' . t('translated by !name', array('!name' => $name)) . ')';
}
break;

case ENTITY_TRANSLATION_METADATA_REPLACE:
$variables['date'] = $date;
if ($name) {
$variables['name'] = $name;
}
break;
}
}
}
}

/**
* Returns whether the given comment type has support for translations.
*
* @return
* Boolean value.
*/
function entity_translation_comment_supported_type($comment_type) {
$type = str_replace('comment_node_', '', $comment_type);
return entity_translation_node_supported_type($type);
}

/**
* Implements hook_query_TAG_alter().
*
* Filters out node comments by content language.
*
* @todo Find a way to track node comment statistics per language.
*/
function entity_translation_query_comment_filter_alter(QueryAlterableInterface $query) {
$node = $query->getMetaData('node');
if (!empty($node->type) && variable_get("entity_translation_comment_filter_{$node->type}", FALSE)) {
// Determine alias for "comment" table.
$comment_alias = FALSE;
foreach ($query->getTables() as $table) {
if (is_string($table['table']) && $table['table'] == 'comment') {
$comment_alias = $table['alias'];
break;
}
}
// Only show comments without language or matching the current content language.
if ($comment_alias) {
$query->condition(db_or()
->condition($comment_alias . '.language', $GLOBALS['language_content']->language)
->condition($comment_alias . '.language', LANGUAGE_NONE)
);
}
}
}

alanburke’s picture

Status: Active » Needs work

Could this be added as a patch file for review and testing?

seanr’s picture

StatusFileSize
new8.91 KB

Fixed some code standards stuff and rolled a patch. This is needs work still; haven't really tested it yet, just wanted to get a patch up so people can take a look at it and collaborate.

seanr’s picture

Status: Needs work » Needs review
StatusFileSize
new10.64 KB

Here's a pass a getting everything into i18n_access avoiding any hacks or patches necessary with entity_translation. Still don't expect it to work, but it's more complete now so we can get some eyes on it.