Compare commits

..
1 Commits
Author SHA1 Message Date
bachy a3196f9486 first import 1.0-rc5
Signed-off-by: bachy <git@g-u-i.net>
2012-10-27 12:36:32 +02:00
41 changed files with 3573 additions and 1737 deletions
+16
View File
@@ -0,0 +1,16 @@
DESCRIPTION
===========
Provides a field type that can reference arbitrary entities.
SITE BUILDERS
=============
Note that when using a select widget, Entity reference loads all the
entities in that list in order to get the entity's label. If there are
too many loaded entities that site might reach its memory limit and crash
(also known as WSOD). In such a case you are advised to change the widget
to "autocomplete". If you get a WSOD when trying to edit the field
settings, you can reach the widget settings directly by navigation to
admin/structure/types/manage/[ENTITY-TYPE]/fields/[FIELD-NAME]/widget-type
Replace ENTITY-TYPE and FIELD_NAME with the correct values.
+4
View File
@@ -0,0 +1,4 @@
.entityreference-settings {
margin-left: 1.5em;
}
+26
View File
@@ -0,0 +1,26 @@
<?php
/**
* @file
* Support for processing entity reference fields in devel generate.
*/
function entityreference_devel_generate($object, $field, $instance, $bundle) {
if (field_behaviors_widget('multiple values', $instance) == FIELD_BEHAVIOR_CUSTOM) {
return devel_generate_multiple('_entityreference_devel_generate', $object, $field, $instance, $bundle);
}
else {
return _entityreference_devel_generate($object, $field, $instance, $bundle);
}
}
function _entityreference_devel_generate($object, $field, $instance, $bundle) {
$object_field = array();
// Get all the entity that are referencable here.
$referencable_entity = entityreference_get_selection_handler($field, $instance)->getReferencableEntities();
if (is_array($referencable_entity) && !empty($referencable_entity)) {
// Get a random key.
$object_field['target_id'] = array_rand($referencable_entity);
}
return $object_field;
}
+121
View File
@@ -0,0 +1,121 @@
<?php
/**
* @file
* Feeds mapping implementation for the Entity reference module
*/
/**
* Implements hook_feeds_processor_targets_alter().
*
* @see FeedsNodeProcessor::getMappingTargets().
*/
function entityreference_feeds_processor_targets_alter(&$targets, $entity_type, $bundle_name) {
foreach (field_info_instances($entity_type, $bundle_name) as $name => $instance) {
$info = field_info_field($name);
if ($info['type'] == 'entityreference') {
$targets[$name] = array(
'name' => check_plain($instance['label']),
'callback' => 'entityreference_feeds_set_target',
'description' => t('The field instance @label of @id', array(
'@label' => $instance['label'],
'@id' => $name,
)),
);
}
}
}
/**
* Entity reference callback for mapping.
*
* When the callback is invoked, $target contains the name of the field the
* user has decided to map to and $value contains the value of the feed item
* element the user has picked as a source.
*
* @param $source
* A FeedsSource object.
* @param $entity
* The entity to map to.
* @param $target
* The target key on $entity to map to.
* @param $value
* The value to map. MUST be an array.
* @param $mapping
* Array of mapping settings for current value.
* @param $input_format
* TRUE if an input format should be applied.
*/
function entityreference_feeds_set_target($source, $entity, $target, $value, $mapping, $input_format = FALSE) {
// Don't do anything if we weren't given any data.
if (empty($value)) {
return;
}
// Assume that the passed in value could really be any number of values.
if (is_array($value)) {
$values = $value;
}
else {
$values = array($value);
}
// Get some useful field information.
$info = field_info_field($target);
// Set the language of the field depending on the mapping.
$language = isset($mapping['language']) ? $mapping['language'] : LANGUAGE_NONE;
// Iterate over all values.
$iterator = 0;
$field = isset($entity->$target) ? $entity->$target : array();
foreach ($values as $value) {
// Only process if this value was set for this instance.
if ($value) {
// Fetch the entity ID resulting from the mapping table look-up.
$entity_id = db_query(
'SELECT entity_id FROM {feeds_item} WHERE guid = :guid',
array(':guid' => $value)
)->fetchField();
/*
* Only add a reference to an existing entity ID if there exists a
* mapping between it and the provided GUID. In cases where no such
* mapping exists (yet), don't do anything here. There may be a mapping
* defined later in the CSV file. If so, and the user re-runs the import
* (as a second pass), we can add this reference then. (The "Update
* existing nodes" option must be selected during the second pass.)
*/
if ($entity_id) {
// Assign the target ID.
$field[$language][$iterator]['target_id'] = $entity_id;
}
else /* there is no $entity_id, no mapping */ {
/*
* Feeds stores a hash of every line imported from CSVs in order to
* make the import process more efficient by ignoring lines it's
* already seen. We need to short-circuit this process in this case
* because users may want to re-import the same line as an update later
* when (and if) a map to a reference exists. So in order to provide
* this opportunity later, we need to destroy the hash.
*/
unset($entity->feeds_item->hash);
}
}
// Break out of the loop if this field is single-valued.
if ($info['cardinality'] == 1) {
break;
}
$iterator++;
}
// Add the field to the entity definition.
$entity->{$target} = $field;
}
+29
View File
@@ -0,0 +1,29 @@
name = Entity Reference
description = Provides a field that can reference other entities.
core = 7.x
package = Fields
dependencies[] = entity
dependencies[] = ctools
; Migrate handler.
files[] = entityreference.migrate.inc
; Our plugins interfaces and abstract implementations.
files[] = plugins/selection/abstract.inc
files[] = plugins/selection/views.inc
files[] = plugins/behavior/abstract.inc
files[] = views/entityreference_plugin_display.inc
files[] = views/entityreference_plugin_style.inc
files[] = views/entityreference_plugin_row_fields.inc
; Tests.
files[] = tests/entityreference.handlers.test
files[] = tests/entityreference.admin.test
; Information added by drupal.org packaging script on 2012-09-25
version = "7.x-1.0-rc5"
core = "7.x"
project = "entityreference"
datestamp = "1348565045"
+164
View File
@@ -0,0 +1,164 @@
<?php
/**
* Implements hook_uninstall().
*/
function entityreference_uninstall() {
variable_del('entityreference:base-tables');
}
/**
* Implements hook_field_schema().
*/
function entityreference_field_schema($field) {
if ($field['type'] == 'entityreference') {
// Load the base table configuration from the cache.
$base_tables = variable_get('entityreference:base-tables', array());
$schema = array(
'columns' => array(
'target_id' => array(
'description' => 'The id of the target entity.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
),
),
'indexes' => array(
'target_id' => array('target_id'),
),
'foreign keys' => array(),
);
// Create a foreign key to the target entity type base type, if available.
$entity_type = $field['settings']['target_type'];
if (isset($base_tables[$entity_type])) {
list($base_table, $id_column) = $base_tables[$entity_type];
$schema['foreign keys'][$base_table] = array(
'table' => $base_table,
'columns' => array('target_id' => $id_column),
);
}
// Invoke the behaviors to allow them to change the schema.
foreach (entityreference_get_behavior_handlers($field) as $handler) {
$handler->schema_alter($schema, $field);
}
return $schema;
}
}
/**
* Update the field configuration to the new plugin structure.
*/
function entityreference_update_7000() {
// Enable ctools.
if (!module_enable(array('ctools'))) {
throw new DrupalUpdateException('This version of Entity Reference requires ctools, but it could not be enabled.');
}
// Get the list of fields of type 'entityreference'.
$fields = array();
foreach (field_info_fields() as $field_name => $field) {
// Update the field configuration.
if ($field['type'] == 'entityreference') {
$settings = &$field['settings'];
if (!isset($settings['handler'])) {
$settings['handler'] = 'base';
$settings['handler_settings']['target_bundles'] = $settings['target_bundles'];
unset($settings['target_bundles']);
field_update_field($field);
}
}
// Update the instance configurations.
foreach ($field['bundles'] as $entity_type => $bundles) {
foreach ($bundles as $bundle) {
$instance = field_info_instance($entity_type, $field_name, $bundle);
$save = FALSE;
if ($instance['widget']['type'] == 'entityreference_autocomplete') {
$instance['widget']['type'] = 'entityreference_autocomplete_tags';
$save = TRUE;
}
// When the autocomplete path is the default value, remove it from
// the configuration.
if (isset($instance['widget']['settings']['path']) && $instance['widget']['settings']['path'] == 'entityreference/autocomplete') {
unset($instance['widget']['settings']['path']);
$save = TRUE;
}
if ($save) {
field_update_instance($instance);
}
}
}
}
}
/**
* Drop "target_type" from the field schema.
*/
function entityreference_update_7001() {
if (!module_exists('field_sql_storage')) {
return;
}
foreach (field_info_fields() as $field_name => $field) {
if ($field['type'] != 'entityreference') {
// Not an entity reference field.
continue;
}
// Update the field settings.
$field = field_info_field($field_name);
unset($field['indexes']['target_entity']);
$field['indexes']['target_id'] = array('target_id');
field_update_field($field);
if ($field['storage']['type'] !== 'field_sql_storage') {
// Field doesn't use SQL storage, we cannot modify the schema.
continue;
}
$table_name = _field_sql_storage_tablename($field);
$revision_name = _field_sql_storage_revision_tablename($field);
db_drop_index($table_name, $field_name . '_target_entity');
db_drop_index($table_name, $field_name . '_target_id');
db_drop_field($table_name, $field_name . '_target_type');
db_add_index($table_name, $field_name . '_target_id', array($field_name . '_target_id'));
db_drop_index($revision_name, $field_name . '_target_entity');
db_drop_index($revision_name, $field_name . '_target_id');
db_drop_field($revision_name, $field_name . '_target_type');
db_add_index($revision_name, $field_name . '_target_id', array($field_name . '_target_id'));
}
}
/**
* Make the target_id column NOT NULL.
*/
function entityreference_update_7002() {
if (!module_exists('field_sql_storage')) {
return;
}
foreach (field_info_fields() as $field_name => $field) {
if ($field['type'] != 'entityreference') {
// Not an entity reference field.
continue;
}
if ($field['storage']['type'] !== 'field_sql_storage') {
// Field doesn't use SQL storage, we cannot modify the schema.
continue;
}
$table_name = _field_sql_storage_tablename($field);
$revision_name = _field_sql_storage_revision_tablename($field);
db_change_field($table_name, $field_name . '_target_id', $field_name . '_target_id', array(
'description' => 'The id of the target entity.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
));
}
}
+24
View File
@@ -0,0 +1,24 @@
<?php
/**
* @file
* Support for processing entity reference fields in Migrate.
*/
/**
* Implement hook_migrate_api().
*/
function entityreference_migrate_api() {
return array('api' => 2);
}
class MigrateEntityReferenceFieldHandler extends MigrateSimpleFieldHandler {
public function __construct() {
parent::__construct(array(
'value_key' => 'target_id',
'skip_empty' => TRUE,
));
$this->registerTypes(array('entityreference'));
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,12 @@
name = Entity Reference Behavior Example
description = Provides some example code for implementing Entity Reference behaviors.
core = 7.x
package = Fields
dependencies[] = entityreference
; Information added by drupal.org packaging script on 2012-09-25
version = "7.x-1.0-rc5"
core = "7.x"
project = "entityreference"
datestamp = "1348565045"
@@ -0,0 +1,15 @@
<?php
/**
* @file
* Example module to demonstrate Entity reference behavior handlers.
*/
/**
* Implements hook_ctools_plugin_directory().
*/
function entityreference_behavior_example_ctools_plugin_directory($module, $plugin) {
if ($module == 'entityreference') {
return 'plugins/' . $plugin;
}
}
@@ -0,0 +1,31 @@
<?php
class EntityReferenceFieldBehaviorExample extends EntityReference_BehaviorHandler_Abstract {
public function load($entity_type, $entities, $field, $instances, $langcode, &$items) {
drupal_set_message(t('Do something on load!'));
}
public function insert($entity_type, $entity, $field, $instance, $langcode, &$items) {
drupal_set_message(t('Do something on insert!'));
}
public function update($entity_type, $entity, $field, $instance, $langcode, &$items) {
drupal_set_message(t('Do something on update!'));
}
public function delete($entity_type, $entity, $field, $instance, $langcode, &$items) {
drupal_set_message(t('Do something on delete!'));
}
/**
* Generate a settings form for this handler.
*/
public function settingsForm($field, $instance) {
$form['test_field'] = array(
'#type' => 'checkbox',
'#title' => t('Field behavior setting'),
);
return $form;
}
}
@@ -0,0 +1,31 @@
<?php
class EntityReferenceInstanceBehaviorExample extends EntityReference_BehaviorHandler_Abstract {
public function load($entity_type, $entities, $field, $instances, $langcode, &$items) {
drupal_set_message(t('Do something on load, on the instance level!'));
}
public function insert($entity_type, $entity, $field, $instance, $langcode, &$items) {
drupal_set_message(t('Do something on insert, on the instance level!'));
}
public function update($entity_type, $entity, $field, $instance, $langcode, &$items) {
drupal_set_message(t('Do something on update, on the instance level!'));
}
public function delete($entity_type, $entity, $field, $instance, $langcode, &$items) {
drupal_set_message(t('Do something on delete, on the instance level!'));
}
/**
* Generate a settings form for this handler.
*/
public function settingsForm($field, $instance) {
$form['test_instance'] = array(
'#type' => 'checkbox',
'#title' => t('Instance behavior setting'),
);
return $form;
}
}
@@ -0,0 +1,8 @@
<?php
$plugin = array(
'title' => t('Test behavior'),
'class' => 'EntityReferenceFieldBehaviorExample',
'weight' => 10,
'behavior type' => 'field',
);
@@ -0,0 +1,8 @@
<?php
$plugin = array(
'title' => t('Test instance behavior'),
'class' => 'EntityReferenceInstanceBehaviorExample',
'weight' => 10,
'behavior type' => 'instance',
);
-5
View File
@@ -1,5 +0,0 @@
The icons contained in this directory are from the FatCow "Farm-Fresh Web Icons"
collection, available under the Creative Commons Attribution 3.0 license. They
can be downloaded here:
http://www.fatcow.com/free-icons
Binary file not shown.

Before

Width:  |  Height:  |  Size: 765 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 766 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 469 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 518 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 224 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 213 B

@@ -0,0 +1,18 @@
<?php
class EntityReferenceBehavior_ViewsFilterSelect extends EntityReference_BehaviorHandler_Abstract {
public function views_data_alter(&$data, $field) {
$entity_info = entity_get_info($field['settings']['target_type']);
$field_name = $field['field_name'] . '_target_id';
foreach ($data as $table_name => &$table_data) {
if (isset($table_data[$field_name])) {
// Set the entity id filter to use the in_operator handler with our
// own callback to return the values.
$table_data[$field_name]['filter']['handler'] = 'views_handler_filter_in_operator';
$table_data[$field_name]['filter']['options callback'] = 'entityreference_views_handler_options_list';
$table_data[$field_name]['filter']['options arguments'] = array($field['field_name']);
}
}
}
}
+187
View File
@@ -0,0 +1,187 @@
<?php
/**
* Additional behaviors for a Entity Reference field.
*
* Implementations that wish to provide an implementation of this should
* register it using CTools' plugin system.
*/
interface EntityReference_BehaviorHandler {
/**
* Constructor for the behavior.
*
* @param $behavior
* The name of the behavior plugin.
*/
public function __construct($behavior);
/**
* Alter the field schema.
*
* @see hook_field_schema()
*/
public function schema_alter(&$schema, $field);
/**
* Alter the properties information of a field instance.
*
* @see entity_hook_field_info()
*/
public function property_info_alter(&$info, $entity_type, $field, $instance, $field_type);
/**
* Alter the views data of a field.
*
* @see entityreference_field_views_data()
*/
public function views_data_alter(&$data, $field);
/**
* Act on loading entity reference fields of entities.
*
* @see hook_field_load()
*/
public function load($entity_type, $entities, $field, $instances, $langcode, &$items);
/**
* Alter the empty status of a field item.
*
* @see hook_field_is_empty()
*/
public function is_empty_alter(&$empty, $item, $field);
/**
* Act on validating an entity reference field.
*
* @see hook_field_validate()
*/
public function validate($entity_type, $entity, $field, $instance, $langcode, $items, &$errors);
/**
* Act on presaving an entity reference field.
*
* @see hook_field_presave()
*/
public function presave($entity_type, $entity, $field, $instance, $langcode, &$items);
/**
* Act before inserting an entity reference field.
*
* @see hook_field_insert()
*/
public function insert($entity_type, $entity, $field, $instance, $langcode, &$items);
/**
* Act after inserting an entity reference field.
*
* @see hook_field_attach_insert()
*/
public function postInsert($entity_type, $entity, $field, $instance);
/**
* Act before updating an entity reference field.
*
* @see hook_field_update()
*/
public function update($entity_type, $entity, $field, $instance, $langcode, &$items);
/**
* Act after updating an entity reference field.
*
* @see hook_field_attach_update()
*/
public function postUpdate($entity_type, $entity, $field, $instance);
/**
* Act before deleting an entity with an entity reference field.
*
* @see hook_field_delete()
*/
public function delete($entity_type, $entity, $field, $instance, $langcode, &$items);
/**
* Act after deleting an entity with an entity reference field.
*
* @see hook_field_attach_delete()
*/
public function postDelete($entity_type, $entity, $field, $instance);
/**
* Generate a settings form for this handler.
*/
public function settingsForm($field, $instance);
/**
* Determine if handler should appear.
*/
public function access($field, $instance);
}
/**
* An abstract implementation of EntityReference_BehaviorHandler.
*/
abstract class EntityReference_BehaviorHandler_Abstract implements EntityReference_BehaviorHandler {
/**
* The name of the behavior plugin.
*/
protected $behavior;
/**
* The plugin definition.
*/
protected $plugin;
public function __construct($behavior) {
$this->behavior = $behavior;
ctools_include('plugins');
$plugin = ctools_get_plugins('entityreference', 'behavior', $behavior);
$this->plugin = $plugin;
}
public function schema_alter(&$schema, $field) {}
public function property_info_alter(&$info, $entity_type, $field, $instance, $field_type) {}
public function views_data_alter(&$data, $field) {}
public function load($entity_type, $entities, $field, $instances, $langcode, &$items) {}
public function is_empty_alter(&$empty, $item, $field) {}
public function validate($entity_type, $entity, $field, $instance, $langcode, $items, &$errors) {}
public function presave($entity_type, $entity, $field, $instance, $langcode, &$items) {}
public function insert($entity_type, $entity, $field, $instance, $langcode, &$items) {}
public function postInsert($entity_type, $entity, $field, $instance) {}
public function update($entity_type, $entity, $field, $instance, $langcode, &$items) {}
public function postUpdate($entity_type, $entity, $field, $instance) {}
public function delete($entity_type, $entity, $field, $instance, $langcode, &$items) {}
public function postDelete($entity_type, $entity, $field, $instance) {}
public function settingsForm($field, $instance) {}
public function access($field, $instance) {
return TRUE;
}
}
/**
* A broken implementation of EntityReference_BehaviorHandler.
*/
class EntityReference_BehaviorHandler_Broken extends EntityReference_BehaviorHandler_Abstract {
public function settingsForm($field, $instance) {
$form['behavior_handler'] = array(
'#markup' => t('The selected behavior handler is broken.'),
);
return $form;
}
}
+10
View File
@@ -0,0 +1,10 @@
<?php
if (module_exists('views')) {
$plugin = array(
'title' => t('Render Views filters as select list'),
'description' => t('Provides a select list for Views filters on this field. This should not be used when there are over 100 entities, as it might cause an out of memory error.'),
'class' => 'EntityReferenceBehavior_ViewsFilterSelect',
'behavior type' => 'field',
);
}
@@ -0,0 +1,489 @@
<?php
/**
* A generic Entity handler.
*
* The generic base implementation has a variety of overrides to workaround
* core's largely deficient entity handling.
*/
class EntityReference_SelectionHandler_Generic implements EntityReference_SelectionHandler {
/**
* Implements EntityReferenceHandler::getInstance().
*/
public static function getInstance($field, $instance = NULL, $entity_type = NULL, $entity = NULL) {
$target_entity_type = $field['settings']['target_type'];
// Check if the entity type does exist and has a base table.
$entity_info = entity_get_info($target_entity_type);
if (empty($entity_info['base table'])) {
return EntityReference_SelectionHandler_Broken::getInstance($field, $instance);
}
if (class_exists($class_name = 'EntityReference_SelectionHandler_Generic_' . $target_entity_type)) {
return new $class_name($field, $instance, $entity_type, $entity);
}
else {
return new EntityReference_SelectionHandler_Generic($field, $instance, $entity_type, $entity);
}
}
protected function __construct($field, $instance = NULL, $entity_type = NULL, $entity = NULL) {
$this->field = $field;
$this->instance = $instance;
$this->entity_type = $entity_type;
$this->entity = $entity;
}
/**
* Implements EntityReferenceHandler::settingsForm().
*/
public static function settingsForm($field, $instance) {
$entity_info = entity_get_info($field['settings']['target_type']);
// Merge-in default values.
$field['settings']['handler_settings'] += array(
'target_bundles' => array(),
'sort' => array(
'type' => 'none',
)
);
if (!empty($entity_info['entity keys']['bundle'])) {
$bundles = array();
foreach ($entity_info['bundles'] as $bundle_name => $bundle_info) {
$bundles[$bundle_name] = $bundle_info['label'];
}
$form['target_bundles'] = array(
'#type' => 'checkboxes',
'#title' => t('Target bundles'),
'#options' => $bundles,
'#default_value' => $field['settings']['handler_settings']['target_bundles'],
'#size' => 6,
'#multiple' => TRUE,
'#description' => t('The bundles of the entity type that can be referenced. Optional, leave empty for all bundles.'),
'#element_validate' => array('_entityreference_element_validate_filter'),
);
}
else {
$form['target_bundles'] = array(
'#type' => 'value',
'#value' => array(),
);
}
$form['sort']['type'] = array(
'#type' => 'select',
'#title' => t('Sort by'),
'#options' => array(
'none' => t("Don't sort"),
'property' => t('A property of the base table of the entity'),
'field' => t('A field attached to this entity'),
),
'#ajax' => TRUE,
'#limit_validation_errors' => array(),
'#default_value' => $field['settings']['handler_settings']['sort']['type'],
);
$form['sort']['settings'] = array(
'#type' => 'container',
'#attributes' => array('class' => array('entityreference-settings')),
'#process' => array('_entityreference_form_process_merge_parent'),
);
if ($field['settings']['handler_settings']['sort']['type'] == 'property') {
// Merge-in default values.
$field['settings']['handler_settings']['sort'] += array(
'property' => NULL,
);
$form['sort']['settings']['property'] = array(
'#type' => 'select',
'#title' => t('Sort property'),
'#required' => TRUE,
'#options' => drupal_map_assoc($entity_info['schema_fields_sql']['base table']),
'#default_value' => $field['settings']['handler_settings']['sort']['property'],
);
}
elseif ($field['settings']['handler_settings']['sort']['type'] == 'field') {
// Merge-in default values.
$field['settings']['handler_settings']['sort'] += array(
'field' => NULL,
);
$fields = array();
foreach (field_info_instances($field['settings']['target_type']) as $bundle_name => $bundle_instances) {
foreach ($bundle_instances as $instance_name => $instance_info) {
$field_info = field_info_field($instance_name);
foreach ($field_info['columns'] as $column_name => $column_info) {
$fields[$instance_name . ':' . $column_name] = t('@label (column @column)', array('@label' => $instance_info['label'], '@column' => $column_name));
}
}
}
$form['sort']['settings']['field'] = array(
'#type' => 'select',
'#title' => t('Sort field'),
'#required' => TRUE,
'#options' => $fields,
'#default_value' => $field['settings']['handler_settings']['sort']['field'],
);
}
if ($field['settings']['handler_settings']['sort']['type'] != 'none') {
// Merge-in default values.
$field['settings']['handler_settings']['sort'] += array(
'direction' => 'ASC',
);
$form['sort']['settings']['direction'] = array(
'#type' => 'select',
'#title' => t('Sort direction'),
'#required' => TRUE,
'#options' => array(
'ASC' => t('Ascending'),
'DESC' => t('Descending'),
),
'#default_value' => $field['settings']['handler_settings']['sort']['direction'],
);
}
return $form;
}
/**
* Implements EntityReferenceHandler::getReferencableEntities().
*/
public function getReferencableEntities($match = NULL, $match_operator = 'CONTAINS', $limit = 0) {
$options = array();
$entity_type = $this->field['settings']['target_type'];
$query = $this->buildEntityFieldQuery($match, $match_operator);
if ($limit > 0) {
$query->range(0, $limit);
}
$results = $query->execute();
if (!empty($results[$entity_type])) {
$entities = entity_load($entity_type, array_keys($results[$entity_type]));
foreach ($entities as $entity_id => $entity) {
$options[$entity_id] = check_plain($this->getLabel($entity));
}
}
return $options;
}
/**
* Implements EntityReferenceHandler::countReferencableEntities().
*/
public function countReferencableEntities($match = NULL, $match_operator = 'CONTAINS') {
$query = $this->buildEntityFieldQuery($match, $match_operator);
return $query
->count()
->execute();
}
/**
* Implements EntityReferenceHandler::validateReferencableEntities().
*/
public function validateReferencableEntities(array $ids) {
if ($ids) {
$entity_type = $this->field['settings']['target_type'];
$query = $this->buildEntityFieldQuery();
$query->entityCondition('entity_id', $ids, 'IN');
$result = $query->execute();
if (!empty($result[$entity_type])) {
return array_keys($result[$entity_type]);
}
}
return array();
}
/**
* Implements EntityReferenceHandler::validateAutocompleteInput().
*/
public function validateAutocompleteInput($input, &$element, &$form_state, $form) {
$entities = $this->getReferencableEntities($input, '=', 6);
if (empty($entities)) {
// Error if there are no entities available for a required field.
form_error($element, t('There are no entities matching "%value"', array('%value' => $input)));
}
elseif (count($entities) > 5) {
// Error if there are more than 5 matching entities.
form_error($element, t('Many entities are called %value. Specify the one you want by appending the id in parentheses, like "@value (@id)"', array(
'%value' => $input,
'@value' => $input,
'@id' => key($entities),
)));
}
elseif (count($entities) > 1) {
// More helpful error if there are only a few matching entities.
$multiples = array();
foreach ($entities as $id => $name) {
$multiples[] = $name . ' (' . $id . ')';
}
form_error($element, t('Multiple entities match this reference; "%multiple"', array('%multiple' => implode('", "', $multiples))));
}
else {
// Take the one and only matching entity.
return key($entities);
}
}
/**
* Build an EntityFieldQuery to get referencable entities.
*/
protected function buildEntityFieldQuery($match = NULL, $match_operator = 'CONTAINS') {
$query = new EntityFieldQuery();
$query->entityCondition('entity_type', $this->field['settings']['target_type']);
if (!empty($this->field['settings']['handler_settings']['target_bundles'])) {
$query->entityCondition('bundle', $this->field['settings']['handler_settings']['target_bundles'], 'IN');
}
if (isset($match)) {
$entity_info = entity_get_info($this->field['settings']['target_type']);
if (isset($entity_info['entity keys']['label'])) {
$query->propertyCondition($entity_info['entity keys']['label'], $match, $match_operator);
}
}
// Add a generic entity access tag to the query.
$query->addTag($this->field['settings']['target_type'] . '_access');
$query->addTag('entityreference');
$query->addMetaData('field', $this->field);
$query->addMetaData('entityreference_selection_handler', $this);
// Add the sort option.
if (!empty($this->field['settings']['handler_settings']['sort'])) {
$sort_settings = $this->field['settings']['handler_settings']['sort'];
if ($sort_settings['type'] == 'property') {
$query->propertyOrderBy($sort_settings['property'], $sort_settings['direction']);
}
elseif ($sort_settings['type'] == 'field') {
list($field, $column) = explode(':', $sort_settings['field'], 2);
$query->fieldOrderBy($field, $column, $sort_settings['direction']);
}
}
return $query;
}
/**
* Implements EntityReferenceHandler::entityFieldQueryAlter().
*/
public function entityFieldQueryAlter(SelectQueryInterface $query) {
}
/**
* Helper method: pass a query to the alteration system again.
*
* This allow Entity Reference to add a tag to an existing query, to ask
* access control mechanisms to alter it again.
*/
protected function reAlterQuery(SelectQueryInterface $query, $tag, $base_table) {
// Save the old tags and metadata.
// For some reason, those are public.
$old_tags = $query->alterTags;
$old_metadata = $query->alterMetaData;
$query->alterTags = array($tag => TRUE);
$query->alterMetaData['base_table'] = $base_table;
drupal_alter(array('query', 'query_' . $tag), $query);
// Restore the tags and metadata.
$query->alterTags = $old_tags;
$query->alterMetaData = $old_metadata;
}
/**
* Implements EntityReferenceHandler::getLabel().
*/
public function getLabel($entity) {
return entity_label($this->field['settings']['target_type'], $entity);
}
}
/**
* Override for the Node type.
*
* This only exists to workaround core bugs.
*/
class EntityReference_SelectionHandler_Generic_node extends EntityReference_SelectionHandler_Generic {
public function entityFieldQueryAlter(SelectQueryInterface $query) {
// Adding the 'node_access' tag is sadly insufficient for nodes: core
// requires us to also know about the concept of 'published' and
// 'unpublished'. We need to do that as long as there are no access control
// modules in use on the site. As long as one access control module is there,
// it is supposed to handle this check.
if (!user_access('bypass node access') && !count(module_implements('node_grants'))) {
$tables = $query->getTables();
$query->condition(key($tables) . '.status', NODE_PUBLISHED);
}
}
}
/**
* Override for the User type.
*
* This only exists to workaround core bugs.
*/
class EntityReference_SelectionHandler_Generic_user extends EntityReference_SelectionHandler_Generic {
public function buildEntityFieldQuery($match = NULL, $match_operator = 'CONTAINS') {
$query = parent::buildEntityFieldQuery($match, $match_operator);
// The user entity doesn't have a label column.
if (isset($match)) {
$query->propertyCondition('name', $match, $match_operator);
}
// Adding the 'user_access' tag is sadly insufficient for users: core
// requires us to also know about the concept of 'blocked' and
// 'active'.
if (!user_access('administer users')) {
$query->propertyCondition('status', 1);
}
return $query;
}
public function entityFieldQueryAlter(SelectQueryInterface $query) {
if (user_access('administer users')) {
// In addition, if the user is administrator, we need to make sure to
// match the anonymous user, that doesn't actually have a name in the
// database.
$conditions = &$query->conditions();
foreach ($conditions as $key => $condition) {
if ($key !== '#conjunction' && is_string($condition['field']) && $condition['field'] === 'users.name') {
// Remove the condition.
unset($conditions[$key]);
// Re-add the condition and a condition on uid = 0 so that we end up
// with a query in the form:
// WHERE (name LIKE :name) OR (:anonymous_name LIKE :name AND uid = 0)
$or = db_or();
$or->condition($condition['field'], $condition['value'], $condition['operator']);
// Sadly, the Database layer doesn't allow us to build a condition
// in the form ':placeholder = :placeholder2', because the 'field'
// part of a condition is always escaped.
// As a (cheap) workaround, we separately build a condition with no
// field, and concatenate the field and the condition separately.
$value_part = db_and();
$value_part->condition('anonymous_name', $condition['value'], $condition['operator']);
$value_part->compile(Database::getConnection(), $query);
$or->condition(db_and()
->where(str_replace('anonymous_name', ':anonymous_name', (string) $value_part), $value_part->arguments() + array(':anonymous_name' => format_username(user_load(0))))
->condition('users.uid', 0)
);
$query->condition($or);
}
}
}
}
}
/**
* Override for the Comment type.
*
* This only exists to workaround core bugs.
*/
class EntityReference_SelectionHandler_Generic_comment extends EntityReference_SelectionHandler_Generic {
public function entityFieldQueryAlter(SelectQueryInterface $query) {
// Adding the 'comment_access' tag is sadly insufficient for comments: core
// requires us to also know about the concept of 'published' and
// 'unpublished'.
if (!user_access('administer comments')) {
$tables = $query->getTables();
$query->condition(key($tables) . '.status', COMMENT_PUBLISHED);
}
// The Comment module doesn't implement any proper comment access,
// and as a consequence doesn't make sure that comments cannot be viewed
// when the user doesn't have access to the node.
$tables = $query->getTables();
$base_table = key($tables);
$node_alias = $query->innerJoin('node', 'n', '%alias.nid = ' . $base_table . '.nid');
// Pass the query to the node access control.
$this->reAlterQuery($query, 'node_access', $node_alias);
// Alas, the comment entity exposes a bundle, but doesn't have a bundle column
// in the database. We have to alter the query ourself to go fetch the
// bundle.
$conditions = &$query->conditions();
foreach ($conditions as $key => &$condition) {
if ($key !== '#conjunction' && is_string($condition['field']) && $condition['field'] === 'node_type') {
$condition['field'] = $node_alias . '.type';
foreach ($condition['value'] as &$value) {
if (substr($value, 0, 13) == 'comment_node_') {
$value = substr($value, 13);
}
}
break;
}
}
// Passing the query to node_query_node_access_alter() is sadly
// insufficient for nodes.
// @see EntityReferenceHandler_node::entityFieldQueryAlter()
if (!user_access('bypass node access') && !count(module_implements('node_grants'))) {
$query->condition($node_alias . '.status', 1);
}
}
}
/**
* Override for the File type.
*
* This only exists to workaround core bugs.
*/
class EntityReference_SelectionHandler_Generic_file extends EntityReference_SelectionHandler_Generic {
public function entityFieldQueryAlter(SelectQueryInterface $query) {
// Core forces us to know about 'permanent' vs. 'temporary' files.
$tables = $query->getTables();
$base_table = key($tables);
$query->condition('status', FILE_STATUS_PERMANENT);
// Access control to files is a very difficult business. For now, we are not
// going to give it a shot.
// @todo: fix this when core access control is less insane.
return $query;
}
public function getLabel($entity) {
// The file entity doesn't have a label. More over, the filename is
// sometimes empty, so use the basename in that case.
return $entity->filename !== '' ? $entity->filename : basename($entity->uri);
}
}
/**
* Override for the Taxonomy term type.
*
* This only exists to workaround core bugs.
*/
class EntityReference_SelectionHandler_Generic_taxonomy_term extends EntityReference_SelectionHandler_Generic {
public function entityFieldQueryAlter(SelectQueryInterface $query) {
// The Taxonomy module doesn't implement any proper taxonomy term access,
// and as a consequence doesn't make sure that taxonomy terms cannot be viewed
// when the user doesn't have access to the vocabulary.
$tables = $query->getTables();
$base_table = key($tables);
$vocabulary_alias = $query->innerJoin('taxonomy_vocabulary', 'n', '%alias.vid = ' . $base_table . '.vid');
$query->addMetadata('base_table', $vocabulary_alias);
// Pass the query to the taxonomy access control.
$this->reAlterQuery($query, 'taxonomy_vocabulary_access', $vocabulary_alias);
// Also, the taxonomy term entity exposes a bundle, but doesn't have a bundle
// column in the database. We have to alter the query ourself to go fetch
// the bundle.
$conditions = &$query->conditions();
foreach ($conditions as $key => &$condition) {
if ($key !== '#conjunction' && is_string($condition['field']) && $condition['field'] === 'vocabulary_machine_name') {
$condition['field'] = $vocabulary_alias . '.machine_name';
break;
}
}
}
}
@@ -0,0 +1,183 @@
<?php
/**
* Entity handler for Views.
*/
class EntityReference_SelectionHandler_Views implements EntityReference_SelectionHandler {
/**
* Implements EntityReferenceHandler::getInstance().
*/
public static function getInstance($field, $instance = NULL, $entity_type = NULL, $entity = NULL) {
return new EntityReference_SelectionHandler_Views($field, $instance);
}
protected function __construct($field, $instance) {
$this->field = $field;
$this->instance = $instance;
}
/**
* Implements EntityReferenceHandler::settingsForm().
*/
public static function settingsForm($field, $instance) {
$view_settings = empty($field['settings']['handler_settings']['view']) ? '' : $field['settings']['handler_settings']['view'];
$displays = views_get_applicable_views('entityreference display');
// Filter views that list the entity type we want, and group the separate
// displays by view.
$entity_info = entity_get_info($field['settings']['target_type']);
$options = array();
foreach ($displays as $data) {
list($view, $display_id) = $data;
if ($view->base_table == $entity_info['base table']) {
$options[$view->name . ':' . $display_id] = $view->name . ' - ' . $view->display[$display_id]->display_title;
}
}
// The value of the 'view_and_display' select below will need to be split
// into 'view_name' and 'view_display' in the final submitted values, so
// we massage the data at validate time on the wrapping element (not
// ideal).
$form['view']['#element_validate'] = array('entityreference_view_settings_validate');
if ($options) {
$default = !empty($view_settings['view_name']) ? $view_settings['view_name'] . ':' . $view_settings['display_name'] : NULL;
$form['view']['view_and_display'] = array(
'#type' => 'select',
'#title' => t('View used to select the entities'),
'#required' => TRUE,
'#options' => $options,
'#default_value' => $default,
'#description' => '<p>' . t('Choose the view and display that select the entities that can be referenced.<br />Only views with a display of type "Entity Reference" are eligible.') . '</p>',
);
$default = !empty($view_settings['args']) ? implode(', ', $view_settings['args']) : '';
$form['view']['args'] = array(
'#type' => 'textfield',
'#title' => t('View arguments'),
'#default_value' => $default,
'#required' => FALSE,
'#description' => t('Provide a comma separated list of arguments to pass to the view.'),
);
}
else {
$form['view']['no_view_help'] = array(
'#markup' => '<p>' . t('No eligible views were found. <a href="@create">Create a view</a> with an <em>Entity Reference</em> display, or add such a display to an <a href="@existing">existing view</a>.', array(
'@create' => url('admin/structure/views/add'),
'@existing' => url('admin/structure/views'),
)) . '</p>',
);
}
return $form;
}
protected function initializeView($match = NULL, $match_operator = 'CONTAINS', $limit = 0, $ids = NULL) {
$view_name = $this->field['settings']['handler_settings']['view']['view_name'];
$display_name = $this->field['settings']['handler_settings']['view']['display_name'];
$args = $this->field['settings']['handler_settings']['view']['args'];
$entity_type = $this->field['settings']['target_type'];
// Check that the view is valid and the display still exists.
$this->view = views_get_view($view_name);
if (!$this->view || !isset($this->view->display[$display_name]) || !$this->view->access($display_name)) {
watchdog('entityreference', 'The view %view_name is no longer eligible for the %field_name field.', array('%view_name' => $view_name, '%field_name' => $this->instance['label']), WATCHDOG_WARNING);
return FALSE;
}
$this->view->set_display($display_name);
// Make sure the query is not cached.
$this->view->is_cacheable = FALSE;
// Pass options to the display handler to make them available later.
$entityreference_options = array(
'match' => $match,
'match_operator' => $match_operator,
'limit' => $limit,
'ids' => $ids,
);
$this->view->display_handler->set_option('entityreference_options', $entityreference_options);
return TRUE;
}
/**
* Implements EntityReferenceHandler::getReferencableEntities().
*/
public function getReferencableEntities($match = NULL, $match_operator = 'CONTAINS', $limit = 0) {
$display_name = $this->field['settings']['handler_settings']['view']['display_name'];
$args = $this->field['settings']['handler_settings']['view']['args'];
$result = array();
if ($this->initializeView($match, $match_operator, $limit)) {
// Get the results.
$result = $this->view->execute_display($display_name, $args);
}
return $result;
}
/**
* Implements EntityReferenceHandler::countReferencableEntities().
*/
function countReferencableEntities($match = NULL, $match_operator = 'CONTAINS') {
$this->getReferencableEntities($match, $match_operator);
return $this->view->total_items;
}
function validateReferencableEntities(array $ids) {
$display_name = $this->field['settings']['handler_settings']['view']['display_name'];
$args = $this->field['settings']['handler_settings']['view']['args'];
$result = array();
if ($this->initializeView(NULL, 'CONTAINS', 0, $ids)) {
// Get the results.
$entities = $this->view->execute_display($display_name, $args);
$result = array_keys($entities);
}
return $result;
}
/**
* Implements EntityReferenceHandler::validateAutocompleteInput().
*/
public function validateAutocompleteInput($input, &$element, &$form_state, $form) {
return NULL;
}
/**
* Implements EntityReferenceHandler::getLabel().
*/
public function getLabel($entity) {
return entity_label($this->field['settings']['target_type'], $entity);
}
/**
* Implements EntityReferenceHandler::entityFieldQueryAlter().
*/
public function entityFieldQueryAlter(SelectQueryInterface $query) {
}
}
function entityreference_view_settings_validate($element, &$form_state, $form) {
// Split view name and display name from the 'view_and_display' value.
if (!empty($element['view_and_display']['#value'])) {
list($view, $display) = explode(':', $element['view_and_display']['#value']);
}
else {
form_error($element, t('The views entity selection mode requires a view.'));
return;
}
// Explode the 'args' string into an actual array. Beware, explode() turns an
// empty string into an array with one empty string. We'll need an empty array
// instead.
$args_string = trim($element['args']['#value']);
if ($args_string === '') {
$args = array();
}
else {
// array_map is called to trim whitespaces from the arguments.
$args = array_map('trim', explode(',', $args_string));
}
$value = array('view_name' => $view, 'display_name' => $display, 'args' => $args);
form_set_value($element, $value, $form_state);
}
+113
View File
@@ -0,0 +1,113 @@
<?php
/**
* @file
* Abstraction of the selection logic of an entity reference field.
*
* Implementations that wish to provide an implementation of this should
* register it using CTools' plugin system.
*/
interface EntityReference_SelectionHandler {
/**
* Factory function: create a new instance of this handler for a given field.
*
* @param $field
* A field datastructure.
* @return EntityReferenceHandler
*/
public static function getInstance($field, $instance = NULL, $entity_type = NULL, $entity = NULL);
/**
* Return a list of referencable entities.
*
* @return
* An array of referencable entities, which keys are entity ids and
* values (safe HTML) labels to be displayed to the user.
*/
public function getReferencableEntities($match = NULL, $match_operator = 'CONTAINS', $limit = 0);
/**
* Count entities that are referencable by a given field.
*/
public function countReferencableEntities($match = NULL, $match_operator = 'CONTAINS');
/**
* Validate that entities can be referenced by this field.
*
* @return
* An array of entity ids that are valid.
*/
public function validateReferencableEntities(array $ids);
/**
* Validate Input from autocomplete widget that has no Id.
*
* @see _entityreference_autocomplete_validate()
*
* @param $input
* Single string from autocomplete widget.
* @param $element
* The form element to set a form error.
* @return
* Value of a matching entity id, or NULL if none.
*/
public function validateAutocompleteInput($input, &$element, &$form_state, $form);
/**
* Give the handler a chance to alter the SelectQuery generated by EntityFieldQuery.
*/
public function entityFieldQueryAlter(SelectQueryInterface $query);
/**
* Return the label of a given entity.
*/
public function getLabel($entity);
/**
* Generate a settings form for this handler.
*/
public static function settingsForm($field, $instance);
}
/**
* A null implementation of EntityReference_SelectionHandler.
*/
class EntityReference_SelectionHandler_Broken implements EntityReference_SelectionHandler {
public static function getInstance($field, $instance = NULL, $entity_type = NULL, $entity = NULL) {
return new EntityReference_SelectionHandler_Broken($field, $instance, $entity_type, $entity);
}
protected function __construct($field, $instance) {
$this->field = $field;
$this->instance = $instance;
}
public static function settingsForm($field, $instance) {
$form['selection_handler'] = array(
'#markup' => t('The selected selection handler is broken.'),
);
return $form;
}
public function getReferencableEntities($match = NULL, $match_operator = 'CONTAINS', $limit = 0) {
return array();
}
public function countReferencableEntities($match = NULL, $match_operator = 'CONTAINS') {
return 0;
}
public function validateReferencableEntities(array $ids) {
return array();
}
public function validateAutocompleteInput($input, &$element, &$form_state, $form) {
return NULL;
}
public function entityFieldQueryAlter(SelectQueryInterface $query) {}
public function getLabel($entity) {
return '';
}
}
+7
View File
@@ -0,0 +1,7 @@
<?php
$plugin = array(
'title' => t('Simple (with optional filter by bundle)'),
'class' => 'EntityReference_SelectionHandler_Generic',
'weight' => -100,
);
+9
View File
@@ -0,0 +1,9 @@
<?php
if (module_exists('views')) {
$plugin = array(
'title' => t('Views: Filter by an entity reference view'),
'class' => 'EntityReference_SelectionHandler_Views',
'weight' => 0,
);
}
-157
View File
@@ -1,157 +0,0 @@
.item-list .field-widget-term-reference-tree ul,
.block .field-widget-term-reference-tree ul,
.field-widget-term-reference-tree ul
{
list-style-type: none;
margin-top: 0;
margin-bottom: 0;
margin-left: 0;
}
.item-list .field-widget-term-reference-tree ul ul,
.block .field-widget-term-reference-tree ul ul,
.field-widget-term-reference-tree ul ul
{
margin-left: 1.5em;
}
.item-list .field-widget-term-reference-tree ul .form-item,
.block .field-widget-term-reference-tree ul .form-item,
.field-widget-term-reference-tree ul .form-item {
margin: 0;
padding: 0;
}
.term-reference-tree-button,
.no-term-reference-tree-button {
width: 16px;
height: 16px;
display: inline-block;
*display: inline;
zoom: 1;
vertical-align: middle;
margin-right: 4px;
}
.term-reference-tree-button {
background-image: url('images/bullet_toggle_minus.png');
}
.no-term-reference-tree-button {
background-color: #ddd;
}
.term-reference-tree-button.term-reference-tree-collapsed {
background-image: url('images/bullet_toggle_plus.png');
}
.field-widget-term-reference-tree .form-item {
display: inline-block;
*display: inline;
zoom: 1;
}
.field-widget-term-reference-tree .disabled {
opacity: .5;
}
.field-widget-term-reference-tree .parent-term {
display: inline-block;
*display: inline;
zoom: 1;
font-weight: bold;
}
.form-type-checkbox-tree .error {
background-image: none;
border: 2px solid red;
padding: 3px;
}
.region-content ul.term-reference-tree-level,
.term-reference-tree-level
{
padding: 0;
}
/*
* Styles for track list of selected options.
*/
.term-reference-tree-track-list li {
list-style-type: none;
list-style-image: none;
margin-left: 10px;
padding-left: 20px;
min-height: 16px;
cursor: pointer;
}
.term-reference-tree-track-list li.track-item:hover {
color: red;
background-image: url("images/bullet_delete.png");
background-repeat: no-repeat;
background-position: middle left;
}
.term-reference-tree-track-list.order-list li.track-item {
padding-left:0;
}
.term-reference-tree-track-list.order-list li.track-item:hover {
color:#000;
background-image: none;
}
.term-reference-tree-track-list.order-list .term-reference-tree-button-move, .term-reference-tree-track-list.order-list .term-reference-tree-button-delete{
display:inline-block; vertical-align:middle; zoom:1;
width:16px; height:16px;
background-repeat: no-repeat;
background-position: bottom center;
margin:0 5px;
}
.term-reference-tree-track-list.order-list li.track-item .term-reference-tree-button-move{
background-image: url("images/bullet_move.png");
}
.term-reference-tree-track-list.order-list li.track-item .term-reference-tree-button-move:hover{
cursor:move;
}
.term-reference-tree-track-list.order-list li.track-item .term-reference-tree-button-delete{
background-image: url("images/bullet_delete.png"); float:right;
}
.term-reference-tree-track-list.order-list li.track-item .term-reference-tree-button-:hover{
cursor:pointer;
}
.term-reference-tree-track-list li.term_ref_tree_nothing_message {
list-style-type: none;
list-style-image: none;
font-style: italic;
cursor: default;
}
.term-reference-track-list-container {
padding: 5px;
}
.term-reference-track-list-label {
font-weight: bold;
}
/*
* Styles for display element
*/
.field-widget-term-reference-tree .selected {
font-weight: bold;
}
.field-widget-term-reference-tree .unselected {
font-weight: normal;
}
.field-widget-term-reference-tree ul {
margin-top: 0;
}
-89
View File
@@ -1,89 +0,0 @@
<?php
/**
* Implements hook_field_formatter_info().
*/
function term_reference_tree_field_formatter_info() {
return array(
'term_reference_tree' => array(
'label' => 'Term reference tree',
'field types' => array('taxonomy_term_reference'),
'settings' => array(
'token_display_selected' => '',
'token_display_unselected' => '',
),
),
);
}
/**
* Implements hook_field_formatter_view().
*/
function term_reference_tree_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
$element = array();
switch ($display['type']) {
case 'term_reference_tree':
$element[] = array(
'#theme' => 'term_tree_list',
'#data' => $items,
'#display' => $display,
'#attached' => array('css' => array(drupal_get_path('module', 'term_reference_tree') . '/term_reference_tree.css')),
);
break;
}
return $element;
}
/**
* Implements hook_field_formatter_settings_form().
*/
function term_reference_tree_field_formatter_settings_form($field, $instance, $view_mode, $form, &$form_state) {
$display = $instance['display'][$view_mode];
$settings = $display['settings'];
$element = array();
if ($display['type'] == 'term_reference_tree' && module_exists('token')) {
$element['token_display_selected'] = array(
'#type' => 'textarea',
'#title' => 'Custom Term Label',
'#description' => t("Use tokens to change the term label. Leave this field blank to use the term name linked to its taxonomy page."),
'#default_value' => $settings['token_display_selected'],
);
$element['token_display_unselected'] = array(
'#type' => 'textarea',
'#title' => 'Custom Term Label (unselected)',
'#description' => t("Use tokens to change the term label for unselected parent terms. Leave this field blank to use the same tokens as above."),
'#default_value' => $settings['token_display_unselected'],
);
$element['tokens_list'] = array(
'#theme' => 'token_tree',
'#token_types' => array('term'),
);
}
return $element;
}
/**
* Implements hook_field_formatter_settings_summary().
*/
function term_reference_tree_field_formatter_settings_summary($field, $instance, $view_mode) {
$display = $instance['display'][$view_mode];
$settings = $display['settings'];
$summary = '';
if ($display['type'] == 'term_reference_tree') {
$summary = t('Uses tokens: ') . t($settings['token_display_selected'] != '' ? 'Yes' : 'No');
}
return $summary;
}
-13
View File
@@ -1,13 +0,0 @@
name = Term Reference Tree
description = An expanding/collapsing tree widget for selecting terms in a taxonomy term reference field
package = Other
core = 7.x
dependencies[] = taxonomy
; Information added by drupal.org packaging script on 2012-03-26
version = "7.x-1.9"
core = "7.x"
project = "term_reference_tree"
datestamp = "1332793846"
-356
View File
@@ -1,356 +0,0 @@
(function($) {
Drupal.behaviors.termReferenceTree = {
attach: function(context, settings) {
// Bind the term expand/contract button to slide toggle the list underneath.
$('.term-reference-tree-button', context).click(function() {
$(this).toggleClass('term-reference-tree-collapsed');
$(this).siblings('ul').slideToggle('fast');
});
// An expand all button (unimplemented)
/*
$('.expandbutton').click(function() {
$(this).siblings('.term-reference-tree-button').trigger('click');
});
*/
$('.term-reference-tree', context).each(function() {
// On page load, check whether the maximum number of choices is already selected.
// If so, disable the other options.
var tree = $(this);
checkMaxChoices(tree, false);
$(this).find('input[type=checkbox]').change(function() {
checkMaxChoices(tree, $(this));
});
//On page load, check if the user wants a track list. If so, add the
//currently selected items to it.
if($(this).hasClass('term-reference-tree-track-list-shown')) {
var track_list_container = $(this).find('.term-reference-tree-track-list');
var tracklist_is_orderable = track_list_container.is('.order-list');
if(tracklist_is_orderable){
track_list_container.sortable({
update: function(event, ui) {
// console.log('sort update : event', event);
// console.log('sort update : ui', ui);
$.each(event.target.children, function(index, val) {
var $item = $(val),
// event.target = ul.list
// ui.item = li.track-item
control_id = $item.data('control_id'),
$hiddenInput = $('#'+control_id).parent('.form-item').next('input[type=hidden]');
// $hiddenInput.attr('value', $item.index());
$hiddenInput.val($item.index());
});
},
});
}
//Var to track whether using checkboxes or radio buttons.
var input_type =
( $(this).has('input[type=checkbox]').size() > 0 ) ? 'checkbox' : 'radio';
//Find all the checked controls.
var checked_controls = $(this).find('input[type=' + input_type + ']:checked');
//Get their labels.
var labels = checked_controls.next();
var label_element;
//get delta
if(tracklist_is_orderable){
var weights = checked_controls.parent('.form-item').next('input[type=hidden]');
}
//For each label of the checked boxes, add item to the track list.
labels.each(function(index) {
label_element = $(labels[index]);
delta = tracklist_is_orderable ? $(weights[index]).val() : -1;
addItemToTrackList(
track_list_container, //Where to add new item.
label_element.html(), //Text of new item.
$(label_element).attr('for'), //Id of control new item is for.
input_type, //checkbox or radio
delta //delta
);
}); //End labels.each
//Show "nothing selected" message, if needed.
showNothingSelectedMessage(track_list_container);
//Event - when an element on the track list is clicked on:
// 1. Delete it.
// 2. Uncheck the associated checkbox.
//The event is bound to the track list container, not each element.
$(track_list_container).click(function(event){
//Remove the "nothing selected" message if showing - add it later if needed.
//removeNothingSelectedMessage(track_list_container);
var event_target = $(event.target);
var event_parent_list = event_target.parent('li');
var control_id = event_parent_list.data('control_id');
// console.log('event', event);
// console.log('event_target.parent("li")', event_target.parent('li'));
// console.log('control_id', control_id);
// console.log('event_target.is(term-reference-tree-delete)', event_target.is('term-reference-tree-delete'));
if(event_target.is('.term-reference-tree-button-delete') && control_id) {
event_parent_list.remove();
var checkbox = $('#' + control_id);
checkbox.removeAttr('checked');
checkMaxChoices(tree, checkbox);
//Show "nothing selected" message, if needed.
showNothingSelectedMessage(track_list_container);
}
});
//Change track list when controls are clicked.
$(this).find('.form-' + input_type).change(function(event){
//Remove the "nothing selected" message if showing - add it later if needed.
removeNothingSelectedMessage(track_list_container);
var event_target = $(event.target);
var control_id = event_target.attr('id');
if ( event_target.attr('checked') ) {
//Control checked - add item to the track list.
label_element = event_target.next();
addItemToTrackList(
track_list_container, //Where to add new item.
label_element.html(), //Text of new item.
$(label_element).attr('for'), //Id of control new item is for.
input_type, //checkbox or radio
-1 // delta
);
}
else {
//Checkbox unchecked. Remove from the track list.
$('#' + control_id + '_list').remove();
}
//Show "nothing selected" message, if needed.
showNothingSelectedMessage(track_list_container);
}); //End process checkbox changes.
} //End Want a track list.
//On page load, check if the user wants a cascading selection.
if($(this).hasClass('term-reference-tree-cascading-selection')) {
//Check children when checkboxes are clicked.
$(this).find('.form-checkbox').change(function(event) {
var event_target = $(event.target);
var control_id = event_target.attr('id');
var children = event_target.parent().next().children().children('div.form-type-checkbox').children('input[id^="' + control_id + '-children"]');
if(event_target.attr('checked')) {
//Checkbox checked - check children if none were checked.
if(!$(children).filter(':checked').length) {
$(children).click().trigger('change');
}
}
else {
//Checkbox unchecked. Uncheck children if all were checked.
if(!$(children).not(':checked').length) {
$(children).click().trigger('change');
}
}
});
//End process checkbox changes.
} //End Want a cascading checking.
});
}
};
/**
* Add a new item to the track list.
* If more than one item can be selected, the new item is positioned to
* match the order of the terms in the checkbox tree.
*
* @param track_list_container Container where the new item will be added.
*
* @param item_text Text of the item to add.
*
* @param control_id Id of the checkbox/radio control the item matches.
*
* @param control_type Control type - 'checkbox' or 'radio'.
*/
function addItemToTrackList(track_list_container, item_text, control_id, control_type, delta) {
// console.log('addItemToTrackList');
var new_item = $('<li class="track-item" delta="'+ delta +'"><div class="term-reference-tree-button-move"></div>' + item_text + '<div class="term-reference-tree-button-delete"></div></li>');
new_item.data('control_id', control_id);
//Add an id for easy finding of the item.
new_item.attr('id', control_id + '_list');
//Process radio controls - only one item can be selected.
if ( control_type == 'radio') {
//Find the existing element on the track list, if there is one.
var current_items = track_list_container.find('li');
//If there are no items on the track list, add the new item.
if ( current_items.size() == 0 ) {
track_list_container.append(new_item);
}
else {
//There is an item on the list.
var current_item = $(current_items.get(0));
//Is the item we want to add different from what is there?
if ( current_item.data('control_id') != control_id ) {
//Remove exiting element from track list, and add the new one.
current_item.remove();
track_list_container.append(new_item);
}
}
return;
}
//Using checkboxes, so there can be more than one selected item.
//Find the right place to put the new item,
// to match the order of the checkboxes.
// OR order of delta
var list_items = track_list_container.find('li');
var item_comparing_to;
//Flag to tell whether the item was inserted.
var inserted_flag = false;
if(!track_list_container.is('.order-list')){
list_items.each(function(index){
item_comparing_to = $(list_items[index]);
//If item is already on the track list, do nothing.
if ( control_id == item_comparing_to.data('control_id') ) {
inserted_flag = true;
return false; //Returning false stops the loop.
}
else if ( control_id < item_comparing_to.data('control_id') ) {
//Add it here.
item_comparing_to.before(new_item);
inserted_flag = true;
return false; //Returning false stops the loop.
}
});
//If not inserted yet, add new item at the end of the track list.
if ( ! inserted_flag ) {
track_list_container.append(new_item);
}
}else{
if( ! track_list_container.find('#'+new_item.attr('id')).size() ){
if(delta == -1){
track_list_container.append(new_item);
inserted_flag = true;
$hiddenInput = $('#'+control_id).parent('.form-item').next('input[type=hidden]');
// console.log('$hiddenInput',$hiddenInput);
$hiddenInput.val(new_item.index());
}else{
list_items.each(function(index){
item_comparing_to = $(this);
if ( parseInt(delta) < parseInt(item_comparing_to.attr('delta')) ) {
//Add it here.
item_comparing_to.before(new_item);
inserted_flag = true;
return false; //Returning false stops the loop.
}
});
//If not inserted yet, add new item at the end of the track list.
if ( ! inserted_flag )
track_list_container.append(new_item);
}
track_list_container.sortable('refresh');
}
}
}
/**
* Show the 'nothing selected' message if it applies.
*
* @param track_list_container Where the message is to be shown.
*/
function showNothingSelectedMessage(track_list_container) {
//Is the message there already?
var message_showing =
(track_list_container.find('.term_ref_tree_nothing_message').size() != 0);
//Number of real items showing.
var num_real_items_showing =
message_showing
? track_list_container.find('li').size() - 1
: track_list_container.find('li').size();
if ( num_real_items_showing == 0 ) {
//No items showing, so show the message.
if ( ! message_showing ) {
track_list_container.append(
'<li class="term_ref_tree_nothing_message">' + termReferenceTreeNothingSelectedText + '</li>'
);
}
}
else { // !(num_real_items_showing == 0)
//There are real items.
if ( message_showing ) {
track_list_container.find('.term_ref_tree_nothing_message').remove();
}
}
}
/**
* Remove the 'nothing selected' message. Makes processing easier.
*
* @param track_list_container Where the message is shown.
*/
function removeNothingSelectedMessage(track_list_container) {
track_list_container.find('.term_ref_tree_nothing_message').remove();
}
// This helper function checks if the maximum number of choices is already selected.
// If so, it disables all the other options. If not, it enables them.
function checkMaxChoices(item, checkbox, order_list) {
var maxChoices = -1;
try {
maxChoices = parseInt(Drupal.settings.term_reference_tree.trees[item.attr('id')]['max_choices']);
}
catch (e){}
var count = item.find(':checked').length;
if(maxChoices > 0 && count >= maxChoices) {
item.find('input[type=checkbox]:not(:checked)').attr('disabled', 'disabled').parent().addClass('disabled');
} else {
item.find('input[type=checkbox]').removeAttr('disabled').parent().removeClass('disabled');
}
if(checkbox) {
if(item.hasClass('select-parents')) {
var track_list_container = item.find('.term-reference-tree-track-list');
var input_type =
( item.has('input[type=checkbox]').size() > 0 ) ? 'checkbox' : 'radio';
if(checkbox.attr('checked')) {
checkbox.parents('ul.term-reference-tree-level li').children('div.form-item').children('input[type=checkbox]').each(function() {
$(this).attr('checked', checkbox.attr('checked'));
if(track_list_container) {
label_element = $(this).next();
addItemToTrackList(
track_list_container, //Where to add new item.
label_element.html(), //Text of new item.
$(label_element).attr('for'), //Id of control new item is for.
input_type //checkbox or radio
);
}
});
}
}
}
}
})(jQuery);
-226
View File
@@ -1,226 +0,0 @@
<?php
module_load_include('inc', 'term_reference_tree', 'term_reference_tree.field');
module_load_include('inc', 'term_reference_tree', 'term_reference_tree.widget');
/**
* Implements hook_element_info().
*/
function term_reference_tree_element_info() {
$types = array(
'checkbox_tree' => array(
'#input' => true,
'#process' => array('term_reference_tree_process_checkbox_tree'),
'#theme' => array('checkbox_tree'),
'#pre_render' => array('form_pre_render_conditional_form_element'),
),
'checkbox_tree_level' => array(
'#input' => false,
'#theme' => array('checkbox_tree_level'),
'#pre_render' => array('form_pre_render_conditional_form_element'),
),
'checkbox_tree_item' => array(
'#input' => false,
'#theme' => array('checkbox_tree_item'),
'#pre_render' => array('form_pre_render_conditional_form_element'),
),
'checkbox_tree_label' => array(
'#input' => false,
'#theme' => array('checkbox_tree_label'),
'#pre_render' => array('form_pre_render_conditional_form_element'),
),
'checkbox_tree_track_list' => array(
'#input' => false,
'#theme' => array('checkbox_tree_track_list'),
'#pre_render' => array('form_pre_render_conditional_form_element'),
)
);
return $types;
}
/**
* Implements hook_theme().
*/
function term_reference_tree_theme() {
return array(
'checkbox_tree' => array(
'render element' => 'element',
),
'checkbox_tree_level' => array(
'render element' => 'element',
),
'checkbox_tree_item' => array(
'render element' => 'element',
),
'checkbox_tree_label' => array(
'render element' => 'element',
),
'checkbox_tree_track_list' => array(
'render element' => 'element',
),
'term_tree_list' => array(
'render element' => 'element',
),
);
}
/**
* This function returns a taxonomy term hierarchy in a nested array.
*
* @param $tid
* The ID of the root term.
* @param $vid
* The vocabulary ID to restrict the child search.
*
* @return
* A nested array of the term's child objects.
*/
function _term_reference_tree_get_term_hierarchy($tid, $vid, &$allowed, $filter, $label, $default = array()) {
$terms = _term_reference_tree_get_children($tid, $vid, $default);
$result = array();
if ($filter != '') {
foreach($allowed as $k => $v) {
if (array_key_exists($k, $terms)) {
$term =& $terms[$k];
$children = _term_reference_tree_get_term_hierarchy($term->tid, $vid, $allowed, $filter, $label, $default);
if (is_array($children)) {
$term->children = $children;
$term->children_selected = _term_reference_tree_children_selected($term, $default);
}
else {
$term->children_selected = FALSE;
}
$term->TEST = $label;
array_push($result, $term);
}
}
}
else {
foreach($terms as &$term) {
if ($filter == '' || array_key_exists($term->tid, $allowed)) {
$children = _term_reference_tree_get_term_hierarchy($term->tid, $vid, $allowed, $filter, $label, $default);
if (is_array($children)) {
$term->children = $children;
$term->children_selected = _term_reference_tree_children_selected($term, $default);
}
else {
$term->children_selected = FALSE;
}
$term->TEST = $label;
array_push($result, $term);
}
}
}
return $result;
}
/**
* This function is like taxonomy_get_children, except it doesn't load the entire term.
*
* @param $tid
* The ID of the term whose children you want to get.
* @param $vid
* The vocabulary ID.
*
* @return
* An array of taxonomy terms, each in the form array('tid' => $tid, 'name' => $name)
*/
function _term_reference_tree_get_children($tid, $vid) {
// DO NOT LOAD TAXONOMY TERMS HERE
// Taxonomy terms take a lot of time and memory to load, and this can be
// very bad on large vocabularies. Instead, we load the term as necessary
// in cases where it's needed (such as using tokens or when the locale
// module is enabled).
$select = db_select('taxonomy_term_data', 'd');
$select->join('taxonomy_term_hierarchy', 'h', 'd.tid = h.tid');
$result = $select->fields('d', array('tid', 'vid', 'name'))
->condition('d.vid', $vid, '=')
->condition('h.parent', $tid, '=')
->orderBy('weight')
->orderBy('name')
->orderBy('tid')
->execute();
$terms = array();
while($term = $result->fetchAssoc()) {
$terms[$term['tid']] = (object) $term;
}
return $terms;
}
function _term_reference_tree_children_selected($terms, $default) {
foreach($terms->children as $term) {
if(isset($default[$term->tid]) || $term->children_selected) {
return true;
}
}
return false;
}
function _term_reference_tree_get_parent($tid) {
$q = db_query_range("select h.parent from {taxonomy_term_hierarchy} h where h.tid = :tid", 0, 1, array(':tid' => $tid));
$t = 0;
foreach($q as $term) {
$t = $term->parent;
}
return $t;
}
/**
* Recursively go through the option tree and return a flat array of
* options
*/
function _term_reference_tree_flatten($element, &$form_state) {
$output = array();
$children = element_children($element);
foreach($children as $c) {
$child = $element[$c];
// dsm($child, '$child');
if (array_key_exists('#type', $child) && ($child['#type'] == 'radio' || $child['#type'] == 'checkbox')) {
$output[] = $child;
}
else {
$output = array_merge($output, _term_reference_tree_flatten($child, $form_state));
}
}
return $output;
}
/**
* Return an array of options.
*
* This function converts a list of taxonomy terms to a key/value list of options.
*
* @param $terms
* An array of taxonomy term IDs.
* @param $allowed
* An array containing the terms allowed by the filter view
* @param $filter
* A string defining the view to filter by (only used to detect whether view
* filtering is enabled
*
* @return
* A key/value array of taxonomy terms (name => id)
*/
function _term_reference_tree_get_options(&$terms, &$allowed, $filter) {
$options = array();
if (is_array($terms) && count($terms) > 0) {
foreach($terms as $term) {
if (!$filter || (is_array($allowed) && $allowed[$term->tid])) {
$options[$term->tid] = entity_label('taxonomy_term', $term);
$options += _term_reference_tree_get_options($term->children, $allowed, $filter);
}
}
}
return $options;
}
-891
View File
@@ -1,891 +0,0 @@
<?php
/**
* Implements hook_field_widget_info().
*/
function term_reference_tree_field_widget_info() {
return array(
'term_reference_tree' => array (
'label' => 'Term reference tree',
'field types' => array('taxonomy_term_reference'),
'behaviors' => array(
'multiple values' => FIELD_BEHAVIOR_CUSTOM,
'default value' => FIELD_BEHAVIOR_DEFAULT,
),
'settings' => array(
'start_minimized' => 0,
'leaves_only' => 0,
'filter_view' => '',
'select_parents' => 0,
'cascading_selection' => 0,
'track_list' => 0,
'track_list_order' => 0,
'token_display' => '',
'parent_term_id' => '',
'max_depth' => '',
'starting_depth' => 1,
),
),
);
}
/**
* Themes the term tree display (as opposed to the select widget).
*/
function theme_term_tree_list($variables) {
$element =& $variables['element'];
$data =& $element['#data'];
$tree = array();
# For each selected term:
foreach($data as $item) {
# Loop if the term ID is not zero:
$values = array();
$tid = $item['tid'];
$original_tid = $tid;
while($tid != 0) {
# Unshift the term onto an array
array_unshift($values, $tid);
# Repeat with parent term
$tid = _term_reference_tree_get_parent($tid);
}
$current =& $tree;
# For each term in the above array:
foreach($values as $tid) {
# current[children][term_id] = new array
if (!isset($current['children'][$tid])) {
$current['children'][$tid] = array('selected' => FALSE);
}
# If this is the last value in the array, tree[children][term_id][selected] = true
if ($tid == $original_tid) {
$current['children'][$tid]['selected'] = TRUE;
}
$current['children'][$tid]['tid'] = $tid;
$current =& $current['children'][$tid];
}
}
return _term_reference_tree_output_list_level($element, $tree);
}
/**
* Implements hook_field_widget_settings_form().
*/
function term_reference_tree_field_widget_settings_form($field, $instance) {
$widget = $instance['widget'];
$settings = $widget['settings'];
$form = array();
if ($widget['type'] == 'term_reference_tree') {
$form['start_minimized'] = array(
'#type' => 'checkbox',
'#title' => t('Start minimized'),
'#description' => t('Make the tree appear minimized on the form by default'),
'#default_value' => $settings['start_minimized'],
'#return_value' => 1,
);
$form['leaves_only'] = array(
'#type' => 'checkbox',
'#title' => t('Leaves only'),
'#description' => t("Don't allow the user to select items that have children"),
'#default_value' => $settings['leaves_only'],
'#return_value' => 1,
);
$form['select_parents'] = array(
'#type' => 'checkbox',
'#title' => t('Select parents automatically'),
'#description' => t("When turned on, this option causes the widget to automatically select the ancestors of all selected items. In Leaves Only mode, the parents will be added invisibly to the selected value. <em>This option is only valid if an unlimited number of values can be selected.</em>"),
'#default_value' => $settings['select_parents'],
'#element_validate' => array('_term_reference_tree_select_parents_validate'),
'#return_value' => 1,
);
$form['cascading_selection'] = array(
'#type' => 'checkbox',
'#title' => t('Cascading selection'),
'#description' => t('On parent selection, automatically select children if none were selected. Some may then be manually unselected. In the same way, on parent unselection, unselect children if all were selected. <em>This option is only valid if an unlimited number of values can be selected.</em>'),
'#default_value' => $settings['cascading_selection'],
'#element_validate' => array('_term_reference_tree_cascading_selection_validate'),
'#return_value' => 1,
);
if (module_exists('views')) {
$views = views_get_all_views();
$options = array('' => 'none');
foreach($views as $name => $view) {
if ($view->base_table == 'taxonomy_term_data') {
foreach($view->display as $display) {
$options["$name:{$display->id}"] = "{$view->human_name}: {$display->display_title}";
}
}
}
$form['filter_view'] = array(
'#type' => 'select',
'#title' => 'Filter by view',
'#description' => t("Filter the available options based on whether they appear in the selected view."),
'#default_value' => $settings['filter_view'],
'#options' => $options,
);
}
else {
$form['filter_view'] = array(
'#type' => 'hidden',
'#value' => $settings['filter_view'],
);
}
if (module_exists('token')) {
$form['token_display'] = array(
'#type' => 'textarea',
'#title' => 'Custom Term Label',
'#description' => t("Use tokens to change the term labels for the checkboxes and/or radio buttons. Leave this field blank to use the term name."),
'#default_value' => $settings['token_display'],
);
$form['tokens_list'] = array(
'#theme' => 'token_tree',
'#token_types' => array('term'),
);
}
else {
$form['token_display'] = array(
'#type' => 'hidden',
'#value' => $settings['token_display'],
);
}
$form['track_list'] = array(
'#type' => 'checkbox',
'#title' => t('Track list'),
'#description' => t(
'Track what the user has chosen in a list below the tree.
Useful when the tree is large, with many levels.'),
'#default_value' => $settings['track_list'],
'#return_value' => 1,
);
$form['track_list_order'] = array(
'#type' => 'checkbox',
'#title' => t('Track list drag and drop order'),
'#description' => t(
'Allow drag and drop selected terms ordering on tracklist.'),
'#default_value' => $settings['track_list_order'],
'#return_value' => 1,
'#element_validate' => array('_term_reference_tree_track_list_order_validate'),
);
$form['max_depth'] = array(
'#type' => 'textfield',
'#title' => t('Maximum Depth'),
'#description' => t("Only show items up to this many levels deep."),
'#default_value' => $settings['max_depth'],
'#size' => 2,
'#return_value' => 1,
);
$form['starting_depth'] = array(
'#type' => 'textfield',
'#title' => t('Starting Depth'),
'#description' => t("Only items equal and down this level will be selectable. First level is 1"),
'#default_value' => $settings['starting_depth'],
'#size' => 2,
'#return_value' => 1,
);
$form['parent_term_id'] = array(
'#type' => 'textfield',
'#title' => t('Parent Term ID'),
'#description' => t("Only show items underneath the taxonomy term with this ID number. Leave this field blank to not limit terms by parent."),
'#default_value' => $settings['parent_term_id'],
'#size' => 8,
'#return_value' => 1,
);
}
return $form;
}
/**
* Helper function to output a single level of the term reference tree
* display.
*/
function _term_reference_tree_output_list_level(&$element, &$tree) {
if (isset($tree['children']) && is_array($tree['children']) && count($tree['children'] > 0)) {
$output = '<ul class="term">';
$settings = $element['#display']['settings'];
$tokens_selected = $settings['token_display_selected'];
$tokens_unselected = ($settings['token_display_unselected'] != '') ? $settings['token_display_unselected'] : $tokens_selected;
foreach($tree['children'] as &$item) {
$term = taxonomy_term_load($item['tid']);
$uri = taxonomy_term_uri($term);
$class = $item['selected'] ? 'selected' : 'unselected';
$output .= "<li class='$class'>";
if ($tokens_selected != '' && module_exists('token')) {
$replace = $item['selected'] ? $tokens_selected : $tokens_unselected;
$output .= token_replace($replace, array('term' => $term), array('clear' => TRUE));
}
else {
$output .= l(entity_label('taxonomy_term', $term), $uri['path'], array('html' => true));
}
if (isset($item['children'])) {
$output .= _term_reference_tree_output_list_level($element, $item);
}
$output .= "</li>";
}
$output .= '</ul>';
return $output;
}
}
/**
* Makes sure that cardinality is unlimited if auto-select parents is enabled.
*/
function _term_reference_tree_select_parents_validate($element, &$form_state) {
if ($form_state['values']['instance']['widget']['settings']['select_parents'] == 1 && $form_state['values']['field']['cardinality'] != -1) {
// This is pretty wonky syntax for the field name in form_set_error, but it's
// correct.
form_set_error('field][cardinality', t('You must select an Unlimited number of values if Select Parents Automatically is enabled.'));
}
}
/**
* Makes sure that cardinality is unlimited if cascading selection is enabled.
*/
function _term_reference_tree_cascading_selection_validate($element, &$form_state) {
if ($form_state['values']['instance']['widget']['settings']['cascading_selection'] == 1 && $form_state['values']['field']['cardinality'] != -1) {
// This is pretty wonky syntax for the field name in form_set_error, but it's
// correct.
form_set_error('field][cardinality', t('You must select an Unlimited number of values if Cascading selection is enabled.'));
}
}
function _term_reference_tree_track_list_order_validate($element, &$form_state){
if ($form_state['values']['instance']['widget']['settings']['track_list'] == 0 && $form_state['values']['instance']['widget']['settings']['track_list_order'] == 1) {
// This is pretty wonky syntax for the field name in form_set_error, but it's
// correct.
form_set_error('field][track_list_order', t('You must enable Track List if Track List Order is enabled.'));
}
/*
TODO check if number of values is diffrent from 1
*/
}
/**
* Process the checkbox_tree widget.
*
* This function processes the checkbox_tree widget.
*
* @param $element
* The element to be drawn.$element['#field_name']
* @param $form_state
* The form state.
*
* @return
* The processed element.
*/
function term_reference_tree_process_checkbox_tree($element, $form_state) {
if (is_array($form_state)) {
if (!empty($element['#max_choices']) && $element['#max_choices'] != '-1')
drupal_add_js(array('term_reference_tree' => array('trees' => array($element['#id'] => array('max_choices'=>$element['#max_choices'])))), 'setting');
$allowed = '';
if ($element['#filter_view'] != '') {
$allowed = _term_reference_tree_get_allowed_values($element['#filter_view']);
}
$value = !empty($element['#default_value']) ? $element['#default_value'] : array();
if (empty($element['#options'])) {
$element['#options_tree'] = _term_reference_tree_get_term_hierarchy($element['#parent_tid'], $element['#vocabulary'], $allowed, $element['#filter_view'], '', $value);
$required = $element['#required'];
if ($element['#max_choices'] == 1 && !$required) {
array_unshift($element['#options_tree'], (object) array(
'tid' => '',
'name' => 'N/A',
'depth' => 0
)
);
}
$element['#options'] = _term_reference_tree_get_options($element['#options_tree'], $allowed, $element['#filter_view']);
}
$terms = !empty($element['#options_tree']) ? $element['#options_tree'] : array();
$max_choices = !empty($element['#max_choices']) ? $element['#max_choices'] : 1;
if (array_key_exists('#select_parents', $element) && $element['#select_parents']) {
$element['#attributes']['class'][] = 'select-parents';
}
if ($max_choices != 1)
$element['#tree'] = TRUE;
$starting_depth = !empty($element['#starting_depth']) ? $element['#starting_depth'] : 0;
$tree = new stdClass;
$tree->children = $terms;
$element[] = _term_reference_tree_build_level($element, $tree, $form_state, $value, $max_choices, $starting_depth, array(), 1);
//Add a track list element?
$track_list = !empty($element['#track_list']) && $element['#track_list'];
if ( $track_list ) {
$element[] = array(
'#type' => 'checkbox_tree_track_list',
'#max_choices' => $max_choices,
'#track_list_order' => $element['#track_list_order'],
);
}
}
return $element;
}
/**
* Returns HTML for a checkbox_tree form element.
*
* @param $variables
* An associative array containing:
* - element: An associative array containing the properties of the element.
*
* @ingroup themeable
*/
function theme_checkbox_tree($variables) {
$element = $variables['element'];
$element['#children'] = drupal_render_children($element);
$attributes = array();
if (isset($element['#id'])) {
$attributes['id'] = $element['#id'];
}
$attributes['class'][] = 'term-reference-tree';
if (form_get_error($element)) {
$attributes['class'][] = 'error';
}
if (!empty($element['#required'])) {
$attributes['class'][] = 'required';
}
if (array_key_exists('#start_minimized', $element) && $element['#start_minimized']) {
$attributes['class'][] = "term-reference-tree-collapsed";
}
if (array_key_exists('#cascading_selection', $element) && $element['#cascading_selection']) {
$attributes['class'][] = "term-reference-tree-cascading-selection";
}
$add_track_list = FALSE;
if (array_key_exists('#track_list', $element) && $element['#track_list']) {
$attributes['class'][] = "term-reference-tree-track-list-shown";
$add_track_list = TRUE;
}
if (!empty($element['#attributes']['class'])) {
$attributes['class'] = array_merge($attributes['class'], $element['#attributes']['class']);
}
return
'<div' . drupal_attributes($attributes) . '>'
. (!empty($element['#children']) ? $element['#children'] : '')
. '</div>';
}
/**
* This function prints a list item with a checkbox and an unordered list
* of all the elements inside it.
*/
function theme_checkbox_tree_level($variables) {
$element = $variables['element'];
$sm = '';
if (array_key_exists('#level_start_minimized', $element) && $element['#level_start_minimized']) {
$collapsed =
$sm = " style='display: none;'";
}
$max_choices = 0;
if (array_key_exists('#max_choices', $element)) {
$max_choices = $element['#max_choices'];
}
$output = "<ul class='term-reference-tree-level '$sm>";
$children = element_children($element);
foreach($children as $child) {
$output .= "<li>";
$output .= drupal_render($element[$child]);
$output .= "</li>";
}
$output .= "</ul>";
return $output;
}
/**
* This function prints a single item in the tree, followed by that item's children
* (which may be another checkbox_tree_level).
*/
function theme_checkbox_tree_item($variables) {
$element = $variables['element'];
$children = element_children($element);
$output = "";
$sm = $element['#level_start_minimized'] ? ' term-reference-tree-collapsed' : '';
if (is_array($children) && count($children) > 1) {
$output .= "<div class='term-reference-tree-button$sm'></div>";
}
elseif (!$element['#leaves_only']) {
$output .= "<div class='no-term-reference-tree-button'></div>";
}
foreach($children as $child) {
$output .= drupal_render($element[$child]);
}
return $output;
}
/**
* This function prints a label that cannot be selected.
*/
function theme_checkbox_tree_label($variables) {
$element = $variables['element'];
$output = "<div class='parent-term'>" . $element['#value'] . "</div>";
return $output;
}
/**
* Shows a list of items that have been checked.
* The display happens on the client-side.
* Use this function to theme the element's label,
* and the "nothing selected" message.
*
* @param $variables Variables available for theming.
*/
function theme_checkbox_tree_track_list($variables) {
//Should the label be singular or plural? Depends on cardinality of term field.
static $nothingselected;
// dsm($variables, 'theme_checkbox_tree_track_list : $variables');
if(!$nothingselected) {
$nothingselected = t('[Nothing selected]');
//Add the "Nothing selected" text. To style it, replace it with whatever you want.
//Could do this with a file instead.
drupal_add_js(
'var termReferenceTreeNothingSelectedText = "' . $nothingselected . '";',
'inline'
);
}
$label = format_plural(
$variables['element']['#max_choices'],
'Selected item (click the item to uncheck it)',
'Selected items (click an item to uncheck it)'
);
$order = $variables['element']['#track_list_order'] ? 'order-list' : '';
$output =
'<div class="term-reference-track-list-container">
<div class="term-reference-track-list-label">' . $label . '</div>
<ul class="term-reference-tree-track-list '.$order.'"><li class="term_ref_tree_nothing_message">'.$nothingselected.'</li></ul>
</div>';
return $output;
}
/**
* Implements hook_widget_field_form().
*/
function term_reference_tree_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {
$settings = $instance['widget']['settings'];
$voc = taxonomy_vocabulary_machine_name_load($field['settings']['allowed_values'][0]['vocabulary']);
$path = drupal_get_path('module', 'term_reference_tree');
$value_key = key($field['columns']);
$type = $instance['widget']['type'];
$default_value = array();
foreach($items as $item) {
$key = $item[$value_key];
if ($key === 0) {
$default_value[$key] = '0';
}
else {
$default_value[$key] = $key;
}
}
$multiple = $field['cardinality'] > 1 || $field['cardinality'] == FIELD_CARDINALITY_UNLIMITED;
$properties = array();
if (!array_key_exists('#value', $element))
$element['#value'] = array();
// A switch statement, in case we ever add more widgets to this module.
switch($instance['widget']['type']) {
case 'term_reference_tree':
$element['#attached']['js'] = array($path . '/term_reference_tree.js');
$element['#attached']['css'] = array($path . '/term_reference_tree.css');
$element['#type'] = 'checkbox_tree';
$element['#default_value'] = $multiple ? $default_value : array(reset($default_value) => reset($default_value));
$element['#max_choices'] = $field['cardinality'];
$element['#max_depth'] = $settings['max_depth'];
$element['#starting_depth'] = $settings['starting_depth'];
$element['#start_minimized'] = $settings['start_minimized'];
$element['#leaves_only'] = $settings['leaves_only'];
$element['#filter_view'] = module_exists('views') ? $settings['filter_view'] : '';
$element['#select_parents'] = $settings['select_parents'];
$element['#cascading_selection'] = $settings['cascading_selection'];
$element['#track_list'] = $settings['track_list'];
$element['#track_list_order'] = $settings['track_list_order'];
$element['#parent_tid'] = $settings['parent_term_id'] || $field['settings']['allowed_values'][0]['parent'];
$element['#vocabulary'] = $voc->vid;
$element['#token_display'] = module_exists('token') ? $settings['token_display'] : '';
break;
}
$element += array(
'#value_key' => $value_key,
'#element_validate' => array('_term_reference_tree_widget_validate'),
'#properties' => $properties,
);
if ($settings['track_list_order']) {
drupal_add_library('system', 'ui.sortable');
}
return $element;
}
/**
* Validates the term reference tree widgets.
*
* This function sets the value of the tree widgets into a form that Drupal
* can understand, and also checks if the field is required and has been
* left empty.
*
* @param $element
* The element to be validated.
* @param $form_state
* The state of the form.
*
* @return
* The validated element.
*/
function _term_reference_tree_widget_validate(&$element, &$form_state) {
// dsm($element, '$element');
// dsm($form_state, '$form_state');
# if the field was in the form
if(isset($form_state['input'][$element['#field_name']])){
$items = _term_reference_tree_flatten($element, $form_state);
$value = array();
if ($element['#max_choices'] != 1) {
if(!$element['#track_list_order']){
foreach($items as $child) {
if (array_key_exists('#value', $child) && $child['#value'] !== 0) {
array_push($value, array( $element['#value_key'] => $child['#value']));
// If the element is leaves only and select parents is on, then automatically
// add all the parents of each selected value.
if ($element['#select_parents'] && $element['#leaves_only']) {
foreach($child['#parent_values'] as $parent_tid) {
if (!in_array(array($element['#value_key'] => $parent_tid), $value)) {
array_push($value, array($element['#value_key'] => $parent_tid));
}
}
}
}
}
}else{
$selected_terms = array();
$deltas = array();
foreach($items as $child) {
if (array_key_exists('#value', $child) && $child['#value'] !== 0) {
$selected_terms[] = array(
"delta" => $form_state['input'][$child['#value'].'-weight'],
"term" => array($element['#value_key'] => $child['#value']),
);
// If the element is leaves only and select parents is on, then automatically
// add all the parents of each selected value.
if ($element['#select_parents'] && $element['#leaves_only']) {
foreach($child['#parent_values'] as $parent_tid) {
if (!in_array(array($element['#value_key'] => $parent_tid), $selected_terms)) {
$selected_terms[] = array(
"delta" => $form_state['input'][$parent_tid.'-weight'],
"term" => array($element['#value_key'] => $child['#value']),
);
}
}
}
}
}
// dsm($deltas, '$deltas');
// dsm($selected_terms, '$selected_terms before sort');
// reorder items
usort($selected_terms, function($a, $b){
return $a['delta'] > $b['delta'];
});
dsm($selected_terms, '$selected_terms after sort');
// record in value
foreach ($selected_terms as $selected_term) {
$value[] = $selected_term['term'];
}
}
}
else {
// If it's a tree of radio buttons, they all have the same value, so we can just
// grab the value of the first one.
if (count($items) > 0) {
$child = reset($items);
if (array_key_exists('#value', $child) && $child['#value'] !== 0) {
array_push($value, array($element['#value_key'] => $child['#value']));
}
}
}
if ($element['#required'] && empty($value)) {
// The title is already check_plained so it's appropriate to use !.
form_error($element, t('!name field is required.', array('!name' => $element['#title'])));
}
form_set_value($element, $value, $form_state);
// dsm($element, '$element afetr form_set_value');
return $element;
}else{
# if the field was not in the form
form_set_value($element, $element['#default_value'], $form_state);
// dsm($element, '$element afetr form_set_value');
return $element;
}
}
/**
* Returns an array of allowed values defined by the given view.
*
* @param $filter
* A view, in the format VIEWNAME:DISPLAYNAME
*
* @return
* An array of term IDs (tid => true) returned by the view.
*/
function _term_reference_tree_get_allowed_values($filter) {
$viewname = "";
$displayname = "";
$allowed = array();
if (module_exists('views') && $filter != '') {
list($viewname, $displayname) = explode(":", $filter);
$view = views_get_view($viewname);
if (is_object($view)) {
if ($view->access($displayname)) {
// Save the page title first, since execute_display() will reset this to the display title.
$title = drupal_get_title();
$view->execute_display($displayname);
$title = drupal_set_title($title, PASS_THROUGH);
foreach($view->result as $item) {
$allowed[$item->tid] = true;
}
}
else {
drupal_set_message("Cannot access view for term reference tree widget.", 'warning');
}
}
else {
drupal_set_message("Term reference tree: no view named '$viewname'", 'warning');
}
}
return $allowed;
}
/**
* Builds a single item in the term reference tree widget.
*
* This function returns an element with a checkbox for a single taxonomy term.
* If that term has children, it appends checkbox_tree_level element that
* contains the children. It is meant to be called recursively when the widget
* is built.
*
* @param $element
* The main checkbox_tree element.
* @param $term
* A taxonomy term object. $term->children should be an array of the term
* objects that are that term's children.
* @param $form_state
* The form state.
* @param $value
* The value of the element.
* @param $max_choices
* The maximum number of allowed selections.
*
* @return
* A completed checkbox_tree_item element, which contains a checkbox and
* possibly a checkbox_tree_level element as well.
*/
function _term_reference_tree_build_item(&$element, &$term, &$form_state, &$value, $max_choices, $starting_depth, $parent_tids, $parent, $depth) {
$start_minimized = FALSE;
if (array_key_exists('#start_minimized', $element)) {
$start_minimized = $element['#start_minimized'];
}
$leaves_only = FALSE;
if (array_key_exists('#leaves_only', $element)) {
$leaves_only = $element['#leaves_only'];
}
$t = null;
if(module_exists('locale')) {
$t = taxonomy_term_load($term->tid);
$term_name = entity_label('taxonomy_term', $t);
} else {
$term_name = $term->name;
}
$container = array(
'#type' => 'checkbox_tree_item',
'#max_choices' => $max_choices,
'#leaves_only' => $leaves_only,
'#term_name' => $term_name,
'#level_start_minimized' => FALSE,
'#depth' => $depth,
);
if ((!$element['#leaves_only'] || count($term->children) == 0) && $depth >= $element['#starting_depth']) {
$name = "edit-" . str_replace('_', '-', $element['#field_name']);
$e = array(
'#type' => ($max_choices == 1) ? 'radio' : 'checkbox',
'#title' => $term_name,
'#on_value' => $term->tid,
'#off_value' => 0,
'#return_value' => $term->tid,
'#parent_values' => $parent_tids,
'#default_value' => isset($value[$term->tid]) ? $term->tid : NULL,
'#attributes' => isset($element['#attributes']) ? $element['#attributes'] : NULL,
'#ajax' => isset($element['#ajax']) ? $element['#ajax'] : NULL,
);
if ($element['#token_display'] != '' && module_exists('token')) {
if(!$t) {
$t = taxonomy_term_load($term->tid);
}
$e['#title'] = token_replace($element['#token_display'], array('term' => $t), array('clear' => TRUE));
}
if ($e['#type'] == 'radio') {
$parents_for_id = array_merge($element['#parents'], array($term->tid));
$e['#id'] = drupal_html_id('edit-' . implode('-', $parents_for_id));
$e['#parents'] = $element['#parents'];
}else if($element['#track_list_order']){
$delta = 0;
$i = -1;
if(isset($value[$term->tid])){
foreach ($value as $tid) {
$i++;
if($term->tid == $tid)
break;
}
}
$e_weight = array(
'#type' => 'hidden',
'#value' => $i,
'#name' => $term->tid.'-weight',
);
}
}
else {
$e = array(
'#type' => 'checkbox_tree_label',
'#value' => $term_name,
);
}
$container[$term->tid] = $e;
if(isset($e_weight)){
$container[$term->tid.'-weight'] = $e_weight;
}
if (($depth + 1 <= $element['#max_depth'] || !$element['#max_depth']) && property_exists($term, 'children') && count($term->children) > 0) {
$parents = $parent_tids;
$parents[] = $term->tid;
$container[$term->tid . '-children'] = _term_reference_tree_build_level($element, $term, $form_state, $value, $max_choices, $starting_depth, $parents, $depth+1);
$container['#level_start_minimized'] = $container[$term->tid . '-children']['#level_start_minimized'];
}
return $container;
}
/**
* Builds a level in the term reference tree widget.
*
* This function returns an element that has a number of checkbox_tree_item elements
* as children. It is meant to be called recursively when the widget is built.
*
* @param $element
* The main checkbox_tree element.
* @param $term
* A taxonomy term object. $term->children should be an array of the term
* objects that are that term's children.
* @param $form_state
* The form state.
* @param $value
* The value of the element.
* @param $max_choices
* The maximum number of allowed selections.
*
* @return
* A completed checkbox_tree_level element.
*/
function _term_reference_tree_build_level(&$element, &$term, &$form_state, &$value, $max_choices, $starting_depth, $parent_tids, $depth) {
$start_minimized = FALSE;
if (array_key_exists('#start_minimized', $element)) {
$start_minimized = $element['#start_minimized'];
}
$leaves_only = FALSE;
if (array_key_exists('#leaves_only', $element)) {
$leaves_only = $element['#leaves_only'];
}
$container = array(
'#type' => 'checkbox_tree_level',
'#max_choices' => $max_choices,
'#leaves_only' => $leaves_only,
'#start_minimized' => $start_minimized,
'#depth' => $depth,
);
$container['#level_start_minimized'] = $depth > 1 && $element['#start_minimized'] && !($term->children_selected);
foreach($term->children as $t) {
$container[$t->tid] = _term_reference_tree_build_item($element, $t, $form_state, $value, $max_choices, $starting_depth, $parent_tids, $container, $depth);
}
return $container;
}
+114
View File
@@ -0,0 +1,114 @@
<?php
/**
* @file
* Contains EntityReferenceHandlersTestCase
*/
/**
* Test for Entity Reference admin UI.
*/
class EntityReferenceAdminTestCase extends DrupalWebTestCase {
public static function getInfo() {
return array(
'name' => 'Entity Reference UI',
'description' => 'Tests for the administrative UI.',
'group' => 'Entity Reference',
);
}
public function setUp() {
parent::setUp(array('field_ui', 'entity', 'ctools', 'entityreference'));
// Create test user.
$this->admin_user = $this->drupalCreateUser(array('access content', 'administer content types'));
$this->drupalLogin($this->admin_user);
// Create content type, with underscores.
$type_name = strtolower($this->randomName(8)) . '_test';
$type = $this->drupalCreateContentType(array('name' => $type_name, 'type' => $type_name));
$this->type = $type->type;
// Store a valid URL name, with hyphens instead of underscores.
$this->hyphen_type = str_replace('_', '-', $this->type);
}
protected function assertFieldSelectOptions($name, $expected_options) {
$xpath = $this->buildXPathQuery('//select[@name=:name]', array(':name' => $name));
$fields = $this->xpath($xpath);
if ($fields) {
$field = $fields[0];
$options = $this->getAllOptionsList($field);
return $this->assertIdentical($options, $expected_options);
}
else {
return $this->fail(t('Unable to find field @name', array('@name' => $name)));
}
}
/**
* Extract all the options of a select element.
*/
protected function getAllOptionsList($element) {
$options = array();
// Add all options items.
foreach ($element->option as $option) {
$options[] = (string) $option['value'];
}
// TODO: support optgroup.
return $options;
}
public function testFieldAdminHandler() {
$bundle_path = 'admin/structure/types/manage/' . $this->hyphen_type;
// First step: 'Add new field' on the 'Manage fields' page.
$this->drupalPost($bundle_path . '/fields', array(
'fields[_add_new_field][label]' => 'Test label',
'fields[_add_new_field][field_name]' => 'test',
'fields[_add_new_field][type]' => 'entityreference',
'fields[_add_new_field][widget_type]' => 'entityreference_autocomplete',
), t('Save'));
// Node should be selected by default.
$this->assertFieldByName('field[settings][target_type]', 'node');
// The base handler should be selected by default.
$this->assertFieldByName('field[settings][handler]', 'base');
// The base handler settings should be diplayed.
$entity_type = 'node';
$entity_info = entity_get_info($entity_type);
foreach ($entity_info['bundles'] as $bundle_name => $bundle_info) {
$this->assertFieldByName('field[settings][handler_settings][target_bundles][' . $bundle_name . ']');
}
// Test the sort settings.
$options = array('none', 'property', 'field');
$this->assertFieldSelectOptions('field[settings][handler_settings][sort][type]', $options);
// Option 0: no sort.
$this->assertFieldByName('field[settings][handler_settings][sort][type]', 'none');
$this->assertNoFieldByName('field[settings][handler_settings][sort][property]');
$this->assertNoFieldByName('field[settings][handler_settings][sort][field]');
$this->assertNoFieldByName('field[settings][handler_settings][sort][direction]');
// Option 1: sort by property.
$this->drupalPostAJAX(NULL, array('field[settings][handler_settings][sort][type]' => 'property'), 'field[settings][handler_settings][sort][type]');
$this->assertFieldByName('field[settings][handler_settings][sort][property]', '');
$this->assertNoFieldByName('field[settings][handler_settings][sort][field]');
$this->assertFieldByName('field[settings][handler_settings][sort][direction]', 'ASC');
// Option 2: sort by field.
$this->drupalPostAJAX(NULL, array('field[settings][handler_settings][sort][type]' => 'field'), 'field[settings][handler_settings][sort][type]');
$this->assertNoFieldByName('field[settings][handler_settings][sort][property]');
$this->assertFieldByName('field[settings][handler_settings][sort][field]', '');
$this->assertFieldByName('field[settings][handler_settings][sort][direction]', 'ASC');
// Set back to no sort.
$this->drupalPostAJAX(NULL, array('field[settings][handler_settings][sort][type]' => 'none'), 'field[settings][handler_settings][sort][type]');
// Second step: 'Instance settings' form.
$this->drupalPost(NULL, array(), t('Save field settings'));
// Third step: confirm.
$this->drupalPost(NULL, array(), t('Save settings'));
// Check that the field appears in the overview form.
$this->assertFieldByXPath('//table[@id="field-overview"]//td[1]', 'Test label', t('Field was created and appears in the overview page.'));
}
}
+426
View File
@@ -0,0 +1,426 @@
<?php
/**
* @file
* Contains EntityReferenceHandlersTestCase
*/
/**
* Test for Entity Reference handlers.
*/
class EntityReferenceHandlersTestCase extends DrupalWebTestCase {
public static function getInfo() {
return array(
'name' => 'Entity Reference Handlers',
'description' => 'Tests for the base handlers provided by Entity Reference.',
'group' => 'Entity Reference',
);
}
public function setUp() {
parent::setUp('entityreference');
}
protected function assertReferencable($field, $tests, $handler_name) {
$handler = entityreference_get_selection_handler($field);
foreach ($tests as $test) {
foreach ($test['arguments'] as $arguments) {
$result = call_user_func_array(array($handler, 'getReferencableEntities'), $arguments);
$this->assertEqual($result, $test['result'], t('Valid result set returned by @handler.', array('@handler' => $handler_name)));
$result = call_user_func_array(array($handler, 'countReferencableEntities'), $arguments);
$this->assertEqual($result, count($test['result']), t('Valid count returned by @handler.', array('@handler' => $handler_name)));
}
}
}
/**
* Test the node-specific overrides of the entity handler.
*/
public function testNodeHandler() {
// Build a fake field instance.
$field = array(
'translatable' => FALSE,
'entity_types' => array(),
'settings' => array(
'handler' => 'base',
'target_type' => 'node',
'handler_settings' => array(
'target_bundles' => array(),
),
),
'field_name' => 'test_field',
'type' => 'entityreference',
'cardinality' => '1',
);
// Build a set of test data.
// Titles contain HTML-special characters to test escaping.
$nodes = array(
'published1' => (object) array(
'type' => 'article',
'status' => 1,
'title' => 'Node published1 (<&>)',
'uid' => 1,
),
'published2' => (object) array(
'type' => 'article',
'status' => 1,
'title' => 'Node published2 (<&>)',
'uid' => 1,
),
'unpublished' => (object) array(
'type' => 'article',
'status' => 0,
'title' => 'Node unpublished (<&>)',
'uid' => 1,
),
);
$node_labels = array();
foreach ($nodes as $key => $node) {
node_save($node);
$node_labels[$key] = check_plain($node->title);
}
// Test as a non-admin.
$normal_user = $this->drupalCreateUser(array('access content'));
$GLOBALS['user'] = $normal_user;
$referencable_tests = array(
array(
'arguments' => array(
array(NULL, 'CONTAINS'),
),
'result' => array(
$nodes['published1']->nid => $node_labels['published1'],
$nodes['published2']->nid => $node_labels['published2'],
),
),
array(
'arguments' => array(
array('published1', 'CONTAINS'),
array('Published1', 'CONTAINS'),
),
'result' => array(
$nodes['published1']->nid => $node_labels['published1'],
),
),
array(
'arguments' => array(
array('published2', 'CONTAINS'),
array('Published2', 'CONTAINS'),
),
'result' => array(
$nodes['published2']->nid => $node_labels['published2'],
),
),
array(
'arguments' => array(
array('invalid node', 'CONTAINS'),
),
'result' => array(),
),
array(
'arguments' => array(
array('Node unpublished', 'CONTAINS'),
),
'result' => array(),
),
);
$this->assertReferencable($field, $referencable_tests, 'Node handler');
// Test as an admin.
$admin_user = $this->drupalCreateUser(array('access content', 'bypass node access'));
$GLOBALS['user'] = $admin_user;
$referencable_tests = array(
array(
'arguments' => array(
array(NULL, 'CONTAINS'),
),
'result' => array(
$nodes['published1']->nid => $node_labels['published1'],
$nodes['published2']->nid => $node_labels['published2'],
$nodes['unpublished']->nid => $node_labels['unpublished'],
),
),
array(
'arguments' => array(
array('Node unpublished', 'CONTAINS'),
),
'result' => array(
$nodes['unpublished']->nid => $node_labels['unpublished'],
),
),
);
$this->assertReferencable($field, $referencable_tests, 'Node handler (admin)');
}
/**
* Test the user-specific overrides of the entity handler.
*/
public function testUserHandler() {
// Build a fake field instance.
$field = array(
'translatable' => FALSE,
'entity_types' => array(),
'settings' => array(
'handler' => 'base',
'target_type' => 'user',
'handler_settings' => array(
'target_bundles' => array(),
),
),
'field_name' => 'test_field',
'type' => 'entityreference',
'cardinality' => '1',
);
// Build a set of test data.
$users = array(
'anonymous' => user_load(0),
'admin' => user_load(1),
'non_admin' => (object) array(
'name' => 'non_admin <&>',
'mail' => 'non_admin@example.com',
'roles' => array(),
'pass' => user_password(),
'status' => 1,
),
'blocked' => (object) array(
'name' => 'blocked <&>',
'mail' => 'blocked@example.com',
'roles' => array(),
'pass' => user_password(),
'status' => 0,
),
);
// The label of the anonymous user is variable_get('anonymous').
$users['anonymous']->name = variable_get('anonymous', t('Anonymous'));
$user_labels = array();
foreach ($users as $key => $user) {
if (!isset($user->uid)) {
$users[$key] = $user = user_save(drupal_anonymous_user(), (array) $user);
}
$user_labels[$key] = check_plain($user->name);
}
// Test as a non-admin.
$GLOBALS['user'] = $users['non_admin'];
$referencable_tests = array(
array(
'arguments' => array(
array(NULL, 'CONTAINS'),
),
'result' => array(
$users['admin']->uid => $user_labels['admin'],
$users['non_admin']->uid => $user_labels['non_admin'],
),
),
array(
'arguments' => array(
array('non_admin', 'CONTAINS'),
array('NON_ADMIN', 'CONTAINS'),
),
'result' => array(
$users['non_admin']->uid => $user_labels['non_admin'],
),
),
array(
'arguments' => array(
array('invalid user', 'CONTAINS'),
),
'result' => array(),
),
array(
'arguments' => array(
array('blocked', 'CONTAINS'),
),
'result' => array(),
),
);
$this->assertReferencable($field, $referencable_tests, 'User handler');
$GLOBALS['user'] = $users['admin'];
$referencable_tests = array(
array(
'arguments' => array(
array(NULL, 'CONTAINS'),
),
'result' => array(
$users['anonymous']->uid => $user_labels['anonymous'],
$users['admin']->uid => $user_labels['admin'],
$users['non_admin']->uid => $user_labels['non_admin'],
$users['blocked']->uid => $user_labels['blocked'],
),
),
array(
'arguments' => array(
array('blocked', 'CONTAINS'),
),
'result' => array(
$users['blocked']->uid => $user_labels['blocked'],
),
),
array(
'arguments' => array(
array('Anonymous', 'CONTAINS'),
array('anonymous', 'CONTAINS'),
),
'result' => array(
$users['anonymous']->uid => $user_labels['anonymous'],
),
),
);
$this->assertReferencable($field, $referencable_tests, 'User handler (admin)');
}
/**
* Test the comment-specific overrides of the entity handler.
*/
public function testCommentHandler() {
// Build a fake field instance.
$field = array(
'translatable' => FALSE,
'entity_types' => array(),
'settings' => array(
'handler' => 'base',
'target_type' => 'comment',
'handler_settings' => array(
'target_bundles' => array(),
),
),
'field_name' => 'test_field',
'type' => 'entityreference',
'cardinality' => '1',
);
// Build a set of test data.
$nodes = array(
'published' => (object) array(
'type' => 'article',
'status' => 1,
'title' => 'Node published',
'uid' => 1,
),
'unpublished' => (object) array(
'type' => 'article',
'status' => 0,
'title' => 'Node unpublished',
'uid' => 1,
),
);
foreach ($nodes as $node) {
node_save($node);
}
$comments = array(
'published_published' => (object) array(
'nid' => $nodes['published']->nid,
'uid' => 1,
'cid' => NULL,
'pid' => 0,
'status' => COMMENT_PUBLISHED,
'subject' => 'Comment Published <&>',
'hostname' => ip_address(),
'language' => LANGUAGE_NONE,
),
'published_unpublished' => (object) array(
'nid' => $nodes['published']->nid,
'uid' => 1,
'cid' => NULL,
'pid' => 0,
'status' => COMMENT_NOT_PUBLISHED,
'subject' => 'Comment Unpublished <&>',
'hostname' => ip_address(),
'language' => LANGUAGE_NONE,
),
'unpublished_published' => (object) array(
'nid' => $nodes['unpublished']->nid,
'uid' => 1,
'cid' => NULL,
'pid' => 0,
'status' => COMMENT_NOT_PUBLISHED,
'subject' => 'Comment Published on Unpublished node <&>',
'hostname' => ip_address(),
'language' => LANGUAGE_NONE,
),
);
$comment_labels = array();
foreach ($comments as $key => $comment) {
comment_save($comment);
$comment_labels[$key] = check_plain($comment->subject);
}
// Test as a non-admin.
$normal_user = $this->drupalCreateUser(array('access content', 'access comments'));
$GLOBALS['user'] = $normal_user;
$referencable_tests = array(
array(
'arguments' => array(
array(NULL, 'CONTAINS'),
),
'result' => array(
$comments['published_published']->cid => $comment_labels['published_published'],
),
),
array(
'arguments' => array(
array('Published', 'CONTAINS'),
),
'result' => array(
$comments['published_published']->cid => $comment_labels['published_published'],
),
),
array(
'arguments' => array(
array('invalid comment', 'CONTAINS'),
),
'result' => array(),
),
array(
'arguments' => array(
array('Comment Unpublished', 'CONTAINS'),
),
'result' => array(),
),
);
$this->assertReferencable($field, $referencable_tests, 'Comment handler');
// Test as a comment admin.
$admin_user = $this->drupalCreateUser(array('access content', 'access comments', 'administer comments'));
$GLOBALS['user'] = $admin_user;
$referencable_tests = array(
array(
'arguments' => array(
array(NULL, 'CONTAINS'),
),
'result' => array(
$comments['published_published']->cid => $comment_labels['published_published'],
$comments['published_unpublished']->cid => $comment_labels['published_unpublished'],
),
),
);
$this->assertReferencable($field, $referencable_tests, 'Comment handler (comment admin)');
// Test as a node and comment admin.
$admin_user = $this->drupalCreateUser(array('access content', 'access comments', 'administer comments', 'bypass node access'));
$GLOBALS['user'] = $admin_user;
$referencable_tests = array(
array(
'arguments' => array(
array(NULL, 'CONTAINS'),
),
'result' => array(
$comments['published_published']->cid => $comment_labels['published_published'],
$comments['published_unpublished']->cid => $comment_labels['published_unpublished'],
$comments['unpublished_published']->cid => $comment_labels['unpublished_published'],
),
),
);
$this->assertReferencable($field, $referencable_tests, 'Comment handler (comment + node admin)');
}
}
+136
View File
@@ -0,0 +1,136 @@
<?php
/**
* @file
* Views integration for Entity Reference.
*/
/**
* Implements hook_field_views_data().
*/
function entityreference_field_views_data($field) {
$data = field_views_field_default_views_data($field);
$entity_info = entity_get_info($field['settings']['target_type']);
foreach ($data as $table_name => $table_data) {
if (isset($entity_info['base table'])) {
$entity = $entity_info['label'];
if ($entity == t('Node')) {
$entity = t('Content');
}
$field_name = $field['field_name'] . '_target_id';
$parameters = array('@entity' => $entity, '!field_name' => $field['field_name']);
$data[$table_name][$field_name]['relationship'] = array(
'handler' => 'views_handler_relationship',
'base' => $entity_info['base table'],
'base field' => $entity_info['entity keys']['id'],
'label' => t('@entity entity referenced from !field_name', $parameters),
'group' => t('Entity Reference'),
'title' => t('Referenced Entity'),
'help' => t('A bridge to the @entity entity that is referenced via !field_name', $parameters),
);
}
}
// Invoke the behaviors to allow them to change the properties.
foreach (entityreference_get_behavior_handlers($field) as $handler) {
$handler->views_data_alter($data, $field);
}
return $data;
}
/**
* Options callback for Views handler views_handler_filter_in_operator.
*/
function entityreference_views_handler_options_list($field_name) {
$field = field_info_field($field_name);
return entityreference_options_list($field);
}
/**
* Implements hook_field_views_data_views_data_alter().
*
* Views integration to provide reverse relationships on entityreference fields.
*/
function entityreference_field_views_data_views_data_alter(&$data, $field) {
foreach ($field['bundles'] as $entity_type => $bundles) {
$target_entity_info = entity_get_info($field['settings']['target_type']);
if (isset($target_entity_info['base table'])) {
$entity_info = entity_get_info($entity_type);
$entity = $entity_info['label'];
if ($entity == t('Node')) {
$entity = t('Content');
}
$target_entity = $target_entity_info['label'];
if ($target_entity == t('Node')) {
$target_entity = t('Content');
}
$pseudo_field_name = 'reverse_' . $field['field_name'] . '_' . $entity_type;
$replacements = array('@entity' => $entity, '@target_entity' => $target_entity, '!field_name' => $field['field_name']);
$data[$target_entity_info['base table']][$pseudo_field_name]['relationship'] = array(
'handler' => 'views_handler_relationship_entity_reverse',
'field_name' => $field['field_name'],
'field table' => _field_sql_storage_tablename($field),
'field field' => $field['field_name'] . '_target_id',
'base' => $entity_info['base table'],
'base field' => $entity_info['entity keys']['id'],
'label' => t('@entity referencing @target_entity from !field_name', $replacements),
'group' => t('Entity Reference'),
'title' => t('Referencing entity'),
'help' => t('A bridge to the @entity entity that is referencing @target_entity via !field_name', $replacements),
);
}
}
}
/**
* Implements hook_views_plugins().
*/
function entityreference_views_plugins() {
$plugins = array(
'display' => array(
'entityreference' => array(
'title' => t('Entity Reference'),
'admin' => t('Entity Reference Source'),
'help' => 'Selects referenceable entities for an entity reference field',
'handler' => 'entityreference_plugin_display',
'uses hook menu' => FALSE,
'use ajax' => FALSE,
'use pager' => FALSE,
'accept attachments' => FALSE,
// Custom property, used with views_get_applicable_views() to retrieve
// all views with a 'Entity Reference' display.
'entityreference display' => TRUE,
),
),
'style' => array(
'entityreference_style' => array(
'title' => t('Entity Reference list'),
'help' => 'Returns results as a PHP array of labels and rendered rows.',
'handler' => 'entityreference_plugin_style',
'theme' => 'views_view_unformatted',
'uses row plugin' => TRUE,
'uses fields' => TRUE,
'uses options' => TRUE,
'type' => 'entityreference',
'even empty' => TRUE,
),
),
'row' => array(
'entityreference_fields' => array(
'title' => t('Inline fields'),
'help' => t('Displays the fields with an optional template.'),
'handler' => 'entityreference_plugin_row_fields',
'theme' => 'views_view_fields',
'theme path' => drupal_get_path('module', 'views') . '/theme',
'theme file' => 'theme.inc',
'uses fields' => TRUE,
'uses options' => TRUE,
'type' => 'entityreference',
),
),
);
return $plugins;
}
+118
View File
@@ -0,0 +1,118 @@
<?php
/**
* @file
* Handler for entityreference_plugin_display.
*/
class entityreference_plugin_display extends views_plugin_display {
function option_definition() {
$options = parent::option_definition();
// Force the style plugin to 'entityreference_style' and the row plugin to
// 'fields'.
$options['style_plugin']['default'] = 'entityreference_style';
$options['defaults']['default']['style_plugin'] = FALSE;
$options['defaults']['default']['style_options'] = FALSE;
$options['row_plugin']['default'] = 'entityreference_fields';
$options['defaults']['default']['row_plugin'] = FALSE;
$options['defaults']['default']['row_options'] = FALSE;
// Set the display title to an empty string (not used in this display type).
$options['title']['default'] = '';
$options['defaults']['default']['title'] = FALSE;
return $options;
}
function get_style_type() {
return 'entityreference';
}
function execute() {
return $this->view->render($this->display->id);
}
function render() {
if (!empty($this->view->result) || !empty($this->view->style_plugin->definition['even empty'])) {
return $this->view->style_plugin->render($this->view->result);
}
return '';
}
function uses_exposed() {
return FALSE;
}
function query() {
$options = $this->get_option('entityreference_options');
// Play nice with Views UI 'preview' : if the view is not executed through
// EntityReference_SelectionHandler_Views::getReferencableEntities(),
// don't alter the query.
if (empty($options)) {
return;
}
// Make sure the id field is included in the results, and save its alias
// so that references_plugin_style can retrieve it.
$this->id_field_alias = $id_field = $this->view->query->add_field($this->view->base_table, $this->view->base_field);
if (strpos($id_field, '.') === FALSE) {
$id_field = $this->view->base_table . '.' . $this->id_field_alias;
}
// Restrict the autocomplete options based on what's been typed already.
if (isset($options['match'])) {
$style_options = $this->get_option('style_options');
$value = db_like($options['match']) . '%';
if ($options['match_operator'] != 'STARTS_WITH') {
$value = '%' . $value;
}
// Multiple search fields are OR'd together
$conditions = db_or();
// Build the condition using the selected search fields
foreach ($style_options['search_fields'] as $field_alias) {
if (!empty($field_alias)) {
// Get the table and field names for the checked field
$field = $this->view->query->fields[$this->view->field[$field_alias]->field_alias];
// Add an OR condition for the field
$conditions->condition($field['table'] . '.' . $field['field'], $value, 'LIKE');
}
}
$this->view->query->add_where(NULL, $conditions);
}
// Add an IN condition for validation.
if (!empty($options['ids'])) {
$this->view->query->add_where(NULL, $id_field, $options['ids']);
}
$this->view->set_items_per_page($options['limit']);
}
/**
* Extend the default validation.
*/
function validate() {
$errors = parent::validate();
// Verify that search fields are set up.
$style_options = $this->get_option('style_options');
if (!isset($style_options['search_fields'])) {
$errors[] = t('Display "@display" needs a selected search fields to work properly. See the settings for the Entity Reference list format.', array('@display' => $this->display->display_title));
}
else {
// Verify that the search fields used actually exist.
//$fields = array_keys($this->view->get_items('field'));
$fields = array_keys($this->handlers['field']);
foreach ($style_options['search_fields'] as $field_alias => $enabled) {
if ($enabled && !in_array($field_alias, $fields)) {
$errors[] = t('Display "@display" uses field %field as search field, but the field is no longer present. See the settings for the Entity Reference list format.', array('@display' => $this->display->display_title, '%field' => $field_alias));
}
}
}
return $errors;
}
}
@@ -0,0 +1,36 @@
<?php
/**
* @file
* Handler for entityreference_plugin_row_fields.
*/
class entityreference_plugin_row_fields extends views_plugin_row_fields {
function option_definition() {
$options = parent::option_definition();
$options['separator'] = array('default' => '-');
return $options;
}
/**
* Provide a form for setting options.
*/
function options_form(&$form, &$form_state) {
parent::options_form($form, $form_state);
// Expand the description of the 'Inline field' checkboxes.
$form['inline']['#description'] .= '<br />' . t("<strong>Note:</strong> In 'Entity Reference' displays, all fields will be displayed inline unless an explicit selection of inline fields is made here." );
}
function pre_render($row) {
// Force all fields to be inline by default.
if (empty($this->options['inline'])) {
$fields = $this->view->get_items('field', $this->display->id);
$this->options['inline'] = drupal_map_assoc(array_keys($fields));
}
return parent::pre_render($row);
}
}
+66
View File
@@ -0,0 +1,66 @@
<?php
/**
* @file
* Handler for entityreference_plugin_style.
*/
class entityreference_plugin_style extends views_plugin_style {
function option_definition() {
$options = parent::option_definition();
$options['search_fields'] = array('default' => NULL);
return $options;
}
// Create the options form.
function options_form(&$form, &$form_state) {
parent::options_form($form, $form_state);
$options = array();
if (isset($form['grouping'])) {
$options = $form['grouping'][0]['field']['#options'];
unset($options['']);
$form['search_fields'] = array(
'#type' => 'checkboxes',
'#title' => t('Search fields'),
'#options' => $options,
'#required' => TRUE,
'#default_value' => $this->options['search_fields'],
'#description' => t('Select the field(s) that will be searched when using the autocomplete widget.'),
'#weight' => -3,
);
}
}
function render() {
$options = $this->display->handler->get_option('entityreference_options');
// Play nice with Views UI 'preview' : if the view is not executed through
// EntityReference_SelectionHandler_Views::getReferencableEntities(), just
// display the HTML.
if (empty($options)) {
return parent::render();
}
// Group the rows according to the grouping field, if specified.
$sets = $this->render_grouping($this->view->result, $this->options['grouping']);
// Grab the alias of the 'id' field added by entityreference_plugin_display.
$id_field_alias = $this->display->handler->id_field_alias;
// @todo We don't display grouping info for now. Could be useful for select
// widget, though.
$results = array();
$this->view->row_index = 0;
foreach ($sets as $records) {
foreach ($records as $values) {
// Sanitize html, remove line breaks and extra whitespace.
$results[$values->{$id_field_alias}] = filter_xss_admin(preg_replace('/\s\s+/', ' ', str_replace("\n", '', $this->row_plugin->render($values))));
$this->view->row_index++;
}
}
unset($this->view->row_index);
return $results;
}
}