conditional_fields.module in Conditional Fields 4.x
Same filename and directory in other branches
Contains conditional_fields.module.
File
conditional_fields.moduleView source
<?php
/**
* @file
* Contains conditional_fields.module.
*/
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\WidgetInterface;
use Drupal\Core\Url;
use Drupal\conditional_fields\ConditionalFieldsFormHelper;
use Drupal\conditional_fields\DependencyHelper;
/**
* Implements hook_help().
*/
function conditional_fields_help($route_name, RouteMatchInterface $route_match) {
switch ($route_name) {
// Main module help for the conditional_fields module.
case 'help.page.conditional_fields':
$output = '';
$output .= '<h3>' . t('About') . '</h3>';
$output .= '<p>' . t('Define dependencies between fields based on their states and values.') . '</p>';
return $output;
default:
}
}
/**
* Implements hook_theme().
*/
function conditional_fields_theme() {
$theme = [];
$theme['conditional_field'] = [
'render element' => 'elements',
'file' => 'conditional_field.page.inc',
'template' => 'conditional_field',
];
$theme['conditional_field_content_add_list'] = [
'render element' => 'content',
'variables' => [
'content' => NULL,
],
'file' => 'conditional_field.page.inc',
];
return $theme;
}
/**
* Implements hook_entity_operation().
*/
function conditional_fields_entity_operation(EntityInterface $entity) {
$operations = [];
if ($entity
->getEntityTypeId() == 'node_type') {
$operations['dependencies'] = [
'title' => t('Dependencies'),
'url' => Url::fromRoute('conditional_fields.tab.node', [
$entity
->getEntityTypeId() => $entity
->id(),
]),
'weight' => 29,
];
}
return $operations;
}
/**
* Implements hook_element_info_alter().
*
* @see conditional_fields_element_after_build()
*/
function conditional_fields_element_info_alter(array &$types) {
foreach ($types as $type => $info) {
$types[$type]['#after_build'][] = 'conditional_fields_element_after_build';
}
}
/**
* Implements hook_conditional_fields().
*
* We implement this on behalf of the core Field module.
*/
function conditional_fields_conditional_fields($entity_type, $bundle_name) {
$fields = [];
$instances = \Drupal::getContainer()
->get("entity_field.manager")
->getFieldDefinitions($entity_type, $bundle_name);
foreach ($instances as $field) {
$fields[$field
->getName()] = $field
->getLabel();
}
return $fields;
}
/**
* Implements hook_conditional_fields_alter().
*/
function conditional_fields_conditional_fields_alter(&$fields, $entity_type, $bundle_name) {
asort($fields);
}
/**
* Processes form elements with dependencies.
*
* Just adds a #conditional_fields property to the form with the needed
* data, which is used later in
* \Drupal\conditional_fields\ConditionalFieldsFormHelper::afterBuild():
* - The fields #parents property.
* - Field dependencies data.
*/
function conditional_fields_element_after_build($element, FormStateInterface &$form_state) {
// A container with a field widget.
// Element::children() is probably a better fit.
if (isset($element['widget'])) {
$field = $element['widget'];
}
else {
$field = $element;
}
$first_parent = reset($field['#parents']);
// No parents, so bail out.
if (!isset($first_parent) || isset($field['#type']) && $field['#type'] == 'value') {
return $element;
}
$form =& $form_state
->getCompleteForm();
// Some fields do not have entity type and bundle properties.
// In this case we try to use the properties from the form.
// This is not an optimal solution, since in case of fields
// in entities within entities they might not correspond,
// and their dependencies will not be loaded.
$build_info = $form_state
->getBuildInfo();
if (method_exists($build_info['callback_object'], 'getEntity')) {
$entity = $build_info['callback_object']
->getEntity();
if ($entity instanceof EntityInterface) {
$bundle = $entity
->bundle();
$entity_type = $entity
->getEntityTypeId();
/**
* @deprecated Not actual from Drupal 8.7.0.
* Media entity returns the actual bundle object, rather than id
*/
if (is_object($bundle) && method_exists($bundle, 'getPluginId')) {
$bundle = $bundle
->getPluginId();
}
$dependencies = conditional_fields_load_dependencies($entity_type, $bundle);
if (!$dependencies) {
return $element;
}
$field_name = reset($field['#array_parents']);
// Attach dependent.
if (isset($dependencies['dependents'][$field_name])) {
foreach ($dependencies['dependents'][$field_name] as $id => $dependency) {
if (!isset($form['#conditional_fields'][$field_name]['dependees'][$id]) || conditional_fields_is_priority_field($field)) {
conditional_fields_attach_dependency($form, $form_state, [
'#field_name' => $dependency['dependee'],
], $field, $dependency['options'], $id);
}
}
}
if (isset($dependencies['dependees'][$field_name])) {
foreach ($dependencies['dependees'][$field_name] as $id => $dependency) {
if (!isset($form['#conditional_fields'][$field_name]['dependents'][$id]) || conditional_fields_is_priority_field($field)) {
conditional_fields_attach_dependency($form, $form_state, $field, [
'#field_name' => $dependency['dependent'],
], $dependency['options'], $id);
}
}
}
}
}
return $element;
}
/**
* Loads all dependencies from the database for a given bundle.
*/
function conditional_fields_load_dependencies($entity_type, $bundle) {
static $dependency_helper;
if (!isset($dependency_helper)) {
$dependency_helper = new DependencyHelper($entity_type, $bundle);
}
return $dependency_helper
->getBundleDependencies();
}
/**
* Attaches a single dependency to a form.
*
* Call this function when defining or altering a form to create dependencies
* dynamically.
*
* @param array $form
* The form where the dependency is attached.
* @param string $dependee
* The dependee field form element. Either a string identifying the element
* key in the form, or a fully built field array. Actually used properties of
* the array are #field_name and #parents.
* @param string $dependent
* The dependent field form element. Either a string identifying the element
* key in the form, or a fully built field array. Actually used properties of
* the array are #field_name and #field_parents.
* @param array $options
* An array of dependency options with the following key/value pairs:
* - state: The state applied to the dependent when the dependency is
* triggered. See conditionalFieldsStates() for available states.
* - condition: The condition for the dependency to be triggered. See
* conditionalFieldsConditions() for available conditions.
* - values_set: One of the following constants:
* - ConditionalFieldsInterface::CONDITIONAL_FIELDS_DEPENDENCY_VALUES_WIDGET: Dependency is
* triggered if the dependee has a certain value defined in 'value'.
* - ConditionalFieldsInterface::CONDITIONAL_FIELDS_DEPENDENCY_VALUES_AND: Dependency is triggered if
* the dependee has all the values defined in 'values'.
* - ConditionalFieldsInterface::CONDITIONAL_FIELDS_DEPENDENCY_VALUES_OR: Dependency is triggered if the
* dependee has any of the values defined in 'values'.
* - ConditionalFieldsInterface::CONDITIONAL_FIELDS_DEPENDENCY_VALUES_XOR: Dependency is triggered if
* the dependee has only one of the values defined in 'values'.
* - ConditionalFieldsInterface::CONDITIONAL_FIELDS_DEPENDENCY_VALUES_NOT: Dependency is triggered if
* the dependee does not have any of the values defined in 'values'.
* - value: The value to be tested when 'values_set' is
* ConditionalFieldsInterface::CONDITIONAL_FIELDS_DEPENDENCY_VALUES_WIDGET. An associative array with
* the same structure of the dependee field values as found in
* $form_states['values] when the form is submitted. You can use
* field_default_extract_form_values() to extract this array.
* - values: The array of values to be tested when 'values_set' is not
* ConditionalFieldsInterface::CONDITIONAL_FIELDS_DEPENDENCY_VALUES_WIDGET.
* - value_form: An associative array with the same structure of the dependee
* field values as found in $form_state['input']['value']['field'] when the
* form is submitted.
* - effect: The jQuery effect associated to the state change. See
* conditionalFieldsEffects() for available effects and options.
* - effect_options: The options for the active effect.
* - selector: (optional) Custom jQuery selector for the dependee.
* @param int $id
* (internal use) The identifier for the dependency. Omit this parameter when
* attaching a custom dependency.
*
* Note that you don't need to manually set all these options, since default
* settings are always provided.
*/
function conditional_fields_attach_dependency(&$form, &$form_state, $dependee, $dependent, $options, $id = 0) {
// The absence of the $id parameter identifies a custom dependency.
if (!$id) {
// String values are accepted to simplify usage of this function with custom
// forms.
if (is_string($dependee) && is_string($dependent)) {
$dependee = [
'#field_name' => $dependee,
'#parents' => [
$dependee,
],
];
$dependent = [
'#field_name' => $dependent,
'#field_parents' => [
$dependent,
],
];
// Custom dependencies have automatically assigned a progressive id.
static $current_id;
if (!$current_id) {
$current_id = 1;
}
$id = $current_id;
$current_id++;
}
}
// Attach dependee.
// Use the #array_parents property of the dependee instead of #field_parents
// since we will need access to the full structure of the widget.
if (isset($dependee['#parents'])) {
$form['#conditional_fields'][$dependee['#parents'][0]]['parents'] = $dependee['#array_parents'];
$form['#conditional_fields'][$dependee['#parents'][0]]['dependents'][$id] = [
'dependent' => $dependent['#field_name'],
'options' => $options,
];
}
// Attach dependent.
if (!empty($dependent['#parents'])) {
$dependent_parents = $dependent['#parents'];
// If the field type is Date, we need to remove the last "date" parent key,
// since it is not part of the $form_state value when we validate it.
if ($dependent['#type'] == 'date') {
array_pop($dependent_parents);
}
}
elseif (isset($dependent['#field_parents'])) {
$dependent_parents = $dependent['#field_parents'];
}
if (isset($dependent_parents)) {
$form['#conditional_fields'][$dependent['#parents'][0]]['field_parents'] = $dependent_parents;
$form['#conditional_fields'][$dependent['#parents'][0]]['dependees'][$id] = [
'dependee' => $dependee['#field_name'],
'options' => $options,
];
}
}
/**
* Builds a jQuery selector from the name or id attribute of a field.
*
* @todo support custom selectors with %lang and %key placeholders.
*
* @param array $field
* The field form element.
*
* @return string|FALSE
* A jQuery selector string.
*/
function conditional_fields_field_selector($field) {
if (isset($field['#attributes']['name'])) {
return '[name="' . $field['#attributes']['name'] . '"]';
}
if (isset($field['#name'])) {
return '[name="' . $field['#name'] . '"]';
}
// Try with id if name is not found.
if (isset($field['#attributes']['id'])) {
return '#' . $field['#attributes']['id'];
}
if (isset($field['#id'])) {
return '#' . $field['#id'];
}
return FALSE;
}
/**
* Implements hook_field_widget_third_party_settings_form().
*
* If the field has conditional fields, then ensure they are added on the form
* display widget settings page so they are not lost.
*/
function conditional_fields_field_widget_third_party_settings_form(WidgetInterface $plugin, FieldDefinitionInterface $field_definition, $form_mode, $form, FormStateInterface $form_state) {
$element = [];
$settings = $plugin
->getThirdPartySettings('conditional_fields');
if (!empty($settings)) {
foreach ($settings as $uuid => $setting) {
$element[$uuid] = [
'#type' => 'value',
'#value' => $setting,
];
}
}
return $element;
}
/**
* Checking if field is priority for rewrite the conditions
* If the field widget is datelist this function help to return correct object for this field
*
*
* @param array $field
* The field form element.
*
* @return bool
* Check the fields is priority and return the boolean result
*/
function conditional_fields_is_priority_field(array $field) {
$priority_fields = [
'datelist',
];
//For modules supports
Drupal::moduleHandler()
->alter([
'conditional_fields_priority_field',
], $priority_fields);
if (isset($field['#type']) && in_array($field['#type'], $priority_fields)) {
return TRUE;
}
return FALSE;
}
/**
* Implements hook_form_alter().
*
* Hook_form_alter() that attaches an 'afterbuild' function to the form.
*
* @param $form
* The form to attach the callback to.
* @param $form_state
* Not used, part of the hook interface.
* @param $form_id
* Not used, part of the hook interface.
*/
function conditional_fields_form_alter(&$form, &$form_state, $form_id) {
$form['#after_build'][] = 'conditional_fields_form_after_build';
}
/**
* Runs the 'afterbuild' validation of a form with dependencies.
*
* This function will run only once per form.
*
* @param $form
* The form to validate
* @param $form_state
* The form's state.
*
* @return mixed
* The form.
*/
function conditional_fields_form_after_build($form, &$form_state) {
return \Drupal::service('conditional_fields.form_helper')
->afterBuild($form, $form_state);
}