array( 'label' => t('Translation Task'), 'module' => 'tmgmt_local', 'entity class' => 'TMGMTLocalTask', 'controller class' => 'TMGMTLocalTaskController', 'metadata controller class' => 'TMGMTLocalTaskMetadataController', 'views controller class' => 'TMGMTLocalTaskViewsController', 'base table' => 'tmgmt_local_task', 'uri callback' => 'entity_class_uri', 'label callback' => 'entity_class_label', 'access callback' => 'tmgmt_local_task_access', 'entity keys' => array( 'id' => 'tltid', ), 'admin ui' => array( 'controller class' => 'TMGMTLocalTaskUIController', 'path' => 'translate', ), ), 'tmgmt_local_task_item' => array( 'label' => t('Translation Task Item'), 'module' => 'tmgmt_local', 'entity class' => 'TMGMTLocalTaskItem', 'controller class' => 'TMGMTLocalTaskItemController', 'metadata controller class' => 'TMGMTLocalTaskItemMetadataController', 'views controller class' => 'TMGMTLocalTaskItemViewsController', 'base table' => 'tmgmt_local_task_item', 'uri callback' => 'entity_class_uri', 'label callback' => 'entity_class_label', 'access callback' => 'tmgmt_local_task_item_access', 'entity keys' => array( 'id' => 'tltiid', ), 'admin ui' => array( 'controller class' => 'TMGMTLocalTaskItemUIController', 'path' => 'translate', ), ), ); return $info; } /** * Implements hook_theme(). */ function tmgmt_local_theme() { return array( 'tmgmt_local_translation_form' => array( 'render element' => 'element', 'file' => 'includes/tmgmt_local.theme.inc', ), 'tmgmt_local_translation_form_element' => array( 'render element' => 'element', 'file' => 'includes/tmgmt_local.theme.inc', ), // @todo - not implemented. 'tmgmt_local_translation_form_element_status' => array( 'render element' => 'status', 'file' => 'includes/tmgmt_local.theme.inc', ), ); } /** * Implements hook_menu(). */ function tmgmt_local_menu() { $items['translate/%tmgmt_local_task/assign-to-me'] = array( 'title' => 'Assign translation task to me', 'description' => 'Assign translation task to current translator user.', 'page callback' => 'tmgmt_local_translation_assign_to_me', 'page arguments' => array(1), 'access callback' => 'tmgmt_local_translation_access', 'access arguments' => array(1), 'file' => 'includes/tmgmt_local.pages.inc', ); $items['manage-translate/assign-tasks'] = array( 'title' => 'Assign translation task', 'description' => 'Assign translation tasks to specific translator.', 'page callback' => 'drupal_get_form', 'page arguments' => array('tmgmt_local_translation_assign_form', 2), 'access arguments' => array('administer translation tasks'), 'file' => 'includes/tmgmt_local.pages.inc', 'type' => MENU_CALLBACK, ); $items['manage-translate/reassign-tasks'] = array( 'title' => 'Reassign translation task', 'description' => 'Ressign translation tasks to specific translator.', 'page callback' => 'drupal_get_form', 'page arguments' => array('tmgmt_local_translation_reassign_form', 2), 'access arguments' => array('administer translation tasks'), 'file' => 'includes/tmgmt_local.pages.inc', 'type' => MENU_CALLBACK, ); return $items; } /** * Implements hook_permission(). */ function tmgmt_local_permission() { return array( 'provide translation services' => array( 'title' => t('Provide translation services'), 'descriptions' => t('Root permission for translation access: Users with this permission are eligible to be granted translation access to a translation task.'), ), 'administer translation tasks' => array( 'title' => t('Administer translation tasks'), 'description' => t('Administer translation tasks.'), ), ); } /** * Implements hook_views_api(). */ function tmgmt_local_views_api() { return array( 'api' => 3.0, 'path' => drupal_get_path('module', 'tmgmt_local') . '/views', ); } /** * Implements hook_default_views(). */ function tmgmt_local_views_default_views() { return _tmgmt_load_exports('tmgmt_local', 'views', 'view.inc', 'view'); } /** * Implements hook_tmgmt_translator_plugin_info(). */ function tmgmt_local_tmgmt_translator_plugin_info() { $info['local'] = array( 'label' => t('Local Translator'), 'description' => t('Allows local users to process translation jobs.'), 'plugin controller class' => 'TMGMTLocalTranslatorPluginController', 'ui controller class' => 'TMGMTLocalTranslatorUIController', 'map remote languages' => FALSE, ); return $info; } /** * @addtogroup tmgmt_local * @{ */ /** * Determine whether the current user is allowed to translate a given * translation task. * * @param $task * The translation task to be translated. * @param $account * (Optional) A user object representing the user that is trying to obtain * translation access. Determines access for a user other than the current * user. * @return bool * TRUE if the user is allowed to translate the given translation job, FALSE * otherwise. */ function tmgmt_local_translation_access(TMGMTLocalTask $task, $account = NULL) { $job = $task->getJob(); if (!$job || !$job->isActive()) { return FALSE; } $rights = &drupal_static(__FUNCTION__); // If no user object is supplied, the access check is for the current user. if (empty($account)) { $account = $GLOBALS['user']; } // If we've already checked access for this job and user, return from cache. if (isset($rights[$account->uid][$job->tjid])) { return $rights[$account->uid][$job->tjid]; } // We grant access to the translation if both of the following conditions are // met: // - User is assigned as a translator to the given task. // - User has 'provide translation services' permission. // - No modules say to deny access. // - At least one module says to grant access. // - User has translation capabilities for this task. if (!user_access('provide translation services')) { $rights[$account->uid][$job->tjid] = FALSE; return FALSE; } if ($task->tuid == $account->uid) { $rights[$account->uid][$job->tjid] = TRUE; return TRUE; } $access = module_invoke_all('tmgmt_local_translation_access', $job, $account); if (in_array(TMGMT_LOCAL_TRANSLATION_ACCESS_DENY, $access, TRUE)) { $rights[$account->uid][$job->tjid] = FALSE; return FALSE; } elseif (in_array(TMGMT_LOCAL_TRANSLATION_ACCESS_ALLOW, $access, TRUE)) { $rights[$account->uid][$job->tjid] = TRUE; return TRUE; } // Lastly, check for the translation capabilities. $target_languages = tmgmt_local_supported_target_languages($job->source_language, array($account->uid)); $rights[$account->uid][$job->tjid] = in_array($job->target_language, $target_languages); return $rights[$account->uid][$job->tjid]; } /** * Helper function for clearing the languages cache of all local translators. * * Can be used in oder to clear the cache for supported target languages after * the translation capabilities of an local have changed. */ function tmgmt_local_clear_languages_cache() { $query = new EntityFieldQuery(); $query->entityCondition('entity_type', 'tmgmt_translator'); $query->propertyCondition('plugin', 'local'); $result = $query->execute(); if ($result) { foreach (tmgmt_translator_load_multiple(array_keys($result['tmgmt_translator'])) as $translator) { $translator->clearLanguageCache(); } } } /** * Loads a local translation task entity. * * @return TMGMTLocalTask */ function tmgmt_local_task_load($tltid) { return entity_load_single('tmgmt_local_task', $tltid); } /** * Loads local translation tasks entity. */ function tmgmt_local_task_load_multiple(array $tltids = array(), $conditions = array()) { return entity_load('tmgmt_local_task', $tltids, $conditions); } /** * Loads a local translation task items entity. * * @return TMGMTLocalTaskItem */ function tmgmt_local_task_item_load($tltiid) { return entity_load_single('tmgmt_local_task_item', $tltiid); } /** * Loads local translation task items entity. */ function tmgmt_local_task_item_load_multiple(array $tltiids = array(), $conditions = array()) { return entity_load('tmgmt_local_task_item', $tltiids, $conditions); } /** * Creates a translation task entity. * * @param $values * (Optional) An array of additional entity values. * * @return TMGMTLocalTask * The local translation task entity. */ function tmgmt_local_task_create(array $values = array()) { return entity_create('tmgmt_local_task', $values); } /** * Deletes multiple local tasks entities. * * @param $tltids * An array of local tasks IDs. */ function tmgmt_local_task_delete_multiple(array $tltids) { entity_get_controller('tmgmt_local_task')->delete($tltids); } /** * Access callback for the local task entity. * * @param $op * The operation being performed. * @param $item * (Optional) A TMGMTLocalTask entity to check access for. If no entity is * given, it will be determined whether access is allowed for all entities. * @param $account * (Optional) The user to check for. Leave it to NULL to check for the global * user. * * @return boolean * TRUE if access is allowed, FALSE otherwise. */ function tmgmt_local_task_access($op, $task = NULL, $account = NULL) { if (user_access('administer tmgmt', $account) || user_access('administer translation tasks', $account)) { // Administrators can do everything. return TRUE; } if (!$account) { global $user; $account = $user; } // @todo - probably need refinement when we introduce more module permissions. switch ($op) { case 'view': case 'update': return user_access('provide translation services', $account); break; case 'unassign': return !empty($task->tuid) && $task->tuid == $account->uid && user_access('provide translation services', $account); } } /** * Access callback for the local task item entity. * * @param $op * The operation being performed. * @param $item * (Optional) A TMGMTLocalTaskItem entity to check access for. If no entity is * given, it will be determined whether access is allowed for all entities. * @param $account * (Optional) The user to check for. Leave it to NULL to check for the global * user. * * @return boolean * TRUE if access is allowed, FALSE otherwise. */ function tmgmt_local_task_item_access($op, TMGMTLocalTaskItem $item = NULL, $account = NULL) { $task = NULL; if ($item) { $task = $item->getTask(); } return entity_access($op, 'tmgmt_local_task', $task, $account); } /** * Loads an array with the word and status statistics of a task. * * @param $tltids * An array of local task ids. * * @return * An array of objects with the keys word_count, count_pending, * count_accepted, count_translated and loop_count. */ function tmgmt_local_task_statistics_load(array $tltids) { $statistics = &drupal_static(__FUNCTION__, array()); // First try to get the values from the cache. $return = array(); $tltids_to_load = array(); foreach ($tltids as $tltid) { if (isset($statistics[$tltid])) { // Info exists in cache, get it from there. $return[$tltid] = $statistics[$tltid]; } else { // Info doesn't exist in cache, add job to the list that needs to be // fetched. $tltids_to_load[] = $tltid; } } // If there are remaining jobs, build a query to fetch them. if (!empty($tltids_to_load)) { // Build the query to fetch the statistics. $query = db_select('tmgmt_local_task_item', 'tlti'); $query->join('tmgmt_local_task', 'tlt', 'tlt.tltid = tlti.tltid'); $query->join('tmgmt_job_item', 'tji', 'tji.tjiid = tlti.tjiid'); $query->fields('tlt', array('tltid')); $query->addExpression('SUM(tji.word_count)', 'word_count'); $query->addExpression('SUM(tlti.count_untranslated)', 'count_untranslated'); $query->addExpression('SUM(tlti.count_translated)', 'count_translated'); $query->addExpression('SUM(tlti.count_completed)', 'count_completed'); $result = $query->groupBy('tlt.tltid') ->condition('tlt.tltid', $tltids_to_load) ->execute(); foreach ($result as $row) { $return[$row->tltid] = $statistics[$row->tltid] = $row; } } return $return; } /** * Returns a specific statistic of a task. * * @param $task * The translation task entity. * @param $key * One of word_count, loop_count, count_pending, count_accepted and * count_translated. * * @return * The requested information as an integer. */ function tmgmt_local_task_statistic(TMGMTLocalTask $task, $key) { $statistics = tmgmt_local_task_statistics_load(array($task->tltid)); if (isset($statistics[$task->tltid]->$key)) { return $statistics[$task->tltid]->$key; } return 0; } /** * Retrieve a labeled list of all available statuses. * * @return array * A list of all available statuses. */ function tmgmt_local_task_statuses() { return $statuses = array( TMGMT_LOCAL_TASK_STATUS_UNASSIGNED => t('Unassigned'), TMGMT_LOCAL_TASK_STATUS_PENDING => t('Pending'), TMGMT_LOCAL_TASK_STATUS_COMPLETED => t('Completed'), TMGMT_LOCAL_TASK_STATUS_REJECTED => t('Rejected'), TMGMT_LOCAL_TASK_STATUS_CLOSED => t('Closed'), ); } /** * Retrieve a labeled list of all available statuses for task items. * * @return array * A list of all available statuses. */ function tmgmt_local_task_item_statuses() { return $statuses = array( TMGMT_LOCAL_TASK_ITEM_STATUS_PENDING => t('Untranslated'), TMGMT_LOCAL_TASK_ITEM_STATUS_COMPLETED => t('Translated'), TMGMT_LOCAL_TASK_ITEM_STATUS_REJECTED => t('Rejected'), TMGMT_LOCAL_TASK_ITEM_STATUS_CLOSED => t('Completed'), ); } /** * Gets all involved language pairs for given tasks. * * @param array $tasks * Array of tasks ids. * * @return array * Array of involved languages. */ function tmgmt_local_tasks_languages($tasks) { $query = db_select('tmgmt_local_task', 't'); $query->condition('tltid', $tasks); $query->join('tmgmt_job', 'j', 't.tjid = j.tjid'); $query->fields('j', array('source_language', 'target_language')); $query->groupBy('target_language'); $result = $query->execute(); $languages = array(); foreach ($result as $row) { if (empty($languages[$row->source_language]) || !in_array($row->target_language, $languages[$row->source_language])) { $languages[$row->source_language][] = $row->target_language; } } return $languages; } /** * Gets translators able to translate all given tasks. * * @param array $tasks * Array of tasks ids. * * @return array * List of uid => name values. */ function tmgmt_local_get_translators_for_tasks($tasks) { $translators = array(); foreach (tmgmt_local_tasks_languages($tasks) as $source_language => $target_languages) { $translators[] = tmgmt_local_translators($source_language, $target_languages); } if (count($translators) > 1) { return call_user_func_array('array_intersect', $translators); } elseif (count($translators) == 1) { return array_shift($translators); } return array(); } /** * @} End of "addtogroup tmgmt_local_task". */ /** * Implements hook_tmgmt_job_item_update(). * * @todo: Move this to the translator plugin API. */ function tmgmt_local_tmgmt_job_item_update(TMGMTJobItem $job_item) { if ($job_item->isAccepted() && !$job_item->original->isAccepted()) { $tltiid = db_query('SELECT tltiid FROM {tmgmt_local_task_item} ti INNER JOIN {tmgmt_local_task} t ON ti.tltid = t.tltid WHERE t.tjid = :tjid AND ti.tjiid = :tjiid', array(':tjid' => $job_item->tjid, ':tjiid' => $job_item->tjiid))->fetchField(); if ($tltiid) { $task_item = tmgmt_local_task_item_load($tltiid); $task_item->closed(); $task_item->save(); // Check if the task can be marked as closed as well. $query = new EntityFieldQuery(); $unclosed_tasks = $query->entityCondition('entity_type', 'tmgmt_local_task_item') ->propertyCondition('tltid', $task_item->tltid) ->propertyCondition('status', TMGMT_LOCAL_TASK_ITEM_STATUS_CLOSED, '<>') ->count() ->execute(); if ($unclosed_tasks == 0) { $task = $task_item->getTask(); $task->setStatus(TMGMT_LOCAL_TASK_STATUS_CLOSED); $task->save(); } } } } /** * Implements hook_tmgmt_job_delete(). */ function tmgmt_local_tmgmt_job_delete(TMGMTJob $job) { $query = new EntityFieldQuery(); $result = $query ->entityCondition('entity_type', 'tmgmt_local_task') ->propertyCondition('tjid', $job->tjid) ->execute(); if (!empty($result['tmgmt_local_task'])) { entity_delete_multiple('tmgmt_local_task', array_keys($result['tmgmt_local_task'])); } } /** * Implements hook_field_access(). */ function tmgmt_local_field_access($op, $field, $entity_type, $entity = NULL, $account = NULL) { if ($field['field_name'] == 'tmgmt_translation_skills' && $entity_type == 'user') { $account = !empty($account) ? $account : $GLOBALS['user']; // If the field is just being viewed, grant access. if ($op == 'view') { return TRUE; } // User can provide transl. services and is dealing with own account. if (!empty($entity) && $entity->uid == $account->uid) { return user_access('provide translation services', $entity); } // Administrators are allowed to deal with others only. if (user_access('administer tmgmt', $account)) { return TRUE; } return FALSE; } } /** * Implements hook_field_attach_insert(). */ function tmgmt_local_field_attach_insert($entity_type, $entity) { if ($entity_type != 'user' || !array_intersect_key(user_roles(TRUE, 'provide translation services'), $entity->roles)) { return; } tmgmt_local_clear_languages_cache(); } /** * Implements hook_field_attach_update(). */ function tmgmt_local_field_attach_update($entity_type, $entity) { if ($entity_type != 'user' || !array_intersect_key(user_roles(TRUE, 'provide translation services'), $entity->roles)) { return; } tmgmt_local_clear_languages_cache(); } /** * Implements hook_field_attach_delete(). */ function tmgmt_local_field_attach_delete($entity_type, $entity) { if ($entity_type != 'user' || !array_intersect_key(user_roles(TRUE, 'provide translation services'), $entity->roles)) { return; } tmgmt_local_clear_languages_cache(); } /** * Gets list of language pairs. * * @param string $source_language * Source language code for which to limit the selection. * @param array $uids * List of user ids for whom to get the language pairs. * * @return array * List of language pairs where a pair is defined by associative array of * source_language and target_language keys. */ function tmgmt_local_supported_language_pairs($source_language = NULL, $uids = array()) { $language_pairs = &drupal_static(__FUNCTION__); $cache_key = $source_language . '_' . implode('_', $uids); if (isset($language_pairs[$cache_key])) { return $language_pairs[$cache_key]; } $language_pairs[$cache_key] = array(); foreach (tmgmt_local_capabilities($source_language, NULL, $uids) as $row) { // Prevent duplicates. $pair_key = $row->tmgmt_translation_skills_language_from . '__' . $row->tmgmt_translation_skills_language_to; $language_pairs[$cache_key][$pair_key] = array( 'source_language' => $row->tmgmt_translation_skills_language_from, 'target_language' => $row->tmgmt_translation_skills_language_to, ); } return $language_pairs[$cache_key]; } /** * Gets supported target languages. * * @param string $source_language * Source language for which to get target languages. * @param array $uids * List of user ids for whom to get the target languages. * * @return array * List of target languages where code is the key as well as the value. */ function tmgmt_local_supported_target_languages($source_language, $uids = array()) { $pairs = tmgmt_local_supported_language_pairs($source_language, $uids); $supported_languages = array(); foreach ($pairs as $pair) { $supported_languages[$pair['target_language']] = $pair['target_language']; } return $supported_languages; } /** * Gets local translator for given language combination. * * @param string $source_language * (optional) Source language to limit on. * @param array $target_languages * (optional) List of target languages to limit to. * * @return array * Array of uid => name translators or empty array if there are no translator * users. */ function tmgmt_local_translators($source_language = NULL, array $target_languages = array()) { $translators = &drupal_static(__FUNCTION__); $key = $source_language . '_' . implode('_', $target_languages); if (isset($translators[$key])) { return $translators[$key]; } // Get all capabilities keyed by uids for given source language. $translators_capabilities = array(); foreach (tmgmt_local_capabilities($source_language) as $row) { $translators_capabilities[$row->uid][] = $row->tmgmt_translation_skills_language_to; } // Filter out translator uids who's capabilities are not sufficient for given // target languages. $translators_uids = array(); foreach ($translators_capabilities as $uid => $translator_capabilities) { // In case provided target languages exceed users capabilities exclude. if (!empty($target_languages) && count(array_diff($target_languages, $translator_capabilities)) > 0) { continue; } $translators_uids[] = $uid; } // Finally build the translators list. $translators[$key] = array(); if (!empty($translators_uids)) { foreach (user_load_multiple($translators_uids) as $account) { $translators[$key][$account->uid] = entity_label('user', $account); } } return $translators[$key]; } /** * Gets language capabilities. * * @param string $source_language * (optional) Limit the source language. * @param string $target_language * (optional) Limit the target language. * @param array $uids * (optional) Limit to specific users. * * @return array * Array of language capabilities with following data: * - tmgmt_translation_skills_language_from * - tmgmt_translation_skills_language_to * - uid * - name * - mail */ function tmgmt_local_capabilities($source_language = NULL, $target_language = NULL, $uids = array()) { $roles = tmgmt_local_translator_roles(); // If there are no roles that have the required permission, return an empty // array. if (empty($roles)) { return array(); } $query = db_select('field_data_tmgmt_translation_skills', 'ts') ->fields('ts', array('tmgmt_translation_skills_language_from', 'tmgmt_translation_skills_language_to')) ->condition('ts.deleted', 0) ->condition('ts.entity_type', 'user'); if ($source_language) { $query->condition('ts.tmgmt_translation_skills_language_from', $source_language); } if ($target_language) { $query->condition('ts.tmgmt_translation_skills_language_to', $target_language); } // Join only active users. $query->innerJoin('users', 'u', 'u.uid = ts.entity_id AND u.status = 1'); $query->fields('u', array('uid', 'name', 'mail')); if (!empty($uids)) { $query->condition('u.uid', $uids); } // If the authenticated user role has the required permission we do not have // to do the role check. if (!in_array('authenticated user', $roles)) { $query->leftJoin('users_roles', 'ur', "ur.uid = u.uid AND ur.rid IN (:roles)", array(':roles' => array_keys($roles))); } // Add a tag so other modules can alter this query at will. $query->addTag('tmgmt_translation_combination'); return $query->execute()->fetchAll(); } /** * Get roles with 'provide translation services' permissions. * * @return array * An associative array with the role id as the key and the role name as * value. */ function tmgmt_local_translator_roles() { return user_roles(TRUE, 'provide translation services'); } /** * Implements hook_rules_action_info_alter(). */ function tmgmt_local_rules_action_info_alter(&$actions) { if (isset($actions['component_rules_tmgmt_local_task_assign_to_me'])) { $actions['component_rules_tmgmt_local_task_assign_to_me']['access callback'] = 'tmgmt_local_rules_component_access'; } if (isset($actions['component_rules_tmgmt_local_task_assign'])) { $actions['component_rules_tmgmt_local_task_assign']['access callback'] = 'tmgmt_local_rules_component_access'; } } /** * Access callback to translation tasks rules component actions. */ function tmgmt_local_rules_component_access($type, $name) { switch ($name) { case 'component_rules_tmgmt_local_task_assign_to_me': return user_access('provide translation services'); case 'component_rules_tmgmt_local_task_assign'; return user_access('administer translation tasks'); } }