entityreference_autocreate.module in Entityreference Autocreate 7
Intercepts entityreference autocomplete submission validation and creates a target node on the fly if it doesn't yet exist.
Config: Edit your entityreference field settings, and choose 'Autocreate if not found'.
File
entityreference_autocreate.moduleView source
<?php
/**
* @file
* Intercepts entityreference autocomplete submission validation and
* creates a target node on the fly if it doesn't yet exist.
*
* Config:
* Edit your entityreference field settings, and choose
* 'Autocreate if not found'.
*/
/**
* Implements hook_widget_info_alter().
*
* Adds settings that we need to declare to widgets we are extending.
* Need this so that the settings we add to the widget settings form get saved.
*/
function entityreference_autocreate_field_widget_info_alter(array &$info) {
$info['entityreference_autocomplete']['settings']['entityreference_autocreate'] = 0;
$info['entityreference_autocomplete_tags']['settings']['entityreference_autocreate'] = 0;
}
/**
* Adds our extra option to the field widget settings form.
*
* eg at
* /admin/structure/types/manage/{CONTENT_TYPE}/fields/{ENTITYREFERENCE_FIELD}
*
* Implements hook_form_FORMID_alter().
*/
function entityreference_autocreate_form_field_ui_field_edit_form_alter(&$form, $form_state) {
$supported_widgets = array(
'entityreference_autocomplete',
'entityreference_autocomplete_tags',
);
if (in_array($form['instance']['widget']['type']['#value'], $supported_widgets)) {
// $field = $form['#field'];
$instance = field_info_instance($form['instance']['entity_type']['#value'], $form['instance']['field_name']['#value'], $form['instance']['bundle']['#value']);
$widget = $instance['widget'];
$defaults = field_info_widget_settings($widget['type']);
$settings = array_merge($defaults, $widget['settings']);
$form['instance']['widget']['settings']['entityreference_autocreate'] = array(
'#type' => 'fieldset',
'#title' => t('EntityReference Autocreate settings'),
'#collapsible' => TRUE,
);
$form['instance']['widget']['settings']['entityreference_autocreate']['active'] = array(
'#type' => 'checkbox',
'#title' => t('Autocreate target if not found'),
'#description' => t('This will replace the normal validation that checks to see if a target item exists with code that just makes up whatever it was you asked for.'),
'#default_value' => isset($settings['entityreference_autocreate']['active']) ? $settings['entityreference_autocreate']['active'] : FALSE,
);
// Use form API UI field visibility toggles to conditionally show the rest.
// The following options only apply to 'node' entities AFAIK.
// So hide them when inappropriate.
$form['instance']['widget']['settings']['entityreference_autocreate']['author_current_user'] = array(
'#type' => 'checkbox',
'#title' => t('Author current user'),
'#description' => t('This will set the current user as author of the new entities. Uncheck to get more options.'),
'#default_value' => isset($settings['entityreference_autocreate']['author_current_user']) ? $settings['entityreference_autocreate']['author_current_user'] : TRUE,
'#states' => array(
'visible' => array(
'#edit-instance-widget-settings-entityreference-autocreate-active' => array(
'checked' => TRUE,
),
'#edit-field-settings-target-type' => array(
'value' => 'node',
),
),
),
);
$form['instance']['widget']['settings']['entityreference_autocreate']['author'] = array(
'#type' => 'textfield',
'#title' => t('Authored by'),
'#maxlength' => 60,
'#autocomplete_path' => 'user/autocomplete',
'#default_value' => isset($settings['entityreference_autocreate']['author']) ? $settings['entityreference_autocreate']['author'] : '',
'#description' => t('This will set the chosen user as author of the new entities. Leave blank for %anonymous.', array(
'%anonymous' => variable_get('anonymous', t('Anonymous')),
)),
'#states' => array(
'visible' => array(
'#edit-instance-widget-settings-entityreference-autocreate-author-current-user' => array(
'checked' => FALSE,
),
'#edit-field-settings-target-type' => array(
'value' => 'node',
),
),
),
);
$form['instance']['widget']['settings']['entityreference_autocreate']['status'] = array(
'#type' => 'select',
'#title' => t('Published status'),
'#options' => array(
-1 => 'Bundle default',
1 => 'Published',
0 => 'Unpublished',
),
'#default_value' => isset($settings['entityreference_autocreate']['status']) ? $settings['entityreference_autocreate']['status'] : -1,
'#states' => array(
'visible' => array(
'#edit-instance-widget-settings-entityreference-autocreate-active' => array(
'checked' => TRUE,
),
'#edit-field-settings-target-type' => array(
'value' => 'node',
),
),
),
);
$form['instance']['widget']['settings']['entityreference_autocreate']['help'] = array(
'#markup' => t('Autocreation can only work if there is exactly one <b>Target bundle</b> selected in "<b>Entity Selection</b>" below.'),
);
// If using views, it's basically impossible to autodetect the type:bundle.
// Will have to let the admin do it manually.
// This means enumerating every possibility, and letting them select.
$options = array();
foreach (entity_get_info() as $entity_type => $entity_info) {
foreach ($entity_info['bundles'] as $bundle_id => $bundle_info) {
$options[$entity_info['label']][$bundle_id] = $bundle_info['label'];
}
}
$form['instance']['widget']['settings']['entityreference_autocreate']['bundle'] = array(
'#type' => 'select',
'#title' => t('Bundle'),
'#description' => t('If using views lookups, you need to define me what <em>type</em> of thing to create. Warning - results can be unreliable!'),
'#options' => $options,
'#default_value' => isset($settings['entityreference_autocreate']['bundle']) ? $settings['entityreference_autocreate']['bundle'] : '',
'#states' => array(
'visible' => array(
':input[name="field[settings][handler]"]' => array(
'value' => 'views',
),
),
),
);
}
}
/**
* Adjust the behaviour of entityreference autocomplete widgets.
*
* Replaces the normal validation that prevents linking to imaginary entities
* with our own, which makes it on the fly if needed.
*
* hook_field_widget_form_alter()
*/
function entityreference_autocreate_field_widget_form_alter(&$element, &$form_state, $context) {
// First check if we are relevant or needed on this field widget.
if ($context['field']['type'] != 'entityreference') {
return;
}
if (empty($context['instance']['widget']['settings']['entityreference_autocreate']['active'])) {
return;
}
// We are an autocomplete. What's the details?
$target_bundle = entityreference_autocreate_get_target_bundle($context['field']);
$title = t('Autocreate enabled - any title put here will cause a "!bundle" to be created if no autocomplete match is found.', array(
'!bundle' => $target_bundle,
));
// So adjust the form field now.
if ($context['instance']['widget']['type'] == 'entityreference_autocomplete') {
// If it's autocomplete standard, there is a 'target_id'
$element['target_id']['#attributes']['title'] = $title;
$element['target_id']['#entityreference_autocreate_settings'] = $context['instance']['widget']['settings']['entityreference_autocreate'];
// To bypass the normal validation, need to REPLACE it totally.
$element['target_id']['#element_validate'] = array(
'entityreference_autocreate_validate',
);
}
if ($context['instance']['widget']['type'] == 'entityreference_autocomplete_tags') {
// If it's autocomplete tags style ..
$element['#attributes']['title'] = $title;
$element['#entityreference_autocreate_settings'] = $context['instance']['widget']['settings']['entityreference_autocreate'];
// To bypass the normal validation, need to REPLACE it totally.
$element['#element_validate'] = array(
'entityreference_autocreate_validate_tags',
);
}
}
/**
* Make a missing target if asked for by name.
*
* An element_validate callback for autocomplete fields.
* Replaces _entityreference_autocomplete_validate().
*
* @see _entityreference_autocomplete_validate()
*/
function entityreference_autocreate_validate($element, &$form_state, $form) {
if (empty($element['#value'])) {
return;
}
$field = field_info_field($element['#field_name']);
$field['settings']['entityreference_autocreate'] = $element['#entityreference_autocreate_settings'];
// Fetch an entity ID, making it on the fly if needed.
if ($value = entityreference_autocreate_get_entity_by_title($field, $element['#value'])) {
form_set_value($element, $value, $form_state);
return;
}
// Something has failed.
// Either could not create the target
// (permissions or something?)
// Or did a lookup and found two identically named targets already existing,
// so bailed.
$strings = array(
'!target' => $element['#value'],
);
form_error($element, t('Failed to create or find a target called !target (entityreference_autocreate). This may be due to permissions, autocreate settings on the widget, or possibly if there are two targets with identical titles already on the system.', $strings));
}
/**
* Validate handler that makes things up on the fly if needed.
*
* @see _entityreference_autocomplete_tags_validate()
*/
function entityreference_autocreate_validate_tags($element, &$form_state, $form) {
$value = array();
// If a value was entered into the autocomplete...
if (!empty($element['#value'])) {
$field = field_info_field($element['#field_name']);
$field['settings']['entityreference_autocreate'] = $element['#entityreference_autocreate_settings'];
$entities = drupal_explode_tags($element['#value']);
foreach ($entities as $title) {
if ($target_id = entityreference_autocreate_get_entity_by_title($field, $title)) {
$value[] = array(
'target_id' => $target_id,
);
}
}
}
// Update the values.
form_set_value($element, $value, $form_state);
}
/**
* Fetch the named entity for the field, create it if not found.
*
* @param array $field_info
* As loaded from field_info_field()
* @param string $title
* Title to search for.
*
* @return object|NULL
* Pre-existing or new entity. is_new should be set on it if it is fresh.
* Returns NULL on unexpected failure. A failure should probably be caught.
*/
function entityreference_autocreate_get_entity_by_title($field_info, $title) {
$title = trim($title);
if (empty($title)) {
return NULL;
}
// Take "label (entity id)', match the id from parenthesis.
if (preg_match("/.+\\((\\d+)\\)/", $title, $matches)) {
return $matches[1];
}
// Try to get a match from the input string when the user didn't use the
// autocomplete but filled in a value manually.
$handler = entityreference_get_selection_handler($field_info);
// Search for matches (exact), limit to 2 so we can detect if there is a
// potential conflict.
$entities = $handler
->getReferencableEntities($title, '=', 2);
// Case where $entities looks like $entities[BUNDLE][ETID] = HTML.
if (is_array(reset($entities))) {
// Extract items from results. The return is keyed by bundle.
$target_bundle = entityreference_autocreate_get_target_bundle($field_info);
if (!empty($target_bundle)) {
$entities = $entities[$target_bundle];
}
}
if (count($entities) == 1) {
// Exact match, no confusion, use that.
return key($entities);
}
if (count($entities) > 1) {
// More than one match.
// This is a genuine form validation error I can't automate.
return NULL;
}
// By now we've eliminated the options. There is no match.
if (count($entities) == 0) {
// Now make one of the named things.
return entityreference_autocreate_new_entity($field_info, $title);
}
return NULL;
}
/**
* Create a placeholder item of the type described in the field settings.
*/
function entityreference_autocreate_new_entity($field_info, $title) {
// Now make one of the named things.
$entity_type = $field_info['settings']['target_type'];
$target_bundle = entityreference_autocreate_get_target_bundle($field_info);
if (empty($target_bundle)) {
watchdog(__FUNCTION__, 'Cannot create new entity underneath field %field_name as the desired target bundle is undefined. See the field settings. Autocreate has trouble with view-based entityreference lookups.', array(
'%field_name' => $field_info['field_name'],
), WATCHDOG_WARNING);
return NULL;
}
// Select user depending on settings.
if (!empty($field_info['settings']['entityreference_autocreate']['author_current_user'])) {
global $user;
}
elseif (!empty($field_info['settings']['entityreference_autocreate']['author'])) {
$user = user_load_by_name($field_info['settings']['entityreference_autocreate']['author']);
}
else {
$user = user_load(0);
}
// Make a skeleton/minimal whatever entity. Probably a node.
// @see entity_create_stub_entity($entity_type, $ids).
$entity_info = entity_get_info($entity_type);
$label_key = 'title';
if (!empty($entity_info['entity keys']['label'])) {
$label_key = $entity_info['entity keys']['label'];
}
$bundle_key = 'type';
if (!empty($info['entity keys']['bundle'])) {
$bundle_key = $info['entity keys']['bundle'];
}
$new_entity = NULL;
// These two attributes seem common to each entity I've met so far.
$new_entity_values = array(
$bundle_key => $target_bundle,
$label_key => $title,
);
switch ($entity_type) {
case 'node':
// Check the expected published status.
$status = TRUE;
if (isset($field_info['settings']['entityreference_autocreate']['status'])) {
$status = $field_info['settings']['entityreference_autocreate']['status'];
if ($status == -1) {
// Use the bundle default.
$node_options = variable_get('node_options_' . $target_bundle, array(
'status',
'promote',
));
$status = in_array('status', $node_options);
}
}
$new_entity_values += array(
'uid' => $user->uid,
'name' => isset($user->name) ? $user->name : '',
'language' => LANGUAGE_NONE,
'status' => $status,
);
$new_entity = entity_create($entity_type, $new_entity_values);
break;
case 'taxonomy_term':
if ($vocabulair = taxonomy_vocabulary_machine_name_load($target_bundle)) {
$new_entity_values += array(
'vid' => $vocabulair->vid,
);
$new_entity = entity_create($entity_type, $new_entity_values);
}
break;
case 'user':
// Creating users on the fly is a bit risky,
// so they are not enabled by default.
//
// Entity_info did not define the label_key,
// and users dont really have bundles.
$label_key = 'name';
$target_bundle = 'user';
$new_entity_values = array(
$bundle_key => $target_bundle,
$label_key => $title,
);
$new_entity = entity_create($entity_type, $new_entity_values);
break;
default:
// It's some unknown/custom entity.
// We really can't guess what shape it is.
// It's likely that each field listed in the infos
// $entity_info['entity keys']
// will be required though?
//
// It's probably a *little* like a node...
// but it's a crap-shoot really.
// Hopefully entity API will take care of the rest of the abstraction
// and validation needed from here.
// YMMV.
$new_entity = entity_create($entity_type, $new_entity_values);
break;
}
// Allow other modules to work on this before we save it.
drupal_alter('entityreference_autocreate_new_entity', $new_entity, $field_info, $title);
if (empty($new_entity)) {
// The entity is unknown so don't continue.
drupal_set_message(t("The entity that needs to be created is unknown (entityreference_autocreate)"), 'error');
return NULL;
}
entity_save($entity_type, $new_entity);
// The return from this isn't reliable, check for an ID instead.
$target_id = entity_id($entity_type, $new_entity);
$uri = entity_uri($entity_type, $new_entity);
$strings = array(
'%entity_type' => $entity_type,
'%target_bundle' => $target_bundle,
'!target' => l($new_entity->{$label_key}, $uri['path']),
'%title' => $title,
);
if ($target_id) {
drupal_alter('entityreference_autocreate_new_saved_entity', $target_id, $entity_type);
drupal_set_message(t('Created a new %entity_type %target_bundle : !target (entityreference_autocreate)', $strings));
return $target_id;
}
else {
// Can't say why, but it's probably worth complaining about.
drupal_set_message(t("Failed to created a new %target_bundle called %title, no id returned (entityreference_autocreate)", $strings), 'error');
return NULL;
}
}
/**
* Load feeds support.
*
* Implements hook_init().
*/
function entityreference_autocreate_init() {
// Include feeds.module integration.
if (module_exists('feeds')) {
module_load_include('inc', 'entityreference_autocreate', 'entityreference_autocreate.feeds');
}
}
/**
* Find the desired target entity type (bundle name).
*
* Utility function.
* Abstracted as we need to know this often, and we also need to handle
* the basic vs views-based lookup each time.
*
* Returns either an array (which usually indicates a config problem)
* or just the first element in that array.
*
* @param $field_info
* The field info from the current context.
* @param bool $multiple
* If set, return an array of bundle names.
*
* @return string|array|null
* The bundle name.
*/
function entityreference_autocreate_get_target_bundle($field_info, $multiple = FALSE) {
// We look in different places depending on the selected lookup 'handler'.
$settings = $field_info['settings'];
if ($settings['handler'] == 'base') {
// The autocomplete is easy.
if (!empty($settings['handler_settings']['target_bundles'])) {
$target_bundles = $settings['handler_settings']['target_bundles'];
}
}
if ($settings['handler'] == 'views') {
// Figuring the base entity bundle from views is difficult.
// We have required it to be defined via UI, not introspection.
// So the needed data is not in field_info (base),
// it was packaged with the widget settings (instance).
// Need to retrieve it from there...
#$field_instance = field_info_instance($entity_type, $field_info['field_name'], $bundle);
// @Squash warning in case it's undefined due to an upgrade.
$target_bundles = array(
@$settings['entityreference_autocreate']['bundle'],
);
if (empty($target_bundles)) {
watchdog('entityreference_autocreate', 'Deducing the bundle from a view config is not yet possible, or it failed. Giving up, sorry.', array(), WATCHDOG_NOTICE);
return NULL;
}
}
// Fallback.
if (empty($target_bundles)) {
// So, not all entities have bundles. 'user' doesn't. Fake it for now.
// User entities are special - they have no bundle.
// (or it's 'user' but not explicit about it in the entityreference options)
// I guess there may be other entities like that also. Try to catch them,
// by assuming that their entity type and their bundle id are the same.
$target_bundles = array(
$settings['target_type'],
);
watchdog('entityreference_autocreate', 'Guessing that the "bundle" for things of type %entity_type is %target_bundle. This may be wrong, please report what you are trying to do as an issue.', array(
'%entity_type' => $settings['target_type'],
'%target_bundle' => reset($target_bundles),
), WATCHDOG_NOTICE);
}
if (count($target_bundles) != 1) {
watchdog('entityreference_autocreate', 'Can only autocreate an entity if there is exactly one target bundle. Check the widget settings for %field_name', array(
'%field_name' => $field_info['field_name'],
), WATCHDOG_NOTICE);
}
if (empty($target_bundles)) {
watchdog('entityreference_autocreate', 'No valid target bundle setting found for field %field_name', array(
'%field_name' => $field_info['field_name'],
), WATCHDOG_NOTICE);
return NULL;
}
return $multiple ? $target_bundles : reset($target_bundles);
}
Functions
Name | Description |
---|---|
entityreference_autocreate_field_widget_form_alter | Adjust the behaviour of entityreference autocomplete widgets. |
entityreference_autocreate_field_widget_info_alter | Implements hook_widget_info_alter(). |
entityreference_autocreate_form_field_ui_field_edit_form_alter | Adds our extra option to the field widget settings form. |
entityreference_autocreate_get_entity_by_title | Fetch the named entity for the field, create it if not found. |
entityreference_autocreate_get_target_bundle | Find the desired target entity type (bundle name). |
entityreference_autocreate_init | Load feeds support. |
entityreference_autocreate_new_entity | Create a placeholder item of the type described in the field settings. |
entityreference_autocreate_validate | Make a missing target if asked for by name. |
entityreference_autocreate_validate_tags | Validate handler that makes things up on the fly if needed. |