title.module 28 KB

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