entity_views_field_handler_helper.inc 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527
  1. <?php
  2. /**
  3. * @file
  4. * Contains the EntityFieldHandlerHelper class.
  5. */
  6. /**
  7. * Helper class containing static implementations of common field handler methods.
  8. *
  9. * Used by the data selection entity field handlers to avoid code duplication.
  10. *
  11. * @see entity_views_table_definition()
  12. */
  13. class EntityFieldHandlerHelper {
  14. /**
  15. * Provide appropriate default options for a handler.
  16. */
  17. public static function option_definition($handler) {
  18. if (isset($handler->definition['type']) && entity_property_list_extract_type($handler->definition['type'])) {
  19. $options['list']['contains']['mode'] = array('default' => 'collapse');
  20. $options['list']['contains']['separator'] = array('default' => ', ');
  21. $options['list']['contains']['type'] = array('default' => 'ul');
  22. }
  23. $options['link_to_entity'] = array('default' => FALSE);
  24. return $options;
  25. }
  26. /**
  27. * Provide an appropriate default option form for a handler.
  28. */
  29. public static function options_form($handler, &$form, &$form_state) {
  30. if (isset($handler->definition['type']) && entity_property_list_extract_type($handler->definition['type'])) {
  31. $form['list']['mode'] = array(
  32. '#type' => 'select',
  33. '#title' => t('List handling'),
  34. '#options' => array(
  35. 'collapse' => t('Concatenate values using a seperator'),
  36. 'list' => t('Output values as list'),
  37. 'first' => t('Show first (if present)'),
  38. 'count' => t('Show item count'),
  39. ),
  40. '#default_value' => $handler->options['list']['mode'],
  41. );
  42. $form['list']['separator'] = array(
  43. '#type' => 'textfield',
  44. '#title' => t('List seperator'),
  45. '#default_value' => $handler->options['list']['separator'],
  46. '#dependency' => array('edit-options-list-mode' => array('collapse')),
  47. );
  48. $form['list']['type'] = array(
  49. '#type' => 'select',
  50. '#title' => t('List type'),
  51. '#options' => array(
  52. 'ul' => t('Unordered'),
  53. 'ol' => t('Ordered'),
  54. ),
  55. '#default_value' => $handler->options['list']['type'],
  56. '#dependency' => array('edit-options-list-mode' => array('list')),
  57. );
  58. }
  59. $form['link_to_entity'] = array(
  60. '#type' => 'checkbox',
  61. '#title' => t('Link this field to its entity'),
  62. '#description' => t("When using this, you should not set any other link on the field."),
  63. '#default_value' => $handler->options['link_to_entity'],
  64. );
  65. }
  66. /**
  67. * Add the field for the entity ID (if necessary).
  68. */
  69. public static function query($handler) {
  70. // Copied over from views_handler_field_entity::query().
  71. // Sets table_alias (entity table), base_field (entity id field) and
  72. // field_alias (the field's alias).
  73. $handler->table_alias = $base_table = $handler->view->base_table;
  74. $handler->base_field = $handler->view->base_field;
  75. if (!empty($handler->relationship)) {
  76. foreach ($handler->view->relationship as $relationship) {
  77. if ($relationship->alias == $handler->relationship) {
  78. $base_table = $relationship->definition['base'];
  79. $handler->table_alias = $relationship->alias;
  80. $table_data = views_fetch_data($base_table);
  81. $handler->base_field = empty($relationship->definition['base field']) ? $table_data['table']['base']['field'] : $relationship->definition['base field'];
  82. }
  83. }
  84. }
  85. // Add the field if the query back-end implements an add_field() method,
  86. // just like the default back-end.
  87. if (method_exists($handler->query, 'add_field')) {
  88. $handler->field_alias = $handler->query->add_field($handler->table_alias, $handler->base_field, '');
  89. }
  90. else {
  91. // To ensure there is an alias just set the field alias to the real field.
  92. $handler->field_alias = $handler->real_field;
  93. }
  94. }
  95. /**
  96. * Extracts the innermost field name from a data selector.
  97. *
  98. * @param $selector
  99. * The data selector.
  100. *
  101. * @return
  102. * The last component of the data selector.
  103. */
  104. public static function get_selector_field_name($selector) {
  105. return ltrim(substr($selector, strrpos($selector, ':')), ':');
  106. }
  107. /**
  108. * Adds a click-sort to the query.
  109. *
  110. * @param $order
  111. * Either 'ASC' or 'DESC'.
  112. */
  113. public static function click_sort($handler, $order) {
  114. // The normal orderby() method for this usually won't work here. So we need
  115. // query plugins to provide their own method for this.
  116. if (method_exists($handler->query, 'add_selector_orderby')) {
  117. $selector = self::construct_property_selector($handler, TRUE);
  118. $handler->query->add_selector_orderby($selector, $order);
  119. }
  120. }
  121. /**
  122. * Load the entities for all rows that are about to be displayed.
  123. *
  124. * Automatically takes care of relationships, including data selection
  125. * relationships. Results are written into @code $handler->wrappers @endcode
  126. * and @code $handler->entity_type @endcode is set.
  127. */
  128. public static function pre_render($handler, &$values, $load_always = FALSE) {
  129. if (empty($values)) {
  130. return;
  131. }
  132. if (!$load_always && empty($handler->options['link_to_entity'])) {
  133. // Check whether we even need to load the entities.
  134. $selector = self::construct_property_selector($handler, TRUE);
  135. $load = FALSE;
  136. foreach ($values as $row) {
  137. if (empty($row->_entity_properties) || !array_key_exists($selector, $row->_entity_properties)) {
  138. $load = TRUE;
  139. break;
  140. }
  141. }
  142. if (!$load) {
  143. return;
  144. }
  145. }
  146. if (method_exists($handler->query, 'get_result_wrappers')) {
  147. list($handler->entity_type, $handler->wrappers) = $handler->query->get_result_wrappers($values, $handler->relationship, $handler->real_field);
  148. }
  149. else {
  150. list($handler->entity_type, $entities) = $handler->query->get_result_entities($values, $handler->relationship, $handler->real_field);
  151. $handler->wrappers = array();
  152. foreach ($entities as $id => $entity) {
  153. $handler->wrappers[$id] = entity_metadata_wrapper($handler->entity_type, $entity);
  154. }
  155. }
  156. }
  157. /**
  158. * Return an Entity API data selector for the given handler's relationship.
  159. *
  160. * A data selector is a concatenation of properties which should be followed
  161. * to arrive at a desired property that may be nested in related entities or
  162. * structures. The separate properties are herein concatenated with colons.
  163. *
  164. * For instance, a data selector of "author:roles" would mean to first
  165. * access the "author" property of the given wrapper, and then for this new
  166. * wrapper to access and return the "roles" property.
  167. *
  168. * Lists of entities are handled automatically by always returning only the
  169. * first entity.
  170. *
  171. * @param $handler
  172. * The handler for which to construct the selector.
  173. * @param $complete
  174. * If TRUE, the complete selector for the field is returned, not just the
  175. * one for its parent. Defaults to FALSE.
  176. *
  177. * @return
  178. * An Entity API data selector for the given handler's relationship.
  179. */
  180. public static function construct_property_selector($handler, $complete = FALSE) {
  181. $return = '';
  182. if ($handler->relationship) {
  183. $current_handler = $handler;
  184. $view = $current_handler->view;
  185. $relationships = array();
  186. // Collect all relationships, keyed by alias.
  187. foreach ($view->relationship as $key => $relationship) {
  188. $key = $relationship->alias ? $relationship->alias : $key;
  189. $relationships[$key] = $relationship;
  190. }
  191. while (!empty($current_handler->relationship) && !empty($relationships[$current_handler->relationship])) {
  192. $current_handler = $relationships[$current_handler->relationship];
  193. $return = $current_handler->real_field . ($return ? ":$return" : '');
  194. }
  195. }
  196. if ($complete) {
  197. $return .= ($return ? ':' : '') . $handler->real_field;
  198. }
  199. elseif ($pos = strrpos($handler->real_field, ':')) {
  200. // If we have a selector as the real_field, append this to the returned
  201. // relationship selector.
  202. $return .= ($return ? ':' : '') . substr($handler->real_field, 0, $pos);
  203. }
  204. return $return;
  205. }
  206. /**
  207. * Extracts data from several metadata wrappers based on a data selector.
  208. *
  209. * All metadata wrappers passed to this function have to be based on the exact
  210. * same property information. The data will be returned wrapped by one or more
  211. * metadata wrappers.
  212. *
  213. * Can be used in query plugins for the get_result_entities() and
  214. * get_result_wrappers() methods.
  215. *
  216. * @param array $wrappers
  217. * The EntityMetadataWrapper objects from which to extract data.
  218. * @param $selector
  219. * The selector specifying the data to extract.
  220. *
  221. * @return array
  222. * An array with numeric indices, containing the type of the extracted
  223. * wrappers in the first element. The second element of the array contains
  224. * the extracted property value(s) for each wrapper, keyed to the same key
  225. * that was used for the respecive wrapper in $wrappers. All extracted
  226. * properties are returned as metadata wrappers.
  227. */
  228. public static function extract_property_multiple(array $wrappers, $selector) {
  229. $parts = explode(':', $selector, 2);
  230. $name = $parts[0];
  231. $results = array();
  232. $entities = array();
  233. $type = '';
  234. foreach ($wrappers as $i => $wrapper) {
  235. try {
  236. $property = $wrapper->$name;
  237. $type = $property->type();
  238. if ($property instanceof EntityDrupalWrapper) {
  239. // Remember the entity IDs to later load all at once (so as to
  240. // properly utilize multiple load functionality).
  241. $id = $property->getIdentifier();
  242. // Only accept valid ids. $id can be FALSE for entity values that are
  243. // NULL.
  244. if ($id) {
  245. $entities[$type][$i] = $id;
  246. }
  247. }
  248. elseif ($property instanceof EntityStructureWrapper) {
  249. $results[$i] = $property;
  250. }
  251. elseif ($property instanceof EntityListWrapper) {
  252. foreach ($property as $item) {
  253. $results[$i] = $item;
  254. $type = $item->type();
  255. break;
  256. }
  257. }
  258. // Do nothing in case it cannot be applied.
  259. }
  260. catch (EntityMetadataWrapperException $e) {
  261. // Skip single empty properties.
  262. }
  263. }
  264. if ($entities) {
  265. // Map back the loaded entities back to the results array.
  266. foreach ($entities as $type => $id_map) {
  267. $loaded = entity_load($type, $id_map);
  268. foreach ($id_map as $i => $id) {
  269. if (isset($loaded[$id])) {
  270. $results[$i] = entity_metadata_wrapper($type, $loaded[$id]);
  271. }
  272. }
  273. }
  274. }
  275. // If there are no further parts in the selector, we are done now.
  276. if (empty($parts[1])) {
  277. return array($type, $results);
  278. }
  279. return self::extract_property_multiple($results, $parts[1]);
  280. }
  281. /**
  282. * Get the value of a certain data selector.
  283. *
  284. * Uses $values->_entity_properties to look for already extracted properties.
  285. *
  286. * @param $handler
  287. * The field handler for which to return a value.
  288. * @param $values
  289. * The values for the current row retrieved from the Views query, as an
  290. * object.
  291. * @param $field
  292. * The field to extract. If no value is given, the field of the given
  293. * handler is used instead. The special "entity object" value can be used to
  294. * get the base entity instead of a special field.
  295. * @param $default
  296. * The value to return if the entity or field are not present.
  297. */
  298. public static function get_value($handler, $values, $field = NULL, $default = NULL) {
  299. // There is a value cache on each handler so parent handlers rendering a
  300. // single field value from a list will get the single value, not the whole
  301. // list.
  302. if (!isset($field) && isset($handler->current_value)) {
  303. return $handler->current_value;
  304. }
  305. $field = isset($field) ? $field : self::get_selector_field_name($handler->real_field);
  306. $selector = self::construct_property_selector($handler);
  307. $selector = $selector ? "$selector:$field" : $field;
  308. if (!isset($values->_entity_properties)) {
  309. $values->_entity_properties = array();
  310. }
  311. if (!array_key_exists($selector, $values->_entity_properties)) {
  312. if (!isset($handler->wrappers[$handler->view->row_index])) {
  313. $values->_entity_properties[$selector] = $default;
  314. }
  315. elseif (is_array($handler->wrappers[$handler->view->row_index])) {
  316. $values->_entity_properties[$selector] = self::extract_list_wrapper_values($handler->wrappers[$handler->view->row_index], $field);
  317. }
  318. else {
  319. $wrapper = $handler->wrappers[$handler->view->row_index];
  320. try {
  321. if ($field === 'entity object') {
  322. $values->_entity_properties[$selector] = $wrapper->value();
  323. }
  324. else {
  325. $values->_entity_properties[$selector] = isset($wrapper->$field) ? $wrapper->$field->value(array('identifier' => TRUE, 'sanitize' => TRUE)) : $default;
  326. }
  327. }
  328. catch (EntityMetadataWrapperException $e) {
  329. $values->_entity_properties[$selector] = $default;
  330. }
  331. }
  332. }
  333. return $values->_entity_properties[$selector];
  334. }
  335. /**
  336. * Helper method for extracting the values from an array of wrappers.
  337. *
  338. * Nested arrays of wrappers are also handled, the values are returned in a
  339. * flat (not nested) array.
  340. */
  341. public static function extract_list_wrapper_values(array $wrappers, $field) {
  342. $return = array();
  343. foreach ($wrappers as $wrapper) {
  344. if (is_array($wrapper)) {
  345. $values = self::extract_list_wrapper_values($wrapper, $field);
  346. if ($values) {
  347. $return = array_merge($return, $values);
  348. }
  349. }
  350. else {
  351. try {
  352. if ($field == 'entity object') {
  353. $return[] = $wrapper->value();
  354. }
  355. elseif (isset($wrapper->$field)) {
  356. $return[] = $wrapper->$field->value(array('identifier' => TRUE));
  357. }
  358. }
  359. catch (EntityMetadataWrapperException $e) {
  360. // An exception probably signifies a non-present property, so we just
  361. // ignore it.
  362. }
  363. }
  364. }
  365. return $return;
  366. }
  367. /**
  368. * Render the field.
  369. *
  370. * Implements the entity link functionality and list handling. Basic handling
  371. * of the single values is delegated back to the field handler.
  372. *
  373. * @param $handler
  374. * The field handler whose field should be rendered.
  375. * @param $values
  376. * The values for the current row retrieved from the Views query, as an
  377. * object.
  378. *
  379. * @return
  380. * The rendered value for the field.
  381. */
  382. public static function render($handler, $values) {
  383. $value = $handler->get_value($values);
  384. if (is_array($value)) {
  385. return self::render_list($handler, $value, $values);
  386. }
  387. return self::render_entity_link($handler, $value, $values);
  388. }
  389. /**
  390. * Render a list of values.
  391. *
  392. * @param $handler
  393. * The field handler whose field is rendered.
  394. * @param $list
  395. * The list of values to render.
  396. * @param $values
  397. * The values for the current row retrieved from the Views query, as an
  398. * object.
  399. *
  400. * @return
  401. * The rendered value for the given list.
  402. */
  403. public static function render_list($handler, $list, $values) {
  404. // Allow easy overriding of this behaviour in the specific field handler.
  405. if (method_exists($handler, 'render_list')) {
  406. return $handler->render_list($list, $values);
  407. }
  408. $mode = isset($handler->options['list']['mode']) ? $handler->options['list']['mode'] : NULL;
  409. switch ($mode) {
  410. case 'first':
  411. $list = count($list) ? array_shift($list) : NULL;
  412. if (is_array($list)) {
  413. return self::render_list($handler, $list, $values);
  414. }
  415. elseif (isset($list)) {
  416. return self::render_entity_link($handler, $list, $values);
  417. }
  418. return NULL;
  419. case 'count':
  420. return count($list);
  421. // Handles both collapse and list output. Fallback is to collapse.
  422. default:
  423. $inner_values = array();
  424. foreach ($list as $value) {
  425. $value = is_array($value) ? self::render_list($handler, $value, $values) : self::render_entity_link($handler, $value, $values);
  426. if ($value) {
  427. $inner_values[] = $value;
  428. }
  429. }
  430. // Format output as list.
  431. if ($mode == 'list') {
  432. $type = isset($handler->options['list']['type']) ? $handler->options['list']['type'] : 'ul';
  433. return theme('item_list', array(
  434. 'items' => $inner_values,
  435. 'type' => $type,
  436. ));
  437. }
  438. $separator = isset($handler->options['list']['separator']) ? $handler->options['list']['separator'] : ', ';
  439. return implode($separator, $inner_values);
  440. }
  441. }
  442. /**
  443. * Render a single value as a link to the entity if applicable.
  444. *
  445. * @param $handler
  446. * The field handler whose field is rendered.
  447. * @param $value
  448. * The single value to render.
  449. * @param $values
  450. * The values for the current row retrieved from the Views query, as an
  451. * object.
  452. *
  453. * @return
  454. * The rendered value.
  455. */
  456. public static function render_entity_link($handler, $value, $values) {
  457. // Allow easy overriding of this behaviour in the specific field handler.
  458. if (method_exists($handler, 'render_entity_link')) {
  459. return $handler->render_entity_link($value, $values);
  460. }
  461. $render = self::render_single_value($handler, $value, $values);
  462. if (!$handler->options['link_to_entity']) {
  463. return $render;
  464. }
  465. $entity = $handler->get_value($values, 'entity object');
  466. if (is_object($entity) && ($url = entity_uri($handler->entity_type, $entity))) {
  467. return l($render, $url['path'], array('html' => TRUE) + $url['options']);
  468. }
  469. return $render;
  470. }
  471. /**
  472. * Render a single value.
  473. *
  474. * @param $handler
  475. * The field handler whose field is rendered.
  476. * @param $value
  477. * The single value to render.
  478. * @param $values
  479. * The values for the current row retrieved from the Views query, as an
  480. * object.
  481. *
  482. * @return
  483. * The rendered value.
  484. */
  485. public static function render_single_value($handler, $value, $values) {
  486. // Try to use the method in the specific field handler.
  487. if (method_exists($handler, 'render_single_value')) {
  488. $handler->current_value = $value;
  489. $return = $handler->render_single_value($value, $values);
  490. unset($handler->current_value);
  491. return $return;
  492. }
  493. // Default fallback in case the field handler doesn't provide the method.
  494. return is_scalar($value) ? check_plain($value) : nl2br(check_plain(print_r($value, TRUE)));
  495. }
  496. }