title.module 36 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037
  1. <?php
  2. /**
  3. * @file
  4. * Replaces entity legacy fields with regular fields.
  5. *
  6. * Provides an API and a basic UI to replace legacy pseudo-fields with regular
  7. * fields. The API only offers synchronization between the two data storage
  8. * systems and data replacement on entity load/save. Field definitions have to
  9. * be provided by the modules exploiting the API.
  10. *
  11. * Title implements its own entity description API to describe core legacy
  12. * pseudo-fields:
  13. * - Node: title
  14. * - Taxonomy Term: name, description
  15. * - Comment: subject
  16. *
  17. * @todo: API PHPdocs
  18. */
  19. module_load_include('inc', 'title', 'title.core');
  20. module_load_include('inc', 'title', 'title.field');
  21. /**
  22. * Implements hook_module_implements_alter().
  23. */
  24. function title_module_implements_alter(&$implementations, $hook) {
  25. if (isset($implementations['title'])) {
  26. $group = $implementations['title'];
  27. unset($implementations['title']);
  28. switch ($hook) {
  29. // The following hook implementations should be executed as last ones.
  30. case 'entity_info_alter':
  31. case 'entity_presave':
  32. case 'field_attach_presave':
  33. $implementations['title'] = $group;
  34. break;
  35. // The following hook implementations should be executed after
  36. // entity_translation.
  37. case 'entity_load':
  38. if (isset($implementations['entity_translation'])) {
  39. $length = array_search('entity_translation', array_keys($implementations)) + 1;
  40. $implementations = array_merge(array_slice($implementations, 0, $length, TRUE), array('title' => $group), array_slice($implementations, $length, count($implementations) - 1, TRUE));
  41. }
  42. break;
  43. // Normally Title needs to act as first module to perform synchronization.
  44. default:
  45. $implementations = array('title' => $group) + $implementations;
  46. }
  47. }
  48. }
  49. /**
  50. * Implements hook_entity_info_alter().
  51. */
  52. function title_entity_info_alter(&$info) {
  53. foreach ($info as $entity_type => $entity_info) {
  54. if (!empty($entity_info['fieldable']) && !empty($info[$entity_type]['field replacement'])) {
  55. foreach ($info[$entity_type]['field replacement'] as $legacy_field => $data) {
  56. // Provide defaults for the replacing field name.
  57. $fr_info = &$info[$entity_type]['field replacement'][$legacy_field];
  58. if (empty($fr_info['field']['field_name'])) {
  59. $fr_info['field']['field_name'] = $legacy_field . '_field';
  60. }
  61. $fr_info['instance']['field_name'] = $fr_info['field']['field_name'];
  62. // Provide defaults for the sync callbacks.
  63. $type = $fr_info['field']['type'];
  64. if (empty($fr_info['callbacks'])) {
  65. $fr_info['callbacks'] = array();
  66. }
  67. $fr_info['callbacks'] += array(
  68. 'sync_get' => "title_field_{$type}_sync_get",
  69. 'sync_set' => "title_field_{$type}_sync_set",
  70. );
  71. // Support add explicit support for entity_label().
  72. if (isset($entity_info['entity keys']['label']) && $entity_info['entity keys']['label'] == $legacy_field) {
  73. // Store the original label callback for compatibility reasons.
  74. if (isset($info[$entity_type]['label callback'])) {
  75. $info[$entity_type]['label fallback']['title'] = $info[$entity_type]['label callback'];
  76. }
  77. $info[$entity_type]['label callback'] = 'title_entity_label';
  78. $fr_info += array('preprocess_key' => $info[$entity_type]['entity keys']['label']);
  79. }
  80. }
  81. }
  82. }
  83. }
  84. /**
  85. * Return field replacement specific information.
  86. *
  87. * @param $entity_type
  88. * The name of the entity type.
  89. * @param $legacy_field
  90. * (Optional) The legacy field name to be replaced.
  91. */
  92. function title_field_replacement_info($entity_type, $legacy_field = NULL) {
  93. $info = entity_get_info($entity_type);
  94. if (empty($info['field replacement'])) {
  95. return FALSE;
  96. }
  97. if (isset($legacy_field)) {
  98. return isset($info['field replacement'][$legacy_field]) ? $info['field replacement'][$legacy_field] : FALSE;
  99. }
  100. return $info['field replacement'];
  101. }
  102. /**
  103. * Implements callback_entity_info_label().
  104. */
  105. function title_entity_label($entity, $type, $langcode = NULL) {
  106. $entity_info = entity_get_info($type);
  107. $legacy_field = $entity_info['entity keys']['label'];
  108. $info = $entity_info['field replacement'][$legacy_field];
  109. list(, , $bundle) = entity_extract_ids($type, $entity);
  110. // If field replacement is enabled we use the replacing field value.
  111. if (title_field_replacement_enabled($type, $bundle, $legacy_field)) {
  112. $langcode = field_language($type, $entity, $info['field']['field_name'], $langcode);
  113. $values = $info['callbacks']['sync_get']($type, $entity, $legacy_field, $info, $langcode);
  114. return isset($values[$legacy_field]) ? $values[$legacy_field] : $entity->{$legacy_field};
  115. }
  116. if (isset($entity_info['label fallback']['title']) && function_exists($entity_info['label fallback']['title'])) {
  117. $label = $entity_info['label fallback']['title']($entity, $type, $langcode);
  118. return isset($label) ? $label : $entity->{$legacy_field};
  119. }
  120. // Otherwise if we have a fallback defined we use the original label callback.
  121. return property_exists($entity, $legacy_field) ? $entity->{$legacy_field} : NULL;
  122. }
  123. /**
  124. * Implements hook_entity_presave().
  125. */
  126. function title_entity_presave($entity, $entity_type) {
  127. $entity_langcode = title_entity_language($entity_type, $entity);
  128. $langcode = $entity_langcode;
  129. // If Entity Translation is enabled and the entity type is transltable,we need
  130. // to check if we have a translation for the current active language. If so we
  131. // need to synchronize the legacy field values into the replacing field
  132. // translations in the active language.
  133. if (module_invoke('entity_translation', 'enabled', $entity_type)) {
  134. $langcode = title_active_language();
  135. $translations = entity_translation_get_handler($entity_type, $entity)->getTranslations();
  136. // If we are removing a translation for the active language we need to skip
  137. // reverse synchronization, as we would store empty values in the original
  138. // replacing fields immediately afterwards.
  139. if (!isset($translations->data[$langcode])) {
  140. $langcode = isset($translations->hook[$langcode]['hook']) && $translations->hook[$langcode]['hook'] == 'delete' ? FALSE : $entity_langcode;
  141. }
  142. }
  143. // Perform reverse synchronization to retain any change in the legacy field
  144. // values. We must avoid doing this twice as we might overwrite the already
  145. // synchronized values, if we are updating an existing entity.
  146. if ($langcode) {
  147. title_entity_sync($entity_type, $entity, $langcode, TRUE);
  148. }
  149. // If we are not dealing with the entity language, we need to synchronize the
  150. // original values into the legacy fields to ensure they are always stored in
  151. // the entity table.
  152. if ($entity_langcode != $langcode) {
  153. list($id, , ) = entity_extract_ids($entity_type, $entity);
  154. $sync = &drupal_static('title_entity_sync', array());
  155. unset($sync[$entity_type][$id]);
  156. title_entity_sync($entity_type, $entity, $entity_langcode);
  157. }
  158. }
  159. /**
  160. * Implements hook_field_attach_update().
  161. */
  162. function title_field_attach_update($entity_type, $entity) {
  163. // Reset the field_attach_presave static cache so that subsequent saves work
  164. // correctly.
  165. $sync = &drupal_static('title_field_attach_presave', array());
  166. list($id, ,) = entity_extract_ids($entity_type, $entity);
  167. unset($sync[$entity_type][$id]);
  168. // Immediately after saving the entity we need to ensure that the legacy field
  169. // holds a value corresponding to the current active language, as it were
  170. // just loaded.
  171. title_entity_sync($entity_type, $entity);
  172. }
  173. /**
  174. * Implements hook_field_attach_load().
  175. *
  176. * Synchronization must be performed as early as possible to prevent other code
  177. * from accessing replaced fields before they get their actual value.
  178. *
  179. * @see title_entity_load()
  180. */
  181. function title_field_attach_load($entity_type, $entities, $age, $options) {
  182. // Allow values to re-sync when field_attach_load_revision() is called.
  183. if ($age == FIELD_LOAD_REVISION) {
  184. title_entity_sync_static_reset($entity_type, array_keys($entities));
  185. }
  186. title_entity_load($entities, $entity_type);
  187. }
  188. /**
  189. * Implements hook_entity_load().
  190. *
  191. * Since the result of field_attach_load() is cached, synchronization must be
  192. * performed also here to ensure that there is always the correct value in the
  193. * replaced fields.
  194. */
  195. function title_entity_load($entities, $type) {
  196. // Load entity translations otherwise field language will not be computed
  197. // correctly.
  198. if (module_exists('entity_translation')) {
  199. module_invoke('entity_translation', 'entity_load', $entities, $type);
  200. }
  201. foreach ($entities as &$entity) {
  202. // Synchronize values from the regular field unless we are intializing it.
  203. // When the node is not being edited, the language must be set to current
  204. // entity language if set, otherwise NULL.
  205. $language = NULL;
  206. if ((preg_match('/node\/\d+\/edit/', current_path()) == 0)
  207. && (preg_match('/node\/\d+$/', current_path()) == 0)) {
  208. if (!empty($entity->language)) {
  209. $language = $entity->language;
  210. }
  211. }
  212. title_entity_sync($type, $entity, $language, !empty($GLOBALS['title_field_replacement_init']));
  213. }
  214. }
  215. /**
  216. * Implements hook_entitycache_load().
  217. *
  218. * Entity cache might cache the entire $entity object, in which case
  219. * synchronization will not be performed on entity load.
  220. */
  221. function title_entitycache_load($entities, $type) {
  222. title_entity_load($entities, $type);
  223. }
  224. /**
  225. * Implements hook_entitycache_reset().
  226. *
  227. * When the entity cache is reset the field sync has to be done again.
  228. */
  229. function title_entitycache_reset($ids, $entity_type) {
  230. title_entity_sync_static_reset($entity_type, $ids);
  231. }
  232. /**
  233. * Implements hook_entity_prepare_view().
  234. *
  235. * On load synchronization is performed using the current display language. A
  236. * different language might be specified while viewing the entity in which case
  237. * synchronization must be performed again.
  238. */
  239. function title_entity_prepare_view($entities, $type, $langcode) {
  240. foreach ($entities as &$entity) {
  241. title_entity_sync($type, $entity, $langcode);
  242. }
  243. }
  244. /**
  245. * Check whether field replacement is enabled for the given field.
  246. *
  247. * @param $entity_type
  248. * The type of $entity.
  249. * @param $bundle
  250. * The bundle the legacy field belongs to.
  251. * @param $legacy_field
  252. * The name of the legacy field to be replaced.
  253. *
  254. * @return
  255. * TRUE if field replacement is enabled for the given field, FALSE otherwise.
  256. */
  257. function title_field_replacement_enabled($entity_type, $bundle, $legacy_field) {
  258. $info = title_field_replacement_info($entity_type, $legacy_field);
  259. if (!empty($info['field']['field_name'])) {
  260. $instance = field_info_instance($entity_type, $info['field']['field_name'], $bundle);
  261. }
  262. return !empty($instance);
  263. }
  264. /**
  265. * Toggle field replacement for the given field.
  266. *
  267. * @param $entity_type
  268. * The name of the entity type.
  269. * @param $bundle
  270. * The bundle the legacy field belongs to.
  271. * @param $legacy_field
  272. * The name of the legacy field to be replaced.
  273. */
  274. function title_field_replacement_toggle($entity_type, $bundle, $legacy_field) {
  275. $info = title_field_replacement_info($entity_type, $legacy_field);
  276. if (!$info) {
  277. return;
  278. }
  279. $field_name = $info['field']['field_name'];
  280. $instance = field_info_instance($entity_type, $field_name, $bundle);
  281. if (empty($instance)) {
  282. $options = variable_get('title_' . $entity_type, array());
  283. $field = field_info_field($field_name);
  284. if (empty($field)) {
  285. field_create_field($info['field']);
  286. }
  287. $info['instance']['entity_type'] = $entity_type;
  288. $info['instance']['bundle'] = $bundle;
  289. $info['instance']['settings']['hide_label']['page'] = isset($options['hide_label']['page']) ? $options['hide_label']['page'] : FALSE;
  290. $info['instance']['settings']['hide_label']['entity'] = isset($options['hide_label']['entity']) ? $options['hide_label']['entity'] : FALSE;
  291. field_create_instance($info['instance']);
  292. return TRUE;
  293. }
  294. field_delete_instance($instance);
  295. return FALSE;
  296. }
  297. /**
  298. * Set a batch process to initialize replacing field values.
  299. *
  300. * @param $entity_type
  301. * The type of $entity.
  302. * @param $bundle
  303. * The bundle the legacy field belongs to.
  304. * @param $legacy_field
  305. * The name of the legacy field to be replaced.
  306. */
  307. function title_field_replacement_batch_set($entity_type, $bundle, $legacy_field) {
  308. $batch = array(
  309. 'title' => t('Replacing field values for %field', array('%field' => $legacy_field)),
  310. 'operations' => array(
  311. array('title_field_replacement_batch', array($entity_type, $bundle, $legacy_field)),
  312. ),
  313. );
  314. batch_set($batch);
  315. }
  316. /**
  317. * Batch operation: initialize a batch of replacing field values.
  318. */
  319. function title_field_replacement_batch($entity_type, $bundle, $legacy_field, &$context) {
  320. $info = entity_get_info($entity_type);
  321. $query = new EntityFieldQuery();
  322. $query->entityCondition('entity_type', $entity_type);
  323. // There is no general way to tell if an entity supports bundle conditions
  324. // (for instance taxonomy terms and comments do not), hence we may need to
  325. // loop over all the entities of the given type.
  326. if (!empty($info['efq bundle conditions'])) {
  327. $query->entityCondition('bundle', $bundle);
  328. }
  329. if (empty($context['sandbox'])) {
  330. $count_query = clone $query;
  331. $total = $count_query
  332. ->count()
  333. ->execute();
  334. $context['sandbox']['steps'] = 0;
  335. $context['sandbox']['progress'] = 0;
  336. $context['sandbox']['total'] = $total;
  337. }
  338. $step = variable_get('title_field_replacement_batch_size', 5);
  339. $start = $step * $context['sandbox']['steps']++;
  340. $results = $query
  341. ->entityCondition('entity_type', $entity_type)
  342. ->range($start, $step)
  343. ->execute();
  344. if (!empty($results[$entity_type])) {
  345. $ids = array_keys($results[$entity_type]);
  346. title_field_replacement_init($entity_type, $bundle, $legacy_field, $ids);
  347. $context['sandbox']['progress'] += count($ids);
  348. }
  349. if ($context['sandbox']['progress'] != $context['sandbox']['total']) {
  350. $context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['total'];
  351. }
  352. }
  353. /**
  354. * Initialize a batch of replacing field values.
  355. *
  356. * @param $entity_type
  357. * The type of $entity.
  358. * @param $bundle
  359. * The bundle the legacy field belongs to.
  360. * @param $legacy_field
  361. * The name of the legacy field to be replaced.
  362. * @param $ids
  363. * An array of entity IDs.
  364. *
  365. * @return
  366. * The number of entities processed.
  367. */
  368. function title_field_replacement_init($entity_type, $bundle, $legacy_field, $ids) {
  369. $GLOBALS['title_field_replacement_init'] = TRUE;
  370. $entities = entity_load($entity_type, $ids);
  371. foreach ($entities as $id => $entity) {
  372. list(, , $entity_bundle) = entity_extract_ids($entity_type, $entity);
  373. if ($entity_bundle == $bundle) {
  374. field_attach_presave($entity_type, $entity);
  375. field_attach_update($entity_type, $entity);
  376. }
  377. }
  378. unset($GLOBALS['title_field_replacement_init']);
  379. }
  380. /**
  381. * Synchronize replaced fields with the regular field values.
  382. *
  383. * @param $entity_type
  384. * The name of the entity type.
  385. * @param $entity
  386. * The entity to work with.
  387. * @param $set
  388. * Specifies the direction synchronization must be performed.
  389. */
  390. function title_entity_sync($entity_type, &$entity, $langcode = NULL, $set = FALSE) {
  391. $sync = &drupal_static(__FUNCTION__, array());
  392. list($id, , $bundle) = entity_extract_ids($entity_type, $entity);
  393. if (!isset($langcode)) {
  394. $langcode = $set ? title_entity_language($entity_type, $entity) : title_active_language();
  395. }
  396. // We do not need to perform synchronization more than once.
  397. if (!$set && !empty($id) && !empty($sync[$entity_type][$id][$langcode][$set])) {
  398. return;
  399. }
  400. $sync[$entity_type][$id][$langcode][$set] = TRUE;
  401. $fr_info = title_field_replacement_info($entity_type);
  402. if ($fr_info) {
  403. foreach ($fr_info as $legacy_field => $info) {
  404. if (title_field_replacement_enabled($entity_type, $bundle, $legacy_field)) {
  405. $function = 'title_field_sync_' . ($set ? 'set' : 'get');
  406. $function($entity_type, $entity, $legacy_field, $info, $langcode);
  407. }
  408. }
  409. }
  410. }
  411. /**
  412. * Reset the list of entities whose fields have already been synchronized.
  413. *
  414. * @param $entity_type
  415. * The name of the entity type.
  416. * @param $entity_ids
  417. * Either an array of entity IDs to reset or NULL to reset all.
  418. */
  419. function title_entity_sync_static_reset($entity_type, $entity_ids = NULL) {
  420. $sync = &drupal_static('title_entity_sync', array());
  421. if (is_array($entity_ids)) {
  422. foreach ($entity_ids as $id) {
  423. unset($sync[$entity_type][$id]);
  424. }
  425. }
  426. else {
  427. unset($sync[$entity_type]);
  428. }
  429. }
  430. /**
  431. * Synchronize a single legacy field with its regular field value.
  432. *
  433. * @param $entity_type
  434. * The name of the entity type.
  435. * @param $entity
  436. * The entity to work with.
  437. * @param $legacy_field
  438. * The name of the legacy field to be replaced.
  439. * @param $info
  440. * Field replacement information for the given entity.
  441. * @param $langcode
  442. * The field language to use for the source value.
  443. */
  444. function title_field_sync_get($entity_type, $entity, $legacy_field, $info, $langcode = NULL) {
  445. if (property_exists($entity, $legacy_field)) {
  446. // Save the legacy field value to LEGACY_FIELD_NAME_original.
  447. $entity->{$legacy_field . '_original'} = $entity->{$legacy_field};
  448. // Find out the actual language to use (field might be untranslatable).
  449. $langcode = field_language($entity_type, $entity, $info['field']['field_name'], $langcode);
  450. $values = $info['callbacks']['sync_get']($entity_type, $entity, $legacy_field, $info, $langcode);
  451. foreach ($values as $name => $value) {
  452. if ($value !== NULL) {
  453. $entity->{$name} = $value;
  454. }
  455. }
  456. // Save the actual language used to LEGACY_FIELD_NAME_language.
  457. $entity->{$legacy_field . '_language'} = $langcode;
  458. // Ensure we do not pollute field language static cache.
  459. $cache = &drupal_static('field_language');
  460. list($id, ,) = entity_extract_ids($entity_type, $entity);
  461. unset($cache[$entity_type][$id]);
  462. }
  463. }
  464. /**
  465. * Synchronize a single regular field from its legacy field value.
  466. *
  467. * @param $entity_type
  468. * The name of the entity type.
  469. * @param $entity
  470. * The entity to work with.
  471. * @param $legacy_field
  472. * The name of the legacy field to be replaced.
  473. * @param $info
  474. * Field replacement information for the given entity.
  475. * @param $langcode
  476. * The field language to use for the target value.
  477. */
  478. function title_field_sync_set($entity_type, $entity, $legacy_field, $info, $langcode) {
  479. if (property_exists($entity, $legacy_field)) {
  480. // Find out the actual language to use (field might be untranslatable).
  481. $field = field_info_field($info['field']['field_name']);
  482. $langcode = field_is_translatable($entity_type, $field) ? $langcode : LANGUAGE_NONE;
  483. $info['callbacks']['sync_set']($entity_type, $entity, $legacy_field, $info, $langcode);
  484. }
  485. }
  486. /**
  487. * Returns and optionally stores the active language.
  488. *
  489. * @param string $langcode
  490. * (optional) The active language to be set. If none is provided the active
  491. * language is just returned.
  492. *
  493. * @return string
  494. * The active language code. Defaults to the current content language.
  495. */
  496. function title_active_language($langcode = NULL) {
  497. static $drupal_static_fast;
  498. if (!isset($drupal_static_fast)) {
  499. $drupal_static_fast['active_language'] = &drupal_static(__FUNCTION__);
  500. }
  501. $active_langcode = &$drupal_static_fast['active_language'];
  502. if (isset($langcode)) {
  503. $active_langcode = $langcode;
  504. }
  505. if (empty($active_langcode)) {
  506. $active_langcode = $GLOBALS['language_content']->language;
  507. }
  508. return $active_langcode;
  509. }
  510. /**
  511. * Provide the original entity language.
  512. *
  513. * If a language property is defined for the current entity we synchronize the
  514. * field value using the entity language, otherwise we fall back to
  515. * LANGUAGE_NONE.
  516. *
  517. * @param $entity_type
  518. * @param $entity
  519. *
  520. * @return
  521. * A language code
  522. */
  523. function title_entity_language($entity_type, $entity) {
  524. if (module_exists('entity_translation') && entity_translation_enabled($entity_type)) {
  525. $handler = entity_translation_get_handler($entity_type, $entity, TRUE);
  526. $langcode = $handler->getLanguage();
  527. }
  528. else {
  529. $langcode = entity_language($entity_type, $entity);
  530. }
  531. return !empty($langcode) ? $langcode : LANGUAGE_NONE;
  532. }
  533. /**
  534. * Implements hook_field_attach_form().
  535. *
  536. * Hide legacy field widgets on the assumption that this is always called on
  537. * fieldable entity forms.
  538. */
  539. function title_field_attach_form($entity_type, $entity, &$form, &$form_state, $langcode) {
  540. list(, , $bundle) = entity_extract_ids($entity_type, $entity);
  541. $fr_info = title_field_replacement_info($entity_type);
  542. if (!empty($fr_info)) {
  543. foreach ($fr_info as $legacy_field => $info) {
  544. if (isset($form[$legacy_field]) && title_field_replacement_enabled($entity_type, $bundle, $legacy_field)) {
  545. // Inherit the access from the title widget, so that other modules
  546. // restricting access to it keep working.
  547. $replaced = isset($form[$legacy_field]['#field_replacement']) ?
  548. $form[$legacy_field]['#field_replacement'] : FALSE;
  549. if (!$replaced && isset($form[$legacy_field]['#access'])) {
  550. $form[$info['field']['field_name']]['#access'] = $form[$legacy_field]['#access'];
  551. }
  552. // Add class from legacy field so behaviors can still be applied on
  553. // title widget.
  554. $form[$info['field']['field_name']]['#attributes']['class'] = array('form-item-' . $legacy_field);
  555. // Restrict access to the legacy field form element and mark it as
  556. // replaced.
  557. $form[$legacy_field]['#access'] = FALSE;
  558. $form[$legacy_field]['#field_replacement'] = TRUE;
  559. }
  560. }
  561. }
  562. }
  563. /**
  564. * Implements hook_field_attach_submit().
  565. *
  566. * Synchronize submitted field values into the corresponding legacy fields.
  567. */
  568. function title_field_attach_submit($entity_type, $entity, $form, &$form_state) {
  569. $fr_info = title_field_replacement_info($entity_type);
  570. if (!empty($fr_info)) {
  571. // We copy (rather than reference) the values from $form_state because the
  572. // subsequent call to drupal_array_get_nested_value() is destructive and
  573. // will affect other hooks relying on data in $form_state. At the end, we
  574. // copy any modified value back into the $form_state array using
  575. // drupal_array_set_nested_value().
  576. $values = $form_state['values'];
  577. $values = drupal_array_get_nested_value($values, $form['#parents']);
  578. $langcode = entity_language($entity_type, $entity);
  579. foreach ($fr_info as $legacy_field => $info) {
  580. if (!empty($form[$legacy_field]['#field_replacement'])) {
  581. // Give a chance to operate on submitted values either.
  582. if (!empty($info['callbacks']['submit'])) {
  583. $info['callbacks']['submit']($entity_type, $entity, $legacy_field, $info, $langcode, $values);
  584. }
  585. drupal_static_reset('field_language');
  586. title_field_sync_get($entity_type, $entity, $legacy_field, $info, $langcode);
  587. }
  588. }
  589. drupal_array_set_nested_value($form_state['values'], $form['#parents'], $values);
  590. }
  591. }
  592. /**
  593. * Implements hook_menu().
  594. */
  595. function title_menu() {
  596. $items = array();
  597. foreach (entity_get_info() as $entity_type => $entity_info) {
  598. if (!empty($entity_info['field replacement'])) {
  599. foreach ($entity_info['bundles'] as $bundle_name => $bundle_info) {
  600. // Blindly taken from field_ui_menu().
  601. if (isset($bundle_info['admin'])) {
  602. $path = $bundle_info['admin']['path'];
  603. if (isset($bundle_info['admin']['bundle argument'])) {
  604. $bundle_arg = $bundle_info['admin']['bundle argument'];
  605. }
  606. else {
  607. $bundle_arg = $bundle_name;
  608. }
  609. $access = array_intersect_key($bundle_info['admin'], drupal_map_assoc(array('access callback', 'access arguments')));
  610. $access += array(
  611. 'access callback' => 'user_access',
  612. 'access arguments' => array('administer site configuration'),
  613. );
  614. $path = "$path/fields/replace/%";
  615. $field_arg = substr_count($path, '/');
  616. $items[$path] = array(
  617. 'load arguments' => array(),
  618. 'title' => 'Replace fields',
  619. 'page callback' => 'drupal_get_form',
  620. 'page arguments' => array('title_field_replacement_form', $entity_type, $bundle_arg, $field_arg),
  621. 'file' => 'title.admin.inc',
  622. ) + $access;
  623. }
  624. }
  625. }
  626. }
  627. $items['admin/config/content/title'] = array(
  628. 'title' => 'Title settings',
  629. 'description' => 'Settings for the Title module.',
  630. 'page callback' => 'drupal_get_form',
  631. 'page arguments' => array('title_admin_settings_form'),
  632. 'access arguments' => array('administer site configuration'),
  633. 'file' => 'title.admin.inc',
  634. );
  635. return $items;
  636. }
  637. /**
  638. * Implements hook help.
  639. */
  640. function title_help($path, $arg) {
  641. switch ($path) {
  642. case 'admin/config/content/title':
  643. return '<p>' . t('The settings below allow to configure the <em>default</em> settings to be used when creating new replacing fields. It is even possible to configure them so that the selected fields are created automatically when a new bundle is created.') . '</p>';
  644. }
  645. }
  646. /**
  647. * Implements hook_field_extra_fields_alter().
  648. */
  649. function title_field_extra_fields_alter(&$info) {
  650. $entity_info = entity_get_info();
  651. foreach ($info as $entity_type => $bundles) {
  652. foreach ($bundles as $bundle_name => $bundle) {
  653. if (!empty($entity_info[$entity_type]['field replacement'])) {
  654. foreach ($entity_info[$entity_type]['field replacement'] as $field_name => $field_replacement_info) {
  655. if (title_field_replacement_enabled($entity_type, $bundle_name, $field_name)) {
  656. // Remove the replaced legacy field.
  657. unset($info[$entity_type][$bundle_name]['form'][$field_name], $info[$entity_type][$bundle_name]['display'][$field_name]);
  658. }
  659. }
  660. }
  661. }
  662. }
  663. }
  664. /**
  665. * Implements hook_form_FORM_ID_alter().
  666. */
  667. function title_form_field_ui_field_overview_form_alter(&$form, &$form_state) {
  668. module_load_include('inc', 'title', 'title.admin');
  669. title_form_field_ui_overview($form, $form_state);
  670. }
  671. /**
  672. * Implements hook_tokens_alter().
  673. *
  674. * Make sure tokens are properly translated.
  675. */
  676. function title_tokens_alter(array &$replacements, array $context) {
  677. $mapping = &drupal_static(__FUNCTION__);
  678. if (empty($mapping)) {
  679. foreach (entity_get_info() as $entity_type => $info) {
  680. if (!empty($info['token type'])) {
  681. $mapping[$info['token type']] = $entity_type;
  682. }
  683. }
  684. }
  685. if (isset($mapping[$context['type']])) {
  686. $entity_type = $mapping[$context['type']];
  687. $fr_info = title_field_replacement_info($entity_type);
  688. if ($fr_info && !empty($context['data'][$context['type']])) {
  689. $entity = $context['data'][$context['type']];
  690. list(, , $bundle) = entity_extract_ids($entity_type, $entity);
  691. $options = $context['options'];
  692. // Since Title tokens are mostly used in storage contexts we default to
  693. // the current working language, that is the entity language. Modules
  694. // using Title tokens in display contexts need to specify the current
  695. // display language.
  696. $langcode = isset($options['language']) ? $options['language']->language : entity_language($entity_type, $entity);
  697. if ($fr_info) {
  698. foreach ($fr_info as $legacy_field => $info) {
  699. if (isset($context['tokens'][$legacy_field])
  700. && title_field_replacement_enabled($entity_type, $bundle, $legacy_field)) {
  701. $langcode = field_language($entity_type, $entity, $info['field']['field_name'], $langcode);
  702. $item = $info['callbacks']['sync_get']($entity_type, $entity, $legacy_field, $info, $langcode);
  703. if (!empty($item)) {
  704. list($value, $format) = array_values($item);
  705. if (empty($format)) {
  706. $replacements[$context['tokens'][$legacy_field]] = check_plain($value);
  707. }
  708. else {
  709. $replacements[$context['tokens'][$legacy_field]] = check_markup($value, $format, $langcode);
  710. }
  711. }
  712. }
  713. }
  714. }
  715. }
  716. }
  717. }
  718. /**
  719. * Implements hook_form_FORM_ID_alter().
  720. */
  721. function title_form_field_ui_field_edit_form_alter(&$form, $form_state) {
  722. $instance = $form['#instance'];
  723. $entity_type = $instance['entity_type'];
  724. if (title_field_replacement_is_label($entity_type, $instance['field_name'])) {
  725. $info = entity_get_info($entity_type);
  726. $form['instance']['settings']['hide_label'] = _title_hide_label_widget($instance['settings'], $info['label']);
  727. }
  728. }
  729. /**
  730. * Returns the hide label form widget.
  731. */
  732. function _title_hide_label_widget($default, $entity_label) {
  733. return array(
  734. '#type' => 'checkboxes',
  735. '#title' => t('Label replacement'),
  736. '#description' => t('Check these options if you wish to hide the main page title or each label when displaying multiple items of type %entity_label.', array('%entity_label' => $entity_label)),
  737. '#default_value' => !empty($default['hide_label']) ? $default['hide_label'] : array(),
  738. '#options' => array(
  739. 'page' => t('Hide page title'),
  740. 'entity' => t('Hide label in %entity_label listings', array('%entity_label' => drupal_strtolower($entity_label))),
  741. ),
  742. );
  743. }
  744. /**
  745. * Checks whether the given field name is a replaced entity label.
  746. *
  747. * @param $entity_type
  748. * The name of the entity type.
  749. * @param $field_name
  750. * The replacing field name.
  751. *
  752. * @return
  753. * TRUE id the give field is replacing the entity label, FALSE otherwise.
  754. */
  755. function title_field_replacement_is_label($entity_type, $field_name) {
  756. $label = FALSE;
  757. $legacy_field = title_field_replacement_get_legacy_field($entity_type, $field_name);
  758. if ($legacy_field) {
  759. $info = entity_get_info($entity_type);
  760. $label = $legacy_field == $info['entity keys']['label'];
  761. }
  762. return $label;
  763. }
  764. /**
  765. * Returns the legacy field replaced by the given field name.
  766. *
  767. * @param $entity_type
  768. * The name of the entity type.
  769. * @param $field_name
  770. * The replacing field name.
  771. *
  772. * @return
  773. * The replaced legacy field name or FALSE if none available.
  774. */
  775. function title_field_replacement_get_legacy_field($entity_type, $field_name) {
  776. $result = FALSE;
  777. $fr_info = title_field_replacement_info($entity_type);
  778. if ($fr_info) {
  779. foreach ($fr_info as $legacy_field => $info) {
  780. if ($info['field']['field_name'] == $field_name) {
  781. $result = $legacy_field;
  782. break;
  783. }
  784. }
  785. }
  786. return $result;
  787. }
  788. /**
  789. * Returns the field instance replacing the given entity type's label.
  790. *
  791. * @param $entity_type
  792. * The name of the entity type.
  793. * @param $bundle
  794. * The name of the bundle the instance is attached to.
  795. *
  796. * @return
  797. * The field instance replacing the label or FALSE if none available.
  798. */
  799. function title_field_replacement_get_label_field($entity_type, $bundle) {
  800. $instance = FALSE;
  801. $info = entity_get_info($entity_type);
  802. if (!empty($info['field replacement'])) {
  803. $fr_info = $info['field replacement'];
  804. $legacy_field = $info['entity keys']['label'];
  805. if (!empty($fr_info[$legacy_field]['field'])) {
  806. $instance = field_info_instance($entity_type, $fr_info[$legacy_field]['field']['field_name'], $bundle);
  807. }
  808. }
  809. return $instance;
  810. }
  811. /**
  812. * Hides the label from the given variables.
  813. *
  814. * @param $entity_type
  815. * The name of the entity type.
  816. * @param $entity
  817. * The entity to work with.
  818. * @param $vaiables
  819. * A reference to the variables array related to the template being processed.
  820. * @param $page
  821. * (optional) The current render phase: page or entity. Defaults to entity.
  822. */
  823. function title_field_replacement_hide_label($entity_type, $entity, &$variables, $page = FALSE) {
  824. list(, , $bundle) = entity_extract_ids($entity_type, $entity);
  825. $instance = title_field_replacement_get_label_field($entity_type, $bundle);
  826. $settings_key = $page ? 'page' : 'entity';
  827. if (!empty($instance['settings']['hide_label'][$settings_key])) {
  828. // If no key is passed default to the label one.
  829. if ($page) {
  830. $key = 'title';
  831. }
  832. else {
  833. $info = entity_get_info($entity_type);
  834. $key = $info['field replacement'][$info['entity keys']['label']]['preprocess_key'];
  835. }
  836. // We cannot simply unset the variable value since this may cause templates
  837. // to throw notices.
  838. $variables[$key] = FALSE;
  839. }
  840. }
  841. /**
  842. * Implements hook_views_api().
  843. */
  844. function title_views_api() {
  845. return array(
  846. 'api' => 3,
  847. 'path' => drupal_get_path('module', 'title') . '/views',
  848. );
  849. }
  850. /**
  851. * Implements hook_field_attach_create_bundle().
  852. *
  853. * Automatically attach the replacement field to the new bundle.
  854. */
  855. function title_field_attach_create_bundle($entity_type, $bundle) {
  856. $entity_info = entity_get_info($entity_type);
  857. if (empty($entity_info['field replacement'])) {
  858. return;
  859. }
  860. $options = variable_get('title_' . $entity_type, array());
  861. foreach (array_keys($entity_info['field replacement']) as $legacy_field) {
  862. if (empty($options['auto_attach'][$legacy_field])) {
  863. continue;
  864. }
  865. // Do not continue if the replacement field already exists.
  866. $field_name = $entity_info['field replacement'][$legacy_field]['field']['field_name'];
  867. if (field_info_instance($entity_type, $field_name, $bundle)) {
  868. continue;
  869. }
  870. title_field_replacement_toggle($entity_type, $bundle, $legacy_field);
  871. $instance = field_info_instance($entity_type, $field_name, $bundle);
  872. if ($instance) {
  873. $params = array(
  874. '@entity_label' => drupal_strtolower($entity_info['label']),
  875. '%field_name' => $instance['label'],
  876. );
  877. drupal_set_message(t('The @entity_label %field_name field was automatically replaced.', $params));
  878. }
  879. }
  880. }
  881. /**
  882. * Implements hook_field_info_alter().
  883. */
  884. function title_field_info_alter(&$info) {
  885. $supported_types = array('taxonomy_term_reference' => TRUE);
  886. foreach ($info as $field_type => &$field_type_info) {
  887. if (isset($supported_types[$field_type])) {
  888. if (!isset($field_type_info['settings'])) {
  889. $field_type_info['settings'] = array();
  890. }
  891. $field_type_info['settings'] += array('options_list_callback' => 'title_taxonomy_allowed_values');
  892. }
  893. }
  894. }
  895. /**
  896. * Return taxonomy term values for taxonomy reference fields.
  897. */
  898. function title_taxonomy_allowed_values($field) {
  899. // Since we call taxonomy_get_tree() with $load_entities = TRUE, there is a
  900. // chance that this will trigger theme functions. For this case, we need to
  901. // ensure that we are not in the process of theme registry rebuilding.
  902. $theme_registry_cache = drupal_static('theme_get_registry');
  903. /* @see theme_get_registry() */
  904. $theme_registry_is_rebuilding = is_array($theme_registry_cache) &&
  905. empty($theme_registry_cache);
  906. $bundle = !empty($field['settings']['allowed_values'][0]['vocabulary']) ?
  907. $field['settings']['allowed_values'][0]['vocabulary'] : NULL;
  908. if ($bundle && ($label = title_field_replacement_get_label_field('taxonomy_term', $bundle)) && !$theme_registry_is_rebuilding) {
  909. $options = array();
  910. foreach ($field['settings']['allowed_values'] as $tree) {
  911. $vocabulary = taxonomy_vocabulary_machine_name_load($tree['vocabulary']);
  912. if ($vocabulary && ($terms = taxonomy_get_tree($vocabulary->vid, $tree['parent'], NULL, TRUE))) {
  913. foreach ($terms as $term) {
  914. $options[$term->tid] = str_repeat('-', $term->depth) . entity_label('taxonomy_term', $term);
  915. }
  916. }
  917. }
  918. return $options;
  919. }
  920. return taxonomy_allowed_values($field);
  921. }
  922. /**
  923. * Implements hook_ctools_context_converter_alter().
  924. */
  925. function title_ctools_context_converter_alter($context, $converter, &$value, &$converter_options) {
  926. // This is a temporary fix for https://www.drupal.org/node/2505311.
  927. // It fixes a language issue for the pane title containing entity
  928. // related keywords.
  929. if (isset($context->type[0]) && 0 === strpos($context->type[0], 'entity:') && empty($converter_options['language'])) {
  930. $plugin = ctools_get_context($context->plugin);
  931. if ($function = ctools_plugin_get_function($plugin, 'convert')) {
  932. // Provide "language" option for converter and perform the conversion
  933. // again.
  934. $converter_options['language'] = $GLOBALS[LANGUAGE_TYPE_CONTENT];
  935. $value = $function($context, $converter, $converter_options);
  936. }
  937. }
  938. }