title.module 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954
  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. foreach ($entities as &$entity) {
  202. // Synchronize values from the regular field unless we are intializing it.
  203. title_entity_sync($type, $entity, NULL, !empty($GLOBALS['title_field_replacement_init']));
  204. }
  205. }
  206. /**
  207. * Implements hook_entitycache_load().
  208. *
  209. * Entity cache might cache the entire $entity object, in which case
  210. * synchronization will not be performed on entity load.
  211. */
  212. function title_entitycache_load($entities, $type) {
  213. title_entity_load($entities, $type);
  214. }
  215. /**
  216. * Implements hook_entitycache_reset().
  217. *
  218. * When the entity cache is reset the field sync has to be done again.
  219. */
  220. function title_entitycache_reset($ids, $entity_type) {
  221. title_entity_sync_static_reset($entity_type, $ids);
  222. }
  223. /**
  224. * Implements hook_entity_prepare_view().
  225. *
  226. * On load synchronization is performed using the current display language. A
  227. * different language might be specified while viewing the entity in which case
  228. * synchronization must be performed again.
  229. */
  230. function title_entity_prepare_view($entities, $type, $langcode) {
  231. foreach ($entities as &$entity) {
  232. title_entity_sync($type, $entity, $langcode);
  233. }
  234. }
  235. /**
  236. * Check whether field replacement is enabled for the given field.
  237. *
  238. * @param $entity_type
  239. * The type of $entity.
  240. * @param $bundle
  241. * The bundle the legacy field belongs to.
  242. * @param $legacy_field
  243. * The name of the legacy field to be replaced.
  244. *
  245. * @return
  246. * TRUE if field replacement is enabled for the given field, FALSE otherwise.
  247. */
  248. function title_field_replacement_enabled($entity_type, $bundle, $legacy_field) {
  249. $info = title_field_replacement_info($entity_type, $legacy_field);
  250. if (!empty($info['field']['field_name'])) {
  251. $instance = field_info_instance($entity_type, $info['field']['field_name'], $bundle);
  252. }
  253. return !empty($instance);
  254. }
  255. /**
  256. * Toggle field replacement for the given field.
  257. *
  258. * @param $entity_type
  259. * The name of the entity type.
  260. * @param $bundle
  261. * The bundle the legacy field belongs to.
  262. * @param $legacy_field
  263. * The name of the legacy field to be replaced.
  264. */
  265. function title_field_replacement_toggle($entity_type, $bundle, $legacy_field) {
  266. $info = title_field_replacement_info($entity_type, $legacy_field);
  267. if (!$info) {
  268. return;
  269. }
  270. $field_name = $info['field']['field_name'];
  271. $instance = field_info_instance($entity_type, $field_name, $bundle);
  272. if (empty($instance)) {
  273. $options = variable_get('title_' . $entity_type, array());
  274. $field = field_info_field($field_name);
  275. if (empty($field)) {
  276. field_create_field($info['field']);
  277. }
  278. $info['instance']['entity_type'] = $entity_type;
  279. $info['instance']['bundle'] = $bundle;
  280. $info['instance']['settings']['hide_label']['page'] = isset($options['hide_label']['page']) ? $options['hide_label']['page'] : FALSE;
  281. $info['instance']['settings']['hide_label']['entity'] = isset($options['hide_label']['entity']) ? $options['hide_label']['entity'] : FALSE;
  282. field_create_instance($info['instance']);
  283. return TRUE;
  284. }
  285. else {
  286. field_delete_instance($instance);
  287. return FALSE;
  288. }
  289. }
  290. /**
  291. * Set a batch process to initialize replacing field values.
  292. *
  293. * @param $entity_type
  294. * The type of $entity.
  295. * @param $bundle
  296. * The bundle the legacy field belongs to.
  297. * @param $legacy_field
  298. * The name of the legacy field to be replaced.
  299. */
  300. function title_field_replacement_batch_set($entity_type, $bundle, $legacy_field) {
  301. $batch = array(
  302. 'title' => t('Replacing field values for %field', array('%field' => $legacy_field)),
  303. 'operations' => array(
  304. array('title_field_replacement_batch', array($entity_type, $bundle, $legacy_field)),
  305. ),
  306. );
  307. batch_set($batch);
  308. }
  309. /**
  310. * Batch operation: initialize a batch of replacing field values.
  311. */
  312. function title_field_replacement_batch($entity_type, $bundle, $legacy_field, &$context) {
  313. $info = entity_get_info($entity_type);
  314. $query = new EntityFieldQuery();
  315. $query->entityCondition('entity_type', $entity_type);
  316. // There is no general way to tell if an entity supports bundle conditions
  317. // (for instance taxonomy terms and comments do not), hence we may need to
  318. // loop over all the entities of the given type.
  319. if (!empty($info['efq bundle conditions'])) {
  320. $query->entityCondition('bundle', $bundle);
  321. }
  322. if (empty($context['sandbox'])) {
  323. $count_query = clone $query;
  324. $total = $count_query
  325. ->count()
  326. ->execute();
  327. $context['sandbox']['steps'] = 0;
  328. $context['sandbox']['progress'] = 0;
  329. $context['sandbox']['total'] = $total;
  330. }
  331. $step = variable_get('title_field_replacement_batch_size', 5);
  332. $start = $step * $context['sandbox']['steps']++;
  333. $results = $query
  334. ->entityCondition('entity_type', $entity_type)
  335. ->range($start, $step)
  336. ->execute();
  337. if (!empty($results[$entity_type])) {
  338. $ids = array_keys($results[$entity_type]);
  339. title_field_replacement_init($entity_type, $bundle, $legacy_field, $ids);
  340. $context['sandbox']['progress'] += count($ids);
  341. }
  342. if ($context['sandbox']['progress'] != $context['sandbox']['total']) {
  343. $context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['total'];
  344. }
  345. }
  346. /**
  347. * Initialize a batch of replacing field values.
  348. *
  349. * @param $entity_type
  350. * The type of $entity.
  351. * @param $bundle
  352. * The bundle the legacy field belongs to.
  353. * @param $legacy_field
  354. * The name of the legacy field to be replaced.
  355. * @param $ids
  356. * An array of entity IDs.
  357. *
  358. * @return
  359. * The number of entities processed.
  360. */
  361. function title_field_replacement_init($entity_type, $bundle, $legacy_field, $ids) {
  362. $GLOBALS['title_field_replacement_init'] = TRUE;
  363. $entities = entity_load($entity_type, $ids);
  364. foreach ($entities as $id => $entity) {
  365. list(, , $entity_bundle) = entity_extract_ids($entity_type, $entity);
  366. if ($entity_bundle == $bundle) {
  367. field_attach_presave($entity_type, $entity);
  368. field_attach_update($entity_type, $entity);
  369. }
  370. }
  371. unset($GLOBALS['title_field_replacement_init']);
  372. }
  373. /**
  374. * Synchronize replaced fields with the regular field values.
  375. *
  376. * @param $entity_type
  377. * The name of the entity type.
  378. * @param $entity
  379. * The entity to work with.
  380. * @param $set
  381. * Specifies the direction synchronization must be performed.
  382. */
  383. function title_entity_sync($entity_type, &$entity, $langcode = NULL, $set = FALSE) {
  384. $sync = &drupal_static(__FUNCTION__, array());
  385. list($id, , $bundle) = entity_extract_ids($entity_type, $entity);
  386. if (!isset($langcode)) {
  387. $langcode = $set ? title_entity_language($entity_type, $entity) : title_active_language();
  388. }
  389. // We do not need to perform synchronization more than once.
  390. if (!$set && !empty($id) && !empty($sync[$entity_type][$id][$langcode][$set])) {
  391. return;
  392. }
  393. $sync[$entity_type][$id][$langcode][$set] = TRUE;
  394. $fr_info = title_field_replacement_info($entity_type);
  395. if ($fr_info) {
  396. foreach ($fr_info as $legacy_field => $info) {
  397. if (title_field_replacement_enabled($entity_type, $bundle, $legacy_field)) {
  398. $function = 'title_field_sync_' . ($set ? 'set' : 'get');
  399. $function($entity_type, $entity, $legacy_field, $info, $langcode);
  400. }
  401. }
  402. }
  403. }
  404. /**
  405. * Reset the list of entities whose fields have already been synchronized.
  406. *
  407. * @param $entity_type
  408. * The name of the entity type.
  409. * @param $entity_ids
  410. * Either an array of entity IDs to reset or NULL to reset all.
  411. */
  412. function title_entity_sync_static_reset($entity_type, $entity_ids = NULL) {
  413. $sync = &drupal_static('title_entity_sync', array());
  414. if (is_array($entity_ids)) {
  415. foreach ($entity_ids as $id) {
  416. unset($sync[$entity_type][$id]);
  417. }
  418. }
  419. else {
  420. unset($sync[$entity_type]);
  421. }
  422. }
  423. /**
  424. * Synchronize a single legacy field with its regular field value.
  425. *
  426. * @param $entity_type
  427. * The name of the entity type.
  428. * @param $entity
  429. * The entity to work with.
  430. * @param $legacy_field
  431. * The name of the legacy field to be replaced.
  432. * @param $field_name
  433. * The regular field to use as source value.
  434. * @param $info
  435. * Field replacement information for the given entity.
  436. * @param $langcode
  437. * The field language to use for the source value.
  438. */
  439. function title_field_sync_get($entity_type, $entity, $legacy_field, $info, $langcode = NULL) {
  440. if (property_exists($entity, $legacy_field)) {
  441. // Save the legacy field value to LEGACY_FIELD_NAME_original.
  442. $entity->{$legacy_field . '_original'} = $entity->{$legacy_field};
  443. // Find out the actual language to use (field might be untranslatable).
  444. $langcode = field_language($entity_type, $entity, $info['field']['field_name'], $langcode);
  445. $values = $info['callbacks']['sync_get']($entity_type, $entity, $legacy_field, $info, $langcode);
  446. foreach ($values as $name => $value) {
  447. $entity->{$name} = $value;
  448. }
  449. }
  450. }
  451. /**
  452. * Synchronize a single regular field from its legacy field value.
  453. *
  454. * @param $entity_type
  455. * The name of the entity type.
  456. * @param $entity
  457. * The entity to work with.
  458. * @param $legacy_field
  459. * The name of the legacy field to be replaced.
  460. * @param $field_name
  461. * The regular field to use as source value.
  462. * @param $info
  463. * Field replacement information for the given entity.
  464. * @param $langcode
  465. * The field language to use for the target value.
  466. */
  467. function title_field_sync_set($entity_type, $entity, $legacy_field, $info, $langcode) {
  468. if (property_exists($entity, $legacy_field)) {
  469. // Find out the actual language to use (field might be untranslatable).
  470. $field = field_info_field($info['field']['field_name']);
  471. $langcode = field_is_translatable($entity_type, $field) ? $langcode : LANGUAGE_NONE;
  472. $info['callbacks']['sync_set']($entity_type, $entity, $legacy_field, $info, $langcode);
  473. }
  474. }
  475. /**
  476. * Returns and optionally stores the active language.
  477. *
  478. * @param string $langcode
  479. * (optional) The active language to be set. If none is provided the active
  480. * language is just returned.
  481. *
  482. * @return string
  483. * The active language code. Defaults to the current content language.
  484. */
  485. function title_active_language($langcode = NULL) {
  486. static $drupal_static_fast;
  487. if (!isset($drupal_static_fast)) {
  488. $drupal_static_fast['active_language'] = &drupal_static(__FUNCTION__);
  489. }
  490. $active_langcode = &$drupal_static_fast['active_language'];
  491. if (isset($langcode)) {
  492. $active_langcode = $langcode;
  493. }
  494. if (empty($active_langcode)) {
  495. $active_langcode = $GLOBALS['language_content']->language;
  496. }
  497. return $active_langcode;
  498. }
  499. /**
  500. * Provide the original entity language.
  501. *
  502. * If a language property is defined for the current entity we synchronize the
  503. * field value using the entity language, otherwise we fall back to
  504. * LANGUAGE_NONE.
  505. *
  506. * @param $entity_type
  507. * @param $entity
  508. *
  509. * @return
  510. * A language code
  511. */
  512. function title_entity_language($entity_type, $entity) {
  513. if (module_exists('entity_translation') && entity_translation_enabled($entity_type)) {
  514. $handler = entity_translation_get_handler($entity_type, $entity, TRUE);
  515. $langcode = $handler->getLanguage();
  516. }
  517. else {
  518. $langcode = entity_language($entity_type, $entity);
  519. }
  520. return !empty($langcode) ? $langcode : LANGUAGE_NONE;
  521. }
  522. /**
  523. * Implements hook_field_attach_form().
  524. *
  525. * Hide legacy field widgets on the assumption that this is always called on
  526. * fieldable entity forms.
  527. */
  528. function title_field_attach_form($entity_type, $entity, &$form, &$form_state, $langcode) {
  529. list(, , $bundle) = entity_extract_ids($entity_type, $entity);
  530. $fr_info = title_field_replacement_info($entity_type);
  531. if (!empty($fr_info)) {
  532. foreach ($fr_info as $legacy_field => $info) {
  533. if (isset($form[$legacy_field]) && title_field_replacement_enabled($entity_type, $bundle, $legacy_field)) {
  534. // Inherit the access from the title widget, so that other modules
  535. // restricting access to it keep working.
  536. if (isset($form[$legacy_field]['#access'])) {
  537. $form[$info['field']['field_name']]['#access'] = $form[$legacy_field]['#access'];
  538. }
  539. // Restrict access to the legacy field form element and mark it as
  540. // replaced.
  541. $form[$legacy_field]['#access'] = FALSE;
  542. $form[$legacy_field]['#field_replacement'] = TRUE;
  543. }
  544. }
  545. }
  546. }
  547. /**
  548. * Implements hook_field_attach_submit().
  549. *
  550. * Synchronize submitted field values into the corresponding legacy fields.
  551. */
  552. function title_field_attach_submit($entity_type, $entity, $form, &$form_state) {
  553. $fr_info = title_field_replacement_info($entity_type);
  554. if (!empty($fr_info)) {
  555. // We copy (rather than reference) the values from $form_state because the
  556. // subsequent call to drupal_array_get_nested_value() is destructive and
  557. // will affect other hooks relying on data in $form_state. At the end, we
  558. // copy any modified value back into the $form_state array using
  559. // drupal_array_set_nested_value().
  560. $values = $form_state['values'];
  561. $values = drupal_array_get_nested_value($values, $form['#parents']);
  562. $langcode = entity_language($entity_type, $entity);
  563. foreach ($fr_info as $legacy_field => $info) {
  564. if (!empty($form[$legacy_field]['#field_replacement'])) {
  565. $field_name = $info['field']['field_name'];
  566. // Give a chance to operate on submitted values either.
  567. if (!empty($info['callbacks']['submit'])) {
  568. $info['callbacks']['submit']($entity_type, $entity, $legacy_field, $info, $langcode, $values);
  569. }
  570. drupal_static_reset('field_language');
  571. title_field_sync_get($entity_type, $entity, $legacy_field, $info, $langcode);
  572. }
  573. }
  574. drupal_array_set_nested_value($form_state['values'], $form['#parents'], $values);
  575. }
  576. }
  577. /**
  578. * Implements of hook_menu().
  579. */
  580. function title_menu() {
  581. $items = array();
  582. foreach (entity_get_info() as $entity_type => $entity_info) {
  583. if (!empty($entity_info['field replacement'])) {
  584. foreach ($entity_info['bundles'] as $bundle_name => $bundle_info) {
  585. // Blindly taken from field_ui_menu().
  586. if (isset($bundle_info['admin'])) {
  587. $path = $bundle_info['admin']['path'];
  588. if (isset($bundle_info['admin']['bundle argument'])) {
  589. $bundle_arg = $bundle_info['admin']['bundle argument'];
  590. }
  591. else {
  592. $bundle_arg = $bundle_name;
  593. }
  594. $access = array_intersect_key($bundle_info['admin'], drupal_map_assoc(array('access callback', 'access arguments')));
  595. $access += array(
  596. 'access callback' => 'user_access',
  597. 'access arguments' => array('administer site configuration'),
  598. );
  599. $path = "$path/fields/replace/%";
  600. $field_arg = count(explode('/', $path)) - 1;
  601. $items[$path] = array(
  602. 'load arguments' => array(),
  603. 'title' => 'Replace fields',
  604. 'page callback' => 'drupal_get_form',
  605. 'page arguments' => array('title_field_replacement_form', $entity_type, $bundle_arg, $field_arg),
  606. 'file' => 'title.admin.inc',
  607. ) + $access;
  608. }
  609. }
  610. }
  611. }
  612. $items['admin/config/content/title'] = array(
  613. 'title' => 'Title settings',
  614. 'description' => 'Settings for the Title module.',
  615. 'page callback' => 'drupal_get_form',
  616. 'page arguments' => array('title_admin_settings_form'),
  617. 'access arguments' => array('administer site configuration'),
  618. 'file' => 'title.admin.inc',
  619. );
  620. return $items;
  621. }
  622. /**
  623. * Implements hook help.
  624. */
  625. function title_help($path, $arg) {
  626. switch ($path) {
  627. case 'admin/config/content/title':
  628. 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>';
  629. }
  630. }
  631. /**
  632. * Implements hook_field_extra_fields_alter().
  633. */
  634. function title_field_extra_fields_alter(&$info) {
  635. $entity_info = entity_get_info();
  636. foreach ($info as $entity_type => $bundles) {
  637. foreach ($bundles as $bundle_name => $bundle) {
  638. if (!empty($entity_info[$entity_type]['field replacement'])) {
  639. foreach ($entity_info[$entity_type]['field replacement'] as $field_name => $field_replacement_info) {
  640. if (title_field_replacement_enabled($entity_type, $bundle_name, $field_name)) {
  641. // Remove the replaced legacy field.
  642. unset($info[$entity_type][$bundle_name]['form'][$field_name], $info[$entity_type][$bundle_name]['display'][$field_name]);
  643. }
  644. }
  645. }
  646. }
  647. }
  648. }
  649. /**
  650. * Implements hook_form_FORM_ID_alter().
  651. */
  652. function title_form_field_ui_field_overview_form_alter(&$form, &$form_state) {
  653. module_load_include('inc', 'title', 'title.admin');
  654. title_form_field_ui_overview($form, $form_state);
  655. }
  656. /**
  657. * Implements hook_tokens_alter().
  658. *
  659. * Make sure tokens are properly translated.
  660. */
  661. function title_tokens_alter(array &$replacements, array $context) {
  662. $mapping = &drupal_static(__FUNCTION__);
  663. if (empty($mapping)) {
  664. foreach (entity_get_info() as $entity_type => $info) {
  665. if (!empty($info['token type'])) {
  666. $mapping[$info['token type']] = $entity_type;
  667. }
  668. }
  669. }
  670. if (isset($mapping[$context['type']])) {
  671. $entity_type = $mapping[$context['type']];
  672. $fr_info = title_field_replacement_info($entity_type);
  673. if ($fr_info && !empty($context['data'][$context['type']])) {
  674. $entity = $context['data'][$context['type']];
  675. list($id, $vid, $bundle) = entity_extract_ids($entity_type, $entity);
  676. $options = $context['options'];
  677. // Since Title tokens are mostly used in storage contexts we default to
  678. // the current working language, that is the entity language. Modules
  679. // using Title tokens in display contexts need to specify the current
  680. // display language.
  681. $langcode = isset($options['language']) ? $options['language']->language : entity_language($entity_type, $entity);
  682. if ($fr_info) {
  683. foreach ($fr_info as $legacy_field => $info) {
  684. if (title_field_replacement_enabled($entity_type, $bundle, $legacy_field)) {
  685. if (isset($context['tokens'][$legacy_field])) {
  686. $langcode = field_language($entity_type, $entity, $info['field']['field_name'], $langcode);
  687. $values = $info['callbacks']['sync_get']($entity_type, $entity, $legacy_field, $info, $langcode);
  688. $item = $values[$legacy_field];
  689. if (!empty($item)) {
  690. if (is_array($item)) {
  691. $item = reset($item);
  692. }
  693. $replacements[$context['tokens'][$legacy_field]] = $item;
  694. }
  695. }
  696. }
  697. }
  698. }
  699. }
  700. }
  701. }
  702. /**
  703. * Implements hook_form_FORM_ID_alter().
  704. */
  705. function title_form_field_ui_field_edit_form_alter(&$form, $form_state) {
  706. $instance = $form['#instance'];
  707. $entity_type = $instance['entity_type'];
  708. if (title_field_replacement_is_label($entity_type, $instance['field_name'])) {
  709. $info = entity_get_info($entity_type);
  710. $form['instance']['settings']['hide_label'] = _title_hide_label_widget($instance['settings'], $info['label']);
  711. }
  712. }
  713. /**
  714. * Returns the hide label form widget.
  715. */
  716. function _title_hide_label_widget($default, $entity_label) {
  717. return array(
  718. '#type' => 'checkboxes',
  719. '#title' => t('Label replacement'),
  720. '#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)),
  721. '#default_value' => !empty($default['hide_label']) ? $default['hide_label'] : array(),
  722. '#options' => array(
  723. 'page' => t('Hide page title'),
  724. 'entity' => t('Hide label in %entity_label listings', array('%entity_label' => drupal_strtolower($entity_label))),
  725. ),
  726. );
  727. }
  728. /**
  729. * Checks whether the given field name is a replaced entity label.
  730. *
  731. * @param $entity_type
  732. * The name of the entity type.
  733. * @param $field_name
  734. * The replacing field name.
  735. *
  736. * @return
  737. * TRUE id the give field is replacing the entity label, FALSE otherwise.
  738. */
  739. function title_field_replacement_is_label($entity_type, $field_name) {
  740. $label = FALSE;
  741. $legacy_field = title_field_replacement_get_legacy_field($entity_type, $field_name);
  742. if ($legacy_field) {
  743. $info = entity_get_info($entity_type);
  744. $label = $legacy_field == $info['entity keys']['label'];
  745. }
  746. return $label;
  747. }
  748. /**
  749. * Returns the legacy field replaced by the given field name.
  750. *
  751. * @param $entity_type
  752. * The name of the entity type.
  753. * @param $field_name
  754. * The replacing field name.
  755. *
  756. * @return
  757. * The replaced legacy field name or FALSE if none available.
  758. */
  759. function title_field_replacement_get_legacy_field($entity_type, $field_name) {
  760. $result = FALSE;
  761. $fr_info = title_field_replacement_info($entity_type);
  762. if ($fr_info) {
  763. foreach ($fr_info as $legacy_field => $info) {
  764. if ($info['field']['field_name'] == $field_name) {
  765. $result = $legacy_field;
  766. break;
  767. }
  768. }
  769. }
  770. return $result;
  771. }
  772. /**
  773. * Returns the field instance replacing the given entity type's label.
  774. *
  775. * @param $entity_type
  776. * The name of the entity type.
  777. * @param $bundle
  778. * The name of the bundle the instance is attached to.
  779. *
  780. * @return
  781. * The field instance replacing the label or FALSE if none available.
  782. */
  783. function title_field_replacement_get_label_field($entity_type, $bundle) {
  784. $instance = FALSE;
  785. $info = entity_get_info($entity_type);
  786. if (!empty($info['field replacement'])) {
  787. $fr_info = $info['field replacement'];
  788. $legacy_field = $info['entity keys']['label'];
  789. if (!empty($fr_info[$legacy_field]['field'])) {
  790. $instance = field_info_instance($entity_type, $fr_info[$legacy_field]['field']['field_name'], $bundle);
  791. }
  792. }
  793. return $instance;
  794. }
  795. /**
  796. * Hides the label from the given variables.
  797. *
  798. * @param $entity_type
  799. * The name of the entity type.
  800. * @param $entity
  801. * The entity to work with.
  802. * @param $vaiables
  803. * A reference to the variables array related to the template being processed.
  804. * @param $page
  805. * (optional) The current render phase: page or entity. Defaults to entity.
  806. */
  807. function title_field_replacement_hide_label($entity_type, $entity, &$variables, $page = FALSE) {
  808. list(, , $bundle) = entity_extract_ids($entity_type, $entity);
  809. $instance = title_field_replacement_get_label_field($entity_type, $bundle);
  810. $settings_key = $page ? 'page' : 'entity';
  811. if (!empty($instance['settings']['hide_label'][$settings_key])) {
  812. // If no key is passed default to the label one.
  813. if ($page) {
  814. $key = 'title';
  815. }
  816. else {
  817. $info = entity_get_info($entity_type);
  818. $key = $info['field replacement'][$info['entity keys']['label']]['preprocess_key'];
  819. }
  820. // We cannot simply unset the variable value since this may cause templates
  821. // to throw notices.
  822. $variables[$key] = FALSE;
  823. }
  824. }
  825. /**
  826. * Implements hook_views_api().
  827. */
  828. function title_views_api() {
  829. return array(
  830. 'api' => 3,
  831. 'path' => drupal_get_path('module', 'title') . '/views',
  832. );
  833. }
  834. /**
  835. * Implements hook_field_attach_create_bundle().
  836. *
  837. * Automatically attach the replacement field to the new bundle.
  838. */
  839. function title_field_attach_create_bundle($entity_type, $bundle) {
  840. $entity_info = entity_get_info($entity_type);
  841. if (empty($entity_info['field replacement'])) {
  842. return;
  843. }
  844. $options = variable_get('title_' . $entity_type, array());
  845. foreach (array_keys($entity_info['field replacement']) as $legacy_field) {
  846. if (empty($options['auto_attach'][$legacy_field])) {
  847. continue;
  848. }
  849. // Do not continue if the replacement field already exists.
  850. $field_name = $entity_info['field replacement'][$legacy_field]['field']['field_name'];
  851. if (field_info_instance($entity_type, $field_name, $bundle)) {
  852. continue;
  853. }
  854. title_field_replacement_toggle($entity_type, $bundle, $legacy_field);
  855. $instance = field_info_instance($entity_type, $field_name, $bundle);
  856. if ($instance) {
  857. $params = array(
  858. '@entity_label' => drupal_strtolower($entity_info['label']),
  859. '%field_name' => $instance['label'],
  860. );
  861. drupal_set_message(t('The @entity_label %field_name field was automatically replaced.', $params));
  862. }
  863. }
  864. }