translation.handler.inc 48 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490
  1. <?php
  2. /**
  3. * @file
  4. * Default translation handler for the translation module.
  5. */
  6. /**
  7. * Interface for enabling entity translation.
  8. *
  9. * Defines a set of methods to allow any entity to be processed by the entity
  10. * translation UI.
  11. */
  12. interface EntityTranslationHandlerInterface {
  13. /**
  14. * Loads the translation data into the wrapped entity.
  15. */
  16. public function loadTranslations();
  17. /**
  18. * Writes the translation status to the storage.
  19. */
  20. public function saveTranslations();
  21. /**
  22. * Returns the translation data for the current (wrapped) entity.
  23. */
  24. public function getTranslations();
  25. /**
  26. * Adds/updates an entity translation.
  27. *
  28. * @param $translation
  29. * A translation array as defined by the translation table's schema.
  30. * @param $values
  31. * (optional) the values that should be assigned to the field translations.
  32. */
  33. public function setTranslation($translation, $values = NULL);
  34. /**
  35. * Removes a translation from the translation set.
  36. *
  37. * @param $langcode
  38. * The language code of the translation to be removed.
  39. */
  40. public function removeTranslation($langcode);
  41. /**
  42. * Initializes the translation set by creating the original translation.
  43. */
  44. public function initTranslations();
  45. /**
  46. * Updates the translation set from the current entity status.
  47. */
  48. public function updateTranslations();
  49. /**
  50. * Removes all translations from the translation set.
  51. */
  52. public function removeTranslations();
  53. /**
  54. * Initialize the language of the original field values.
  55. *
  56. * Ensure that the original translation language matches the language assigned
  57. * to the original field values.
  58. *
  59. * @return
  60. * TRUE if any initialization was necessary, FALSE otherwise.
  61. */
  62. public function initOriginalTranslation();
  63. /**
  64. * Returns the entity language.
  65. */
  66. public function getLanguage();
  67. /**
  68. * Returns the translation object key for the wrapped entity type.
  69. */
  70. public function getLanguageKey();
  71. /**
  72. * Returns the default language for the wrapped entity type and bundle.
  73. */
  74. public function getDefaultLanguage();
  75. /**
  76. * Sets the language of the orginal translation.
  77. *
  78. * @param $langcode
  79. * The language code of the original content values.
  80. */
  81. public function setOriginalLanguage($langcode);
  82. /**
  83. * Returns TRUE if the entity is currently being translated.
  84. */
  85. public function isTranslating();
  86. /**
  87. * Notifies the translation handler that its entity is being translated.
  88. *
  89. * @param $translating
  90. * A boolean value.
  91. */
  92. public function setTranslating($translating);
  93. /**
  94. * Return TRUE if a new revision of the entity has just been created.
  95. */
  96. public function isRevision();
  97. /**
  98. * Replaces the wrapped entity.
  99. *
  100. * @param $entity
  101. * The entity to be translated.
  102. */
  103. public function setEntity($entity);
  104. /**
  105. * Checks that the wrapped entity matches the give entity
  106. *
  107. * @param $entity_type
  108. * The type of the entity to be matched.
  109. * @param $entity
  110. * The entity to be matched.
  111. */
  112. public function isWrappedEntity($entity_type, $entity);
  113. /**
  114. * Sets the translation update status.
  115. *
  116. * @param $outdated
  117. * A boolean value.
  118. */
  119. public function setOutdated($outdated);
  120. /**
  121. * Returns the base path for the current entity.
  122. *
  123. * This path will be prepended to the URL of any administration page.
  124. *
  125. * @return
  126. * A string to be used as a URL path prefix.
  127. */
  128. public function getBasePath();
  129. /**
  130. * Returns the path of the entity edit form.
  131. *
  132. * @param $langcode
  133. * (optional) The language the edit form should be presented in.
  134. */
  135. public function getEditPath($langcode = NULL);
  136. /**
  137. * Returns the path of the translation overview page.
  138. */
  139. public function getTranslatePath();
  140. /**
  141. * Returns the path of the entity view page.
  142. */
  143. public function getViewPath();
  144. /**
  145. * Returns the active path scheme.
  146. */
  147. public function getPathScheme();
  148. /**
  149. * Changes the active path scheme.
  150. *
  151. * @param $scheme
  152. * The new path scheme.
  153. */
  154. public function setPathScheme($scheme);
  155. /**
  156. * Initializes the most suited path scheme based on the given path.
  157. *
  158. * @param $path
  159. * (optional) The path to match the defined path schemes against. Defaults
  160. * to the current path.
  161. *
  162. * @return
  163. * The matched path scheme key.
  164. */
  165. public function initPathScheme($path = NULL);
  166. /**
  167. * A string allowing the user to identify the entity.
  168. */
  169. public function getLabel();
  170. /**
  171. * Checks if the user can perform the given operation on the wrapped entity.
  172. *
  173. * @param $op
  174. * The operation to be performed.
  175. *
  176. * @return
  177. * TRUE if the user is allowed to perform the given operation, FALSE
  178. * otherwise.
  179. */
  180. public function getAccess($op);
  181. /**
  182. * Checks if a user is allowed to edit the given translation.
  183. */
  184. public function getTranslationAccess($langcode);
  185. /**
  186. * Checks if a user is allowed to edit shared fields on the active form.
  187. */
  188. public function getSharedFieldsAccess();
  189. /**
  190. * Return TRUE if the entity supports URL aliasing.
  191. */
  192. public function isAliasEnabled();
  193. /**
  194. * Sets the active form language.
  195. */
  196. public function setFormLanguage($langcode);
  197. /**
  198. * Retrieves the active form language.
  199. */
  200. public function getFormLanguage();
  201. /**
  202. * Sets the source language for the translation being created.
  203. */
  204. public function setSourceLanguage($langcode);
  205. /**
  206. * Retrieves the source language for the translation being created.
  207. */
  208. public function getSourceLanguage();
  209. /**
  210. * Returns TRUE if a new entity is currently wrapped.
  211. */
  212. public function isNewEntity();
  213. /**
  214. * Returns TRUE whether we are displying an entity form.
  215. */
  216. public function isEntityForm();
  217. /**
  218. * Performs the needed alterations to the entity form.
  219. */
  220. public function entityForm(&$form, &$form_state);
  221. /**
  222. * Adds an language selection widget to the entity form.
  223. */
  224. public function entityFormLanguageWidget(&$form, &$form_state);
  225. /**
  226. * Performs submission tasks on the submitted entity language.
  227. */
  228. public function entityFormLanguageWidgetSubmit($form, &$form_state);
  229. /**
  230. * Handle shared form elements.
  231. */
  232. public function entityFormSharedElements(&$element);
  233. /**
  234. * Performs validation tasks on the submitted entity forms.
  235. */
  236. public function entityFormValidate($form, &$form_state);
  237. /**
  238. * Performs submission tasks on the submitted entity forms.
  239. */
  240. public function entityFormSubmit($form, &$form_state);
  241. /**
  242. * Alters the local tasks render array to populate the language tabs.
  243. */
  244. public function localTasksAlter(&$data, $router_item, $root_path);
  245. }
  246. /**
  247. * Class implementing the default entity translation behaviours.
  248. */
  249. class EntityTranslationDefaultHandler implements EntityTranslationHandlerInterface {
  250. protected $entityType;
  251. protected $entity;
  252. protected $entityInfo;
  253. protected $entityId;
  254. protected $bundle;
  255. private $entityForm;
  256. private $translating;
  257. private $outdated;
  258. private $formLanguage;
  259. private $sourceLanguage;
  260. private $pathScheme;
  261. private $pathWildcard;
  262. private $basePath;
  263. private $editPath;
  264. private $translatePath;
  265. private $viewPath;
  266. private $routerMap;
  267. /**
  268. * Initializes an instance of the translation handler.
  269. *
  270. * @param $entity_type
  271. * The type of the entity being wrapped.
  272. * @param $entity_info
  273. * The entity information for the entity being wrapped.
  274. * @param $entity
  275. * The entity being wrapped.
  276. */
  277. public function __construct($entity_type, $entity_info, $entity) {
  278. $this->entityType = $entity_type;
  279. $this->entityInfo = $entity_info;
  280. $this->setEntity($entity);
  281. $this->entityForm = FALSE;
  282. $this->translating = FALSE;
  283. $this->outdated = FALSE;
  284. $this->formLanguage = FALSE;
  285. $this->sourceLanguage = FALSE;
  286. $this->pathScheme = 'default';
  287. $this->routerMap = array();
  288. if (entity_translation_enabled($entity_type)) {
  289. $this->initPathVariables();
  290. }
  291. }
  292. /**
  293. * Read the translation data from the storage.
  294. */
  295. public static function loadMultiple($entity_type, $entities) {
  296. $entity_info = entity_get_info($entity_type);
  297. if (isset($entity_info['entity keys']['translations'])){
  298. $translations_key = $entity_info['entity keys']['translations'];
  299. }
  300. else {
  301. // If no translations key is defined we cannot proceed.
  302. return;
  303. }
  304. foreach ($entities as $id => $entity) {
  305. $entities[$id]->{$translations_key} = self::emptyTranslations();
  306. }
  307. $results = db_select('entity_translation', 'et')
  308. ->fields('et')
  309. ->condition('entity_type', $entity_type)
  310. ->condition('entity_id', array_keys($entities), 'IN')
  311. ->orderBy('entity_id')
  312. ->orderBy('created')
  313. ->execute();
  314. foreach ($results as $row) {
  315. $id = $row->entity_id;
  316. $entities[$id]->{$translations_key}->data[$row->language] = (array) $row;
  317. // Only the original translation has an empty source.
  318. if (empty($row->source)) {
  319. $entities[$id]->{$translations_key}->original = $row->language;
  320. }
  321. }
  322. }
  323. /**
  324. * Returns the localized links for the given path.
  325. */
  326. public static function languageSwitchLinks($path) {
  327. $links = language_negotiation_get_switch_links(LANGUAGE_TYPE_CONTENT, $path);
  328. if (empty($links)) {
  329. // If content language is set up to fall back to the interface language,
  330. // then there will be no switch links for LANGUAGE_TYPE_CONTENT, ergo we
  331. // also need to use interface switch links.
  332. $links = language_negotiation_get_switch_links(LANGUAGE_TYPE_INTERFACE, $path);
  333. }
  334. return $links;
  335. }
  336. /**
  337. * @see EntityTranslationHandlerInterface::loadTranslations()
  338. */
  339. public function loadTranslations() {
  340. if (isset($this->entityId)) {
  341. $this->loadMultiple($this->entityType, array($this->entityId => $this->entity));
  342. }
  343. elseif ($translations_key = $this->getTranslationsKey()) {
  344. $this->entity->{$translations_key} = $this->emptyTranslations();
  345. }
  346. }
  347. /**
  348. * @see EntityTranslationHandlerInterface::saveTranslations()
  349. */
  350. public function saveTranslations() {
  351. // Delete and insert, rather than update, in case a value was added.
  352. db_delete('entity_translation')
  353. ->condition('entity_type', $this->entityType)
  354. ->condition('entity_id', $this->entityId)
  355. ->execute();
  356. $translations = $this->getTranslations();
  357. if (count($translations->data)) {
  358. global $user;
  359. $columns = array('entity_type', 'entity_id', 'language', 'source', 'uid', 'status', 'translate', 'created', 'changed');
  360. $query = db_insert('entity_translation')->fields($columns);
  361. // These values should overridde the translation ones as they are not
  362. // supposed to change.
  363. $overrides = array(
  364. 'entity_id' => $this->entityId,
  365. 'entity_type' => $this->entityType,
  366. );
  367. // These instead are just defaults.
  368. $defaults = array(
  369. 'source' => '',
  370. 'uid' => $user->uid,
  371. 'translate' => 0,
  372. 'status' => 0,
  373. 'created' => REQUEST_TIME,
  374. 'changed' => REQUEST_TIME,
  375. );
  376. foreach ($translations->data as $langcode => $translation) {
  377. $translation = $overrides + $translation + $defaults;
  378. $query->values($translation);
  379. }
  380. $query->execute();
  381. }
  382. // The translation handler interface decouples operations on translations at
  383. // data structure level from CRUD operations. Hence hooks must be fired
  384. // after changes are actually persisted.
  385. if (!empty($translations->hook)) {
  386. // Hook info is keyed by language code so that subsequent operations at
  387. // data structure level do not cause multiple hooks for the same data to
  388. // be fired. For instance if a translation is first updated and then
  389. // deleted, only the 'delete' hook should be fired, because it is the only
  390. // change that has actually been persisted.
  391. foreach ($translations->hook as $langcode => $info) {
  392. $translation = isset($translations->data[$langcode]) ? $translations->data[$langcode] : $langcode;
  393. $data = isset($info['data']) ? $info['data'] : NULL;
  394. module_invoke_all('entity_translation_' . $info['hook'], $this->entityType, $this->entity, $translation, $data);
  395. // Provide Rules events integration if available.
  396. if (module_exists('rules')) {
  397. // Pass the entity as a wrapped one since rules can't do it for us
  398. // when using the variable type 'entity'.
  399. rules_invoke_event('entity_translation_' . $info['hook'], $this->entityType, entity_metadata_wrapper($this->entityType, $this->entity), $translation, $data);
  400. }
  401. }
  402. // Avoid firing hooks more than once for the same changes.
  403. $translations->hook = array();
  404. }
  405. }
  406. /**
  407. * @see EntityTranslationHandlerInterface::getTranslations()
  408. */
  409. public function getTranslations() {
  410. if ($translations_key = $this->getTranslationsKey()) {
  411. // Lazy load translations if for some reason the wrapped entity did not go
  412. // through hook_entity_load().
  413. if (!isset($this->entity->{$translations_key})) {
  414. $this->loadTranslations();
  415. }
  416. return $this->entity->{$translations_key};
  417. }
  418. return self::emptyTranslations();
  419. }
  420. /**
  421. * @see EntityTranslationHandlerInterface::setTranslation()
  422. */
  423. public function setTranslation($translation, $values = NULL) {
  424. if (isset($translation['source']) && $translation['language'] == $translation['source']) {
  425. throw new Exception('Invalid translation language');
  426. }
  427. $translations = $this->getTranslations();
  428. $langcode = $translation['language'];
  429. $this->setTranslating(TRUE);
  430. if (isset($translations->data[$langcode])) {
  431. $translation = array_merge($translations->data[$langcode], $translation);
  432. $translation['changed'] = REQUEST_TIME;
  433. // If a newly inserted translation has not been stored yet do not fire an
  434. // update hook.
  435. $hook = empty($translations->hook[$langcode]['hook']) ? 'update' : $translations->hook[$langcode]['hook'];
  436. }
  437. else {
  438. $hook = 'insert';
  439. }
  440. // Store the translation data.
  441. $translations->data[$langcode] = $translation;
  442. // Keep track that the translation has been inserted or updated.
  443. $translations->hook[$langcode] = array('hook' => $hook, 'data' => $values);
  444. if (is_array($values)) {
  445. // Update field translations.
  446. foreach (field_info_instances($this->entityType, $this->bundle) as $instance) {
  447. $field_name = $instance['field_name'];
  448. $field = field_info_field($field_name);
  449. if ($field['translatable'] && isset($values[$field_name])) {
  450. $this->entity->{$field_name}[$langcode] = $values[$field_name][$langcode];
  451. }
  452. }
  453. }
  454. }
  455. /**
  456. * @see EntityTranslationHandlerInterface::removeTranslation()
  457. */
  458. public function removeTranslation($langcode) {
  459. $translations_key = $this->getTranslationsKey();
  460. if (empty($translations_key)) {
  461. return;
  462. }
  463. $hook_info = array('hook' => 'delete');
  464. if (!empty($langcode)) {
  465. unset($this->entity->{$translations_key}->data[$langcode]);
  466. // Keep track that the current translation has been removed.
  467. $this->entity->{$translations_key}->hook[$langcode] = $hook_info;
  468. }
  469. elseif (!empty($this->entity->{$translations_key}->data)) {
  470. $keys = array_keys($this->entity->{$translations_key}->data);
  471. $values = array_fill(0, count($keys), $hook_info);
  472. // Keep track that the all translations have been removed.
  473. $this->entity->{$translations_key}->hook = array_combine($keys, $values);
  474. // Actually remove translations.
  475. $this->entity->{$translations_key}->data = array();
  476. }
  477. // Remove field translations.
  478. foreach (field_info_instances($this->entityType, $this->bundle) as $instance) {
  479. $field_name = $instance['field_name'];
  480. $field = field_info_field($field_name);
  481. if ($field['translatable']) {
  482. if (!empty($langcode)) {
  483. $this->entity->{$field_name}[$langcode] = array();
  484. }
  485. else {
  486. $this->entity->{$field_name} = array();
  487. }
  488. }
  489. }
  490. // Clear the cache for this entity.
  491. entity_get_controller($this->entityType)->resetCache(array($this->getEntityId()));
  492. }
  493. /**
  494. * @see EntityTranslationHandlerInterface::initTranslations()
  495. */
  496. public function initTranslations() {
  497. $langcode = $this->getLanguage();
  498. if (!empty($langcode)) {
  499. $translation = array('language' => $langcode, 'status' => 1);
  500. $this->setTranslation($translation);
  501. $this->setOriginalLanguage($langcode);
  502. }
  503. }
  504. /**
  505. * @see EntityTranslationHandlerInterface::updateTranslations()
  506. */
  507. public function updateTranslations() {
  508. $langcode = $this->getLanguage();
  509. // Only create a translation on edit if the translation set is empty:
  510. // the entity might have been created with language set to "language
  511. // neutral".
  512. if (empty($this->getTranslations()->data)) {
  513. $this->initTranslations();
  514. }
  515. elseif (!empty($langcode) && !$this->isTranslating()) {
  516. $this->setOriginalLanguage($langcode);
  517. }
  518. }
  519. /**
  520. * @see EntityTranslationHandlerInterface::removeTranslations()
  521. */
  522. public function removeTranslations() {
  523. $this->removeTranslation(NULL);
  524. }
  525. /**
  526. * @see EntityTranslationHandlerInterface::initOriginalTranslation()
  527. */
  528. public function initOriginalTranslation() {
  529. $fixed = FALSE;
  530. $translations = $this->getTranslations();
  531. foreach (field_info_instances($this->entityType, $this->bundle) as $instance) {
  532. $field_name = $instance['field_name'];
  533. $field = field_info_field($field_name);
  534. $langcode = count($this->entity->{$field_name}) == 1 ? key($this->entity->{$field_name}) : $translations->original;
  535. if ($langcode == LANGUAGE_NONE && $field['translatable']) {
  536. $this->entity->{$field_name}[$translations->original] = $this->entity->{$field_name}[$langcode];
  537. $this->entity->{$field_name}[$langcode] = array();
  538. $fixed = TRUE;
  539. }
  540. }
  541. return $fixed;
  542. }
  543. /**
  544. * @see EntityTranslationHandlerInterface::getLanguage()
  545. */
  546. public function getLanguage() {
  547. if (!empty($this->entityInfo['entity keys']['language'])) {
  548. $language_key = $this->entityInfo['entity keys']['language'];
  549. if (!empty($this->entity->{$language_key})) {
  550. return $this->entity->{$language_key};
  551. }
  552. }
  553. $translations = $this->getTranslations();
  554. if (!empty($translations->original)) {
  555. return $translations->original;
  556. }
  557. else {
  558. // When we are creating an entity and no language is specified fall back
  559. // to the default language for the current entity and bundle.
  560. return $this->getDefaultLanguage();
  561. }
  562. }
  563. /**
  564. * @see EntityTranslationHandlerInterface::getLanguageKey()
  565. */
  566. public function getLanguageKey() {
  567. return !empty($this->entityInfo['entity keys']['language']) ? $this->entityInfo['entity keys']['language'] : 'language';
  568. }
  569. /**
  570. * @see EntityTranslationHandlerInterface::getDefaultLanguage()
  571. */
  572. public function getDefaultLanguage() {
  573. $settings = variable_get('entity_translation_settings_' . $this->entityType . '__' . $this->bundle, array());
  574. if (!empty($settings['default_language'])) {
  575. switch ($settings['default_language']) {
  576. case ENTITY_TRANSLATION_LANGUAGE_DEFAULT:
  577. $langcode = language_default()->language;
  578. break;
  579. case ENTITY_TRANSLATION_LANGUAGE_CURRENT:
  580. $langcode = $GLOBALS[LANGUAGE_TYPE_CONTENT]->language;
  581. break;
  582. case ENTITY_TRANSLATION_LANGUAGE_AUTHOR:
  583. $langcode = $GLOBALS['user']->language;
  584. break;
  585. default:
  586. // An actual language code has been explicitly configured.
  587. $langcode = $settings['default_language'];
  588. }
  589. }
  590. else {
  591. // Fall back to the default language to keep backward compatibility.
  592. $langcode = language_default()->language;
  593. }
  594. return $langcode;
  595. }
  596. /**
  597. * @see EntityTranslationHandlerInterface::setOriginalLanguage()
  598. */
  599. public function setOriginalLanguage($langcode) {
  600. $translations = $this->getTranslations();
  601. if (isset($translations->original) && $translations->original != $langcode) {
  602. $translations->data[$langcode] = $translations->data[$translations->original];
  603. $translations->data[$langcode]['language'] = $langcode;
  604. unset($translations->data[$translations->original]);
  605. }
  606. $translations->original = $langcode;
  607. }
  608. /**
  609. * @see EntityTranslationHandlerInterface::isTranslating()
  610. */
  611. public function isTranslating() {
  612. return $this->translating;
  613. }
  614. /**
  615. * @see EntityTranslationHandlerInterface::setTranslating()
  616. */
  617. public function setTranslating($translating) {
  618. $this->translating = $translating;
  619. }
  620. /**
  621. * @see EntityTranslationHandlerInterface::isRevision()
  622. */
  623. public function isRevision() {
  624. return FALSE;
  625. }
  626. /**
  627. * @see EntityTranslationHandlerInterface::setEntity()
  628. */
  629. public function setEntity($entity) {
  630. $this->entity = $entity;
  631. // Ensure translations data is populated.
  632. $translations_key = $this->getTranslationsKey();
  633. if ($translations_key && !isset($this->entity->{$translations_key})) {
  634. $this->entity->{$translations_key} = self::emptyTranslations();
  635. }
  636. // Update bundle and entity id properties.
  637. list($this->entityId, , $this->bundle) = entity_extract_ids($this->entityType, $this->entity);
  638. }
  639. /**
  640. * @see EntityTranslationHandlerInterface::isWrappedEntity()
  641. */
  642. public function isWrappedEntity($entity_type, $entity) {
  643. list($id,,) = entity_extract_ids($entity_type, $entity);
  644. return $entity_type == $this->entityType && $id == $this->entityId;
  645. }
  646. /**
  647. * @see EntityTranslationHandlerInterface::setOutdated()
  648. */
  649. public function setOutdated($outdated) {
  650. if ($outdated) {
  651. $translations = $this->getTranslations();
  652. foreach ($translations->data as $langcode => &$translation) {
  653. if ($langcode != $this->getFormLanguage()) {
  654. $translation['translate'] = 1;
  655. }
  656. }
  657. }
  658. }
  659. /**
  660. * @see EntityTranslationHandlerInterface::getBasePath()
  661. */
  662. public function getBasePath() {
  663. return $this->basePath;
  664. }
  665. /**
  666. * @see EntityTranslationHandlerInterface::getEditPath()
  667. */
  668. public function getEditPath($langcode = NULL) {
  669. return empty($this->editPath) ? FALSE : (empty($langcode) ? $this->editPath : $this->editPath . '/' . $langcode);
  670. }
  671. /**
  672. * @see EntityTranslationHandlerInterface::getTranslatePath()
  673. */
  674. public function getTranslatePath() {
  675. return $this->translatePath;
  676. }
  677. /**
  678. * @see EntityTranslationHandlerInterface::getViewPath()
  679. */
  680. public function getViewPath() {
  681. return $this->viewPath;
  682. }
  683. /**
  684. * @see EntityTranslationHandlerInterface::getPathScheme()
  685. */
  686. public function getPathScheme() {
  687. return $this->pathScheme;
  688. }
  689. /**
  690. * @see EntityTranslationHandlerInterface::setPathScheme()
  691. */
  692. public function setPathScheme($scheme) {
  693. if ($scheme != $this->pathScheme) {
  694. $this->pathScheme = $scheme;
  695. $this->initPathVariables();
  696. }
  697. }
  698. /**
  699. * @see EntityTranslationHandlerInterface::initPathScheme()
  700. */
  701. public function initPathScheme($path = NULL) {
  702. $scheme = 'default';
  703. // If only one path scheme is defined no need to find one.
  704. if (count($this->entityInfo['translation']['entity_translation']['path schemes']) > 1) {
  705. $item = menu_get_item($path);
  706. if (!empty($item['path'])) {
  707. $current_path_scheme = $this->findMatchingPathScheme($item['path']);
  708. if ($current_path_scheme) {
  709. $scheme = $current_path_scheme;
  710. $this->routerMap = $item['original_map'];
  711. }
  712. }
  713. }
  714. $this->setPathScheme($scheme);
  715. return $scheme;
  716. }
  717. /**
  718. * Find a path scheme matching the given path.
  719. *
  720. * @param $router_path
  721. * The path to match against.
  722. *
  723. * @return
  724. * The key of the path scheme if found, FALSE otherwise.
  725. */
  726. protected function findMatchingPathScheme($router_path) {
  727. $path_keys = array_flip(array('base path', 'view path', 'edit path', 'translate path'));
  728. foreach ($this->entityInfo['translation']['entity_translation']['path schemes'] as $delta => $scheme) {
  729. // Construct regular expression pattern for determining whether any path
  730. // in the current scheme matches the current request path.
  731. $path_elements = array_intersect_key($scheme, $path_keys);
  732. // Add additional path elements which were added during
  733. // entity_translation_menu_alter().
  734. if (isset($path_elements['edit path'])) {
  735. $path_elements[] = $path_elements['edit path'] . '/%entity_translation_language';
  736. $path_elements[] = $path_elements['edit path'] . '/add/%entity_translation_language/%entity_translation_language';
  737. }
  738. if (isset($path_elements['translate path'])) {
  739. $path_elements[] = $path_elements['translate path'] . '/delete/%entity_translation_language';
  740. }
  741. // Replace wildcards with % for matching parameters.
  742. $path_elements = array_flip(preg_replace('|%[^/]+|', '%', $path_elements));
  743. if (isset($path_elements[$router_path])) {
  744. return $delta;
  745. }
  746. }
  747. return FALSE;
  748. }
  749. /**
  750. * @see EntityTranslationHandlerInterface::getLabel()
  751. */
  752. public function getLabel() {
  753. if (($label = entity_label($this->entityType, $this->entity)) !== FALSE) {
  754. return $label;
  755. }
  756. else {
  757. return "{$this->entityType}:{$this->getEntityId()}" ;
  758. }
  759. }
  760. /**
  761. * @see EntityTranslationHandlerInterface::getAccess()
  762. */
  763. public function getAccess($op) {
  764. return TRUE;
  765. }
  766. /**
  767. * @see EntityTranslationHandlerInterface::getTranslationAccess()
  768. */
  769. public function getTranslationAccess($langcode) {
  770. return !empty($this->entityInfo['translation']['entity_translation']['skip original values access']) || !entity_translation_workflow_enabled() || $langcode != $this->getLanguage() || user_access('edit original values') || user_access("edit {$this->entityType} original values");
  771. }
  772. /**
  773. * @see EntityTranslationHandlerInterface::getSharedFieldsAccess()
  774. */
  775. public function getSharedFieldsAccess() {
  776. $settings = entity_translation_settings($this->entityType, $this->bundle);
  777. return ($settings['shared_fields_original_only'] == FALSE || $this->getLanguage() == $this->getFormLanguage()) &&
  778. (!entity_translation_workflow_enabled() || user_access('edit translation shared fields') || user_access("edit {$this->entityType} translation shared fields"));
  779. }
  780. /**
  781. * @see EntityTranslationHandlerInterface::isAliasEnabled()
  782. */
  783. public function isAliasEnabled() {
  784. return !empty($this->entityInfo['translation']['entity_translation']['alias']);
  785. }
  786. /**
  787. * @see EntityTranslationHandlerInterface::setFormLanguage()
  788. */
  789. public function setFormLanguage($langcode) {
  790. $this->formLanguage = $langcode;
  791. }
  792. /**
  793. * @see EntityTranslationHandlerInterface::getFormLanguage()
  794. */
  795. public function getFormLanguage() {
  796. return !empty($this->formLanguage) ? $this->formLanguage : $this->getLanguage();
  797. }
  798. /**
  799. * @see EntityTranslationHandlerInterface::setSourceLanguage()
  800. */
  801. public function setSourceLanguage($langcode) {
  802. $this->sourceLanguage = $langcode;
  803. }
  804. /**
  805. * @see EntityTranslationHandlerInterface::getSourceLanguage()
  806. */
  807. public function getSourceLanguage() {
  808. return $this->sourceLanguage;
  809. }
  810. /**
  811. * @see EntityTranslationHandlerInterface::isNewEntity()
  812. */
  813. public function isNewEntity() {
  814. $id = $this->getEntityId();
  815. return empty($id);
  816. }
  817. /**
  818. * @see EntityTranslationHandlerInterface::isEntityForm()
  819. */
  820. public function isEntityForm() {
  821. return $this->entityForm;
  822. }
  823. /**
  824. * @see EntityTranslationHandlerInterface::entityForm()
  825. */
  826. public function entityForm(&$form, &$form_state) {
  827. $this->entityForm = TRUE;
  828. $translations = $this->getTranslations();
  829. $form_langcode = $this->getFormLanguage();
  830. $langcode = $this->getLanguage();
  831. $is_translation = $this->isTranslationForm();
  832. $new_translation = !isset($translations->data[$form_langcode]);
  833. $no_translations = count($translations->data) < 2;
  834. $languages = language_list();
  835. $access = user_access('translate any entity') || user_access("translate $this->entityType entities");
  836. // The only way to determine whether we are editing the original values is
  837. // comparing form language and entity language. Since a language change
  838. // might render impossible to make this check after form submission, we
  839. // store the related information here.
  840. $form_state['entity_translation']['is_translation'] = $is_translation;
  841. // Adjust page title to specify the current language being edited, if we
  842. // have at least one translation.
  843. if ($form_langcode != LANGUAGE_NONE && (!$no_translations || $new_translation)) {
  844. drupal_set_title($this->entityFormTitle() . ' [' . t($languages[$form_langcode]->name) . ']', PASS_THROUGH);
  845. }
  846. // Display source language selector only if we are creating a new
  847. // translation and there are at least two translations available.
  848. if (!$no_translations && $new_translation) {
  849. $form['source_language'] = array(
  850. '#type' => 'fieldset',
  851. '#title' => t('Source language'),
  852. '#collapsible' => TRUE,
  853. '#collapsed' => TRUE,
  854. '#tree' => TRUE,
  855. '#weight' => -100,
  856. '#access' => $access,
  857. '#multilingual' => TRUE,
  858. 'language' => array(
  859. '#type' => 'select',
  860. '#default_value' => $this->getSourceLanguage(),
  861. '#options' => array(),
  862. ),
  863. 'submit' => array(
  864. '#type' => 'submit',
  865. '#value' => t('Change'),
  866. '#submit' => array('entity_translation_entity_form_source_language_submit'),
  867. ),
  868. );
  869. foreach (language_list() as $language) {
  870. if (isset($translations->data[$language->language])) {
  871. $form['source_language']['language']['#options'][$language->language] = t($language->name);
  872. }
  873. }
  874. }
  875. // Add the entity language switcher.
  876. $this->entityFormLanguageWidget($form, $form_state);
  877. if ($is_translation) {
  878. // Replace the delete button with the delete translation one.
  879. if (!$new_translation) {
  880. $weight = 100;
  881. foreach (array('delete', 'submit') as $key) {
  882. if (isset($form['actions'][$key]['weight'])) {
  883. $weight = $form['actions'][$key]['weight'];
  884. break;
  885. }
  886. }
  887. $form['actions']['delete_translation'] = array(
  888. '#type' => 'submit',
  889. '#value' => t('Delete translation'),
  890. '#weight' => $weight,
  891. '#submit' => array('entity_translation_entity_form_delete_translation_submit'),
  892. );
  893. }
  894. // Always remove the delete button on translation forms.
  895. unset($form['actions']['delete']);
  896. }
  897. // We need to display the translation tab only when there is at least one
  898. // translation available or a new one is about to be created.
  899. if ($new_translation || count($translations->data) > 1) {
  900. $form['translation'] = array(
  901. '#type' => 'fieldset',
  902. '#title' => t('Translation'),
  903. '#collapsible' => TRUE,
  904. '#collapsed' => TRUE,
  905. '#tree' => TRUE,
  906. '#weight' => 10,
  907. '#access' => $access,
  908. '#multilingual' => TRUE,
  909. );
  910. // A new translation is enabled by default.
  911. $status = $new_translation || $translations->data[$form_langcode]['status'];
  912. // If there is only one published translation we cannot unpublish it,
  913. // since there would be no content left to display. The whole entity
  914. // should be unpublished instead, where possible.
  915. $enabled = !$status;
  916. if (!empty($status)) {
  917. // A new translation is not available in the translation data hence it
  918. // should count as one more.
  919. $published = $new_translation;
  920. foreach ($translations->data as $langcode => $translation) {
  921. $published += $translation['status'];
  922. }
  923. $enabled = $published > 1;
  924. }
  925. $description = $enabled ?
  926. t('An unpublished translation will not be visible for non-administrators.') :
  927. t('Only this translation is published. You must publish at least one more translation to unpublish this one.');
  928. $form['translation']['status'] = array(
  929. '#type' => 'checkbox',
  930. '#title' => t('This translation is published'),
  931. '#default_value' => $status,
  932. '#description' => $description,
  933. '#disabled' => !$enabled,
  934. );
  935. $translate = !$new_translation && $translations->data[$form_langcode]['translate'];
  936. if (!$translate) {
  937. $form['translation']['retranslate'] = array(
  938. '#type' => 'checkbox',
  939. '#title' => t('Flag translations as outdated'),
  940. '#default_value' => 0,
  941. '#description' => t('If you made a significant change, which means translations should be updated, you can flag all translations of this post as outdated. This will not change any other property of those posts, like whether they are published or not.'),
  942. );
  943. }
  944. else {
  945. $form['translation']['translate'] = array(
  946. '#type' => 'checkbox',
  947. '#title' => t('This translation needs to be updated'),
  948. '#default_value' => $translate,
  949. '#description' => t('When this option is checked, this translation needs to be updated because the source post has changed. Uncheck when the translation is up to date again.'),
  950. '#disabled' => !$translate,
  951. );
  952. }
  953. $name = $new_translation ? $GLOBALS['user']->name : user_load($translations->data[$form_langcode]['uid'])->name;
  954. $form['translation']['name'] = array(
  955. '#type' => 'textfield',
  956. '#title' => t('Authored by'),
  957. '#maxlength' => 60,
  958. '#autocomplete_path' => 'user/autocomplete',
  959. '#default_value' => $name,
  960. '#description' => t('Leave blank for %anonymous.', array('%anonymous' => variable_get('anonymous', t('Anonymous')))),
  961. );
  962. $date = $new_translation ? REQUEST_TIME : $translations->data[$form_langcode]['created'];
  963. $form['translation']['created'] = array(
  964. '#type' => 'textfield',
  965. '#title' => t('Authored on'),
  966. '#maxlength' => 25,
  967. '#description' => t('Format: %time. The date format is YYYY-MM-DD and %timezone is the time zone offset from UTC. Leave blank to use the time of form submission.', array('%time' => format_date($date, 'custom', 'Y-m-d H:i:s O'), '%timezone' => format_date($date, 'custom', 'O'))),
  968. '#default_value' => $new_translation ? '' : format_date($date, 'custom', 'Y-m-d H:i:s O'),
  969. );
  970. }
  971. // If Menu translation is available translate the menu strings.
  972. if (module_exists('entity_translation_i18n_menu')) {
  973. $this->menuForm($form, $form_state);
  974. }
  975. // Process entity form submission.
  976. $form['#submit'][] = 'entity_translation_entity_form_submit';
  977. // This allows to intercept deletions. The check is needed because
  978. // action-specific submit handlers prevent global ones from being called.
  979. if (!empty($form['actions']['delete']['#submit'])) {
  980. $form['actions']['delete']['#submit'][] = 'entity_translation_entity_form_submit';
  981. }
  982. }
  983. /**
  984. * @see EntityTranslationHandlerInterface::entityFormSharedElements()
  985. *
  986. * Either remove access or add a translatability clue depending on the current
  987. * user's "edit translation shared fields" permissions.
  988. */
  989. public function entityFormSharedElements(&$element) {
  990. static $ignored_types, $shared_labels, $access;
  991. if (!isset($ignored_types)) {
  992. $ignored_types = array_flip(array('actions', 'value', 'hidden', 'vertical_tabs', 'token'));
  993. }
  994. if (!isset($shared_labels)) {
  995. $shared_labels = variable_get('entity_translation_shared_labels', TRUE);
  996. }
  997. if (!isset($access)) {
  998. $access = $this->getSharedFieldsAccess();
  999. }
  1000. foreach (element_children($element) as $key) {
  1001. if (!isset($element[$key]['#type'])) {
  1002. $this->entityFormSharedElements($element[$key]);
  1003. }
  1004. else {
  1005. // Ignore non-widget form elements.
  1006. if (isset($ignored_types[$element[$key]['#type']])) {
  1007. continue;
  1008. }
  1009. // Elements are considered to be non multilingual by default.
  1010. // Update #access only if it has not been set already or if we have
  1011. // explicit mlutilingual support.
  1012. if (!isset($element[$key]['#access']) || isset($element[$key]['#multilingual'])) {
  1013. $element[$key]['#access'] = (!isset($element[$key]['#access']) || $element[$key]['#access']) && ($access || !empty($element[$key]['#multilingual']));
  1014. }
  1015. // Add translatability clue for visible elements.
  1016. if ($access && $shared_labels) {
  1017. _entity_translation_element_add_callback($element[$key], '#process', 'entity_translation_element_translatability_clue');
  1018. }
  1019. }
  1020. }
  1021. }
  1022. /**
  1023. * @see EntityTranslationHandlerInterface::entityFormLanguageWidget()
  1024. */
  1025. public function entityFormLanguageWidget(&$form, &$form_state) {
  1026. if (entity_translation_enabled($this->entityType, $this->bundle)) {
  1027. $is_new = $this->isNewEntity();
  1028. $is_translation = !$is_new && !empty($form_state['entity_translation']['is_translation']);
  1029. $translations = $this->getTranslations();
  1030. $settings = entity_translation_settings($this->entityType, $this->bundle);
  1031. $languages = entity_translation_languages($this->entityType, $this->entity);
  1032. $options = count($translations->data) > 1 || !empty($settings['exclude_language_none']) ? array() : array(LANGUAGE_NONE => t('Language neutral'));
  1033. foreach ($languages as $langcode => $language) {
  1034. // Disable languages for existing translations, so it is not possible to
  1035. // switch this entity to some language which is already in the translation
  1036. // set.
  1037. if (!isset($translations->data[$langcode]) || empty($translations->data[$langcode]['source'])) {
  1038. $options[$langcode] = t($language->name);
  1039. }
  1040. }
  1041. $langcode = $is_new ? $this->getDefaultLanguage() : $this->getLanguage();
  1042. $language_key = $this->getLanguageKey();
  1043. $form[$language_key] = array(
  1044. '#type' => 'select',
  1045. '#title' => t('Language'),
  1046. '#default_value' => $langcode,
  1047. '#options' => $options,
  1048. '#access' => empty($settings['hide_language_selector']),
  1049. '#disabled' => $is_translation || (!$is_new && !empty($settings['lock_language'])),
  1050. '#multilingual' => TRUE,
  1051. );
  1052. if ($is_translation) {
  1053. // @todo Consider supporting the ability to change translation language.
  1054. $form[$language_key]['#title'] = t('Original language');
  1055. }
  1056. }
  1057. if (!empty($form['actions']['submit']['#submit'])) {
  1058. $submit = &$form['actions']['submit']['#submit'];
  1059. }
  1060. else {
  1061. if (!isset($form['#submit'])) {
  1062. $form['#submit'] = array();
  1063. }
  1064. $submit = &$form['#submit'];
  1065. }
  1066. array_unshift($submit, 'entity_translation_language_widget_submit');
  1067. }
  1068. /**
  1069. * Performs alterations to the menu widget if available.
  1070. */
  1071. protected function menuForm(&$form, &$form_state) {
  1072. // By default do nothing: only nodes are currently supported.
  1073. }
  1074. /**
  1075. * @see EntityTranslationHandlerInterface::entityFormValidate()
  1076. */
  1077. public function entityFormValidate($form, &$form_state) {
  1078. if (!empty($form_state['values']['translation'])) {
  1079. $values = $form_state['values']['translation'];
  1080. // Validate the "authored by" field.
  1081. if (!empty($values['name']) && !($account = user_load_by_name($values['name']))) {
  1082. form_set_error('translation][name', t('The translation authoring username %name does not exist.', array('%name' => $values['name'])));
  1083. }
  1084. // Validate the "authored on" field.
  1085. if (!empty($values['created']) && strtotime($values['created']) === FALSE) {
  1086. form_set_error('translation][created', t('You have to specify a valid translation authoring date.'));
  1087. }
  1088. }
  1089. }
  1090. /**
  1091. * Update the current form language based on the submitted value.
  1092. */
  1093. protected function updateFormLanguage($form_state) {
  1094. // Update the form language as it might have changed. We exploit the
  1095. // validation phase to be sure to act as early as possible.
  1096. if (isset($form_state['values']['language']) && !$this->isTranslationForm()) {
  1097. $this->setFormLanguage($form_state['values'][$this->getLanguageKey()]);
  1098. }
  1099. }
  1100. /**
  1101. * @see EntityTranslationHandlerInterface::entityFormLanguageWidgetSubmit()
  1102. */
  1103. function entityFormLanguageWidgetSubmit($form, &$form_state) {
  1104. $this->updateFormLanguage($form_state);
  1105. $form_langcode = $this->getFormLanguage();
  1106. foreach (field_info_instances($this->entityType, $this->bundle) as $instance) {
  1107. $field_name = $instance['field_name'];
  1108. $field = field_info_field($field_name);
  1109. $previous_langcode = $form[$field_name]['#language'];
  1110. // Handle a possible language change: new language values are inserted,
  1111. // previous ones are deleted.
  1112. if ($field['translatable'] && $previous_langcode != $form_langcode) {
  1113. $form_state['values'][$field_name][$form_langcode] = $form_state['values'][$field_name][$previous_langcode];
  1114. $form_state['values'][$field_name][$previous_langcode] = array();
  1115. }
  1116. }
  1117. }
  1118. /**
  1119. * @see EntityTranslationHandlerInterface::entityFormSubmit()
  1120. */
  1121. public function entityFormSubmit($form, &$form_state) {
  1122. $form_langcode = $this->getFormLanguage();
  1123. $translations = $this->getTranslations();
  1124. $is_translation = !empty($form_state['entity_translation']['is_translation']);
  1125. $new_translation = !isset($translations->data[$form_langcode]);
  1126. $values = isset($form_state['values']['translation']) ? $form_state['values']['translation'] : array();
  1127. // Ensure every key has at least a default value. Subclasses may provide use
  1128. // entity-specific values to alter them.
  1129. $values += array(
  1130. 'status' => TRUE,
  1131. 'retranslate' => 0,
  1132. 'name' => isset($GLOBALS['user']->name) ? $GLOBALS['user']->name : '',
  1133. );
  1134. if (!isset($translations->data[$form_langcode])) {
  1135. // If we have a new translation the language is the original entity
  1136. // language.
  1137. $translation = $is_translation ? array('language' => $form_langcode, 'source' => $this->getSourceLanguage()) : array('language' => $form_langcode, 'source' => '');
  1138. }
  1139. else {
  1140. $translation = $translations->data[$form_langcode];
  1141. }
  1142. if (isset($values['translate'])) {
  1143. $translation['translate'] = intval($values['translate']);
  1144. }
  1145. else {
  1146. $this->setOutdated($values['retranslate']);
  1147. }
  1148. // Handle possible language changes for the original values.
  1149. if (!$is_translation) {
  1150. $this->setOriginalLanguage($form_langcode);
  1151. }
  1152. $translation['status'] = intval($values['status']);
  1153. $translation['uid'] = $values['name'] ? user_load_by_name($values['name'])->uid : 0;
  1154. $translation['created'] = empty($values['created']) ? REQUEST_TIME : strtotime($values['created']);
  1155. $this->setTranslation($translation);
  1156. // If no redirect has been explicitly set, go to the edit form for the
  1157. // current form language.
  1158. if ($new_translation && empty($form_state['redirect']) && !$this->isNewEntity()) {
  1159. $form_state['redirect'] = $this->getEditPath($form_langcode);
  1160. }
  1161. }
  1162. /**
  1163. * @see EntityTranslationHandlerInterface::localTasksAlter()
  1164. */
  1165. public function localTasksAlter(&$data, $router_item, $root_path) {
  1166. $translations = $this->getTranslations();
  1167. if (count($translations->data) > 0) {
  1168. $languages = language_list();
  1169. $form_langcode = $this->getFormLanguage();
  1170. $language_tabs = array();
  1171. if ($this->getSourceLanguage()) {
  1172. foreach ($data['tabs'][1]['output'] as $index => &$add_tab) {
  1173. if ($add_tab['#link']['path'] == $root_path) {
  1174. $add_tab['#link']['title'] = $languages[$form_langcode]->name;
  1175. $add_tab['#link']['weight'] = $languages[$form_langcode]->weight;
  1176. $add_tab['#active'] = TRUE;
  1177. $add_tab['#language_tab'] = TRUE;
  1178. $language_tabs[] = $add_tab;
  1179. unset($data['tabs'][1]['output'][$index]);
  1180. break;
  1181. }
  1182. }
  1183. }
  1184. foreach ($translations->data as $langcode => $translation) {
  1185. if ($this->getTranslationAccess($langcode)) {
  1186. $links = $this->languageSwitchLinks($this->getEditPath($langcode));
  1187. $link = !empty($links->links[$langcode]) ? $links->links[$langcode] : array();
  1188. if (isset($link['href'])) {
  1189. $tab = array();
  1190. $tab['#theme'] = 'menu_local_task';
  1191. $tab['#active'] = $langcode == $form_langcode;
  1192. $tab['#language_tab'] = TRUE;
  1193. $tab['#link'] = array(
  1194. 'href' => $link['href'],
  1195. 'title' => t($languages[$langcode]->name),
  1196. 'weight' => $languages[$langcode]->weight,
  1197. 'localized_options' => $link,
  1198. ) + $router_item;
  1199. $language_tabs[] = $tab;
  1200. }
  1201. }
  1202. }
  1203. // Reorder tabs to make the add tab respect language weights.
  1204. usort($language_tabs, array($this, 'translationTabSort'));
  1205. // Merge the reordered language tabs into the second level tabs.
  1206. if (count($language_tabs) > 1) {
  1207. if (empty($data['tabs'][1])) {
  1208. $data['tabs'][1] = array('output' => array());
  1209. }
  1210. $data['tabs'][1]['output'] = array_merge($data['tabs'][1]['output'], $language_tabs);
  1211. $data['tabs'][1]['count'] = count($data['tabs'][1]['output']);
  1212. }
  1213. }
  1214. }
  1215. /**
  1216. * Helper callback. Sorts language tabs by weight.
  1217. */
  1218. protected function translationTabSort($a, $b) {
  1219. return $a['#link']['weight'] > $b['#link']['weight'];
  1220. }
  1221. /**
  1222. * Returns the title to be used for the entity form page.
  1223. */
  1224. protected function entityFormTitle() {
  1225. return $this->getLabel();
  1226. }
  1227. /**
  1228. * Returns TRUE if an entity translation is being edited.
  1229. */
  1230. protected function isTranslationForm() {
  1231. return !$this->isNewEntity() && $this->getFormLanguage() != $this->getLanguage();
  1232. }
  1233. /**
  1234. * Returns the translation object key for the wrapped entity type.
  1235. */
  1236. protected function getTranslationsKey() {
  1237. return isset($this->entityInfo['entity keys']['translations']) ? $this->entityInfo['entity keys']['translations'] : FALSE;
  1238. }
  1239. /**
  1240. * Returns the entity accessibility.
  1241. */
  1242. protected function getStatus() {
  1243. return TRUE;
  1244. }
  1245. /**
  1246. * Returns the entity identifier.
  1247. */
  1248. protected function getEntityId() {
  1249. return $this->entityId;
  1250. }
  1251. /**
  1252. * Initializes handler path variables based on the active path scheme.
  1253. *
  1254. * @throws Exception
  1255. */
  1256. private function initPathVariables() {
  1257. if (empty($this->pathScheme) || !isset($this->entityInfo['translation']['entity_translation']['path schemes'][$this->pathScheme])) {
  1258. throw new Exception("Cannot initialize entity translation path variables (invalid path scheme).");
  1259. }
  1260. $path_scheme = $this->entityInfo['translation']['entity_translation']['path schemes'][$this->pathScheme];
  1261. $this->pathWildcard = $path_scheme['path wildcard'];
  1262. $this->basePath = isset($path_scheme['base path']) ? $this->getPathInstance($path_scheme['base path']) : FALSE;
  1263. $this->editPath = isset($path_scheme['edit path']) ? $this->getPathInstance($path_scheme['edit path']) : FALSE;
  1264. $this->translatePath = isset($path_scheme['translate path']) ? $this->getPathInstance($path_scheme['translate path']) : FALSE;
  1265. $this->viewPath = isset($path_scheme['view path']) ? $this->getPathInstance($path_scheme['view path']) : FALSE;
  1266. }
  1267. /**
  1268. * Returns an instance of the given path.
  1269. *
  1270. * @param $path
  1271. * An internal path containing the entity id wildcard.
  1272. *
  1273. * @return
  1274. * The instantiated path.
  1275. */
  1276. protected function getPathInstance($path) {
  1277. $path_segments = explode('/', $path);
  1278. foreach ($path_segments as $index => $segment) {
  1279. if ($segment == $this->pathWildcard) {
  1280. $path_segments[$index] = $this->getEntityId();
  1281. }
  1282. elseif ($segment{0} == '%' && isset($this->routerMap[$index])) {
  1283. $path_segments[$index] = $this->routerMap[$index];
  1284. }
  1285. }
  1286. return implode('/', $path_segments);
  1287. }
  1288. /**
  1289. * Returns an empty translations data structure.
  1290. */
  1291. protected static function emptyTranslations() {
  1292. return (object) array('original' => NULL, 'data' => array());
  1293. }
  1294. }