form.inc in Finder 7.2
The finder form.
File
includes/form.incView source
<?php
/**
* @file
* The finder form.
*/
/**
* FAPI definition for the finder form.
*
* @param &$form
* The Forms API form array.
* @param &$form_state
* The Forms API form state.
* @param $finder
* The finder object.
* @see finder_form_submit()
*/
function finder_form($form, &$form_state, $finder) {
global $user;
ctools_include('plugins');
// Make sure drupal core loads this file when handling this form.
form_load_include($form_state, 'inc', 'finder', 'includes/form');
$form_state['finder'] = $finder;
// see if there is already a form state we should be using.
$finder_form_state = finder_form_state($finder);
if (is_array($finder_form_state)) {
$form_state = array_merge($form_state, $finder_form_state);
}
$finder->form_state = $form_state;
module_invoke_all('finder_form', $finder);
$form['finder_name'] = array(
'#type' => 'value',
'#value' => $finder->name,
);
$form['#action'] = url($finder->path);
if ($user->uid) {
$form['#token'] = FALSE;
}
$form['finder_form'] = array(
'#weight' => 0,
'#prefix' => '<div class="finder-form finder-' . $finder->name . '">',
'#suffix' => '</div>',
);
$header = $finder
->setting('header');
if (!empty($header['value']) && isset($header['format'])) {
$form['finder_form']['#prefix'] .= '<div class="prefix">' . check_markup($header['value'], $header['format'], FALSE) . '</div>';
}
$max_weight = 0;
$rendered_elements = array();
$ajax_settings = array();
foreach ($finder->elements as $element) {
// Ensure the module plugin file is always loaded.
if (isset($element->element_handler['file'])) {
$file = $element->element_handler['file'];
if (isset($element->element_handler['path'])) {
$file = $element->element_handler['path'] . '/' . $file;
}
$pathinfo = pathinfo($file);
form_load_include($form_state, $pathinfo['extension'], $element->element_handler['plugin module'], str_replace(drupal_get_path('module', $element->element_handler['plugin module']) . '/', '', $pathinfo['dirname']) . '/' . $pathinfo['filename']);
}
// Skip element callback - decide whether to render this element now.
if (!empty($element->parent) && !in_array($element->parent, $rendered_elements)) {
continue;
}
if ($handler_function = ctools_plugin_get_function($element->element_handler, 'skip element callback')) {
$skip_element_result = $handler_function($element, $form_state);
if ($skip_element_result) {
continue;
}
}
// Register this element to signify that it is being rendered.
$rendered_elements[] = $element->id;
$max_weight = max($max_weight, $element->weight);
if ($finder
->esetting($element, 'reduce')) {
$ajax_settings['reduce'][$element->id] = $finder
->esetting($element, 'reduce');
}
if ($finder
->esetting($element, 'ajax_load')) {
$form_element = array(
'#markup' => '<div class="finder-form-element finder-form-element-' . $element->id . '"></div>',
);
$ajax_settings['load'][] = $element->id;
}
else {
$form_element = finder_form_element($finder, $element, $form_state);
}
$parents = array_reverse($finder
->element_parents($element));
$value =& $form['finder_form'];
foreach ($parents as $p) {
$value =& $value[$p];
}
$value[$element->id] = $form_element;
}
if ($finder
->setting('autosubmit')) {
$ajax_settings['autosubmit'][] = $finder->build_id;
}
if (!empty($form_state['finished']) && $finder
->setting('ajax_results_load')) {
$ajax_settings['results_load'][] = $finder->build_id;
}
if ($finder
->setting('ajax_results_update')) {
$ajax_settings['results_update'][] = $finder->build_id;
}
if (!empty($ajax_settings)) {
drupal_add_js(array(
'finder' => array(
$finder->name => $ajax_settings,
),
), 'setting');
drupal_add_js(drupal_get_path('module', 'finder') . '/finder.js');
}
// Skip empty.
$form['finder_form'] = finder_form_skip_empty($form['finder_form'], $finder);
$form['finder_form']['actions']['#weight'] = $max_weight + 100;
if ($finder
->setting('find_button')) {
$form['finder_form']['actions']['find'] = array(
'#type' => 'submit',
'#name' => 'find',
'#value' => $finder
->setting('find_text'),
);
if ($finder
->setting('ajax')) {
$wrapper = $finder->build_id;
$form_state['ajax_display'] = 'block';
// Check if the 'ajax_remote' setting is on, that we are on the finder page, and that we are in a block.
if ($finder
->setting('ajax_remote') && strpos($_GET['q'], $finder->path) === 0 && $finder->build_display === 'block') {
// We are using the block as a remote control for the page. Change the wrapper for Ajax purposes.
$wrapper = 'finder-page-' . $finder->name . '-wrapper';
$form_state['ajax_display'] = 'page';
}
$form['finder_form']['actions']['find']['#ajax'] = array(
'callback' => 'finder_ajax',
'wrapper' => $wrapper,
'method' => 'replace',
'effect' => $finder
->setting('ajax_effect'),
);
}
}
if ($finder
->setting('go_button')) {
$form['finder_form']['actions']['go'] = array(
'#type' => 'submit',
'#name' => 'go',
'#value' => $finder
->setting('go_text'),
);
}
$footer = $finder
->setting('footer');
if (!empty($footer['value']) && isset($footer['format'])) {
$form['finder_form']['#suffix'] = '<div class="suffix">' . check_markup($footer['value'], $footer['format'], FALSE) . '</div>' . $form['finder_form']['#suffix'];
}
$form['#submit'] = array(
'finder_form_submit',
);
$form['#validate'] = array(
'finder_form_validate',
);
return $form;
}
/**
* Get the form array for an element.
*
* @param $finder
* A loaded finder object.
* @param $element
* The element object
* @param $form_state
* The form state. Should only use $form_state['values'] in this function
* because of finder_element_ajax().
* @return
* The form element array.
*/
function finder_form_element(&$finder, &$element, &$form_state) {
if (isset($form_state['values'][$element->id])) {
$element_default = $form_state['values'][$element->id];
}
else {
$element_default = $finder
->esetting($element, 'default_value');
$finder->form_state['values'][$element->id] = $form_state['values'][$element->id] = $element_default;
}
$form_element = array(
'#title' => check_plain($element->title),
'#weight' => $element->weight,
'#description' => check_markup($finder
->esetting($element, 'description')),
'#default_value' => $element_default,
'#required' => $finder
->esetting($element, 'required'),
'#executes_submit_callback' => TRUE,
'#field_prefix' => check_markup($finder
->esetting($element, 'field_prefix')),
'#field_suffix' => check_markup($finder
->esetting($element, 'field_suffix')),
'#title_display' => $finder
->esetting($element, 'title_display', 'before'),
'#attributes' => array(
'class' => array(
'finder-element',
'finder-element-' . $element->id,
),
),
);
$header = $finder
->esetting($element, 'header');
$form_element['#prefix'] = '';
if (!empty($header['value']) && isset($header['format'])) {
$form_element['#prefix'] = check_markup($header['value'], $header['format'], FALSE);
}
$form_element['#prefix'] = '<div class="finder-form-element finder-form-element-' . $element->id . '">' . $form_element['#prefix'];
$footer = $finder
->esetting($element, 'footer');
$form_element['#suffix'] = '';
if (!empty($footer['value']) && isset($footer['format'])) {
$form_element['#suffix'] = check_markup($footer['value'], $footer['format'], FALSE);
}
$form_element['#suffix'] .= '</div>';
if ($handler_function = ctools_plugin_get_function($element->element_handler, 'element callback')) {
$handler_function($element, $form_element, $form_state);
}
return $form_element;
}
/**
* Skip empty.
*
* Recursively handle skip empty setting.
*/
function finder_form_skip_empty($form, $finder) {
$children = element_children($form);
foreach ($children as $child) {
if (isset($finder->elements[$child])) {
$form[$child] = finder_form_skip_empty($form[$child], $finder);
if ($finder->elements[$child]->element_handler['type'] == 'container') {
// Containers are considered empty if they contain no child elements.
if ($finder
->esetting($finder->elements[$child], 'skip_empty')) {
$child_children = element_children($form[$child]);
if (empty($child_children)) {
unset($form[$child]);
}
}
}
else {
if ($finder
->esetting($finder->elements[$child], 'skip_empty')) {
// Forms are considered empty if there are zero or one choices.
$finder->find = array(
'mode' => 'choices',
'keywords' => array(
$finder->elements[$child]->id => array(
NULL,
),
),
'element' => $finder->elements[$child],
);
$finder
->find();
if (count($finder->find['results']) <= 1) {
unset($form[$child]);
}
}
}
}
}
return $form;
}
/**
* Validate function for finder form.
*
* Implements the 'validate_empty' functionality.
*
* @see finder_form()
*/
function finder_form_validate($form, &$form_state) {
$finder = $form_state['finder'];
if ($finder
->setting('validate_empty')) {
$all_empty = TRUE;
foreach ($finder->elements as $element) {
if (!empty($form_state['values'][$element->id])) {
$all_empty = FALSE;
break;
}
}
if ($all_empty) {
form_set_error('form', t('Please complete the %finder form.', array(
'%finder' => $finder->title,
)));
}
}
}
/**
* Submit function for finder form.
*
* Adds some needed data to $form_state and calls finder_form_state().
*
* @see finder_form()
*/
function finder_form_submit($form, &$form_state) {
$form_state['rebuild'] = TRUE;
$finder = $form_state['finder'];
$form_state['finished'] = TRUE;
if (!$finder
->setting('ajax') || $form_state['clicked_button']['#name'] != 'find') {
finder_form_state($finder, $form_state);
}
}
/**
* Statically 'get' or 'set' the FAPI form state in a per-finder cache.
*
* When used to 'set' the form state it will also check to see if a redirect
* is required to go to the results path with arguments. When used to 'get'
* the form state it will check the static cache for a stored form state, then
* it will check the session for a form state carried over from another page,
* and finally it will attempt to build a form state out of the path arguments.
*
* @param $finder
* The finder.
* @param $form_state
* The Forms API form state (if supplied, will 'set' the form state).
* @return
* A copy of the Forms API form state.
*/
function finder_form_state($finder, $form_state = NULL) {
static $finder_form_state = NULL;
if ($form_state) {
// we are setting the form_state in a submit.
// last chance for modules to intefere before potential redirect.
drupal_alter('finder_form_state', $form_state, $finder);
$finder_form_state[$finder->name] = $form_state;
// handle URL stuff.
if ($finder
->setting('url') == 'enabled') {
$sep = $finder
->setting('url_delimiter');
$empty_symbol = $finder
->setting('url_empty') ? $finder
->setting('url_empty') : ' ';
$query = array();
$keywords = array();
foreach ($finder->elements as $element) {
if ($element->element_handler['type'] == 'form' && isset($form_state['values'][$element->id])) {
$keyword = (array) $form_state['values'][$element->id];
foreach ($keyword as $k => $v) {
// Handle forward slashes in input.
$v = str_replace("/", "%2f%2f", $v);
// Handle new lines in input.
$v = str_replace("\r\n", "\n", $v);
$v = str_replace("\n", urlencode("***BREAK***"), $v);
if (strpos($v, $sep) !== FALSE) {
$v = '"' . $v . '"';
}
$keyword[$k] = $v ? trim($v) : $empty_symbol;
}
$keywords[$element->id] = implode(',', $keyword);
}
}
// Trim the keywords from the end to neaten it up.
$keywords = array_reverse($keywords);
$keywords_count = count($keywords);
foreach ($keywords as $keywords_key => $keywords_value) {
// If the keyword is empty, and it's not the first keyword.
$keywords_count--;
if ($keywords_value == $empty_symbol && $keywords_count) {
unset($keywords[$keywords_key]);
}
else {
break;
}
}
$keywords = array_reverse($keywords);
if ($form_state['clicked_button']['#name'] == 'go') {
$query['go'] = '1';
}
$context['sep'] = $sep;
$context['path'] = $finder->path . '/' . implode('/', $keywords);
$context['query'] = $query;
$context['form_state'] = $form_state;
drupal_alter('finder_form_redirect', $context);
finder_form_goto($context['sep'], $finder
->setting('url_empty'), $context['path'], $context['query']);
}
}
elseif (!isset($finder_form_state[$finder->name])) {
if ($finder
->setting('url') == 'disabled' && isset($_GET['finder'])) {
// check the session
$finder_form_state[$finder->name] = $_SESSION['finder'][$_GET['finder']];
}
elseif (!isset($_GET['finder']) && strlen($finder->path) < strlen($_GET['q']) && stripos($_GET['q'], $finder->path) === 0) {
// check the URL
// Get the seperator for element values - this is usually a comma.
$sep = $finder
->setting('url_delimiter');
// Get the finder arguments.
$args = str_replace($finder->path . '/', '', $_GET['q']);
// Handle new lines from input.
$args = str_replace("***BREAK***", "\n", $args);
// Forward slashes were encoded as double forward slashes. We must temporarily replace those here to prevent the explode() affecting this.
// Rawurlencode() doesn't work but can be fixed using Apache's AllowEncodedSlashes Directive, but how do you tell people to switch that on?
//$args = str_replace('//', '[-finder-forward-slash-]', $args);
$args = str_replace('%2f%2f', '[-finder-forward-slash-]', $args);
// Double seperators break the $csv_regex below, and I'm not clever enough to fix the regex.
$args = str_replace($sep . $sep, '[-finder-double-sep-]', $args);
// Break arguments apart into a string for each element.
$args = explode('/', $args);
$form_state['finished'] = TRUE;
$empty_symbol = !$finder
->setting('url_empty') ? $finder
->setting('url_empty') : ' ';
$csv_regex = "/" . $sep . "(?!(?:[^\\\"" . $sep . "]|[^\\\"]" . $sep . "[^\\\"])+\\\")/";
$args_key = 0;
foreach ($finder->elements as $key => $element) {
if ($element->element_handler['type'] == 'form') {
$keywords = array();
if (isset($args[$args_key])) {
$keywords = preg_split($csv_regex, $args[$args_key]);
}
$args_key++;
foreach ($keywords as $k => $v) {
$v = str_replace('[-finder-double-sep-]', $sep . $sep, $v);
$v = str_replace('[-finder-forward-slash-]', '/', $v);
$v = str_replace(urlencode($sep), $sep, trim($v));
if (trim($v) == trim($empty_symbol)) {
$v = NULL;
}
if (strpos($v, $sep) !== FALSE && $v[0] == '"' && $v[strlen($v) - 1] == '"') {
$v = substr($v, 1, strlen($v) - 2);
}
unset($keywords[$k]);
if ($v) {
$keywords[$v] = $v;
}
}
if (count($keywords) === 1) {
$keywords = current($keywords);
}
elseif (!count($keywords)) {
$keywords = NULL;
}
$form_state['values'][$element->id] = $keywords;
}
}
$finder_form_state[$finder->name] = $form_state;
}
}
if (!empty($finder_form_state[$finder->name])) {
return $finder_form_state[$finder->name];
}
}
/**
* Redirect from a finder form.
*
* The difference between this and drupal_goto() is that this undoes the
* encoding of the arguments seperator, as such encoding inteferes with finder.
*
* @param $sep
* The arguments seperator string.
* @param $path
* A Drupal path or a full URL.
* @param $query
* A query string component, if any.
* @param $fragment
* A destination fragment identifier (named anchor).
* @param $http_response_code
* Valid values for an actual "goto" as per RFC 2616 section 10.3 are:
* - 301 Moved Permanently (the recommended value for most redirects)
* - 302 Found (default in Drupal and PHP, sometimes used for spamming search
* engines)
* - 303 See Other
* - 304 Not Modified
* - 305 Use Proxy
* - 307 Temporary Redirect (alternative to "503 Site Down for Maintenance")
* Note: Other values are defined by RFC 2616, but are rarely used and poorly
* supported.
* @see drupal_goto()
*/
function finder_form_goto($sep, $url_empty_sep, $path = '', $query = NULL, $fragment = NULL, $http_response_code = 302) {
// A destination in $_GET always overrides the function arguments.
// We do not allow absolute URLs to be passed via $_GET, as this can be an attack vector.
if (isset($_GET['destination']) && !url_is_external($_GET['destination'])) {
$destination = drupal_parse_url($_GET['destination']);
$path = $destination['path'];
$query = $destination['query'];
$fragment = $destination['fragment'];
}
$url = url($path, array(
'query' => $query,
'fragment' => $fragment,
'absolute' => TRUE,
));
// custom changes - undo separator encoding
$url = str_replace(urlencode($url_empty_sep), $url_empty_sep, str_replace(urlencode($sep), $sep, $url));
// Remove newlines from the URL to avoid header injection attacks.
$url = str_replace(array(
"\n",
"\r",
), '', $url);
header('Location: ' . $url, TRUE, $http_response_code);
// The "Location" header sends a redirect status code to the HTTP daemon. In
// some cases this can be wrong, so we make sure none of the code below the
// drupal_goto() call gets executed upon redirection.
drupal_exit($url);
}
Functions
Name | Description |
---|---|
finder_form | FAPI definition for the finder form. |
finder_form_element | Get the form array for an element. |
finder_form_goto | Redirect from a finder form. |
finder_form_skip_empty | Skip empty. |
finder_form_state | Statically 'get' or 'set' the FAPI form state in a per-finder cache. |
finder_form_submit | Submit function for finder form. |
finder_form_validate | Validate function for finder form. |