'blockreference autocomplete', 'page callback' => 'blockreference_autocomplete', 'page arguments' => array(2, 3, 4, 5), 'access callback' => 'blockreference_autocomplete_access', 'access arguments' => array(2, 3, 4, 5), 'type' => MENU_CALLBACK, ); return $items; } /** * Menu access callback for the autocomplete path. */ function blockreference_autocomplete_access($entity_type, $bundle, $field_name, $eid) { if ($eid == 0) { $entity_info = entity_get_info($entity_type); $entity_dummy = entity_create($entity_type, array( $entity_info['entity keys']['bundle'] => $bundle, )); $entity_access = entity_access('create', $entity_type, $entity_dummy); } else { $entity = entity_load($entity_type, array($eid)); $entity = reset($entity) ?: FALSE; $entity_access = entity_access('update', $entity_type, $entity); } return user_access('access content') && $entity_access; } /** * Menu callback for the autocomplete results. */ function blockreference_autocomplete($entity_type, $bundle, $field_name, $entity_id, $search_string) { $search_string = trim(implode('/', array_slice(func_get_args(), 4)), '/'); $entity = FALSE; if ($entity_id) { $entity_load = entity_load($entity_type, array($entity_id)); if ($entity_load) { $entity = reset($entity_load); } } $instance = field_info_instance($entity_type, $field_name, $bundle); $context = array( 'type' => 'autocomplete', 'entity' => $entity, ); $blocks = _blockreference_find_blocks($instance, $search_string, $context); $options = _blockreference_options($blocks, TRUE); drupal_json_output($options); } /** * Value callback for a blockreference autocomplete element. */ function blockreference_autocomplete_value($element, $input = FALSE, $form_state) { $ac_string = $input !== FALSE ? $input : (isset($element['#value']) ? $element['#value'] : (string) @$element['#default_value']); $module = $delta = ''; // Find module & delta from an ac string. if (is_string($ac_string)) { $block_arr = _blockreference_block_from_ac_string($ac_string); if ($block_arr) { list($module, $delta) = array_values($block_arr); } } // Found it? Does is exist? if ($module && $delta) { $infos = module_invoke($module, 'block_info'); if (isset($infos[$delta])) { $block = _blockreference_block($module, $delta, $infos[$delta]); $value = trim($block->info) . ' [' . $block->module . ':' . $block->delta . ']'; return $value; } } return ''; } /** * Validation callback for a blockreference autocomplete element. */ function blockreference_autocomplete_validate($element, &$form_state, $form) { $instance = field_info_instance($element['#entity_type'], $element['#field_name'], $element['#bundle']); $value = $element['#value']; if (!$value) { form_set_value($element, '', $form_state); return; } $block_arr = _blockreference_block_from_ac_string($value); // Validate input to be a block. if (!$block_arr) { return form_error($element, t('%name: Title mismatch. Please check your selection.', array('%name' => $instance['label']))); } // Validate block's usage in this context. $allowed_blocks = _blockreference_find_blocks($instance, '', array( 'entity' => $element['#entity'], 'type' => 'autocomplete', )); $moddelta = implode(':', $block_arr); if (!isset($allowed_blocks[$moddelta])) { return form_error($element, t('This block is not allowed in this field/context.')); } form_set_value($element, $moddelta, $form_state); } /** * Implements hook_theme(). */ function blockreference_theme() { return array( 'blockreference_formatter_default' => array( 'variables' => array('element' => NULL), ), 'blockreference_formatter_without_title' => array( 'variables' => array('element' => NULL), ), 'blockreference_formatter_title' => array( 'variables' => array('element' => NULL), ), 'blockreference_formatter_plain' => array( 'variables' => array('element' => NULL), ), 'blockreference_formatter_config_link' => array( 'variables' => array('element' => NULL), ), ); } /** * Theme function for 'default' blockreference field formatter. */ function theme_blockreference_formatter_default($variables) { $element = $variables['element']; $block = _blockreference_formatter_get_block($element); if ($block) { $bid = $block->module . '_' . $block->delta; if (!isset($block->title)) { $block->title = ''; } if (!isset($block->region)) { $block->region = 'none'; } $block_content = _block_render_blocks(array($block)); $build = _block_get_renderable_array($block_content); $build[$bid]['#blockreference_element'] = $element; return drupal_render($build); } return ''; } /** * Theme function for 'without_title' blockreference field formatter. */ function theme_blockreference_formatter_without_title($variables) { $element = $variables['element']; $block = _blockreference_formatter_get_block($element); if ($block) { $bid = $block->module . '_' . $block->delta; if (!isset($block->title)) { $block->title = ''; } if (!isset($block->region)) { $block->region = 'none'; } $block_content = _block_render_blocks(array($block)); if ($block_content) { reset($block_content)->subject = ''; $build = _block_get_renderable_array($block_content); $build[$bid]['#blockreference_element'] = $element; return drupal_render($build); } } return ''; } /** * Theme function for 'plain' blockreference field formatter. */ function theme_blockreference_formatter_plain($variables) { $element = $variables['element']; $block = _blockreference_formatter_get_block($element); if ($block) { $info = module_invoke($block->module, 'block_info'); return $info[$block->delta]['info']; } return ''; } /** * Theme function for 'title' blockreference field formatter. */ function theme_blockreference_formatter_title($variables) { $element = $variables['element']; $block = _blockreference_formatter_get_block($element); if ($block) { $block_content = _block_render_blocks(array($block)); if ($block_content) { return reset($block_content)->subject; } } return ''; } /** * Theme function for 'config_link' blockreference field formatter. */ function theme_blockreference_formatter_config_link($variables) { $element = $variables['element']; $item = $element['item']; if (@$item['moddelta']) { list($module, $delta) = explode(':', $item['moddelta']); $settings = $element['display']['settings']; $label = ''; switch ($settings['label']) { case 'custom': $label = t($settings['custom_label']); break; case 'info': $label = theme_blockreference_formatter_plain($variables); break; case 'config': $label = db_query(' SELECT title FROM {block} WHERE module = ? AND delta = ? ', array($module, $delta))->fetchField(); break; case 'rendered': $label = theme_blockreference_formatter_title($variables); break; } if (!$label) { $label = t('configure block'); } return l($label, 'admin/structure/block/manage/' . $module . '/' . $delta . '/configure'); } return ''; } /** * The referenecable blocks in field instance settings. */ function _blockreference_get_block_modules_from_field($instance, $field = NULL) { $modules = @$instance['settings']['blockreference_modules']; if (!$modules) { $field or $field = field_info_field($instance['field_name']); $modules = @$field['settings']['referenceable_modules']; } $modules = drupal_map_assoc(array_keys(array_filter($modules))); return (array) $modules; } /** * The autocomplete match method in field instance settings. */ function _blockreference_get_ac_match_method_from_field($instance, $field = NULL) { $method = @$instance['widget']['settings']['blockreference_ac_match_method']; return $method ?: 'contains'; } /** * Return options list of modules that provide blocks, keyed by module machine * name. */ function _blockreference_get_block_modules() { $modules = &drupal_static(__FUNCTION__); if (!$modules) { // Get modules that define blocks. $modules = module_implements('block_info'); $modules = array_flip($modules); // And get their pretty names. $all_modules = system_list('module_enabled'); foreach ($modules as $machine_name => $foo) { $modules[$machine_name] = @$all_modules[$machine_name]->info['name'] ?: $machine_name; } natcasesort($modules); } return $modules; } /** * Helper to create an autocomplete string from a pretty block. */ function _blockreference_block_string($block) { if (!$block) { return ''; } $moddelta = $block->module . ':' . $block->delta; $label = $block->info; return $label . ' [' . $moddelta . ']'; } /** * Helper to return a array(module => ..., delta => ...) array from an AC string. */ function _blockreference_block_from_ac_string($ac_string) { if (preg_match(BLOCKREFERENCE_AC_REGEX, $ac_string, $match)) { $module = $match[1]; $delta = $match[2]; return compact('module', 'delta'); } } /** * Helper to create a pretty block object from a moddelta. */ function _blockreference_block($module, $delta, $info = NULL) { if (!$info) { $infos = module_invoke($module, 'block_info'); $info = @$infos[$delta]; } if ($info) { $info = compact('module', 'delta') + $info; $info['info'] = trim($info['info']); return (object) $info; } } /** * Helper to find blocks that potentially match a search string, in the right * order, altered by custom modules. */ function _blockreference_find_blocks($instance, $search_string = '', $context = array()) { $all_block_infos = &drupal_static(__FUNCTION__, array()); $iid = $instance['id']; // Cache ALL potential block objects for this instance. if (!isset($all_block_infos[$iid])) { $referenceable_modules = _blockreference_get_block_modules_from_field($instance); $all_block_infos[$iid] = array(); foreach (module_implements('block_info') as $module) { if (!$referenceable_modules || isset($referenceable_modules[$module])) { if ($blocks = module_invoke($module, 'block_info')) { foreach ($blocks as $delta => $info) { $moddelta = $module . ':' . $delta; $all_block_infos[$iid][$moddelta] = _blockreference_block($module, $delta, $info); } } } } } $blocks = $all_block_infos[$iid]; $context['instance'] = $instance; drupal_alter('blockreference_blocks_pre', $blocks, $context); // Filter by search string. if ($search_string) { $check_string = drupal_strtolower($search_string); foreach ($blocks as $moddelta => $block) { if (strpos(drupal_strtolower($block->info), $check_string) === FALSE) { unset($blocks[$moddelta]); } } } // Sort. uasort($blocks, function ($a, $b) { return strnatcasecmp($a->info, $b->info); }); drupal_alter('blockreference_blocks_post', $blocks, $context); return $blocks; } /** * Helper to make pretty options from block objects. */ function _blockreference_options($blocks, $ac_style = FALSE) { $options = array(); foreach ($blocks as $block) { $moddelta = $block->module . ':' . $block->delta; $label = $block->info; $value = $ac_style ? _blockreference_block_string($block) : $moddelta; $options[$value] = $label; } return $options; } /** * Implements hook_options_list(). */ function blockreference_options_list($field, $instance, $entity_type, $entity) { $context = array( 'type' => 'options_list', 'entity' => $entity, ); $blocks = _blockreference_find_blocks($instance, '', $context); return _blockreference_options($blocks); } /** * Helper function for theming normal block views, returns appropriate block. */ function _blockreference_formatter_get_block($element) { $item = $element['item']; if (@$item['moddelta']) { list($module, $delta) = explode(':', $item['moddelta']); $block = _blockreference_block($module, $delta); if ($block) { // Fetch all `block` columns for this block, any theme. $query = db_select('block', 'b') ->fields('b') ->condition('module', $module) ->condition('delta', $delta) ->execute(); $result = $query->fetchAssoc(); if ($result) { // Copy all themes columns over to our block object, except the useless, theme-specific. foreach ($result as $column => $value) { if (!isset($block->$column) && !in_array($column, array('bid', 'theme'))) { $block->$column = $value; } } } $block->region = 'none'; return $block; } } return FALSE; } /** * Implements hook_entity_view(). * * Pre-render blockreference fields attached to entities to ensure forms are * rendered early enough. */ function blockreference_entity_view($entity, $entity_type, $view_mode, $langcode) { if (empty($entity->content)) { return; } foreach (element_children($entity->content) as $field_name) { $field = $entity->content[$field_name]; // This is a blockreference field. if (isset($field['#field_type']) && $field['#field_type'] == 'blockreference') { // Unset renderable field items. foreach (element_children($field) as $n) { unset($entity->content[$field_name][$n]); } // Render. $html = render($field); // Add back to entity content. $keep_meta = array_flip(array( '#weight', '#title', '#access', '#label_display', '#field_name', '#entity_type', '#bundle', )); $entity->content[$field_name] = array('#markup' => $html) + array_intersect_key($field, $keep_meta); } } } /** * Implements hook_migrate_api(). */ function blockreference_migrate_api() { return array( 'api' => 2, 'field handlers' => array('MigrateBlockReferenceFieldHandler'), ); } /** * Implements hook_block_usage(). */ function blockreference_block_usage($module, $delta) { $moddelta = $module . ':' . $delta; // Cycle through potential fields/sources to find field items. $fields = field_info_field_map(); $entity_loads = $loads_by_field_name = array(); foreach ($fields as $field_name => $field) { if ($field['type'] == 'blockreference') { $table_name = 'field_data_' . $field_name; $column_name = $field_name . '_moddelta'; // Query this field table for above bids. $items = db_query("SELECT entity_type, entity_id FROM $table_name WHERE $column_name = :moddelta", array( ':moddelta' => $moddelta, )); // Collect entity_type's and entity_id's associatively. foreach ($items as $item) { $entity_loads[$item->entity_type][] = $item->entity_id; $loads_by_field_name[$item->entity_type][$item->entity_id][] = $field_name; } } } // Load entities and stack 'em up into results. $matches = array(); foreach ($entity_loads as $entity_type => $ids) { $entities = entity_load($entity_type, $ids); foreach ($entities as $id => $entity) { $uri = entity_uri($entity_type, $entity); $path = $uri['path']; $options = @$uri['options'] ?: array(); $fields = $loads_by_field_name[$entity_type][$id]; $matches[] = l($entity_type . ' # ' . $id, $path, $options) . ' (' . implode(', ', $fields) . ')'; } } return $matches; } /** * Implements hook_entityreference_link_config(). */ function blockreference_entityreference_link_config() { return array( 'blockreference' => array( 'value_column' => 'moddelta', 'links_callback' => '_blockreference_entityreference_link_links', ), ); } /** * Helper to make simple links from blocks for entityreference_link. */ function _blockreference_entityreference_link_links($entity_type, $moddeltas, $options, $context, $element) { $links = array(); foreach ($moddeltas as $moddelta) { list($module, $delta) = explode(':', $moddelta); $block = _blockreference_block($module, $delta); if ($block) { $links[] = l($block->info, 'admin/structure/block/manage/' . $module . '/' . $delta . '/configure', $options); } } return $links; }