'Blockreference autocomplete', 'page callback' => 'blockreference_autocomplete', 'access arguments' => array('access content'), 'type' => MENU_CALLBACK ); return $items; } /** * Implementation of hook_theme(). */ function blockreference_theme() { return array( 'blockreference_item_simple' => array( 'arguments' => array('item' => NULL), ), 'blockreference_item_advanced' => array( 'arguments' => array('item' => NULL, 'view' => NULL), ), 'blockreference_select' => array( 'arguments' => array('element' => NULL), ), 'blockreference_autocomplete' => array( 'arguments' => array('element' => NULL), ), 'blockreference_formatter_default' => array( 'arguments' => array('element'), ), 'blockreference_formatter_plain' => array( 'arguments' => array('element'), ), 'blockreference_formatter_title' => array( 'arguments' => array('element'), ), ); } /** * Implementation of hook_field_info(). * * Here we indicate that the content module will use its default * handling for the view of this field. * * Callbacks can be omitted if default handing is used. * They're included here just so this module can be used * as an example for custom modules that might do things * differently. */ function blockreference_field_info() { return array( 'blockreference' => array( 'label' => t('Block reference'), 'description' => t('Store the ID of a related block as an integer value.'), 'callbacks' => array( 'tables' => CONTENT_CALLBACK_DEFAULT, 'arguments' => CONTENT_CALLBACK_DEFAULT, ), ), ); } /** * Implementation of hook_field_settings(). */ function blockreference_field_settings($op, $field) { switch ($op) { case 'form': $form = array(); $form['referenceable_regions'] = array( '#type' => 'checkboxes', '#title' => t('Regions containing blocks that can be referenced'), '#multiple' => TRUE, '#default_value' => is_array($field['referenceable_regions']) ? $field['referenceable_regions'] : array(), '#options' => array('' => '(Disabled)') + system_region_list(variable_get('theme_default', 'garland')), ); return $form; case 'save': $settings = array('referenceable_regions'); return $settings; case 'database columns': $columns = array( 'bid' => array('type' => 'int', 'unsigned' => TRUE, 'not null' => FALSE), ); return $columns; case 'views data': $data = content_views_field_views_data($field); $db_info = content_database_info($field); $table_alias = content_views_tablename($field); // Swap the filter handler to the 'in' operator. $data[$table_alias][$field['field_name'] .'_bid']['filter']['handler'] = 'views_handler_filter_many_to_one_content'; // Add a relationship for related block. $data[$table_alias][$field['field_name'] .'_bid']['relationship'] = array( 'base' => 'blocks', 'field' => $db_info['columns']['bid']['column'], 'handler' => 'views_handler_relationship', ); return $data; } } /** * Implementation of hook_field(). */ function blockreference_field($op, &$node, $field, &$items, $teaser, $page) { switch ($op) { case 'validate': $refs = _blockreference_potential_references($field, TRUE); foreach ($items as $delta => $item) { if (is_array($item) && !empty($item['error_field'])) { $error_field = $item['error_field']; unset($item['error_field']); if (!empty($item['bid'])) { if (!in_array($item['bid'], array_keys($refs))) { form_set_error($error_field, t("%name : This block can't be referenced.", array('%name' => t($field['widget']['label'])))); } } } } return $items; } } /** * Implementation of hook_content_is_empty(). */ function blockreference_content_is_empty($item, $field) { if (empty($item['bid'])) { return TRUE; } return FALSE; } /** * Implementation of hook_field_formatter_info(). */ function blockreference_field_formatter_info() { return array( 'default' => array( 'label' => t('Default'), 'field types' => array('blockreference'), 'multiple values' => CONTENT_HANDLE_CORE, ), 'plain' => array( 'label' => t('Plain (info)'), 'field types' => array('blockreference'), 'multiple values' => CONTENT_HANDLE_CORE, ), 'title' => array( 'label' => t('Title only'), 'field types' => array('blockreference'), 'multiple values' => CONTENT_HANDLE_CORE, ), ); } /** * Theme function for 'default' blockreference field formatter. */ function theme_blockreference_formatter_default($element) { $field_name = $element['#field_name']; $field = content_fields($field_name); $output = ''; if (!empty($element['#item']['bid']) && is_numeric($element['#item']['bid'])) { $block = db_fetch_object(db_query(db_rewrite_sql("SELECT * FROM {blocks} WHERE bid = ". $element['#item']['bid'], 'blocks', 'bid'))); $block->enabled = TRUE; $block->status = TRUE; $block->page_match = TRUE; $block_view = module_invoke($block->module, 'block', 'view', $block->delta); $block->content = $block_view['content']; $block->subject = $block->title ? $block->title : $block_view['subject']; if ($block->content) { $output = theme('block', $block); } } return $output; } /** * Theme function for 'plain' blockreference field formatter. */ function theme_blockreference_formatter_plain($element) { $output = ''; if (isset($element['#item']['bid'])) { $args[] = $element['#item']['bid']; $result = db_query(db_rewrite_sql("SELECT b.module, b.delta FROM {blocks} b WHERE b.bid = '%s'", 'blocks', 'bid', $args), $args); $block = db_fetch_object($result); $info = module_invoke($block->module, 'block', 'list'); $output = $info[$block->delta]['info']; } return $output; } /** * Theme function for 'title' blockreference field formatter. */ function theme_blockreference_formatter_title($element) { $field_name = $element['#field_name']; $field = content_fields($field_name); $output = ''; if (!empty($element['#item']['bid']) && is_numeric($element['#item']['bid'])) { $block = db_fetch_object(db_query(db_rewrite_sql("SELECT * FROM {blocks} WHERE bid = ". $element['#item']['bid'], 'blocks', 'bid'))); $block->enabled = TRUE; $block->status = TRUE; $block->page_match = TRUE; $block_view = module_invoke($block->module, 'block', 'view', $block->delta); $subject = $block->title ? $block->title : $block_view['subject']; if ($block_view['content']) { $output = check_plain($subject); } } return $output; } /** * Implementation of hook_widget_info(). * * We need custom handling of multiple values for the blockreference_select * widget because we need to combine them into a options list rather * than display multiple elements. * * We will use the content module's default handling for default value. * * Callbacks can be omitted if default handing is used. * They're included here just so this module can be used * as an example for custom modules that might do things * differently. */ function blockreference_widget_info() { return array( 'blockreference_select' => array( 'label' => t('Select list'), 'field types' => array('blockreference'), 'multiple values' => CONTENT_HANDLE_MODULE, 'callbacks' => array( 'default value' => CONTENT_CALLBACK_DEFAULT, ), ), 'blockreference_autocomplete' => array( 'label' => t('Autocomplete text field'), 'field types' => array('blockreference'), 'multiple values' => CONTENT_HANDLE_CORE, 'callbacks' => array( 'default value' => CONTENT_CALLBACK_DEFAULT, ), ), ); } /** * Implementation of FAPI hook_elements(). * * Any FAPI callbacks needed for individual widgets can be declared here, * and the element will be passed to those callbacks for processing. * * Drupal will automatically theme the element using a theme with * the same name as the hook_elements key. * * Autocomplete_path is not used by text_widget but other widgets can use it * (see blockreference and userreference). */ function blockreference_elements() { return array( 'blockreference_select' => array( '#input' => TRUE, '#columns' => array('uid'), '#delta' => 0, '#process' => array('blockreference_select_process'), ), 'blockreference_autocomplete' => array( '#input' => TRUE, '#columns' => array('name'), '#delta' => 0, '#process' => array('blockreference_autocomplete_process'), '#autocomplete_path' => FALSE, ), ); } /** * Implementation of hook_widget(). * * Attach a single form element to the form. It will be built out and * validated in the callback(s) listed in hook_elements. We build it * out in the callbacks rather than here in hook_widget so it can be * plugged into any module that can provide it with valid * $field information. * * Content module will set the weight, field name and delta values * for each form element. This is a change from earlier CCK versions * where the widget managed its own multiple values. * * If there are multiple values for this field, the content module will * call this function as many times as needed. * * @param $form * the entire form array, $form['#node'] holds node information * @param $form_state * the form_state, $form_state['values'][$field['field_name']] * holds the field's form values. * @param $field * the field array * @param $items * array of default values for this field * @param $delta * the order of this item in the array of subelements (0, 1, 2, etc) * * @return * the form item for a single element for this field */ function blockreference_widget(&$form, &$form_state, $field, $items, $delta = 0) { switch ($field['widget']['type']) { case 'blockreference_select': $element = array( '#type' => 'blockreference_select', '#default_value' => $items, ); break; case 'blockreference_autocomplete': $element = array( '#type' => 'blockreference_autocomplete', '#default_value' => isset($items[$delta]) ? $items[$delta] : NULL, '#value_callback' => 'blockreference_autocomplete_value', ); break; } return $element; } /** * Value for a blockreference autocomplete element. * * Substitute in the block title for the block bid. */ function blockreference_autocomplete_value($element, $edit = FALSE) { $field_key = $element['#columns'][0]; if (!empty($element['#default_value'][$field_key])) { $bid = $element['#default_value'][$field_key]; $args[] = $bid; $result = db_query(db_rewrite_sql("SELECT b.module, b.delta FROM {blocks} b WHERE b.bid = '%d'", 'blocks', 'bid', $args), $args); $block = db_fetch_object($result); $info = module_invoke($block->module, 'block', 'list'); $value = $info[$block->delta]['info']; $value .= ' [bid:'. $bid .']'; return array($field_key => $value); } return array($field_key => NULL); } /** * Process an individual element. * * Build the form element. When creating a form using FAPI #process, * note that $element['#value'] is already set. * * The $fields array is in $form['#field_info'][$element['#field_name']]. */ function blockreference_select_process($element, $edit, $form_state, $form) { // The blockreference_select widget doesn't need to create its own // element, it can wrap around the optionwidgets_select element. // Add a validation step where the value can be unwrapped. $field_key = $element['#columns'][0]; $element[$field_key] = array( '#type' => 'optionwidgets_select', '#default_value' => isset($element['#value']) ? $element['#value'] : '', '#element_validate' => array('optionwidgets_validate', 'blockreference_select_validate'), // The following values were set by the content module and need // to be passed down to the nested element. '#field_name' => $element['#field_name'], '#delta' => $element['#delta'], '#columns' => $element['#columns'], '#title' => $element['#title'], '#required' => $element['#required'], '#description' => $element['#description'], ); return $element; } /** * Process an individual element. * * Build the form element. When creating a form using FAPI #process, * note that $element['#value'] is already set. * */ function blockreference_autocomplete_process($element, $edit, $form_state, $form) { // The blockreference autocomplete widget doesn't need to create its own // element, it can wrap around the text_textfield element and add an autocomplete // path and some extra processing to it. // Add a validation step where the value can be unwrapped. $field_key = $element['#columns'][0]; $element[$field_key] = array( '#type' => 'text_textfield', '#default_value' => isset($element['#value']) ? $element['#value'] : '', '#autocomplete_path' => 'blockreference/autocomplete/'. $element['#field_name'], '#element_validate' => array('blockreference_autocomplete_validate'), // The following values were set by the content module and need // to be passed down to the nested element. '#field_name' => $element['#field_name'], '#delta' => $element['#delta'], '#columns' => $element['#columns'], '#title' => $element['#title'], '#required' => $element['#required'], '#description' => $element['#description'], ); return $element; } /** * Validate an select element. * * Remove the wrapper layer and set the right element's value. * We don't know exactly where this element is, so we drill down * through the element until we get to our key. */ function blockreference_select_validate($element, &$form_state) { $field_key = $element['#columns'][0]; $new_parents = array(); $value = $form_state['values']; foreach ($element['#parents'] as $parent) { $value = $value[$parent]; if ($parent == $field_key) { $element['#parents'] = $new_parents; form_set_value($element, $value, $form_state); break; } $new_parents[] = $parent; } } /** * Validate an autocomplete element. * * Remove the wrapper layer and set the right element's value. */ function blockreference_autocomplete_validate($element, &$form_state) { $field_name = $element['#field_name']; $field = content_fields($field_name); $field_key = $element['#columns'][0]; $delta = $element['#delta']; $value = $element['#value'][$field_key]; $bid = NULL; if (!empty($value)) { preg_match('/^(?:\s*|(.*) )?\[\s*bid\s*:\s*(\d+)\s*\]$/', $value, $matches); if (!empty($matches)) { // explicit bid list(, $info, $bid) = $matches; $args[] = $bid; $result = db_query(db_rewrite_sql("SELECT b.module, b.delta FROM {blocks} b WHERE b.bid = '%s'", 'blocks', 'bid', $args), $args); $block = db_fetch_object($result); $info = module_invoke($block->module, 'block', 'list'); $block->info = $info[$block->delta]['info']; if (!empty($title) && ($b = db_fetch_object($result)) && $info != $b->info) { form_set_error($element[$field_key], t('%name: Title mismatch. Please check your selection.'), array('%name' => t($element[$field_key]['#title']))); } } else { // no explicit bid // TODO : // the best thing would be to present the user with an additional form, // allowing the user to choose between valid candidates with the same title // ATM, we pick the first matching candidate... $bids = _blockreference_potential_references($field, FALSE, $value, TRUE); $bid = (!empty($bids)) ? array_shift(array_keys($bids)) : 0; } } form_set_value($element, $bid, $form_state); return $element; } /** * Implementation of hook_allowed_values(). */ function blockreference_allowed_values($field) { $options = _blockreference_potential_references($field, TRUE); foreach ($options as $key => $value) { $options[$key] = _blockreference_item($field, $value); } if (!$field['required']) { $options = array(0 => '<'. t('none') .'>') + $options; } return $options; } /** * Fetch an array of all candidate referenced blocks, * for use in presenting the selection form to the user. * */ function _blockreference_potential_references($field, $return_full_blocks = FALSE, $string = '', $exact_string = false) { // build the appropriate query $related_regions = array(); $args = array(); if (is_array($field['referenceable_regions'])) { foreach ((array)$field['referenceable_regions'] as $related_region) { if (isset($related_region)) { $related_regions[] = " b.region = '%s'"; $args[] = $related_region; } } } $related_clause = implode(' OR ', $related_regions); if (!count($related_regions)) { return array(); } if (isset($string)) { //$string_clause = $exact_string ? " AND b.title = '%s'" : " AND b.title LIKE '%%%s%'"; <- doesn't work because the title is often blank or $string_clause = ""; $related_clause = "(". $related_clause .")". $string_clause; //$args[] = $string; } $result = db_query(db_rewrite_sql("SELECT b.bid, b.module, b.delta, b.title, b.region FROM {blocks} b WHERE b.theme = '". variable_get('theme_default', 'garland') ."' AND ". $related_clause ." ORDER BY b.region, b.weight", 'blocks', 'bid', $args), $args); $rows = array(); while ($block = db_fetch_object($result)) { $allowed = FALSE; $info = module_invoke($block->module, 'block', 'list'); $block->info = $info[$block->delta]['info']; // reject rows that don't match if ($exact_string && !empty($string)) { if ($string == $info[$block->delta]['info']) { $allowed = TRUE; } } else if (!empty($string)) { if (!empty($block->info) && stripos($block->info, $string) !== FALSE) { $allowed = TRUE; } else if (!empty($block->title) && stripos($block->title, $string) !== FALSE) { $allowed = TRUE; } else if (!empty($block->module) && stripos($block->module ." ". $block->delta, $string) !== FALSE) { $allowed = TRUE; } else if (!empty($block->bid) && stripos($block->bid, $string) !== FALSE) { $allowed = TRUE; } } else { $allowed = TRUE; } if ($return_full_blocks && $allowed) { $rows[$block->bid] = $block; } else if ($allowed) { $rows[$block->bid] = $block->info; } } return $rows; } /** * Retrieve a pipe delimited string of autocomplete suggestions */ function blockreference_autocomplete($field_name, $string = '') { $fields = content_fields(); $field = $fields[$field_name]; $matches = array(); $references = _blockreference_potential_references($field, TRUE, $string); foreach ($references as $rowbid => $rowname) { $matches[_blockreference_item($field, $rowname) .' [bid:'. $rowbid .']'] = _blockreference_item($field, $rowname); } drupal_json($matches); } function _blockreference_item($field, $item, $html = FALSE) { $output = theme('blockreference_item_simple', $item); $output = $html ? check_plain($output) : $output; return $output; } function theme_blockreference_item_advanced($item, $field_names, $view) { $item_fields = array(); $item = (array) $item; foreach ($item as $delta => $value) { // remove link tags (ex : for block titles) $value = preg_replace('/]*>(.*)<\/a>/iU', '$1', $value); if (!empty($value)) { $item_fields[] = "$value";; } } $output = implode(' - ', $item_fields); $output = "$output"; return $output; } function theme_blockreference_item_simple($item) { return $item->info; } /** * FAPI theme for an individual elements. * * The textfield or select is already rendered by the * textfield or select themes and the html output * lives in $element['#children']. Override this theme to * make custom changes to the output. * * $element['#field_name'] contains the field name * $element['#delta] is the position of this element in the group */ function theme_blockreference_select($element) { return $element['#children']; } function theme_blockreference_autocomplete($element) { return $element['#children']; }