881 lines
24 KiB
PHP

<?php
/*
* @file
* Contains job entity class.
*/
/**
* Entity class for the tmgmt_job entity.
*
* @ingroup tmgmt_job
*/
class TMGMTJob extends Entity {
/**
* Translation job identifier.
*
* @var integer
*/
public $tjid;
/**
* A custom label for this job.
*/
public $label;
/**
* Current state of the translation job
* @var type
*/
public $state;
/**
* Language to be translated from.
*
* @var string
*/
public $source_language;
/**
* Language into which the data needs to be translated.
*
* @var varchar
*/
public $target_language;
/**
* Reference to the used translator of this job.
*
* @see TMGMTJob::getTranslatorController()
*
* @var string
*/
public $translator;
/**
* Translator specific configuration and context information for this job.
*
* @var array
*/
public $settings;
/**
* Remote identification of this job.
*
* @var integer
*/
public $reference;
/**
* The time when the job was created as a timestamp.
*
* @var integer
*/
public $created;
/**
* The time when the job was changed as a timestamp.
*
* @var integer
*/
public $changed;
/**
* The user id of the creator of the job.
*
* @var integer
*/
public $uid;
/**
* {@inheritdoc}
*/
public function __construct(array $values = array()) {
parent::__construct($values, 'tmgmt_job');
if (empty($this->tjid)) {
$this->created = REQUEST_TIME;
}
if (!isset($this->state)) {
$this->state = TMGMT_JOB_STATE_UNPROCESSED;
}
}
/**
* Clones job as unprocessed.
*/
public function cloneAsUnprocessed() {
$clone = clone $this;
$clone->tjid = NULL;
$clone->uid = NULL;
$clone->changed = NULL;
$clone->reference = NULL;
$clone->created = REQUEST_TIME;
$clone->state = TMGMT_JOB_STATE_UNPROCESSED;
return $clone;
}
/**
* {@inheritdoc}
*/
public function defaultLabel() {
// In some cases we might have a user-defined label.
if (!empty($this->label)) {
return $this->label;
}
$items = $this->getItems();
$count = count($items);
if ($count > 0) {
$source_label = reset($items)->getSourceLabel();
$t_args = array('!title' => $source_label, '!more' => $count - 1);
$label = format_plural($count, '!title', '!title and !more more', $t_args);
// If the label length exceeds maximum allowed then cut off exceeding
// characters from the title and use it to recreate the label.
if (strlen($label) > TMGMT_JOB_LABEL_MAX_LENGTH) {
$max_length = strlen($source_label) - (strlen($label) - TMGMT_JOB_LABEL_MAX_LENGTH);
$source_label = truncate_utf8($source_label, $max_length, TRUE);
$t_args['!title'] = $source_label;
$label = format_plural($count, '!title', '!title and !more more', $t_args);
}
}
else {
$wrapper = entity_metadata_wrapper($this->entityType, $this);
$source = $wrapper->source_language->label();
if (empty($source)) {
$source = '?';
}
$target = $wrapper->target_language->label();
if (empty($target)) {
$target = '?';
}
$label = t('From !source to !target', array('!source' => $source, '!target' => $target));
}
return $label;
}
/**
* {@inheritdoc}
*/
public function defaultUri() {
return array('path' => 'admin/tmgmt/jobs/' . $this->tjid);
}
/**
* {@inheritdoc}
*/
public function buildContent($view_mode = 'full', $langcode = NULL) {
$content = array();
if (module_exists('tmgmt_ui')) {
$content = entity_ui_get_form('tmgmt_job', $this);
}
return entity_get_controller($this->entityType)->buildContent($this, $view_mode, $langcode, $content);
}
/**
* Adds an item to the translation job.
*
* @param $plugin
* The plugin name.
* @param $item_type
* The source item type.
* @param $item_id
* The source item id.
*
* @return TMGMTJobItem
* The job item that was added to the job or FALSE if it couldn't be saved.
* @throws TMGMTException
* On zero item word count.
*/
public function addItem($plugin, $item_type, $item_id) {
$transaction = db_transaction();
$is_new = FALSE;
if (empty($this->tjid)) {
$this->save();
$is_new = TRUE;
}
$item = tmgmt_job_item_create($plugin, $item_type, $item_id, array('tjid' => $this->tjid));
$item->save();
if ($item->getWordCount() == 0) {
$transaction->rollback();
// In case we got word count 0 for the first job item, NULL tjid so that
// if there is another addItem() call the rolled back job object will get
// persisted.
if ($is_new) {
$this->tjid = NULL;
}
throw new TMGMTException('Job item @label (@type) has no translatable content.',
array('@label' => $item->label(), '@type' => $item->getSourceType()));
}
return $item;
}
/**
* Add a given TMGMTJobItem to this job.
*
* @param TMGMTJobItem $job
* The job item to add.
*/
function addExistingItem(TMGMTJobItem &$item) {
$item->tjid = $this->tjid;
$item->save();
}
/**
* Add a log message for this job.
*
* @param $message
* The message to store in the log. Keep $message translatable by not
* concatenating dynamic values into it! Variables in the message should be
* added by using placeholder strings alongside the variables argument to
* declare the value of the placeholders. See t() for documentation on how
* $message and $variables interact.
* @param $variables
* (Optional) An array of variables to replace in the message on display.
* @param $type
* (Optional) The type of the message. Can be one of 'status', 'error',
* 'warning' or 'debug'. Messages of the type 'debug' will not get printed
* to the screen.
*/
public function addMessage($message, $variables = array(), $type = 'status') {
// Save the job if it hasn't yet been saved.
if (!empty($this->tjid) || $this->save()) {
$message = tmgmt_message_create($message, $variables, array(
'tjid' => $this->tjid,
'type' => $type,
'uid' => $GLOBALS['user']->uid,
));
if ($message->save()) {
return $message;
}
}
return FALSE;
}
/**
* Returns all job items attached to this job.
*
* @param array $conditions
* Additional conditions to pass into EFQ.
*
* @return TMGMTJobItem[]
* An array of translation job items.
*/
public function getItems($conditions = array()) {
$query = new EntityFieldQuery();
$query->entityCondition('entity_type', 'tmgmt_job_item');
$query->propertyCondition('tjid', $this->tjid);
foreach ($conditions as $key => $condition) {
if (is_array($condition)) {
$operator = isset($condition['operator']) ? $condition['operator'] : '=';
$query->propertyCondition($key, $condition['value'], $operator);
}
else {
$query->propertyCondition($key, $condition);
}
}
$results = $query->execute();
if (!empty($results['tmgmt_job_item'])) {
return entity_load('tmgmt_job_item', array_keys($results['tmgmt_job_item']));
}
return array();
}
/**
* Returns all job messages attached to this job.
*
* @return array
* An array of translation job messages.
*/
public function getMessages($conditions = array()) {
$query = new EntityFieldQuery();
$query->entityCondition('entity_type', 'tmgmt_message');
$query->propertyCondition('tjid', $this->tjid);
foreach ($conditions as $key => $condition) {
if (is_array($condition)) {
$operator = isset($condition['operator']) ? $condition['operator'] : '=';
$query->propertyCondition($key, $condition['value'], $operator);
}
else {
$query->propertyCondition($key, $condition);
}
}
$results = $query->execute();
if (!empty($results['tmgmt_message'])) {
return entity_load('tmgmt_message', array_keys($results['tmgmt_message']));
}
return array();
}
/**
* Returns all job messages attached to this job with timestamp newer than
* $time.
*
* @param $time
* (Optional) Messages need to have a newer timestamp than $time. Defaults
* to REQUEST_TIME.
*
* @return array
* An array of translation job messages.
*/
public function getMessagesSince($time = NULL) {
$time = isset($time) ? $time : REQUEST_TIME;
$conditions = array('created' => array('value' => $time, 'operator' => '>='));
return $this->getMessages($conditions);
}
/**
* Retrieves a setting value from the job settings. Pulls the default values
* (if defined) from the plugin controller.
*
* @param $name
* The name of the setting.
*
* @return
* The setting value or $default if the setting value is not set. Returns
* NULL if the setting does not exist at all.
*/
public function getSetting($name) {
if (isset($this->settings[$name])) {
return $this->settings[$name];
}
// The translator might provide default settings.
if ($translator = $this->getTranslator()) {
if (($setting = $translator->getSetting($name)) !== NULL) {
return $setting;
}
}
if ($controller = $this->getTranslatorController()) {
$defaults = $controller->defaultSettings();
if (isset($defaults[$name])) {
return $defaults[$name];
}
}
}
/**
* Returns the translator for this job.
*
* @return TMGMTTranslator
* The translator entity or FALSE if there was a problem.
*/
public function getTranslator() {
if (isset($this->translator)) {
return tmgmt_translator_load($this->translator);
}
return FALSE;
}
/**
* Returns the state of the job. Can be one of the job state constants.
*
* @return integer
* The state of the job or NULL if it hasn't been set yet.
*/
public function getState() {
// We don't need to check if the state is actually set because we always set
// it in the constructor.
return $this->state;
}
/**
* Updates the state of the job.
*
* @param $state
* The new state of the job. Has to be one of the job state constants.
* @param $message
* (Optional) The log message to be saved along with the state change.
* @param $variables
* (Optional) An array of variables to replace in the message on display.
*
* @return int
* The updated state of the job if it could be set.
*
* @see TMGMTJob::addMessage()
*/
public function setState($state, $message = NULL, $variables = array(), $type = 'debug') {
// Return TRUE if the state could be set. Return FALSE otherwise.
if (array_key_exists($state, tmgmt_job_states())) {
$this->state = $state;
$this->save();
// If a message is attached to this state change add it now.
if (!empty($message)) {
$this->addMessage($message, $variables, $type);
}
}
return $this->state;
}
/**
* Checks whether the passed value matches the current state.
*
* @param $state
* The value to check the current state against.
*
* @return boolean
* TRUE if the passed state matches the current state, FALSE otherwise.
*/
public function isState($state) {
return $this->getState() == $state;
}
/**
* Checks whether the user described by $account is the author of this job.
*
* @param $account
* (Optional) A user object. Defaults to the currently logged in user.
*/
public function isAuthor($account = NULL) {
$account = isset($account) ? $account : $GLOBALS['user'];
return $this->uid == $account->uid;
}
/**
* Returns whether the state of this job is 'unprocessed'.
*
* @return boolean
* TRUE if the state is 'unprocessed', FALSE otherwise.
*/
public function isUnprocessed() {
return $this->isState(TMGMT_JOB_STATE_UNPROCESSED);
}
/**
* Returns whether the state of this job is 'aborted'.
*
* @return boolean
* TRUE if the state is 'aborted', FALSE otherwise.
*/
public function isAborted() {
return $this->isState(TMGMT_JOB_STATE_ABORTED);
}
/**
* Returns whether the state of this job is 'active'.
*
* @return boolean
* TRUE if the state is 'active', FALSE otherwise.
*/
public function isActive() {
return $this->isState(TMGMT_JOB_STATE_ACTIVE);
}
/**
* Returns whether the state of this job is 'rejected'.
*
* @return boolean
* TRUE if the state is 'rejected', FALSE otherwise.
*/
public function isRejected() {
return $this->isState(TMGMT_JOB_STATE_REJECTED);
}
/**
* Returns whether the state of this jon is 'finished'.
*
* @return boolean
* TRUE if the state is 'finished', FALSE otherwise.
*/
public function isFinished() {
return $this->isState(TMGMT_JOB_STATE_FINISHED);
}
/**
* Checks whether a job is translatable.
*
* @return boolean
* TRUE if the job can be translated, FALSE otherwise.
*/
public function isTranslatable() {
if ($translator = $this->getTranslator()) {
if ($translator->canTranslate($this)) {
return TRUE;
}
}
return FALSE;
}
/**
* Checks whether a job is abortable.
*
* @return boolean
* TRUE if the job can be aborted, FALSE otherwise.
*/
public function isAbortable() {
// Only non-submitted translation jobs can be aborted.
return $this->isActive();
}
/**
* Checks whether a job is submittable.
*
* @return boolean
* TRUE if the job can be submitted, FALSE otherwise.
*/
public function isSubmittable() {
return $this->isUnprocessed() || $this->isRejected();
}
/**
* Checks whether a job is deletable.
*
* @return boolean
* TRUE if the job can be deleted, FALSE otherwise.
*/
public function isDeletable() {
return !$this->isActive();
}
/**
* Set the state of the job to 'submitted'.
*
* @param $message
* The log message to be saved along with the state change.
* @param $variables
* (Optional) An array of variables to replace in the message on display.
*
* @return TMGMTJob
* The job entity.
*
* @see TMGMTJob::addMessage()
*/
public function submitted($message = NULL, $variables = array(), $type = 'status') {
if (!isset($message)) {
$message = 'The translation job has been submitted.';
}
$this->setState(TMGMT_JOB_STATE_ACTIVE, $message, $variables, $type);
}
/**
* Set the state of the job to 'finished'.
*
* @param $message
* The log message to be saved along with the state change.
* @param $variables
* (Optional) An array of variables to replace in the message on display.
*
* @return TMGMTJob
* The job entity.
*
* @see TMGMTJob::addMessage()
*/
public function finished($message = NULL, $variables = array(), $type = 'status') {
if (!isset($message)) {
$message = 'The translation job has been finished.';
}
return $this->setState(TMGMT_JOB_STATE_FINISHED, $message, $variables, $type);
}
/**
* Sets the state of the job to 'aborted'.
*
* @param $message
* The log message to be saved along with the state change.
* @param $variables
* (Optional) An array of variables to replace in the message on display.
*
* Use TMGMTJob::abortTranslation() to abort a translation.
*
* @return TMGMTJob
* The job entity.
*
* @see TMGMTJob::addMessage()
*/
public function aborted($message = NULL, $variables = array(), $type = 'status') {
if (!isset($message)) {
$message = 'The translation job has been aborted.';
}
/** @var TMGMTJobItem $item */
foreach ($this->getItems() as $item) {
$item->setState(TMGMT_JOB_ITEM_STATE_ABORTED);
}
return $this->setState(TMGMT_JOB_STATE_ABORTED, $message, $variables, $type);
}
/**
* Sets the state of the job to 'rejected'.
*
* @param $message
* The log message to be saved along with the state change.
* @param $variables
* (Optional) An array of variables to replace in the message on display.
*
* @return TMGMTJob
* The job entity.
*
* @see TMGMTJob::addMessage()
*/
public function rejected($message = NULL, $variables = array(), $type = 'error') {
if (!isset($message)) {
$message = 'The translation job has been rejected by the translation provider.';
}
return $this->setState(TMGMT_JOB_STATE_REJECTED, $message, $variables, $type);
}
/**
* Request the translation of a job from the translator.
*
* @return integer
* The updated job status.
*/
public function requestTranslation() {
if (!$this->isTranslatable() || !$controller = $this->getTranslatorController()) {
return FALSE;
}
// We don't know if the translator plugin already processed our
// translation request after this point. That means that the plugin has to
// set the 'submitted', 'needs review', etc. states on its own.
$controller->requestTranslation($this);
}
/**
* Attempts to abort the translation job. Already accepted jobs can not be
* aborted, submitted jobs only if supported by the translator plugin.
* Always use this method if you want to abort a translation job.
*
* @return boolean
* TRUE if the translation job was aborted, FALSE otherwise.
*/
public function abortTranslation() {
if (!$this->isAbortable() || !$controller = $this->getTranslatorController()) {
return FALSE;
}
// We don't know if the translator plugin was able to abort the translation
// job after this point. That means that the plugin has to set the
// 'aborted' state on its own.
return $controller->abortTranslation($this);
}
/**
* Returns the translator plugin controller of the translator of this job.
*
* @return TMGMTTranslatorPluginControllerInterface
* The controller of the translator plugin.
*/
public function getTranslatorController() {
if ($translator = $this->getTranslator($this)) {
return $translator->getController();
}
return FALSE;
}
/**
* Returns the source data of all job items.
*
* @param $key
* If present, only the subarray identified by key is returned.
* @param $index
* Optional index of an attribute below $key.
* @return array
* A nested array with the source data where the most upper key is the job
* item id.
*/
public function getData(array $key = array(), $index = NULL) {
$data = array();
if (!empty($key)) {
$tjiid = array_shift($key);
$item = entity_load_single('tmgmt_job_item', $tjiid);
if ($item) {
$data[$tjiid] = $item->getData($key, $index);
// If not set, use the job item label as the data label.
if (!isset($data[$tjiid]['#label'])) {
$data[$tjiid]['#label'] = $item->getSourceLabel();
}
}
}
else {
foreach ($this->getItems() as $tjiid => $item) {
$data[$tjiid] = $item->getData();
// If not set, use the job item label as the data label.
if (!isset($data[$tjiid]['#label'])) {
$data[$tjiid]['#label'] = $item->getSourceLabel();
}
}
}
return $data;
}
/**
* Sums up all pending counts of this jobs job items.
*
* @return
* The sum of all pending counts
*/
public function getCountPending() {
return tmgmt_job_statistic($this, 'count_pending');
}
/**
* Sums up all translated counts of this jobs job items.
*
* @return
* The sum of all translated counts
*/
public function getCountTranslated() {
return tmgmt_job_statistic($this, 'count_translated');
}
/**
* Sums up all accepted counts of this jobs job items.
*
* @return
* The sum of all accepted data items.
*/
public function getCountAccepted() {
return tmgmt_job_statistic($this, 'count_accepted');
}
/**
* Sums up all accepted counts of this jobs job items.
*
* @return
* The sum of all accepted data items.
*/
public function getCountReviewed() {
return tmgmt_job_statistic($this, 'count_reviewed');
}
/**
* Sums up all word counts of this jobs job items.
*
* @return
* The total word count of this job.
*/
public function getWordCount() {
return tmgmt_job_statistic($this, 'word_count');
}
/**
* Store translated data back into the items.
*
* @param $data
* Partially or complete translated data, the most upper key needs to be
* the translation job item id.
* @param $key
* (Optional) Either a flattened key (a 'key1][key2][key3' string) or a nested
* one, e.g. array('key1', 'key2', 'key2'). Defaults to an empty array which
* means that it will replace the whole translated data array. The most
* upper key entry needs to be the job id (tjiid).
*/
public function addTranslatedData($data, $key = NULL) {
$key = tmgmt_ensure_keys_array($key);
$items = $this->getItems();
// If there is a key, get the specific item and forward the call.
if (!empty($key)) {
$item_id = array_shift($key);
if (isset($items[$item_id])) {
$items[$item_id]->addTranslatedData($data, $key);
}
}
else {
foreach ($data as $key => $value) {
if (isset($items[$key])) {
$items[$key]->addTranslatedData($value);
}
}
}
}
/**
* Propagates the returned job item translations to the sources.
*
* @return boolean
* TRUE if we were able to propagate the translated data, FALSE otherwise.
*/
public function acceptTranslation() {
foreach ($this->getItems() as $item) {
$item->acceptTranslation();
}
}
/**
* Gets remote mappings for current job.
*
* @return array
* List of TMGMTRemote entities.
*/
public function getRemoteMappings() {
$query = new EntityFieldQuery();
$query->entityCondition('entity_type', 'tmgmt_remote');
$query->propertyCondition('tjid', $this->tjid);
$result = $query->execute();
if (isset($result['tmgmt_remote'])) {
return entity_load('tmgmt_remote', array_keys($result['tmgmt_remote']));
}
return array();
}
/**
* Invoke the hook 'hook_tmgmt_source_suggestions' to get all suggestions.
*
* @param arary $conditions
* Conditions to pass only some and not all items to the hook.
*
* @return array
* An array with all additional translation suggestions.
* - job_item: A TMGMTJobItem instance.
* - referenced: A string which indicates where this suggestion comes from.
* - from_job: The main TMGMTJob-ID which suggests this translation.
*/
public function getSuggestions(array $conditions = array()) {
$suggestions = module_invoke_all('tmgmt_source_suggestions', $this->getItems($conditions), $this);
// Each TMGMTJob needs a job id to be able to count the words, because the
// source-language is stored in the job and not the item.
foreach ($suggestions as &$suggestion) {
$jobItem = $suggestion['job_item'];
$jobItem->tjid = $this->tjid;
$jobItem->recalculateStatistics();
}
return $suggestions;
}
/**
* Removes all suggestions from the given list which should not be processed.
*
* This function removes all suggestions from the given list which are already
* assigned to a translation job or which should not be processed because
* there are no words, no translation is needed, ...
*
* @param array &$suggestions
* Associative array of translation suggestions. It must contain at least:
* - tmgmt_job: An instance of a TMGMTJobItem.
*/
public function cleanSuggestionsList(array &$suggestions) {
foreach ($suggestions as $k => $suggestion) {
if (is_array($suggestion) && isset($suggestion['job_item']) && ($suggestion['job_item'] instanceof TMGMTJobItem)) {
$jobItem = $suggestion['job_item'];
// Items with no words to translate should not be presented.
if ($jobItem->getWordCount() <= 0) {
unset($suggestions[$k]);
continue;
}
// Check if there already exists a translation job for this item in the
// current language.
$items = tmgmt_job_item_load_all_latest($jobItem->plugin, $jobItem->item_type, $jobItem->item_id, $this->source_language);
if ($items && isset($items[$this->target_language])) {
unset($suggestions[$k]);
continue;
}
} else {
unset($suggestions[$k]);
continue;
}
}
}
}