You are here

bundle_inherit.module in Bundle Inherit 7

Bundle Inherit module.

File

bundle_inherit.module
View source
<?php

/**
 * @file
 * Bundle Inherit module.
 */

/**
 * Perform necesary inherit operations.
 */
function bundle_inherit_perform($entity_type, $bundle, $bundle_parent, $strict = TRUE) {

  // Get fields from parent bundle.
  $instances = field_info_instances($entity_type, $bundle_parent);
  foreach ($instances as $instance) {
    $new_instance = $instance;
    $new_instance['bundle'] = $bundle;
    if ($strict) {
      $new_instance['locked'] = TRUE;
    }
    $new_instance = field_create_instance($new_instance);
    $query = db_select('field_config_instance', 'fci');
    $query
      ->addField('fci', 'id');
    $query
      ->condition('fci.bundle', $bundle);
    $new_instance['id'] = $query
      ->execute()
      ->fetchField();
  }

  // Check if we perform strict inheritance.
  if ($strict) {
    $exists = db_query('SELECT 1 FROM {bundle_inherit} WHERE entity_type = :entity_type AND bundle = :bundle', array(
      ':entity_type' => $entity_type,
      ':bundle' => $bundle,
    ))
      ->fetchField();
    if ($exists) {
      db_update('bundle_inherit')
        ->fields(array(
        'bundle_parent' => $bundle_parent,
      ))
        ->condition('entity_type', $entity_type)
        ->condition('bundle', $bundle)
        ->execute();
    }
    else {
      db_insert('bundle_inherit')
        ->fields(array(
        'entity_type' => $entity_type,
        'bundle' => $bundle,
        'bundle_parent' => $bundle_parent,
      ))
        ->execute();
    }
    watchdog('bundle_inherit', 'The %bundle bundle of the entity %type was STRICTLY inherited from %parent_bundle bundle.', array(
      '%bundle' => $bundle,
      '%bundle_parent' => $bundle_parent,
      '%type' => $entity_type,
    ));
    drupal_static_reset('bundle_inherit_bundle_get_children');
  }
  else {
    watchdog('bundle_inherit', 'The %bundle bundle of the entity %type was SOFTLY inherited from %parent_bundle bundle.', array(
      '%bundle' => $bundle,
      '%bundle_parent' => $bundle_parent,
      '%type' => $entity_type,
    ));
  }

  // Allow third party modules to implement there own inherit logic.
  module_invoke_all('bundle_inherit_perform', $entity_type, $bundle, $bundle_parent, $strict = TRUE);
}

/**
 * Implements hook_field_create_instance().
 */
function bundle_inherit_field_create_instance($instance) {
  $children = bundle_inherit_bundle_get_children($instance['entity_type'], $instance['bundle']);
  foreach ($children as $bundle) {
    $new_instance = $instance;
    unset($new_instance['id']);
    $new_instance['bundle'] = $bundle['type'];
    $new_instance['locked'] = TRUE;
    field_create_instance($new_instance);
  }
}

/**
 * Implements hook_field_update_instance().
 */
function bundle_inherit_field_update_instance($instance, $prior_instance) {
  $children = bundle_inherit_bundle_get_children($prior_instance['entity_type'], $prior_instance['bundle']);
  foreach ($children as $bundle) {
    $old_instance = field_info_instance($instance['entity_type'], $instance['field_name'], $bundle['type']);
    $new_instance = array(
      'id' => $old_instance['id'],
      'bundle' => $old_instance['bundle'],
      'locked' => TRUE,
    );
    $new_instance += $instance;
    field_update_instance($new_instance);
  }
}

/**
 * Implements hook_field_delete_instance().
 */
function bundle_inherit_field_delete_instance($instance) {
  $children = bundle_inherit_bundle_get_children($instance['entity_type'], $instance['bundle']);
  foreach ($children as $bundle) {
    $new_instance = $instance;
    $new_instance['bundle'] = $bundle['type'];
    $new_instance['locked'] = FALSE;
    try {
      field_update_instance($new_instance);
    } catch (Exception $e) {
      drupal_set_message($e
        ->getMessage(), 'error');
    }
  }
}

/**
 * Implements hook_form_FORMID_alter().
 *
 * Attach additional validation callback to the field_ui_field_overview_form.
 * When adding new field instance to the parent we should check that all of it
 * childrens hase not that field instances.
 */
function bundle_inherit_form_field_ui_field_overview_form_alter(&$form, &$form_instance, $form_id) {
  $form['#validate'][] = 'bundle_inherit_validate_field_instance_creation';
  $bundle = $form['#bundle'];
  $entity_type = $form['#entity_type'];

  // Remove links for inherited fields.
  foreach ($form['fields'] as $field_name => &$value) {
    if (isset($value['#row_type']) && $value['#row_type'] === 'field') {

      // Check if this field is inherited.
      $parent = bundle_inherit_bundle_get_parent($entity_type, $bundle);
      if (!empty($parent)) {

        // Check if parent bundle has this field attached
        if (field_info_instance($entity_type, $field_name, $parent)) {
          $value['type'] = array(
            '#markup' => $value['type']['#title'],
          );
          $value['widget_type'] = array(
            '#markup' => $value['widget_type']['#title'],
          );
        }
      }
    }
  }
}

/**
 * Additional validation function to the field_ui_field_overview_form.
 *
 * While adding existing field instance, get this form is created for and set
 * form error if any of this children has instance of this field.
 */
function bundle_inherit_validate_field_instance_creation($form, &$form_state) {
  $form_values = $form_state['values']['fields'];
  if (!empty($form_values['_add_existing_field']['field_name'])) {
    $children = bundle_inherit_bundle_get_children_all($form['#entity_type'], $form['#bundle']);
    $bundles_with_instance = array();
    foreach ($children as $child) {
      $prior_instance = field_info_instance($form['#entity_type'], $form_values['_add_existing_field']['field_name'], $child);
      if (!empty($prior_instance)) {
        $bundles_with_instance[] = $prior_instance['bundle'];
      }
    }
    if (count($bundles_with_instance) > 0) {
      $string = implode(", ", $bundles_with_instance);
      form_set_error('fields][_add_existing_field', t("Instance of the field %field can't be attached to %bundle bundle because this field instances are already attached to some of this bundle children bundles: %children", array(
        '%bundle' => $form['#bundle'],
        '%field' => $form_values['_add_existing_field']['field_name'],
        '%children' => $string,
      )));
    }
  }
}

/**
 * Get direct children bundles of the selected entity bundle.
 */
function bundle_inherit_bundle_get_children($entity_type, $bundle_parent) {
  $children =& drupal_static(__FUNCTION__);
  if (!isset($children[$entity_type])) {
    $children[$entity_type] = array();
  }
  if (!isset($children[$entity_type][$bundle_parent])) {
    $children[$entity_type][$bundle_parent] = array();
    $entity_type_info = entity_get_info($entity_type);
    $_children = db_select('bundle_inherit', 'bi')
      ->fields('bi', array(
      'bundle',
    ))
      ->condition('bundle_parent', $bundle_parent)
      ->condition('entity_type', $entity_type)
      ->execute()
      ->fetchCol();
    foreach ($_children as $child) {
      $children[$entity_type][$bundle_parent][$child] = array(
        'type' => $child,
        'label' => $entity_type_info['bundles'][$child]['label'],
      );
    }
  }
  return $children[$entity_type][$bundle_parent];
}

/**
 * Get all children bundles of the selected entity bundle.
 */
function bundle_inherit_bundle_get_children_all($entity_type, $bundle_parent) {
  $children = array();
  $children = bundle_inherit_bundle_get_children($entity_type, $bundle_parent);
  foreach ($children as $child) {
    $children = array_merge($children, bundle_inherit_bundle_get_children_all($entity_type, $child['type']));
  }
  return $children;
}

/**
 * Get parent of the selected entity bundle.
 *
 * @return
 *   Entity type parent type.
 */
function bundle_inherit_bundle_get_parent($entity_type, $bundle) {
  $parent =& drupal_static(__FUNCTION__);
  if (!isset($parent[$entity_type][$bundle])) {
    $parent[$entity_type][$bundle] = db_select('bundle_inherit', 'bi')
      ->fields('bi', array(
      'bundle_parent',
    ))
      ->condition('bi.bundle', $bundle)
      ->execute()
      ->fetchField();
    if (!$parent[$entity_type][$bundle]) {
      $parent[$entity_type][$bundle] = '';
    }
  }
  return $parent[$entity_type][$bundle];
}

/**
 * Attach ineritance form to selected form element.
 *
 * @param $form
 *   Parent form element to attach inheritance form to.
 * @param $form_state
 *   From state from the parent form.
 * @param $entity_type
 *   Entity for which bundle is creating for.
 * @param $bundle
 *   If editing existing bundle value for this argument should be provided.
 */
function bundle_inherit_attach_inherit_form(&$form, &$form_state, $entity_type, $bundle = '') {
  $entity = entity_get_info($entity_type);
  if (count($entity['bundles']) > 0) {
    if (empty($bundle)) {
      $form['bundle_inherit'] = array(
        '#type' => 'fieldset',
        '#tree' => TRUE,
        '#title' => t('Inheritance'),
      );
      $form['bundle_inherit']['entity_type'] = array(
        '#type' => 'value',
        '#value' => $entity_type,
      );
      $form['bundle_inherit']['#parents'] = array(
        'bundle_inherit',
      );
      $form['bundle_inherit']['inherit'] = array(
        '#type' => 'checkbox',
        '#title' => t('Inherit from other'),
      );
      foreach ($entity['bundles'] as $bundle_name => $bundle) {
        $options[$bundle_name] = $bundle['label'];
      }
      $form['bundle_inherit']['parent_type'] = array(
        '#type' => 'select',
        '#options' => $options,
        '#title' => t('Parent'),
        '#states' => array(
          // Hide the inheritance settings when inherit checkbox is disabled.
          'invisible' => array(
            'input[name="bundle_inherit[inherit]"]' => array(
              'checked' => FALSE,
            ),
          ),
        ),
      );
      $form['bundle_inherit']['mode'] = array(
        '#type' => 'radios',
        '#options' => array(
          'strict' => t('Strict inherit'),
          'soft' => t('Soft inherit'),
        ),
        '#default_value' => 'strict',
        '#required' => TRUE,
        '#states' => array(
          // Hide the inheritance settings when inherit checkbox is disabled.
          'invisible' => array(
            'input[name="bundle_inherit[inherit]"]' => array(
              'checked' => FALSE,
            ),
          ),
        ),
        '#title' => t('Inheritance mode'),
      );
    }
    else {
      $parent_bundle_name = bundle_inherit_bundle_get_parent($entity_type, $bundle);
      if (!empty($parent_bundle_name)) {
        $form['bundle_inherit'] = array(
          '#type' => 'fieldset',
          '#tree' => TRUE,
          '#title' => t('Inheritance'),
        );
        $form['bundle_inherit']['message'] = array(
          '#markup' => t('This bundle was inherited from !parent_bundle bundle.', array(
            '!parent_bundle' => l($entity['bundles'][$parent_bundle_name]['label'], $entity['bundles'][$parent_bundle_name]['admin']['real path'] . '/fields'),
          )),
        );
      }
    }
  }
}

/**
 * Should be executed when entity creation form is submiting.
 *
 * @param $bundle
 *   Newly created bundle name.
 */
function bundle_inherit_attach_inherit_form_submit($bundle, &$form, &$form_state) {
  if (isset($form_state['values']['bundle_inherit']) && $form_state['values']['bundle_inherit']['inherit']) {
    $bundle_inherit_values = $form_state['values']['bundle_inherit'];
    bundle_inherit_perform($bundle_inherit_values['entity_type'], $bundle, $bundle_inherit_values['parent_type'], $bundle_inherit_values['mode'] == 'strict' ? TRUE : FALSE);
  }
}

/**
 * Implements hook_field_attach_create_bundle().
 */
function bundle_inherit_field_attach_create_bundle($entity_type, $bundle) {
  $exists = db_query('SELECT 1 FROM {bundle_inherit} WHERE entity_type = :entity_type AND bundle = :bundle', array(
    ':entity_type' => $entity_type,
    ':bundle' => $bundle,
  ))
    ->fetchField();
  if (!$exists) {
    db_insert('bundle_inherit')
      ->fields(array(
      'entity_type' => $entity_type,
      'bundle' => $bundle,
      'bundle_parent' => '',
    ))
      ->execute();
  }
}

/**
 * Implements hook_field_attach_delete_bundle().
 */
function bundle_inherit_field_attach_delete_bundle($entity_type, $bundle, $instances) {
  db_delete('bundle_inherit')
    ->condition('entity_type', $entity_type)
    ->condition(db_or()
    ->condition('bundle_parent', $bundle)
    ->condition('bundle', $bundle))
    ->execute();
}

/**
 * Implements hook_field_attach_rename_bundle().
 */
function bundle_inherit_field_attach_rename_bundle($entity_type, $bundle_old, $bundle_new) {
  db_update('bundle_inherit')
    ->condition('entity_type', $entity_type)
    ->condition('bundle', $bundle_old)
    ->fields(array(
    'bundle' => $bundle_new,
  ))
    ->execute();
  db_update('bundle_inherit')
    ->condition('entity_type', $entity_type)
    ->condition('bundle_parent', $bundle_old)
    ->fields(array(
    'bundle_parent' => $bundle_new,
  ))
    ->execute();
}

/**
 * Sort rows hierarchycly and allow to indent them.
 * @param $entity_type
 *   Entity type.
 * @param $rows
 *   Rows.
 * @param $apply_indention
 *   Whether ty apply indention or not. Default to TRUE.
 * @param $indention_options
 *   (optional) Indention options. Structured array with following keys:
 *   - 'indention callback':  The function to call to indent row.
 *   Takes $row, $indention_options and $level as arguments. $level is the
 *   bundle level in hierarchy started from zero. Default callback is
 *   bundle_inherit_sort_rows_default_indent.
 *   - 'name col index': Used in default indention callback. If each row is an
 *   array you can specify the column to indent. If not you cen set 'name col
 *   index' to 'self' and entire row will be indented.
 *   - 'indenter': Used in default indention callback. It is like a delimiter
 *   in implode() function. Will be multiplied on the bundle level and prepend
 *   the row value.
 */
function bundle_inherit_sort_rows($entity_type, &$rows, $apply_indention = TRUE, $indention_options = array()) {
  $indention_options += array(
    'name col index' => 0,
    'indenter' => '-&nbsp;-&nbsp;',
    'indention callback' => 'bundle_inherit_sort_rows_default_indent',
    'apply indention' => $apply_indention,
  );
  $tree = bundle_inherit_get_tree($entity_type);
  $new_rows = array();
  foreach ($tree as $bundle_type => $bundle) {
    if (isset($rows[$bundle_type])) {
      $new_row = $rows[$bundle_type];
      if ($indention_options['apply indention']) {
        $indention_options['indention callback']($new_row, $indention_options, $bundle['depth']);
      }
      $new_rows[] = $new_row;
    }
  }
  $rows = $new_rows;
}

/**
 * Default indention function for bundle_inherit_sort_rows.
 */
function bundle_inherit_sort_rows_default_indent(&$row, $indention_options, $level) {
  if ($indention_options['name col index'] === 'self') {
    if (!is_array($row)) {
      $row['data'] = str_repeat($indention_options['indenter'], $level) . $row['data'];
    }
    elseif (isset($row['data'])) {
      $row = str_repeat($indention_options['indenter'], $level) . $row;
    }
  }
  else {
    if (!is_array($row[$indention_options['name col index']])) {
      $row[$indention_options['name col index']] = str_repeat($indention_options['indenter'], $level) . $row[$indention_options['name col index']];
    }
    elseif (isset($row[$indention_options['name col index']]['data'])) {
      $row[$indention_options['name col index']]['data'] = str_repeat($indention_options['indenter'], $level) . $row[$indention_options['name col index']]['data'];
    }
  }
}

/**
 * Get bundles tree.
 */
function bundle_inherit_get_tree($entity_type, $parent_bundle_type = '', $depth = 0) {
  if ($depth == 0) {
    $tree =& drupal_static(__FUNCTION__ . ':' . $parent_bundle_type);
  }
  if (!isset($tree)) {
    $tree = array();
    $entity_type_info = entity_get_info($entity_type);
    $children = bundle_inherit_bundle_get_children($entity_type, $parent_bundle_type);
    foreach ($children as $child) {
      $tree[$child['type']] = array(
        'type' => $child['type'],
        'label' => $child['label'],
        'depth' => $depth,
      );
      $tree = array_merge($tree, bundle_inherit_get_tree($entity_type, $child['type'], $depth + 1));
    }
  }
  return $tree;
}

/**
 * Implements hook_menu_alter().
 */
function bundle_inherit_menu_alter(&$items) {
  $access_callback = 'bundle_inherit_access_gate';
  foreach (entity_get_info() as $entity_type => $entity_info) {
    if ($entity_info['fieldable']) {
      foreach ($entity_info['bundles'] as $bundle_name => $bundle_info) {
        if (isset($bundle_info['admin'])) {

          // Extract path information from the bundle.
          $path = $bundle_info['admin']['path'];

          // This is the position of the %field_ui_menu placeholder in the
          // items below.
          $field_position = count(explode('/', $path)) + 1;
          $items["{$path}/fields/%field_ui_menu"]['access arguments'] = array(
            $items["{$path}/fields/%field_ui_menu"]['access callback'],
            $items["{$path}/fields/%field_ui_menu"]['access arguments'],
            $field_position,
          );
          $items["{$path}/fields/%field_ui_menu"]['access callback'] = $access_callback;
          $items["{$path}/fields/%field_ui_menu/edit"]['access arguments'] = array(
            $items["{$path}/fields/%field_ui_menu/edit"]['access callback'],
            $items["{$path}/fields/%field_ui_menu/edit"]['access arguments'],
            $field_position,
          );
          $items["{$path}/fields/%field_ui_menu/edit"]['access callback'] = $access_callback;
          $items["{$path}/fields/%field_ui_menu/delete"]['access arguments'] = array(
            $items["{$path}/fields/%field_ui_menu/delete"]['access callback'],
            $items["{$path}/fields/%field_ui_menu/delete"]['access arguments'],
            $field_position,
          );
          $items["{$path}/fields/%field_ui_menu/delete"]['access callback'] = $access_callback;
          $items["{$path}/fields/%field_ui_menu/field-settings"]['access arguments'] = array(
            $items["{$path}/fields/%field_ui_menu/field-settings"]['access callback'],
            $items["{$path}/fields/%field_ui_menu/field-settings"]['access arguments'],
            $field_position,
          );
          $items["{$path}/fields/%field_ui_menu/field-settings"]['access callback'] = $access_callback;
          $items["{$path}/fields/%field_ui_menu/widget-type"]['access arguments'] = array(
            $items["{$path}/fields/%field_ui_menu/widget-type"]['access callback'],
            $items["{$path}/fields/%field_ui_menu/widget-type"]['access arguments'],
            $field_position,
          );
          $items["{$path}/fields/%field_ui_menu/widget-type"]['access callback'] = $access_callback;
          $items["{$path}/fields/%field_ui_menu/delete"]['access arguments'] = array(
            $items["{$path}/fields/%field_ui_menu/delete"]['access callback'],
            $items["{$path}/fields/%field_ui_menu/delete"]['access arguments'],
            $field_position,
          );
          $items["{$path}/fields/%field_ui_menu/delete"]['access callback'] = $access_callback;
        }
      }
    }
  }
}

/**
 * Prevent user from editing\deleting inherited field instance.
 */
function bundle_inherit_access_gate($callback, $arguments, $instance) {

  // First check parent restrictions
  $access = call_user_func_array($callback, $arguments);
  if (false == $access) {
    return false;
  }

  // Check if this field was inherited
  $bundle = $instance['bundle'];
  $entity_type = $instance['entity_type'];
  $field_name = $instance['field_name'];
  $parent = bundle_inherit_bundle_get_parent($entity_type, $bundle);
  if (!empty($parent)) {

    // Check if parent bundle has this field attached
    if (field_info_instance($entity_type, $field_name, $parent)) {
      return false;
    }
  }
  return true;
}

Functions

Namesort descending Description
bundle_inherit_access_gate Prevent user from editing\deleting inherited field instance.
bundle_inherit_attach_inherit_form Attach ineritance form to selected form element.
bundle_inherit_attach_inherit_form_submit Should be executed when entity creation form is submiting.
bundle_inherit_bundle_get_children Get direct children bundles of the selected entity bundle.
bundle_inherit_bundle_get_children_all Get all children bundles of the selected entity bundle.
bundle_inherit_bundle_get_parent Get parent of the selected entity bundle.
bundle_inherit_field_attach_create_bundle Implements hook_field_attach_create_bundle().
bundle_inherit_field_attach_delete_bundle Implements hook_field_attach_delete_bundle().
bundle_inherit_field_attach_rename_bundle Implements hook_field_attach_rename_bundle().
bundle_inherit_field_create_instance Implements hook_field_create_instance().
bundle_inherit_field_delete_instance Implements hook_field_delete_instance().
bundle_inherit_field_update_instance Implements hook_field_update_instance().
bundle_inherit_form_field_ui_field_overview_form_alter Implements hook_form_FORMID_alter().
bundle_inherit_get_tree Get bundles tree.
bundle_inherit_menu_alter Implements hook_menu_alter().
bundle_inherit_perform Perform necesary inherit operations.
bundle_inherit_sort_rows Sort rows hierarchycly and allow to indent them.
bundle_inherit_sort_rows_default_indent Default indention function for bundle_inherit_sort_rows.
bundle_inherit_validate_field_instance_creation Additional validation function to the field_ui_field_overview_form.