diff.diff.inc 13 KB

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