diff.diff.inc 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362
  1. <?php
  2. /**
  3. * @file
  4. * Includes the hooks defined by diff_hook_info().
  5. */
  6. /**
  7. * Implements hook_entity_diff().
  8. *
  9. * Helper function to invoke the depreciated hook_diff() for node entities.
  10. *
  11. * This manually invokes hook_diff() to avoid a function name clash with the
  12. * PHP 5 (>= 5.3.0) date_diff() function or the Dates modules implementation.
  13. */
  14. function diff_entity_diff($old_entity, $new_entity, $context) {
  15. $return = array();
  16. $entity_type = $context['entity_type'];
  17. $info = entity_get_info($entity_type);
  18. if (!empty($info['fieldable'])) {
  19. $return = diff_entity_fields_diff($old_entity, $new_entity, $context);
  20. }
  21. return $return;
  22. }
  23. /**
  24. * Internal callback to handle fieldable entities.
  25. *
  26. * Field comparison is handled for core modules, but is expandable to any other
  27. * fields if the module defines MODULE_field_diff_view().
  28. *
  29. * @param object $old_entity
  30. * The older entity entity revision.
  31. * @param object $new_entity
  32. * The newer entity entity revision.
  33. * @param array $context
  34. * An associative array containing:
  35. * - entity_type: The entity type; e.g., 'node' or 'user'.
  36. * - old_entity: The older entity.
  37. * - new_entity: The newer entity.
  38. * - view_mode: The view mode to use. Defaults to FALSE.
  39. * @param string $default_langcode
  40. * (optional) Language code to force comparison in.
  41. *
  42. * @return array
  43. * An associative array of values keyed by the field name and delta value.
  44. */
  45. function diff_entity_fields_diff($old_entity, $new_entity, $context, $default_langcode = NULL) {
  46. $result = array();
  47. $entity_type = $context['entity_type'];
  48. $view_mode = $context['view_mode'];
  49. $field_context = $context;
  50. $actual_mode = FALSE;
  51. list(, , $bundle_name) = entity_extract_ids($entity_type, $new_entity);
  52. $instances = field_info_instances($entity_type, $bundle_name);
  53. // Some fields piggy back the display settings, so we need to fake these by
  54. // ensuring that the field mode is always set.
  55. if (empty($view_mode)) {
  56. $actual_mode = 'diff_standard';
  57. $field_context['custom_settings'] = FALSE;
  58. }
  59. $view_mode_settings = field_view_mode_settings($entity_type, $bundle_name);
  60. $actual_mode = (!empty($view_mode_settings[$view_mode]['custom_settings'])) ? $view_mode : 'default';
  61. if (!isset($field_context['custom_settings'])) {
  62. $field_context['custom_settings'] = $actual_mode && $actual_mode == $view_mode;
  63. }
  64. $field_context['old_entity'] = $old_entity;
  65. $field_context['new_entity'] = $new_entity;
  66. $field_context['bundle_name'] = $bundle_name;
  67. foreach ($instances as $instance) {
  68. // Any view mode is supported in relation to hiding fields, but only if
  69. // selected (todo see if this is a valid option).
  70. if ($actual_mode && $instance['display'][$actual_mode]['type'] == 'hidden') {
  71. continue;
  72. }
  73. $field_name = $instance['field_name'];
  74. $field = field_info_field($field_name);
  75. $field_context['field'] = $field;
  76. $field_context['instance'] = $instance;
  77. $field_context['display'] = $instance['display'][$actual_mode];
  78. // We provide a loose check on the field access.
  79. if (field_access('view', $field, $entity_type) || field_access('edit', $field, $entity_type)) {
  80. $langcode = $default_langcode ? $default_langcode : field_language($entity_type, $new_entity, $field_name);
  81. $field_context['language'] = $langcode;
  82. $field_context['field'] = $field;
  83. $field_context['instance'] = $instance;
  84. $old_items = array();
  85. if (!empty($old_entity->{$field_name}[$langcode])) {
  86. $old_items = $old_entity->{$field_name}[$langcode];
  87. }
  88. $new_items = array();
  89. if (!empty($new_entity->{$field_name}[$langcode])) {
  90. $new_items = $new_entity->{$field_name}[$langcode];
  91. }
  92. // Load files containing the field callbacks.
  93. _diff_autoload($field);
  94. $field_context['settings'] = diff_get_field_settings($field_context);
  95. // Reference fields can optionally prepare objects in bulk to reduce
  96. // overheads related to multiple database calls. If a field considers
  97. // that the delta values is meaningless, they can order and rearrange
  98. // to provide cleaner results.
  99. $func = $field['module'] . '_field_diff_view_prepare';
  100. if (function_exists($func)) {
  101. $func($old_items, $new_items, $field_context);
  102. }
  103. // Allow other modules to act safely on behalf of the core field module.
  104. drupal_alter('field_diff_view_prepare', $old_items, $new_items, $field_context);
  105. // These functions compiles the items into comparable arrays of strings.
  106. $func = $field['module'] . '_field_diff_view';
  107. if (!function_exists($func)) {
  108. $func = 'diff_field_diff_view';
  109. }
  110. // Copy the static ID cache to ensure this is the same for each comparison.
  111. $original_html_ids = drupal_static('drupal_html_id');
  112. $html_ids = &drupal_static('drupal_html_id');
  113. // These callbacks should be independent of revision.
  114. $old_context = $field_context;
  115. $old_context['entity'] = $old_entity;
  116. $old_values = $func($old_items, $old_context);
  117. // Restores the ID cache to the original.
  118. $html_ids = $original_html_ids;
  119. $new_context = $field_context;
  120. $new_context['entity'] = $new_entity;
  121. $new_values = $func($new_items, $new_context);
  122. // Allow other modules to act safely on behalf of the core field module.
  123. drupal_alter('field_diff_view', $old_values, $old_items, $old_context);
  124. drupal_alter('field_diff_view', $new_values, $new_items, $new_context);
  125. $max = max(array(count($old_values), count($new_values)));
  126. if ($max) {
  127. $result[$field_name] = array(
  128. '#name' => $instance['label'],
  129. '#old' => array(),
  130. '#new' => array(),
  131. '#settings' => $field_context['settings'],
  132. );
  133. for ($delta = 0; $delta < $max; $delta++) {
  134. if (isset($old_values[$delta])) {
  135. $result[$field_name]['#old'][] = is_array($old_values[$delta]) ? implode("\n", $old_values[$delta]) : $old_values[$delta];
  136. }
  137. if (isset($new_values[$delta])) {
  138. $result[$field_name]['#new'][] = is_array($new_values[$delta]) ? implode("\n", $new_values[$delta]) : $new_values[$delta];
  139. }
  140. }
  141. $result[$field_name]['#old'] = implode("\n", $result[$field_name]['#old']);
  142. $result[$field_name]['#new'] = implode("\n", $result[$field_name]['#new']);
  143. if ($actual_mode) {
  144. $result[$field_name]['#weight'] = $instance['display'][$actual_mode]['weight'];
  145. }
  146. }
  147. }
  148. }
  149. return $result;
  150. }
  151. /**
  152. * A generic handler for parsing field values.
  153. *
  154. * This callback can only handle the most basic of fields that populates the
  155. * safe_value during field load or use the value column for data storage.
  156. *
  157. * @param array $items
  158. * An array of field items.
  159. * @param array $context
  160. * An associative array containing:
  161. * - entity: The entity that the items belong to.
  162. * - entity_type: The entity type; e.g., 'node' or 'user'.
  163. * - bundle: The bundle name.
  164. * - field: The field that the items belong to.
  165. * - instance: The instance that the items belong to.
  166. * - language: The language associated with $items.
  167. * - old_entity: The older entity.
  168. * - new_entity: The newer entity.
  169. *
  170. * @return array
  171. * An array of strings representing the value, keyed by delta index.
  172. */
  173. function diff_field_diff_view($items, $context) {
  174. // Prevent unnecessary rendering of the field. This also prevents issues
  175. // where field_view_field() will use a language fallback for display that
  176. // may not match the requested diff comparison language.
  177. if (!$items) {
  178. return array();
  179. }
  180. $diff_items = array();
  181. $entity = clone $context['entity'];
  182. $langcode = field_language($context['entity_type'], $entity, $context['field']['field_name']);
  183. $view_mode = empty($context['view_mode']) ? 'diff_standard' : $context['view_mode'];
  184. $element = field_view_field($context['entity_type'], $entity, $context['field']['field_name'], $view_mode, $langcode);
  185. foreach (element_children($element) as $delta) {
  186. $diff_items[$delta] = drupal_render($element[$delta]);
  187. }
  188. return $diff_items;
  189. }
  190. /**
  191. * Helper function to get the settings for a given field or formatter.
  192. *
  193. * @param array $field_context
  194. * This will get the settings for a field.
  195. * - field (required): The field that the items belong to.
  196. * - entity: The entity that we are looking up.
  197. * - instance: The instance that the items belong to.
  198. * - view_mode: The view mode to use. Defaults to FALSE.
  199. *
  200. * @return array
  201. * The settings for this field type.
  202. */
  203. function diff_get_field_settings($field_context) {
  204. $field = $field_context['field'];
  205. // Update saved settings from the global settings for this field type.
  206. $settings = variable_get("diff_{$field['module']}_field_{$field['type']}_default_options", array());
  207. $settings = _diff_field_default_settings($field['module'], $field['type'], $settings);
  208. // Allow modules to alter the field settings based on the current context.
  209. drupal_alter('diff_field_settings', $settings, $field_context);
  210. return $settings;
  211. }
  212. /**
  213. * Helper function to initiate any global form elements.
  214. */
  215. function diff_global_settings_form(&$subform, $form_state, $type, $settings) {
  216. $subform['show_header'] = array(
  217. '#type' => 'checkbox',
  218. '#title' => t('Show field title'),
  219. '#default_value' => $settings['show_header'],
  220. '#weight' => -5,
  221. );
  222. $subform['markdown'] = array(
  223. '#type' => 'select',
  224. '#title' => t('Markdown callback'),
  225. '#default_value' => $settings['markdown'],
  226. '#options' => array(
  227. 'drupal_html_to_text' => t('Drupal HTML to Text'),
  228. 'filter_xss' => t('Filter XSS (some tags)'),
  229. 'diff_filter_xss' => t('Filter XSS (all tags)'),
  230. ),
  231. '#description' => t('These provide ways to clean markup tags to make comparisons easier to read.'),
  232. '#empty_option' => t('- Do not process -'),
  233. );
  234. $subform['line_counter'] = array(
  235. '#type' => 'radios',
  236. '#title' => t('Line counter'),
  237. '#default_value' => $settings['line_counter'],
  238. '#description' => t('This outputs the (approximate) line numbers as a heading before every change.'),
  239. '#options' => array(
  240. '' => t('None. Counter ignore and not incremented.'),
  241. 'hidden' => t('Count lines but do not show line headers.'),
  242. 'line' => t('Count and show lines, restarting counter at 0.'),
  243. 'line_continuous' => t('Count and show lines, incrementing counter from last item.'),
  244. ),
  245. );
  246. }
  247. /**
  248. * Helper function to populate the settings array.
  249. */
  250. function _diff_field_default_settings($module, $field_type, $settings = array()) {
  251. // Load files containing the field callbacks.
  252. _diff_autoload($module);
  253. // Populate any missing values from CALLBACK_field_diff_default_options().
  254. $func = $module . '_field_diff_default_options';
  255. if (function_exists($func)) {
  256. $settings += $func($field_type);
  257. }
  258. // Check for Diff support. If it doesn't exist, the default markdown should
  259. // escape the field display, otherwise a raw format should be used.
  260. $func = $module . '_field_diff_view';
  261. // Global settings.
  262. $settings += array(
  263. 'markdown' => function_exists($func) ? '' : 'drupal_html_to_text',
  264. 'line_counter' => '',
  265. 'show_header' => 1,
  266. );
  267. return $settings;
  268. }
  269. /**
  270. * Private helper function to load field includes.
  271. *
  272. * @param array|string $field_or_module
  273. * The field definition array or the module that implements the field.
  274. */
  275. function _diff_autoload($field_or_module) {
  276. $includes = &drupal_static(__FUNCTION__, FALSE);
  277. if (!$includes) {
  278. $includes = array(
  279. 'file' => module_exists('file'),
  280. 'image' => module_exists('image'),
  281. 'list' => module_exists('list'),
  282. 'taxonomy' => module_exists('taxonomy'),
  283. 'text' => module_exists('text'),
  284. 'number' => module_exists('number'),
  285. );
  286. }
  287. $module = is_string($field_or_module) ? $field_or_module : $field_or_module['module'];
  288. // Since field hooks are not real hooks, we manually load the field modules
  289. // MODULE.diff.inc. We handle the five core field defining modules.
  290. if (!isset($includes[$module])) {
  291. module_load_include('diff.inc', $module);
  292. $includes[$module] = 0;
  293. }
  294. elseif (!empty($includes[$module])) {
  295. module_load_include('inc', 'diff', 'includes/' . $module);
  296. $includes[$module] = 0;
  297. }
  298. }
  299. /**
  300. * Helper function to parse out the state in the diff results.
  301. */
  302. function diff_extract_state($diff, $state = 'raw') {
  303. $states = array(
  304. 0 => NULL,
  305. 1 => NULL,
  306. );
  307. if (isset($diff['#states'][$state])) {
  308. if (isset($diff['#states'][$state]['#old'])) {
  309. $states[0] = $diff['#states'][$state]['#old'];
  310. }
  311. if (isset($diff['#states'][$state]['#new'])) {
  312. $states[1] = $diff['#states'][$state]['#new'];
  313. }
  314. }
  315. return $states;
  316. }