title.module 34 KB

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