| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600 | <?php/** * Implements hook_element_info(). */function elements_element_info() {  $types['emailfield'] = array(    '#input' => TRUE,    '#size' => 60,    '#maxlength' => 128,    '#autocomplete_path' => FALSE,    '#process' => array('ajax_process_form', 'elements_process_pattern'),    '#element_validate' => array('elements_validate_email'),    '#theme' => 'emailfield',    '#theme_wrappers' => array('form_element'),  );  $types['searchfield'] = array(    '#input' => TRUE,    '#size' => 60,    '#maxlength' => 128,    '#autocomplete_path' => FALSE,    '#process' => array('ajax_process_form'),    '#theme' => 'searchfield',    '#theme_wrappers' => array('form_element'),  );  $types['telfield'] = array(    '#input' => TRUE,    '#size' => 20,    '#maxlength' => 64,    '#process' => array('ajax_process_form', 'elements_process_pattern'),    '#theme' => 'telfield',    '#theme_wrappers' => array('form_element'),  );  $types['urlfield'] = array(    '#input' => TRUE,    '#size' => 80,    '#maxlength' => 128,    '#autocomplete_path' => FALSE,    '#process' => array('ajax_process_form', 'elements_process_pattern'),    '#element_validate' => array('elements_validate_url'),    '#theme' => 'urlfield',    '#theme_wrappers' => array('form_element'),  );  $types['numberfield'] = array(    '#input' => TRUE,    '#step' => 1,    '#process' => array('ajax_process_form'),    '#element_validate' => array('elements_validate_number'),    '#theme' => 'numberfield',    '#theme_wrappers' => array('form_element'),  );  $types['rangefield'] = array(    '#input' => TRUE,    '#step' => 1,    '#min' => 0,    '#max' => 100,    '#process' => array('ajax_process_form'),    '#element_validate' => array('elements_validate_number'),    '#theme' => 'rangefield',    '#theme_wrappers' => array('form_element'),  );  // Backported table element from https://drupal.org/node/80855  $types['table'] = array(    '#header' => array(),    '#rows' => array(),    '#empty' => '',    // Properties for tableselect support.    '#input' => TRUE,    '#tree' => TRUE,    '#tableselect' => FALSE,    '#multiple' => TRUE,    '#js_select' => TRUE,    '#value_callback' => 'elements_table_value',    '#process' => array('elements_table_process'),    '#element_validate' => array('elements_table_validate'),    // Properties for tabledrag support.    // The value is a list of arrays that are passed to drupal_add_tabledrag().    // elements_pre_render_table() prepends the HTML ID of the table to each set    // of arguments.    // @see drupal_add_tabledrag()    '#tabledrag' => array(),    // Render properties.    '#pre_render' => array('elements_pre_render_table'),    '#theme' => 'table',  );  return $types;}/** * Implements hook_element_info_alter(). */function elements_element_info_alter(&$types) {  // Add placeholder and pattern support to core form elements.  foreach (array_keys($types) as $type) {    switch ($type) {      case 'textfield':      case 'textarea':      case 'password':        $types[$type]['#process'][] = 'elements_process_placeholder';        $types[$type]['#process'][] = 'elements_process_pattern';        break;    }  }}/** * Implements hook_theme(). */function elements_theme() {  return array(    'emailfield' => array(      'arguments' => array('element' => NULL),      'render element' => 'element',      'file' => 'elements.theme.inc',    ),    'searchfield' => array(      'arguments' => array('element' => NULL),      'render element' => 'element',      'file' => 'elements.theme.inc',    ),    'telfield' => array(      'arguments' => array('element' => NULL),      'render element' => 'element',      'file' => 'elements.theme.inc',    ),    'urlfield' => array(      'arguments' => array('element' => NULL),      'render element' => 'element',      'file' => 'elements.theme.inc',    ),    'numberfield' => array(      'arguments' => array('element' => NULL),      'render element' => 'element',      'file' => 'elements.theme.inc',    ),    'rangefield' => array(      'arguments' => array('element' => NULL),      'render element' => 'element',      'file' => 'elements.theme.inc',    ),  );}/** * Return the autocompletion HTML for a form element. * * @param $element *   The renderable element to process for autocompletion. * * @return *   The rendered autocompletion element HTML, or an empty string if the field *   has no autocompletion enabled. */function elements_add_autocomplete(&$element) {  $extra = '';  if (!empty($element['#autocomplete_path']) && drupal_valid_path($element['#autocomplete_path'])) {    drupal_add_library('system', 'drupal.autocomplete');    $element['#attributes']['class'][] = 'form-autocomplete';    $attributes = array();    $attributes['type'] = 'hidden';    $attributes['id'] = $element['#attributes']['id'] . '-autocomplete';    $attributes['value'] = url($element['#autocomplete_path'], array('absolute' => TRUE));    $attributes['disabled'] = 'disabled';    $attributes['class'][] = 'autocomplete';    $extra = '<input' . drupal_attributes($attributes) . ' />';  }  return $extra;}/** * #process callback for #placeholder form element property. * * @param $element *   An associative array containing the properties and children of the *   generic input element. * * @return *   The processed element. */function elements_process_placeholder($element) {  if (isset($element['#placeholder']) && !isset($element['#attributes']['placeholder'])) {    $element['#attributes']['placeholder'] = $element['#placeholder'];  }  return $element;}/** * #process callback for #pattern form element property. * * @param $element *   An associative array containing the properties and children of the *   generic input element. * * @return *   The processed element. * * @see elements_validate_pattern() */function elements_process_pattern($element) {  if (isset($element['#pattern']) && !isset($element['#attributes']['pattern'])) {    $element['#attributes']['pattern'] = $element['#pattern'];    $element['#element_validate'][] = 'form_validate_pattern';  }  return $element;}/** * #element_validate callback for #pattern form element property. * * @param $element *   An associative array containing the properties and children of the *   generic form element. * @param $form_state *   The $form_state array for the form this element belongs to. * * @see element_process_pattern() */function elements_validate_pattern($element, &$form_state) {  if ($element['#value'] !== '') {    // The pattern must match the entire string and should have the same    // behavior as the RegExp object in ECMA 262.    // - Use bracket-style delimiters to avoid introducing a special delimiter    //   character like '/' that would have to be escaped.    // - Put in brackets so that the pattern can't interfere with what's    //   prepended and appended.    $pattern = '{^(?:' . $element['#pattern'] . ')$}';    if (!preg_match($pattern, $element['#value'])) {      form_error($element, t('%name field is not in the right format.', array('%name' => $element['#title'])));    }  }}/** * Form element validation handler for #type 'email'. * * Note that #maxlength and #required is validated by _form_validate() already. */function elements_validate_email(&$element, &$form_state) {  if ($element['#value'] && !valid_email_address($element['#value'])) {    form_error($element, t('The e-mail address %mail is not valid.', array('%mail' => $element['#value'])));  }}/** * Form element validation handler for #type 'url'. * * Note that #maxlength and #required is validated by _form_validate() already. */function elements_validate_url(&$element, &$form_state) {  if ($element['#value'] && !valid_url($element['#value'], TRUE)) {    form_error($element, t('The URL %url is not valid.', array('%url' => $element['#value'])));  }}/** * Form element validation handler for #type 'number'. * * Note that #required is validated by _form_validate() already. */function elements_validate_number(&$element, &$form_state) {  $value = $element['#value'];  if ($value === '') {    return;  }  $name = empty($element['#title']) ? $element['#parents'][0] : $element['#title'];  // Ensure the input is numeric.  if (!is_numeric($value)) {    form_error($element, t('%name must be a number.', array('%name' => $name)));    return;  }  // Ensure that the input is greater than the #min property, if set.  if (isset($element['#min']) && $value < $element['#min']) {    form_error($element, t('%name must be higher or equal to %min.', array('%name' => $name, '%min' => $element['#min'])));  }  // Ensure that the input is less than the #max property, if set.  if (isset($element['#max']) && $value > $element['#max']) {    form_error($element, t('%name must be below or equal to %max.', array('%name' => $name, '%max' => $element['#max'])));  }  if (isset($element['#step']) && strtolower($element['#step']) != 'any') {    // Check that the input is an allowed multiple of #step (offset by #min if    // #min is set).    $offset = isset($element['#min']) ? $element['#min'] : 0.0;    if (!elements_valid_number_step($value, $element['#step'], $offset)) {      form_error($element, t('%name is not a valid number.', array('%name' => $name)));    }  }}/** * Verifies that a number is a multiple of a given step. * * The implementation assumes it is dealing with IEEE 754 double precision * floating point numbers that are used by PHP on most systems. * * This is based on the number/range verification methods of webkit. * * @param $value *   The value that needs to be checked. * @param $step *   The step scale factor. Must be positive. * @param $offset *   (optional) An offset, to which the difference must be a multiple of the *   given step. * * @return bool *   TRUE if no step mismatch has occured, or FALSE otherwise. * * @see http://opensource.apple.com/source/WebCore/WebCore-1298/html/NumberInputType.cpp */function elements_valid_number_step($value, $step, $offset = 0.0) {  $double_value = (double) abs($value - $offset);  // The fractional part of a double has 53 bits. The greatest number that could  // be represented with that is 2^53. If the given value is even bigger than  // $step * 2^53, then dividing by $step will result in a very small remainder.  // Since that remainder can't even be represented with a single precision  // float the following computation of the remainder makes no sense and we can  // safely ignore it instead.  if ($double_value / pow(2.0, 53) > $step) {    return TRUE;  }  // Now compute that remainder of a division by $step.  $remainder = (double) abs($double_value - $step * round($double_value / $step));  // $remainder is a double precision floating point number. Remainders that  // can't be represented with single precision floats are acceptable. The  // fractional part of a float has 24 bits. That means remainders smaller than  // $step * 2^-24 are acceptable.  $computed_acceptable_error = (double) ($step / pow(2.0, 24));  return $computed_acceptable_error >= $remainder || $remainder >= ($step - $computed_acceptable_error);}/** * Determines the value of a table form element. * * @param array $element *   The form element whose value is being populated. * @param array|false $input *   The incoming input to populate the form element. If this is FALSE, *   the element's default value should be returned. * * @return array *   The data that will appear in the $form_state['values'] collection *   for this element. Return nothing to use the default. */function elements_table_value(array $element, $input = FALSE) {  // If #multiple is FALSE, the regular default value of radio buttons is used.  if (!empty($element['#tableselect']) && !empty($element['#multiple'])) {    // Contrary to #type 'checkboxes', the default value of checkboxes in a    // table is built from the array keys (instead of array values) of the    // #default_value property.    // @todo D8: Remove this inconsistency.    if ($input === FALSE) {      $element += array('#default_value' => array());      return drupal_map_assoc(array_keys(array_filter($element['#default_value'])));    }    else {      return is_array($input) ? drupal_map_assoc($input) : array();    }  }}/** * Creates checkbox or radio elements to populate a tableselect table. * * @param $element *   An associative array containing the properties and children of the *   tableselect element. * * @return *   The processed element. */function elements_table_process($element, &$form_state) {  if ($element['#tableselect']) {    if ($element['#multiple']) {      $value = is_array($element['#value']) ? $element['#value'] : array();    }    // Advanced selection behaviour makes no sense for radios.    else {      $element['#js_select'] = FALSE;    }    // Add a "Select all" checkbox column to the header.    // @todo D8: Rename into #select_all?    if ($element['#js_select']) {      $element['#attached']['js'][] = 'misc/tableselect.js';      array_unshift($element['#header'], array('class' => array('select-all')));    }    // Add an empty header column for radio buttons or when a "Select all"    // checkbox is not desired.    else {      array_unshift($element['#header'], '');    }    if (!isset($element['#default_value']) || $element['#default_value'] === 0) {      $element['#default_value'] = array();    }    // Create a checkbox or radio for each row in a way that the value of the    // tableselect element behaves as if it had been of #type checkboxes or    // radios.    foreach (element_children($element) as $key) {      // Do not overwrite manually created children.      if (!isset($element[$key]['select'])) {        // Determine option label; either an assumed 'title' column, or the        // first available column containing a #title or #markup.        // @todo Consider to add an optional $element[$key]['#title_key']        //   defaulting to 'title'?        $title = '';        if (!empty($element[$key]['title']['#title'])) {          $title = $element[$key]['title']['#title'];        }        else {          foreach (element_children($element[$key]) as $column) {            if (isset($element[$key][$column]['#title'])) {              $title = $element[$key][$column]['#title'];              break;            }            if (isset($element[$key][$column]['#markup'])) {              $title = $element[$key][$column]['#markup'];              break;            }          }        }        if ($title !== '') {          $title = t('Update !title', array('!title' => $title));        }        // Prepend the select column to existing columns.        $element[$key] = array('select' => array()) + $element[$key];        $element[$key]['select'] += array(          '#type' => $element['#multiple'] ? 'checkbox' : 'radio',          '#title' => $title,          '#title_display' => 'invisible',          // @todo If rows happen to use numeric indexes instead of string keys,          //   this results in a first row with $key === 0, which is always FALSE.          '#return_value' => $key,          '#attributes' => $element['#attributes'],        );        $element_parents = array_merge($element['#parents'], array($key));        if ($element['#multiple']) {          $element[$key]['select']['#default_value'] = isset($value[$key]) ? $key : NULL;          $element[$key]['select']['#parents'] = $element_parents;        }        else {          $element[$key]['select']['#default_value'] = ($element['#default_value'] == $key ? $key : NULL);          $element[$key]['select']['#parents'] = $element['#parents'];          $element[$key]['select']['#id'] = drupal_html_id('edit-' . implode('-', $element_parents));        }      }    }  }  return $element;}/** * #element_validate callback for #type 'table'. * * @param array $element *   An associative array containing the properties and children of the *   table element. * @param array $form_state *   The current state of the form. */function elements_table_validate($element, &$form_state) {  // Skip this validation if the button to submit the form does not require  // selected table row data.  //if (empty($form_state['triggering_element']['#tableselect'])) {  //  return;  //}  if ($element['#multiple']) {    if (!is_array($element['#value']) || !count(array_filter($element['#value']))) {      form_error($element, t('No items selected.'));    }  }  elseif (!isset($element['#value']) || $element['#value'] === '') {    form_error($element, t('No item selected.'));  }}/** * #pre_render callback to transform children of an element into #rows suitable for theme_table(). * * This function converts sub-elements of an element of #type 'table' to be * suitable for theme_table(): * - The first level of sub-elements are table rows. Only the #attributes *   property is taken into account. * - The second level of sub-elements is converted into columns for the *   corresponding first-level table row. * * Simple example usage: * @code * $form['table'] = array( *   '#type' => 'table', *   '#header' => array(t('Title'), array('data' => t('Operations'), 'colspan' => '1')), *   // Optionally, to add tableDrag support: *   '#tabledrag' => array( *     array('order', 'sibling', 'thing-weight'), *   ), * ); * foreach ($things as $row => $thing) { *   $form['table'][$row]['#weight'] = $thing['weight']; * *   $form['table'][$row]['title'] = array( *     '#type' => 'textfield', *     '#default_value' => $thing['title'], *   ); * *   // Optionally, to add tableDrag support: *   $form['table'][$row]['#attributes']['class'][] = 'draggable'; *   $form['table'][$row]['weight'] = array( *     '#type' => 'textfield', *     '#title' => t('Weight for @title', array('@title' => $thing['title'])), *     '#title_display' => 'invisible', *     '#size' => 4, *     '#default_value' => $thing['weight'], *     '#attributes' => array('class' => array('thing-weight')), *   ); * *   // The amount of link columns should be identical to the 'colspan' *   // attribute in #header above. *   $form['table'][$row]['edit'] = array( *     '#type' => 'link', *     '#title' => t('Edit'), *     '#href' => 'thing/' . $row . '/edit', *   ); * } * @endcode * * @param array $element *   A structured array containing two sub-levels of elements. Properties used: *   - #tabledrag: The value is a list of arrays that are passed to *     drupal_add_tabledrag(). The HTML ID of the table is prepended to each set *     of arguments. * * @see elements_element_info() * @see theme_table() * @see drupal_process_attached() * @see drupal_add_tabledrag() */function elements_pre_render_table(array $element) {  foreach (element_children($element) as $first) {    $row = array('data' => array());    // Apply attributes of first-level elements as table row attributes.    if (isset($element[$first]['#attributes'])) {      $row += $element[$first]['#attributes'];    }    // Turn second-level elements into table row columns.    // @todo Do not render a cell for children of #type 'value'.    // @see http://drupal.org/node/1248940    foreach (element_children($element[$first]) as $second) {      // Assign the element by reference, so any potential changes to the      // original element are taken over.      $column = array('data' => &$element[$first][$second]);      // Apply wrapper attributes of second-level elements as table cell      // attributes.      //if (isset($element[$first][$second]['#wrapper_attributes'])) {      //  $column += $element[$first][$second]['#wrapper_attributes'];      //}      $row['data'][] = $column;    }    $element['#rows'][] = $row;  }  // Take over $element['#id'] as HTML ID attribute, if not already set.  element_set_attributes($element, array('id'));  // If the custom #tabledrag is set and there is a HTML ID, inject the table's  // HTML ID as first callback argument and attach the behavior.  if (!empty($element['#tabledrag']) && isset($element['#attributes']['id'])) {    foreach ($element['#tabledrag'] as &$args) {      array_unshift($args, $element['#attributes']['id']);    }    $element['#attached']['drupal_add_tabledrag'] = $element['#tabledrag'];  }  if (!empty($element['#tabledrag']) && !empty($element['#tableselect'])) {    $element['#attached']['css'][] = drupal_get_path('module', 'elements') . '/elements.table.css';  }  return $element;}
 |