title.module 34 KB

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