commiting uncommited changes on live site
[weblabels.fsf.org.git] / crm.fsf.org / 20131203 / files / sites / all / modules-old / entity / views / entity.views.inc
1 <?php
2
3 /**
4 * @file
5 * Provide views data for modules making use of the entity CRUD API.
6 */
7
8 /**
9 * Implements hook_views_data().
10 *
11 * Provides Views integration for entities if they satisfy one of these
12 * conditions:
13 * - hook_entity_info() specifies a 'views controller class' key.
14 * - hook_entity_info() specifies a 'module' key, and the module does not
15 * implement hook_views_data().
16 *
17 * @see entity_crud_hook_entity_info()
18 * @see entity_views_table_definition()
19 */
20 function entity_views_data() {
21 $data = array();
22
23 foreach (entity_crud_get_info() as $type => $info) {
24 // Provide default integration with the basic controller class if we know
25 // the module providing the entity and it does not provide views integration.
26 if (!isset($info['views controller class'])) {
27 $info['views controller class'] = isset($info['module']) && !module_hook($info['module'], 'views_data') ? 'EntityDefaultViewsController' : FALSE;
28 }
29 if ($info['views controller class']) {
30 $controller = new $info['views controller class']($type);
31 // Relationship data may return views data for already existing tables,
32 // so merge results on the second level.
33 foreach ($controller->views_data() as $table => $table_data) {
34 $data += array($table => array());
35 $data[$table] = array_merge($data[$table], $table_data);
36 }
37 }
38 }
39
40 // Add tables based upon data selection "queries" for all entity types.
41 foreach (entity_get_info() as $type => $info) {
42 $table = entity_views_table_definition($type);
43 if ($table) {
44 $data['entity_' . $type] = $table;
45 }
46 // Generally expose properties marked as 'entity views field'.
47 $data['views_entity_' . $type] = array();
48 foreach (entity_get_all_property_info($type) as $key => $property) {
49 if (!empty($property['entity views field'])) {
50 entity_views_field_definition($key, $property, $data['views_entity_' . $type]);
51 }
52 }
53 }
54
55 // Expose generally usable entity-related fields.
56 foreach (entity_get_info() as $entity_type => $info) {
57 if (entity_type_supports($entity_type, 'view')) {
58 // Expose a field allowing to display the rendered entity.
59 $data['views_entity_' . $entity_type]['rendered_entity'] = array(
60 'title' => t('Rendered @entity-type', array('@entity-type' => $info['label'])),
61 'help' => t('The @entity-type of the current relationship rendered using a view mode.', array('@entity-type' => $info['label'])),
62 'field' => array(
63 'handler' => 'entity_views_handler_field_entity',
64 'type' => $entity_type,
65 // The EntityFieldHandlerHelper treats the 'entity object' data
66 // selector as special case for loading the base entity.
67 'real field' => 'entity object',
68 ),
69 );
70 }
71 }
72
73 $data['entity__global']['table']['group'] = t('Entity');
74 $data['entity__global']['table']['join'] = array(
75 // #global let's it appear all the time.
76 '#global' => array(),
77 );
78 $data['entity__global']['entity'] = array(
79 'title' => t('Rendered entity'),
80 'help' => t('Displays a single chosen entity.'),
81 'area' => array(
82 'handler' => 'entity_views_handler_area_entity',
83 ),
84 );
85
86 return $data;
87 }
88
89 /**
90 * Helper function for getting data selection based entity Views table definitions.
91 *
92 * This creates extra tables for each entity type that are not associated with a
93 * query plugin (and thus are not base tables) and just rely on the entities to
94 * retrieve the displayed data. To obtain the entities corresponding to a
95 * certain result set, the field handlers defined on the table use a generic
96 * interface defined for query plugins that are based on entity handling, and
97 * which is described in the entity_views_example_query class.
98 *
99 * These tables are called "data selection tables".
100 *
101 * Other modules providing Views integration with new query plugins that are
102 * based on entities can then use these tables as a base for their own tables
103 * (by directly using this method and modifying the returned table) and/or by
104 * specifying relationships to them. The tables returned here already specify
105 * relationships to each other wherever an entity contains a reference to
106 * another (e.g., the node author constructs a relationship from nodes to
107 * users).
108 *
109 * As filtering and other query manipulation is potentially more plugin-specific
110 * than the display, only field handlers and relationships are provided with
111 * these tables. By providing a add_selector_orderby() method, the query plugin
112 * can, however, support click-sorting for the field handlers in these tables.
113 *
114 * For a detailed discussion see http://drupal.org/node/1266036
115 *
116 * For example use see the Search API views module in the Search API project:
117 * http://drupal.org/project/search_api
118 *
119 * @param $type
120 * The entity type whose table definition should be returned.
121 * @param $exclude
122 * Whether properties already exposed as 'entity views field' should be
123 * excluded. Defaults to TRUE, as they are available for all views tables for
124 * the entity type anyways.
125 *
126 * @return
127 * An array containing the data selection Views table definition for the
128 * entity type.
129 *
130 * @see entity_views_field_definition()
131 */
132 function entity_views_table_definition($type, $exclude = TRUE) {
133 // As other modules might want to copy these tables as a base for their own
134 // Views integration, we statically cache the tables to save some time.
135 $tables = &drupal_static(__FUNCTION__, array());
136
137 if (!isset($tables[$type])) {
138 // Work-a-round to fix updating, see http://drupal.org/node/1330874.
139 // Views data might be rebuilt on update.php before the registry is rebuilt,
140 // thus the class cannot be auto-loaded.
141 if (!class_exists('EntityFieldHandlerHelper')) {
142 module_load_include('inc', 'entity', 'views/handlers/entity_views_field_handler_helper');
143 }
144
145 $info = entity_get_info($type);
146 $tables[$type]['table'] = array(
147 'group' => $info['label'],
148 'entity type' => $type,
149 );
150 foreach (entity_get_all_property_info($type) as $key => $property) {
151 if (!$exclude || empty($property['entity views field'])) {
152 entity_views_field_definition($key, $property, $tables[$type]);
153 }
154 }
155 }
156
157 return $tables[$type];
158 }
159
160 /**
161 * Helper function for adding a Views field definition to data selection based Views tables.
162 *
163 * @param $field
164 * The data selector of the field to add. E.g. "title" would derive the node
165 * title property, "body:summary" the node body's summary.
166 * @param array $property_info
167 * The property information for which to create a field definition.
168 * @param array $table
169 * The table into which the definition should be inserted.
170 * @param $title_prefix
171 * Internal use only.
172 *
173 * @see entity_views_table_definition()
174 */
175 function entity_views_field_definition($field, array $property_info, array &$table, $title_prefix = '') {
176 $additional = array();
177 $additional_field = array();
178
179 // Create a valid Views field identifier (no colons, etc.). Keep the original
180 // data selector as real field though.
181 $key = _entity_views_field_identifier($field, $table);
182 if ($key != $field) {
183 $additional['real field'] = $field;
184 }
185 $field_name = EntityFieldHandlerHelper::get_selector_field_name($field);
186
187 $field_handlers = entity_views_get_field_handlers();
188
189 $property_info += entity_property_info_defaults();
190 $type = entity_property_extract_innermost_type($property_info['type']);
191 $title = $title_prefix . $property_info['label'];
192 if ($info = entity_get_info($type)) {
193 $additional['relationship'] = array(
194 'handler' => $field_handlers['relationship'],
195 'base' => 'entity_' . $type,
196 'base field' => $info['entity keys']['id'],
197 'relationship field' => $field,
198 'label' => $title,
199 );
200 if ($property_info['type'] != $type) {
201 // This is a list of entities, so we should mark the relationship as such.
202 $additional['relationship']['multiple'] = TRUE;
203 }
204 // Implementers of the field handlers alter hook could add handlers for
205 // specific entity types.
206 if (!isset($field_handlers[$type])) {
207 $type = 'entity';
208 }
209 }
210 elseif (!empty($property_info['field'])) {
211 $type = 'field';
212 // Views' Field API field handler needs some extra definitions to work.
213 $additional_field['field_name'] = $field_name;
214 $additional_field['entity_tables'] = array();
215 $additional_field['entity type'] = $table['table']['entity type'];
216 $additional_field['is revision'] = FALSE;
217 }
218 // Copied from EntityMetadataWrapper::optionsList()
219 elseif (isset($property_info['options list']) && is_callable($property_info['options list'])) {
220 // If this is a nested property, we need to get rid of all prefixes first.
221 $type = 'options';
222 $additional_field['options callback'] = array(
223 'function' => $property_info['options list'],
224 'info' => $property_info,
225 );
226 }
227 elseif ($type == 'decimal') {
228 $additional_field['float'] = TRUE;
229 }
230
231 if (isset($field_handlers[$type])) {
232 $table += array($key => array());
233 $table[$key] += array(
234 'title' => $title,
235 'help' => empty($property_info['description']) ? t('(No information available)') : $property_info['description'],
236 'field' => array(),
237 );
238 $table[$key]['field'] += array(
239 'handler' => $field_handlers[$type],
240 'type' => $property_info['type'],
241 );
242 $table[$key] += $additional;
243 $table[$key]['field'] += $additional_field;
244 }
245 if (!empty($property_info['property info'])) {
246 foreach ($property_info['property info'] as $nested_key => $nested_property) {
247 entity_views_field_definition($field . ':' . $nested_key, $nested_property, $table, $title . ' » ');
248 }
249 }
250 }
251
252 /**
253 * @return array
254 * The handlers to use for the data selection based Views tables.
255 *
256 * @see hook_entity_views_field_handlers_alter()
257 */
258 function entity_views_get_field_handlers() {
259 $field_handlers = drupal_static(__FUNCTION__);
260 if (!isset($field_handlers)) {
261 // Field handlers for the entity tables, by type.
262 $field_handlers = array(
263 'text' => 'entity_views_handler_field_text',
264 'token' => 'entity_views_handler_field_text',
265 'integer' => 'entity_views_handler_field_numeric',
266 'decimal' => 'entity_views_handler_field_numeric',
267 'date' => 'entity_views_handler_field_date',
268 'duration' => 'entity_views_handler_field_duration',
269 'boolean' => 'entity_views_handler_field_boolean',
270 'uri' => 'entity_views_handler_field_uri',
271 'options' => 'entity_views_handler_field_options',
272 'field' => 'entity_views_handler_field_field',
273 'entity' => 'entity_views_handler_field_entity',
274 'relationship' => 'entity_views_handler_relationship',
275 );
276 drupal_alter('entity_views_field_handlers', $field_handlers);
277 }
278 return $field_handlers;
279 }
280
281 /**
282 * Helper function for creating valid Views field identifiers out of data selectors.
283 *
284 * Uses $table to test whether the identifier is already used, and also
285 * recognizes if a definition for the same field is already present and returns
286 * that definition's identifier.
287 *
288 * @return string
289 * A valid Views field identifier that is not yet used as a key in $table.
290 */
291 function _entity_views_field_identifier($field, array $table) {
292 $key = $base = preg_replace('/[^a-zA-Z0-9]+/S', '_', $field);
293 $i = 0;
294 // The condition checks whether this sanitized field identifier is already
295 // used for another field in this table (and whether the identifier is
296 // "table", which can never be used).
297 // If $table[$key] is set, the identifier is already used, but this might be
298 // already for the same field. To test that, we need the original field name,
299 // which is either $table[$key]['real field'], if set, or $key. If this
300 // original field name is equal to $field, we can use that key. Otherwise, we
301 // append numeric suffixes until we reach an unused key.
302 while ($key == 'table' || (isset($table[$key]) && (isset($table[$key]['real field']) ? $table[$key]['real field'] : $key) != $field)) {
303 $key = $base . '_' . ++$i;
304 }
305 return $key;
306 }
307
308 /**
309 * Implements hook_views_plugins().
310 */
311 function entity_views_plugins() {
312 // Have views cache the table list for us so it gets
313 // cleared at the appropriate times.
314 $data = views_cache_get('entity_base_tables', TRUE);
315 if (!empty($data->data)) {
316 $base_tables = $data->data;
317 }
318 else {
319 $base_tables = array();
320 foreach (views_fetch_data() as $table => $data) {
321 if (!empty($data['table']['entity type']) && !empty($data['table']['base'])) {
322 $base_tables[] = $table;
323 }
324 }
325 views_cache_set('entity_base_tables', $base_tables, TRUE);
326 }
327 if (!empty($base_tables)) {
328 return array(
329 'module' => 'entity',
330 'row' => array(
331 'entity' => array(
332 'title' => t('Rendered entity'),
333 'help' => t('Renders a single entity in a specific view mode (e.g. teaser).'),
334 'handler' => 'entity_views_plugin_row_entity_view',
335 'uses fields' => FALSE,
336 'uses options' => TRUE,
337 'type' => 'normal',
338 'base' => $base_tables,
339 ),
340 ),
341 );
342 }
343 }
344
345 /**
346 * Default controller for generating basic views integration.
347 *
348 * The controller tries to generate suiting views integration for the entity
349 * based upon the schema information of its base table and the provided entity
350 * property information.
351 * For that it is possible to map a property name to its schema/views field
352 * name by adding a 'schema field' key with the name of the field as value to
353 * the property info.
354 */
355 class EntityDefaultViewsController {
356
357 protected $type, $info, $relationships;
358
359 public function __construct($type) {
360 $this->type = $type;
361 $this->info = entity_get_info($type);
362 }
363
364 /**
365 * Defines the result for hook_views_data().
366 */
367 public function views_data() {
368 $data = array();
369 $this->relationships = array();
370
371 if (!empty($this->info['base table'])) {
372 $table = $this->info['base table'];
373 // Define the base group of this table. Fields that don't
374 // have a group defined will go into this field by default.
375 $data[$table]['table']['group'] = drupal_ucfirst($this->info['label']);
376 $data[$table]['table']['entity type'] = $this->type;
377
378 // If the plural label isn't available, use the regular label.
379 $label = isset($this->info['plural label']) ? $this->info['plural label'] : $this->info['label'];
380 $data[$table]['table']['base'] = array(
381 'field' => $this->info['entity keys']['id'],
382 'access query tag' => $this->type . '_access',
383 'title' => drupal_ucfirst($label),
384 'help' => isset($this->info['description']) ? $this->info['description'] : '',
385 );
386 $data[$table]['table']['entity type'] = $this->type;
387 $data[$table] += $this->schema_fields();
388
389 // Add in any reverse-relationships which have been determined.
390 $data += $this->relationships;
391 }
392 if (!empty($this->info['revision table']) && !empty($this->info['entity keys']['revision'])) {
393 $revision_table = $this->info['revision table'];
394
395 $data[$table]['table']['default_relationship'] = array(
396 $revision_table => array(
397 'table' => $revision_table,
398 'field' => $this->info['entity keys']['revision'],
399 ),
400 );
401
402 // Define the base group of this table. Fields that don't
403 // have a group defined will go into this field by default.
404 $data[$revision_table]['table']['group'] = drupal_ucfirst($this->info['label']) . ' ' . t('Revisions');
405 $data[$revision_table]['table']['entity type'] = $this->type;
406
407 // If the plural label isn't available, use the regular label.
408 $label = isset($this->info['plural label']) ? $this->info['plural label'] : $this->info['label'];
409 $data[$revision_table]['table']['base'] = array(
410 'field' => $this->info['entity keys']['revision'],
411 'access query tag' => $this->type . '_access',
412 'title' => drupal_ucfirst($label) . ' ' . t('Revisions'),
413 'help' => (isset($this->info['description']) ? $this->info['description'] . ' ' : '') . t('Revisions'),
414 );
415 $data[$revision_table]['table']['entity type'] = $this->type;
416 $data[$revision_table] += $this->schema_revision_fields();
417
418 // Add in any reverse-relationships which have been determined.
419 $data += $this->relationships;
420
421 // For other base tables, explain how we join.
422 $data[$revision_table]['table']['join'] = array(
423 // Directly links to base table.
424 $table => array(
425 'left_field' => $this->info['entity keys']['revision'],
426 'field' => $this->info['entity keys']['revision'],
427 ),
428 );
429 $data[$revision_table]['table']['default_relationship'] = array(
430 $table => array(
431 'table' => $table,
432 'field' => $this->info['entity keys']['id'],
433 ),
434 );
435 }
436 return $data;
437 }
438
439 /**
440 * Try to come up with some views fields with the help of the schema and
441 * the entity property information.
442 */
443 protected function schema_fields() {
444 $schema = drupal_get_schema($this->info['base table']);
445 $properties = entity_get_property_info($this->type) + array('properties' => array());
446 $data = array();
447
448 foreach ($properties['properties'] as $name => $property_info) {
449 if (isset($property_info['schema field']) && isset($schema['fields'][$property_info['schema field']])) {
450 if ($views_info = $this->map_from_schema_info($name, $schema['fields'][$property_info['schema field']], $property_info)) {
451 $data[$name] = $views_info;
452 }
453 }
454 }
455 return $data;
456 }
457
458 /**
459 * Try to come up with some views fields with the help of the revision schema
460 * and the entity property information.
461 */
462 protected function schema_revision_fields() {
463 $data = array();
464 if (!empty($this->info['revision table'])) {
465 $schema = drupal_get_schema($this->info['revision table']);
466 $properties = entity_get_property_info($this->type) + array('properties' => array());
467
468 foreach ($properties['properties'] as $name => $property_info) {
469 if (isset($property_info['schema field']) && isset($schema['fields'][$property_info['schema field']])) {
470 if ($views_info = $this->map_from_schema_info($name, $schema['fields'][$property_info['schema field']], $property_info)) {
471 $data[$name] = $views_info;
472 }
473 }
474 }
475 }
476 return $data;
477 }
478
479 /**
480 * Comes up with views information based on the given schema and property
481 * info.
482 */
483 protected function map_from_schema_info($property_name, $schema_field_info, $property_info) {
484 $type = isset($property_info['type']) ? $property_info['type'] : 'text';
485 $views_field_name = $property_info['schema field'];
486
487 $return = array();
488
489 if (!empty($schema_field_info['serialize'])) {
490 return FALSE;
491 }
492
493 $description = array(
494 'title' => $property_info['label'],
495 'help' => isset($property_info['description']) ? $property_info['description'] : NULL,
496 );
497
498 // Add in relationships to related entities.
499 if (($info = entity_get_info($type)) && !empty($info['base table'])) {
500
501 // Prepare reversed relationship data.
502 $label_lowercase = drupal_strtolower($this->info['label'][0]) . drupal_substr($this->info['label'], 1);
503 $property_label_lowercase = drupal_strtolower($property_info['label'][0]) . drupal_substr($property_info['label'], 1);
504
505 // We name the field of the first reverse-relationship just with the
506 // base table to be backward compatible, for subsequents relationships we
507 // append the views field name in order to get a unique name.
508 $name = !isset($this->relationships[$info['base table']][$this->info['base table']]) ? $this->info['base table'] : $this->info['base table'] . '_' . $views_field_name;
509 $this->relationships[$info['base table']][$name] = array(
510 'title' => $this->info['label'],
511 'help' => t("Associated @label via the @label's @property.", array('@label' => $label_lowercase, '@property' => $property_label_lowercase)),
512 'relationship' => array(
513 'label' => $this->info['label'],
514 'handler' => $this->getRelationshipHandlerClass($this->type, $type),
515 'base' => $this->info['base table'],
516 'base field' => $views_field_name,
517 'relationship field' => isset($info['entity keys']['name']) ? $info['entity keys']['name'] : $info['entity keys']['id'],
518 ),
519 );
520
521 $return['relationship'] = array(
522 'label' => drupal_ucfirst($info['label']),
523 'handler' => $this->getRelationshipHandlerClass($type, $this->type),
524 'base' => $info['base table'],
525 'base field' => isset($info['entity keys']['name']) ? $info['entity keys']['name'] : $info['entity keys']['id'],
526 'relationship field' => $views_field_name,
527 );
528
529 // Add in direct field/filters/sorts for the id itself too.
530 $type = isset($info['entity keys']['name']) ? 'token' : 'integer';
531 // Append the views-field-name to the title if it is different to the
532 // property name.
533 if ($property_name != $views_field_name) {
534 $description['title'] .= ' ' . $views_field_name;
535 }
536 }
537
538 switch ($type) {
539 case 'token':
540 case 'text':
541 $return += $description + array(
542 'field' => array(
543 'real field' => $views_field_name,
544 'handler' => 'views_handler_field',
545 'click sortable' => TRUE,
546 ),
547 'sort' => array(
548 'real field' => $views_field_name,
549 'handler' => 'views_handler_sort',
550 ),
551 'filter' => array(
552 'real field' => $views_field_name,
553 'handler' => 'views_handler_filter_string',
554 ),
555 'argument' => array(
556 'real field' => $views_field_name,
557 'handler' => 'views_handler_argument_string',
558 ),
559 );
560 break;
561
562 case 'decimal':
563 case 'integer':
564 $return += $description + array(
565 'field' => array(
566 'real field' => $views_field_name,
567 'handler' => 'views_handler_field_numeric',
568 'click sortable' => TRUE,
569 'float' => ($type == 'decimal'),
570 ),
571 'sort' => array(
572 'real field' => $views_field_name,
573 'handler' => 'views_handler_sort',
574 ),
575 'filter' => array(
576 'real field' => $views_field_name,
577 'handler' => 'views_handler_filter_numeric',
578 ),
579 'argument' => array(
580 'real field' => $views_field_name,
581 'handler' => 'views_handler_argument_numeric',
582 ),
583 );
584 break;
585
586 case 'date':
587 $return += $description + array(
588 'field' => array(
589 'real field' => $views_field_name,
590 'handler' => 'views_handler_field_date',
591 'click sortable' => TRUE,
592 ),
593 'sort' => array(
594 'real field' => $views_field_name,
595 'handler' => 'views_handler_sort_date',
596 ),
597 'filter' => array(
598 'real field' => $views_field_name,
599 'handler' => 'views_handler_filter_date',
600 ),
601 'argument' => array(
602 'real field' => $views_field_name,
603 'handler' => 'views_handler_argument_date',
604 ),
605 );
606 break;
607
608 case 'uri':
609 $return += $description + array(
610 'field' => array(
611 'real field' => $views_field_name,
612 'handler' => 'views_handler_field_url',
613 'click sortable' => TRUE,
614 ),
615 'sort' => array(
616 'real field' => $views_field_name,
617 'handler' => 'views_handler_sort',
618 ),
619 'filter' => array(
620 'real field' => $views_field_name,
621 'handler' => 'views_handler_filter_string',
622 ),
623 'argument' => array(
624 'real field' => $views_field_name,
625 'handler' => 'views_handler_argument_string',
626 ),
627 );
628 break;
629
630 case 'boolean':
631 $return += $description + array(
632 'field' => array(
633 'real field' => $views_field_name,
634 'handler' => 'views_handler_field_boolean',
635 'click sortable' => TRUE,
636 ),
637 'sort' => array(
638 'real field' => $views_field_name,
639 'handler' => 'views_handler_sort',
640 ),
641 'filter' => array(
642 'real field' => $views_field_name,
643 'handler' => 'views_handler_filter_boolean_operator',
644 ),
645 'argument' => array(
646 'real field' => $views_field_name,
647 'handler' => 'views_handler_argument_string',
648 ),
649 );
650 break;
651 }
652
653 // If there is an options list callback, add to the filter and field.
654 if (isset($return['filter']) && !empty($property_info['options list'])) {
655 $return['filter']['handler'] = 'views_handler_filter_in_operator';
656 $return['filter']['options callback'] = array('EntityDefaultViewsController', 'optionsListCallback');
657 $return['filter']['options arguments'] = array($this->type, $property_name, 'view');
658 }
659 // @todo: This class_exists is needed until views 3.2.
660 if (isset($return['field']) && !empty($property_info['options list']) && class_exists('views_handler_field_machine_name')) {
661 $return['field']['handler'] = 'views_handler_field_machine_name';
662 $return['field']['options callback'] = array('EntityDefaultViewsController', 'optionsListCallback');
663 $return['field']['options arguments'] = array($this->type, $property_name, 'view');
664 }
665 return $return;
666 }
667
668 /**
669 * Determines the handler to use for a relationship to an entity type.
670 *
671 * @param $entity_type
672 * The entity type to join to.
673 * @param $left_type
674 * The data type from which to join.
675 */
676 function getRelationshipHandlerClass($entity_type, $left_type) {
677 // Look for an entity type which is used as bundle for the given entity
678 // type. If there is one, allow filtering the relation by bundle by using
679 // our own handler.
680 foreach (entity_get_info() as $type => $info) {
681 // In case we already join from the bundle entity we do not need to filter
682 // by bundle entity any more, so we stay with the general handler.
683 if (!empty($info['bundle of']) && $info['bundle of'] == $entity_type && $type != $left_type) {
684 return 'entity_views_handler_relationship_by_bundle';
685 }
686 }
687 return 'views_handler_relationship';
688 }
689
690 /**
691 * A callback returning property options, suitable to be used as views options callback.
692 */
693 public static function optionsListCallback($type, $selector, $op = 'view') {
694 $wrapper = entity_metadata_wrapper($type, NULL);
695 $parts = explode(':', $selector);
696 foreach ($parts as $part) {
697 $wrapper = $wrapper->get($part);
698 }
699 return $wrapper->optionsList($op);
700 }
701 }