the term, // so that the depth attribute remains correct. $term = clone $term; } $term->depth = $depth; unset($term->parent); $term->parents = $parents[$vid][$term->tid]; $tree[] = $term; if (!empty($children[$vid][$term->tid])) { $has_children = TRUE; // We have to continue with this parent later. $process_parents[] = $parent; // Use the current term as parent for the next iteration. $process_parents[] = $term->tid; // Reset pointers for child lists because we step in there more often // with multi parents. reset($children[$vid][$term->tid]); // Move pointer so that we get the correct term the next time. next($children[$vid][$parent]); break; } } while ($child = next($children[$vid][$parent])); if (!$has_children) { // We processed all terms in this hierarchy-level, reset pointer // so that this function works the next time it gets called. reset($children[$vid][$parent]); } } } return $tree; } /** * Try to map a string to an existing term, as for glossary use. * * Provides a case-insensitive and trimmed mapping, to maximize the * likelihood of a successful match. * * @param name * Name of the term to search for. * * @return * An array of matching term objects. */ function taxonomy_get_term_by_name($name) { return taxonomy_term_load_multiple(array(), array('name' => trim($name))); } /** * Controller class for taxonomy terms. * * This extends the DrupalDefaultEntityController class. Only alteration is * that we match the condition on term name case-independently. */ class TaxonomyTermController extends DrupalDefaultEntityController { protected function buildQuery($ids, $conditions = array(), $revision_id = FALSE) { $query = parent::buildQuery($ids, $conditions, $revision_id); $query->addTag('translatable'); $query->addTag('term_access'); // When name is passed as a condition use LIKE. if (isset($conditions['name'])) { $query_conditions = &$query->conditions(); foreach ($query_conditions as $key => $condition) { if ($condition['field'] == 'base.name') { $query_conditions[$key]['operator'] = 'LIKE'; $query_conditions[$key]['value'] = db_like($query_conditions[$key]['value']); } } } // Add the machine name field from the {taxonomy_vocabulary} table. $query->innerJoin('taxonomy_vocabulary', 'v', 'base.vid = v.vid'); $query->addField('v', 'machine_name', 'vocabulary_machine_name'); return $query; } protected function cacheGet($ids, $conditions = array()) { $terms = parent::cacheGet($ids, $conditions); // Name matching is case insensitive, note that with some collations // LOWER() and drupal_strtolower() may return different results. foreach ($terms as $term) { $term_values = (array) $term; if (isset($conditions['name']) && drupal_strtolower($conditions['name'] != drupal_strtolower($term_values['name']))) { unset($terms[$term->tid]); } } return $terms; } } /** * Controller class for taxonomy vocabularies. * * This extends the DrupalDefaultEntityController class, adding required * special handling for taxonomy vocabulary objects. */ class TaxonomyVocabularyController extends DrupalDefaultEntityController { protected function buildQuery($ids, $conditions = array(), $revision_id = FALSE) { $query = parent::buildQuery($ids, $conditions, $revision_id); $query->addTag('translatable'); $query->orderBy('base.weight'); $query->orderBy('base.name'); return $query; } } /** * Load multiple taxonomy terms based on certain conditions. * * This function should be used whenever you need to load more than one term * from the database. Terms are loaded into memory and will not require * database access if loaded again during the same page request. * * @see entity_load() * @see EntityFieldQuery * * @param $tids * An array of taxonomy term IDs. * @param $conditions * (deprecated) An associative array of conditions on the {taxonomy_term} * table, where the keys are the database fields and the values are the * values those fields must have. Instead, it is preferable to use * EntityFieldQuery to retrieve a list of entity IDs loadable by * this function. * * @return * An array of term objects, indexed by tid. When no results are found, an * empty array is returned. * * @todo Remove $conditions in Drupal 8. */ function taxonomy_term_load_multiple($tids = array(), $conditions = array()) { return entity_load('taxonomy_term', $tids, $conditions); } /** * Load multiple taxonomy vocabularies based on certain conditions. * * This function should be used whenever you need to load more than one * vocabulary from the database. Terms are loaded into memory and will not * require database access if loaded again during the same page request. * * @see entity_load() * * @param $vids * An array of taxonomy vocabulary IDs, or FALSE to load all vocabularies. * @param $conditions * An array of conditions to add to the query. * * @return * An array of vocabulary objects, indexed by vid. */ function taxonomy_vocabulary_load_multiple($vids = array(), $conditions = array()) { return entity_load('taxonomy_vocabulary', $vids, $conditions); } /** * Return the vocabulary object matching a vocabulary ID. * * @param $vid * The vocabulary's ID. * * @return * The vocabulary object with all of its metadata, if exists, FALSE otherwise. * Results are statically cached. */ function taxonomy_vocabulary_load($vid) { $vocabularies = taxonomy_vocabulary_load_multiple(array($vid)); return reset($vocabularies); } /** * Return the vocabulary object matching a vocabulary machine name. * * @param $name * The vocabulary's machine name. * * @return * The vocabulary object with all of its metadata, if exists, FALSE otherwise. * Results are statically cached. */ function taxonomy_vocabulary_machine_name_load($name) { $vocabularies = taxonomy_vocabulary_load_multiple(NULL, array('machine_name' => $name)); return reset($vocabularies); } /** * Return the term object matching a term ID. * * @param $tid * A term's ID * * @return * A term object. Results are statically cached. */ function taxonomy_term_load($tid) { if (!is_numeric($tid)) { return FALSE; } $term = taxonomy_term_load_multiple(array($tid), array()); return $term ? $term[$tid] : FALSE; } /** * Helper function for array_map purposes. */ function _taxonomy_get_tid_from_term($term) { return $term->tid; } /** * Implodes a list of tags of a certain vocabulary into a string. * * @see drupal_explode_tags() */ function taxonomy_implode_tags($tags, $vid = NULL) { $typed_tags = array(); foreach ($tags as $tag) { // Extract terms belonging to the vocabulary in question. if (!isset($vid) || $tag->vid == $vid) { // Make sure we have a completed loaded taxonomy term. if (isset($tag->name)) { // Commas and quotes in tag names are special cases, so encode 'em. if (strpos($tag->name, ',') !== FALSE || strpos($tag->name, '"') !== FALSE) { $typed_tags[] = '"' . str_replace('"', '""', $tag->name) . '"'; } else { $typed_tags[] = $tag->name; } } } } return implode(', ', $typed_tags); } /** * Implements hook_field_info(). * * Field settings: * - allowed_values: a list array of one or more vocabulary trees: * - vocabulary: a vocabulary machine name. * - parent: a term ID of a term whose children are allowed. This should be * '0' if all terms in a vocabulary are allowed. The allowed values do not * include the parent term. * */ function taxonomy_field_info() { return array( 'taxonomy_term_reference' => array( 'label' => t('Term reference'), 'description' => t('This field stores a reference to a taxonomy term.'), 'default_widget' => 'options_select', 'default_formatter' => 'taxonomy_term_reference_link', 'settings' => array( 'allowed_values' => array( array( 'vocabulary' => '', 'parent' => '0', ), ), ), ), ); } /** * Implements hook_field_widget_info(). */ function taxonomy_field_widget_info() { return array( 'taxonomy_autocomplete' => array( 'label' => t('Autocomplete term widget (tagging)'), 'field types' => array('taxonomy_term_reference'), 'settings' => array( 'size' => 60, 'autocomplete_path' => 'taxonomy/autocomplete', ), 'behaviors' => array( 'multiple values' => FIELD_BEHAVIOR_CUSTOM, ), ), ); } /** * Implements hook_field_widget_info_alter(). */ function taxonomy_field_widget_info_alter(&$info) { $info['options_select']['field types'][] = 'taxonomy_term_reference'; $info['options_buttons']['field types'][] = 'taxonomy_term_reference'; } /** * Implements hook_options_list(). */ function taxonomy_options_list($field) { $function = !empty($field['settings']['options_list_callback']) ? $field['settings']['options_list_callback'] : 'taxonomy_allowed_values'; return $function($field); } /** * Implements hook_field_validate(). * * Taxonomy field settings allow for either a single vocabulary ID, multiple * vocabulary IDs, or sub-trees of a vocabulary to be specified as allowed * values, although only the first of these is supported via the field UI. * Confirm that terms entered as values meet at least one of these conditions. * * Possible error codes: * - 'taxonomy_term_illegal_value': The value is not part of the list of allowed values. */ function taxonomy_field_validate($entity_type, $entity, $field, $instance, $langcode, $items, &$errors) { // Build an array of existing term IDs so they can be loaded with // taxonomy_term_load_multiple(); foreach ($items as $delta => $item) { if (!empty($item['tid']) && $item['tid'] != 'autocreate') { $tids[] = $item['tid']; } } if (!empty($tids)) { $terms = taxonomy_term_load_multiple($tids); // Check each existing item to ensure it can be found in the // allowed values for this field. foreach ($items as $delta => $item) { $validate = TRUE; if (!empty($item['tid']) && $item['tid'] != 'autocreate') { $validate = FALSE; foreach ($field['settings']['allowed_values'] as $settings) { // If no parent is specified, check if the term is in the vocabulary. if (isset($settings['vocabulary']) && empty($settings['parent'])) { if ($settings['vocabulary'] == $terms[$item['tid']]->vocabulary_machine_name) { $validate = TRUE; break; } } // If a parent is specified, then to validate it must appear in the // array returned by taxonomy_get_parents_all(). elseif (!empty($settings['parent'])) { $ancestors = taxonomy_get_parents_all($item['tid']); foreach ($ancestors as $ancestor) { if ($ancestor->tid == $settings['parent']) { $validate = TRUE; break 2; } } } } } if (!$validate) { $errors[$field['field_name']][$langcode][$delta][] = array( 'error' => 'taxonomy_term_reference_illegal_value', 'message' => t('%name: illegal value.', array('%name' => $instance['label'])), ); } } } } /** * Implements hook_field_is_empty(). */ function taxonomy_field_is_empty($item, $field) { if (!is_array($item) || (empty($item['tid']) && (string) $item['tid'] !== '0')) { return TRUE; } return FALSE; } /** * Implements hook_field_formatter_info(). */ function taxonomy_field_formatter_info() { return array( 'taxonomy_term_reference_link' => array( 'label' => t('Link'), 'field types' => array('taxonomy_term_reference'), ), 'taxonomy_term_reference_plain' => array( 'label' => t('Plain text'), 'field types' => array('taxonomy_term_reference'), ), ); } /** * Implements hook_field_formatter_view(). */ function taxonomy_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) { $element = array(); // Terms whose tid is 'autocreate' do not exist // yet and $item['taxonomy_term'] is not set. Theme such terms as // just their name. switch ($display['type']) { case 'taxonomy_term_reference_link': foreach ($items as $delta => $item) { if ($item['tid'] == 'autocreate') { $element[$delta] = array( '#markup' => check_plain($item['name']), ); } else { $term = $item['taxonomy_term']; $uri = entity_uri('taxonomy_term', $term); $element[$delta] = array( '#type' => 'link', '#title' => $term->name, '#href' => $uri['path'], '#options' => $uri['options'], ); } } break; case 'taxonomy_term_reference_plain': foreach ($items as $delta => $item) { $name = ($item['tid'] != 'autocreate' ? $item['taxonomy_term']->name : $item['name']); $element[$delta] = array( '#markup' => check_plain($name), ); } break; } return $element; } /** * Returns the set of valid terms for a taxonomy field. * * @param $field * The field definition. * @return * The array of valid terms for this field, keyed by term id. */ function taxonomy_allowed_values($field) { $options = array(); foreach ($field['settings']['allowed_values'] as $tree) { if ($vocabulary = taxonomy_vocabulary_machine_name_load($tree['vocabulary'])) { if ($terms = taxonomy_get_tree($vocabulary->vid, $tree['parent'])) { foreach ($terms as $term) { $options[$term->tid] = str_repeat('-', $term->depth) . $term->name; } } } } return $options; } /** * Implements hook_field_formatter_prepare_view(). * * This preloads all taxonomy terms for multiple loaded objects at once and * unsets values for invalid terms that do not exist. */ function taxonomy_field_formatter_prepare_view($entity_type, $entities, $field, $instances, $langcode, &$items, $displays) { $tids = array(); // Collect every possible term attached to any of the fieldable entities. foreach ($entities as $id => $entity) { foreach ($items[$id] as $delta => $item) { // Force the array key to prevent duplicates. if ($item['tid'] != 'autocreate') { $tids[$item['tid']] = $item['tid']; } } } if ($tids) { $terms = taxonomy_term_load_multiple($tids); // Iterate through the fieldable entities again to attach the loaded term data. foreach ($entities as $id => $entity) { $rekey = FALSE; foreach ($items[$id] as $delta => $item) { // Check whether the taxonomy term field instance value could be loaded. if (isset($terms[$item['tid']])) { // Replace the instance value with the term data. $items[$id][$delta]['taxonomy_term'] = $terms[$item['tid']]; } // Terms to be created are not in $terms, but are still legitimate. else if ($item['tid'] == 'autocreate') { // Leave the item in place. } // Otherwise, unset the instance value, since the term does not exist. else { unset($items[$id][$delta]); $rekey = TRUE; } } if ($rekey) { // Rekey the items array. $items[$id] = array_values($items[$id]); } } } } /** * Title callback for term pages. * * @param $term * A term object. * * @return * The term name to be used as the page title. */ function taxonomy_term_title($term) { return $term->name; } /** * Implements hook_field_widget_form(). */ function taxonomy_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) { $tags = array(); foreach ($items as $item) { $tags[$item['tid']] = isset($item['taxonomy_term']) ? $item['taxonomy_term'] : taxonomy_term_load($item['tid']); } $element += array( '#type' => 'textfield', '#default_value' => taxonomy_implode_tags($tags), '#autocomplete_path' => $instance['widget']['settings']['autocomplete_path'] . '/' . $field['field_name'], '#size' => $instance['widget']['settings']['size'], '#maxlength' => 1024, '#element_validate' => array('taxonomy_autocomplete_validate'), ); return $element; } /** * Form element validate handler for taxonomy term autocomplete element. */ function taxonomy_autocomplete_validate($element, &$form_state) { // Autocomplete widgets do not send their tids in the form, so we must detect // them here and process them independently. $value = array(); if ($tags = $element['#value']) { // Collect candidate vocabularies. $field = field_widget_field($element, $form_state); $vocabularies = array(); foreach ($field['settings']['allowed_values'] as $tree) { if ($vocabulary = taxonomy_vocabulary_machine_name_load($tree['vocabulary'])) { $vocabularies[$vocabulary->vid] = $vocabulary; } } // Translate term names into actual terms. $typed_terms = drupal_explode_tags($tags); foreach ($typed_terms as $typed_term) { // See if the term exists in the chosen vocabulary and return the tid; // otherwise, create a new 'autocreate' term for insert/update. if ($possibilities = taxonomy_term_load_multiple(array(), array('name' => trim($typed_term), 'vid' => array_keys($vocabularies)))) { $term = array_pop($possibilities); } else { $vocabulary = reset($vocabularies); $term = array( 'tid' => 'autocreate', 'vid' => $vocabulary->vid, 'name' => $typed_term, 'vocabulary_machine_name' => $vocabulary->machine_name, ); } $value[] = (array)$term; } } form_set_value($element, $value, $form_state); } /** * Implements hook_field_widget_error(). */ function taxonomy_field_widget_error($element, $error, $form, &$form_state) { form_error($element, $error['message']); } /** * Implements hook_field_settings_form(). */ function taxonomy_field_settings_form($field, $instance, $has_data) { // Get proper values for 'allowed_values_function', which is a core setting. $vocabularies = taxonomy_get_vocabularies(); $options = array(); foreach ($vocabularies as $vocabulary) { $options[$vocabulary->machine_name] = $vocabulary->name; } $form['allowed_values'] = array( '#tree' => TRUE, ); foreach ($field['settings']['allowed_values'] as $delta => $tree) { $form['allowed_values'][$delta]['vocabulary'] = array( '#type' => 'select', '#title' => t('Vocabulary'), '#default_value' => $tree['vocabulary'], '#options' => $options, '#required' => TRUE, '#description' => t('The vocabulary which supplies the options for this field.'), '#disabled' => $has_data, ); $form['allowed_values'][$delta]['parent'] = array( '#type' => 'value', '#value' => $tree['parent'], ); } return $form; } /** * Implements hook_rdf_mapping(). * * @return array * The rdf mapping for vocabularies and terms. */ function taxonomy_rdf_mapping() { return array( array( 'type' => 'taxonomy_term', 'bundle' => RDF_DEFAULT_BUNDLE, 'mapping' => array( 'rdftype' => array('skos:Concept'), 'name' => array( 'predicates' => array('rdfs:label', 'skos:prefLabel'), ), 'description' => array( 'predicates' => array('skos:definition'), ), 'vid' => array( 'predicates' => array('skos:inScheme'), 'type' => 'rel', ), 'parent' => array( 'predicates' => array('skos:broader'), 'type' => 'rel', ), ), ), array( 'type' => 'taxonomy_vocabulary', 'bundle' => RDF_DEFAULT_BUNDLE, 'mapping' => array( 'rdftype' => array('skos:ConceptScheme'), 'name' => array( 'predicates' => array('dc:title'), ), 'description' => array( 'predicates' => array('rdfs:comment'), ), ), ), ); } /** * @defgroup taxonomy_index Taxonomy indexing * @{ * Functions to maintain taxonomy indexing. * * Taxonomy uses default field storage to store canonical relationships * between terms and fieldable entities. However h_update('taxonomy_term', $term); if (isset($term->parent)) { db_delete('taxonomy_term_hierarchy') ->condition('tid', $term->tid) ->execute(); } } if (isset($term->parent)) { if (!is_array($term->parent)) { $term->parent = array($term->parent); } $query = db_insert('taxonomy_term_hierarchy') ->fields(array('tid', 'parent')); foreach ($term->parent as $parent) { if (is_array($parent)) { foreach ($parent as $tid) { $query->values(array( 'tid' => $term->tid, 'parent' => $tid )); } } else { $query->values(array( 'tid' => $term->tid, 'parent' => $parent )); } } $query->execute(); } // Reset the taxonomy term static variables. taxonomy_terms_static_reset(); // Invoke the taxonomy hooks. module_invoke_all("taxonomy_term_$op", $term); module_invoke_all("entity_$op", $term, 'taxonomy_term'); unset($term->original); return $status; } /** * Delete a term. * * @param $tid * The term ID. * @return * Status constant indicating deletion. */ function taxonomy_term_delete($tid) { $transaction = db_transaction(); try { $tids = array($tid); while ($tids) { $children_tids = $orphans = array(); foreach ($tids as $tid) { // See if any of the term's children are about to be become orphans: if ($children = taxonomy_get_children($tid)) { foreach ($children as $child) { // If the term has multiple parents, we don't delete it. $parents = taxonomy_get_parents($child->tid); if (count($parents) == 1) { $orphans[] = $child->tid; } } } if ($term = taxonomy_term_load($tid)) { db_delete('taxonomy_term_data') ->condition('tid', $tid) ->execute(); db_delete('taxonomy_term_hierarchy') ->condition('tid', $tid) ->execute(); field_attach_delete('taxonomy_term', $term); module_invoke_all('taxonomy_term_delete', $term); module_invoke_all('entity_delete', $term, 'taxonomy_term'); taxonomy_terms_static_reset(); } } $tids = $orphans; } return SAVED_DELETED; } catch (Exception $e) { $transaction->rollback(); watchdog_exception('taxonomy', $e); throw $e; } } /** * Generate an array for rendering the given term. * * @param $term * A term object. * @param $view_mode * View mode, e.g. 'full', 'teaser'... * @param $langcode * (optional) A language code to use for rendering. Defaults to the global * content language of the current request. * * @return * An array as expected by drupal_render(). */ function taxonomy_term_view($term, $view_mode = 'full', $langcode = NULL) { if (!isset($langcode)) { $langcode = $GLOBALS['language_content']->language; } field_attach_prepare_view('taxonomy_term', array($term->tid => $term), $view_mode, $langcode); entity_prepare_view('taxonomy_term', array($term->tid => $term), $langcode); $build = array( '#theme' => 'taxonomy_term', '#term' => $term, '#view_mode' => $view_mode, '#language' => $langcode, ); $build += field_attach_view('taxonomy_term', $term, $view_mode, $langcode); // Add term description if the term has one. if (!empty($term->description)) { $build['description'] = array( '#markup' => check_markup($term->description, $term->format, '', TRUE), '#weight' => 0, '#prefix' => '
', '#suffix' => '
', ); } $build['#attached']['css'][] = drupal_get_path('module', 'taxonomy') . '/taxonomy.css'; // Allow modules to modify the structured term. $type = 'taxonomy_term'; drupal_alter(array('taxonomy_term_view', 'entity_view'), $build, $type); return $build; } /** * Process variables for taxonomy-term.tpl.php. */ function template_preprocess_taxonomy_term(&$variables) { $variaboken/flush-cache')), ), ); } function token_type_load($token_type) { $info = token_get_info(); return isset($info['types'][$token_type]) ? $info['types'][$token_type] : FALSE; } /** * Implements hook_theme(). */ function token_theme() { return array( 'tree_table' => array( 'variables' => array('header' => array(), 'rows' => array(), 'attributes' => array(), 'empty' => '', 'caption' => ''), 'file' => 'token.pages.inc', ), 'token_tree' => array( 'variables' => array('token_types' => array(), 'global_types' => TRUE, 'click_insert' => TRUE, 'show_restricted' => FALSE, 'recursion_limit' => 4), 'file' => 'token.pages.inc', ), ); } /** * Implements hook_library(). */ function token_library() { // jQuery treeTable plugin. $libraries['treeTable'] = array( 'title' => 'jQuery treeTable', 'website' => 'http://plugins.jquery.com/project/treetable', 'version' => '2.3.0', 'js' => array( drupal_get_path('module', 'token') . '/jquery.treeTable.js' => array(), ), 'css' => array( drupal_get_path('module', 'token') . '/jquery.treeTable.css' => array(), ), ); return $libraries; } /** * Implements hook_form_alter(). * * Adds a submit handler to forms which could affect the tokens available on * the site. */ function token_form_alter(&$form, $form_state, $form_id) { switch ($form_id) { // Profile field forms. case 'profile_field_form': case 'profile_field_delete': // User picture form. case 'user_admin_settings': // System date type form. // @todo Remove when http://drupal.org/node/1173706 is fixed. case 'system_add_date_format_type_form': case 'system_delete_date_format_type_form': $form += array('#submit' => array()); array_unshift($form['#submit'], 'token_clear_cache'); break; } } /** * Implements hook_field_info_alter(). */ function token_field_info_alter(&$info) { $defaults = array( 'taxonomy_term_reference' => 'taxonomy_term_reference_plain', 'number_integer' => 'number_unformatted', 'number_decimal' => 'number_unformatted', 'number_float' => 'number_unformatted', 'file' => 'file_url_plain', 'image' => 'file_url_plain', ); foreach ($defaults as $field_type => $default_token_formatter) { if (isset($info[$field_type])) { $info[$field_type] += array('default_token_formatter' => $default_token_formatter); } } } /** * Implements hook_field_display_alter(). */ function token_field_display_alter(&$display, $context) { if ($context['view_mode'] == 'token') { $view_mode_settings = field_view_mode_settings($context['instance']['entity_type'], $context['instance']['bundle']); // If the token view mode fell back to the 'default' view mode, then // use the default token formatter. if (empty($view_mode_settings[$context['view_mode']]['custom_settings'])) { $field_type_info = field_info_field_types($context['field']['type']); if (!empty($field_type_info['default_token_formatter'])) { $display['type'] = $field_type_info['default_token_formatter']; $formatter_info = field_info_formatter_types($display['type']); $display['settings'] = isset($formatter_info['settings']) ? $formatter_info['settings'] : array(); $display['settings']['label'] = 'hidden'; $display['module'] = $formatter_info['module']; } } } } /** * Implements hook_field_create_instance(). */ function token_field_create_instance($instance) { token_clear_cache(); } /** * Implements hook_field_update_instance(). */ function token_field_update_instance($instance) { token_clear_cache(); } /** * Implements hook_field_delete_instance(). */ function token_field_delete_instance($instance) { token_clear_cache(); } /** * Clear token caches and static variables. */ function token_clear_cache() { cache_clear_all('*', 'cache_token', TRUE); drupal_static_reset('token_get_info'); drupal_static_reset('token_get_global_token_types'); drupal_static_reset('token_get_entity_mapping'); * A taxonomy term ID. * @param $vid * An optional vocabulary ID to restrict the child search. * * @return * An array of term objects that are the children of the term $tid, or an * empty array when no children exist. */ function taxonomy_get_children($tid, $vid = 0) { $children = &drupal_static(__FUNCTION__, array()); if ($tid && !isset($children[$tid])) { $query = db_select('taxonomy_term_data', 't'); $query->join('taxonomy_term_hierarchy', 'h', 'h.tid = t.tid'); $query->addField('t', 'tid'); $query->condition('h.parent', $tid); if ($vid) { $query->condition('t.vid', $vid); } $query->addTag('term_access'); $query->orderBy('t.weight'); $query->orderBy('t.name'); $tids = $query->execute()->fetchCol(); $children[$tid] = taxonomy_term_load_multiple($tids); } return isset($children[$tid]) ? $children[$tid] : array(); } /** * Create a hierarchical representation of a vocabulary. * * @param $vid * Which vocabulary to generate the tree for. * @param $parent * The term ID under which to generate the tree. If 0, generate the tree * for the entire vocabulary. * @param $max_depth * The number of levels of the tree to return. Leave NULL to return all levels. * @param $load_entities * If TRUE, a full entity load will occur on the term objects. Otherwise they * are partial objects queried directly from the {taxonomy_term_data} table to * save execution time and memory consumption when listing large numbers of * terms. Defaults to FALSE. * * @return * An array of all term objects in the tree. Each term object is extended * to have "depth" and "parents" attributes in addition to its normal ones. * Results are statically cached. Term objects will be partial or complete * depending on the $load_entities parameter. */ function taxonomy_get_tree($vid, $parent = 0, $max_depth = NULL, $load_entities = FALSE) { $children = &drupal_static(__FUNCTION__, array()); $parents = &drupal_static(__FUNCTION__ . ':parents', array()); $terms = &drupal_static(__FUNCTION__ . ':terms', array()); // We cache trees, so it's not CPU-intensive to call get_tree() on a term // and its children, too. if (!isset($children[$vid])) { $children[$vid] = array(); $parents[$vid] = array(); $terms[$vid] = array(); $query = db_select('taxonomy_term_data', 't'); $query->join('taxonomy_term_hierarchy', 'h', 'h.tid = t.tid'); $result = $query ->addTag('translatable') ->addTag('term_access') ->fields('t') ->fields('h', array('parent')) ->condition('t.vid', $vid) ->orderBy('t.weight') ->orderBy('t.name') ->execute(); foreach ($result as $term) { $children[$vid][$term->parent][] = $term->tid; $parents[$vid][$term->tid][] = $term->parent; $terms[$vid][$term->tid] = $term; } } // Load full entities, if necessary. The entity controller statically // caches the results. if ($load_entities) { $term_entities = taxonomy_term_load_multiple(array_keys($terms[$vid])); } $max_depth = (!isset($max_depth)) ? count($children[$vid]) : $max_depth; $tree = array(); // Keeps track of the parents we have to process, the last entry is used // for the next processing step. $process_parents = array(); $process_parents[] = $parent; // Loops over the parent terms and adds its children to the tree array. // Uses a loop instead of a recursion, because it's more efficient. while (count($process_parents)) { $parent = array_pop($process_parents); // The number of parents determines the current depth. $depth = count($process_parents); if ($max_depth > $depth && !empty($children[$vid][$parent])) { $has_children = FALSE; $child = current($children[$vid][$parent]); do { if (empty($child)) { break; } $term = $load_entities ? $term_entities[$child] : $terms[$vid][$child]; if (count($parents[$vid][$term->tid]) > 1) { // We have a term with multi parents here. Clone the term, // so that the depth attribute remains correct. $term = clone $term; } $term->depth = $depth; unset($term->parent); $term->parents = $parents[$vid][$term->tid]; $tree[] = $term; if (!empty($children[$vid][$term->tid])) { $has_children = TRUE; // We have to continue with this parent later. $process_parents[] = $parent; // Use the current term as parent for the next iteration. $process_parents[] = $term->tid; // Reset pointers for child lists because we step in there more often // with multi parents. reset($children[$vid][$term->tid]); // Move pointer so that we get the correct term the next time. next($children[$vid][$parent]); break; } } while ($child = next($children[$vid][$parent])); if (!$has_children) { // We processed all terms in this hierarchy-level, reset pointer // so that this function works the next time it gets called. reset($children[$vid][$parent]); } } } return $tree; } /** * Try to map a string to an existing term, as for glossary use. * * Provides a case-insensitive and trimmed mapping, to maximize the * likelihood of a successful match. * * @param name * Name of the term to search for. * * @return * An array of matching term objects. */ function taxonomy_get_term_by_name($name) { return taxonomy_term_load_multiple(array(), array('name' => trim($name))); } /** * Controller class for taxonomy terms. * * This extends the DrupalDefaultEntityController class. Only alteration is * that we match the condition on term name case-independently. */ class TaxonomyTermController extends DrupalDefaultEntityController { protected function buildQuery($ids, $conditions = array(), $revision_id = FALSE) { $query = parent::buildQuery($ids, $conditions, $revision_id); $query->addTag('translatable'); $query->addTag('term_access'); // When name is passed as a condition use LIKE. if (isset($conditions['name'])) { $query_conditions = &$query->conditions(); foreach ($query_conditions as $key => $condition) { if ($condition['field'] == 'base.name') { $query_conditions[$key]['operator'] = 'LIKE'; $query_conditions[$key]['value'] = db_like($query_conditions[$key]['value']); } } } // Add the machine name field from the {taxonomy_vocabulary} table. $query->innerJoin('taxonomy_vocabulary', 'v', 'base.vid = v.vid'); $query->addField('v', 'machine_name', 'vocabulary_machine_name'); return $query; } protected function cacheGet($ids, $conditions = array()) { $terms = parent::cacheGet($ids, $conditions); // Name matching is case insensitive, note that with some collations // LOWER() and drupal_strtolower() may return different results. foreach ($terms as $term) { $term_values = (array) $term; if (isset($conditions['name']) && drupal_strtolower($conditions['name'] != drupal_strtolower($term_values['name']))) { unset($terms[$term->tid]); } } return $terms; } } /** * Controller class for taxonomy vocabularies. * * This extends the DrupalDefaultEntityController class, adding required * special handling for taxonomy vocabulary objects. */ class TaxonomyVocabularyController extends DrupalDefaultEntityController { protected function buildQuery($ids, $conditions = array(), $revision_id = FALSE) { $query = parent::buildQuery($ids, $conditions, $revision_id); $query->addTag('translatable'); $query->orderBy('base.weight'); $query->orderBy('base.name'); return $query; } } /** * Load multiple taxonomy terms based on certain conditions. * * This function should be used whenever you need to load more than one term * from the database. Terms are loaded into memory and will not require * database access if loaded again during the same page request. * * @see entity_load() * @see EntityFieldQuery * * @param $tids * An array of taxonomy term IDs. * @param $conditions * (deprecated) An associative array of conditions on the {taxonomy_term} * table, where the keys are the database fields and the values are the * values those fields must have. Instead, it is preferable to use * EntityFieldQuery to retrieve a list of entity IDs loadable by * this function. * * @return * An array of term objects, indexed by tid. When no results are found, an * empty array is returned. * * @todo Remove $conditions in Drupal 8. */ function taxonomy_term_load_multiple($tids = array(), $conditions = array()) { return entity_load('taxonomy_term', $tids, $conditions); } /** * Load multiple taxonomy vocabularies based on certain conditions. * * This function should be used whenever you need to load more than one * vocabulary from the database. Terms are loaded into memory and will not * require database access if loaded again during the same page request. * * @see entity_load() * * @param $vids * An array of taxonomy vocabulary IDs, or FALSE to load all vocabularies. * @param $conditions * An array of conditions to add to the query. * * @return * An array of vocabulary objects, indexed by vid. */ function taxonomy_vocabulary_load_multiple($vids = array(), $conditions = array()) { return entity_load('taxonomy_vocabulary', $vids, $conditions); } /** * Return the vocabulary object matching a vocabulary ID. * * @param $vid * The vocabulary's ID. * * @return * The vocabulary object with all of its metadata, if exists, FALSE otherwise. * Results are statically cached. */ function taxonomy_vocabulary_load($vid) { $vocabularies = taxonomy_vocabulary_load_multiple(array($vid)); return reset($vocabularies); } /** * Return the vocabulary object matching a vocabulary machine name. * * @param $name * The vocabulary's machine name. * * @return * The vocabulary object with all of its metadata, if exists, FALSE otherwise. * Results are statically cached. */ function taxonomy_vocabulary_machine_name_load($name) { $vocabularies = taxonomy_vocabulary_load_multiple(NULL, array('machine_name' => $name)); return reset($vocabularies); } /** * Return the term object matching a term ID. * * @param $tid * A term's ID * * @return * A term object. Results are statically cached. */ function taxonomy_term_load($tid) { if (!is_numeric($tid)) { return FALSE; } $term = taxonomy_term_load_multiple(array($tid), array()); return $term ? $term[$tid] : FALSE; } /** * Helper function for array_map purposes. */ function _taxonomy_get_tid_from_term($term) { return $term->tid; } /** * Implodes a list of tags of a certain vocabulary into a string. * * @see drupal_explode_tags() */ function taxonomy_implode_tags($tags, $vid = NULL) { $typed_tags = array(); foreach ($tags as $tag) { // Extract terms belonging to the vocabulary in question. if (!isset($vid) || $tag->vid == $vid) { // Make sure we have a completed loaded taxonomy term. if (isset($tag->name)) { // Commas and quotes in tag names are special cases, so encode 'em. if (strpos($tag->name, ',') !== FALSE || strpos($tag->name, '"') !== FALSE) { $typed_tags[] = '"' . str_replace('"', '""', $tag->name) . '"'; } else { $typed_tags[] = $tag->name; } } } } return implode(', ', $typed_tags); } /** * Implements hook_field_info(). * * Field settings: * - allowed_values: a list array of one or more vocabulary trees: * - vocabulary: a vocabulary machine name. * - parent: a term ID of a term whose children are allowed. This should be * '0' if all terms in a vocabulary are allowed. The allowed values do not * include the parent term. * */ function taxonomy_field_info() { return array( 'taxonomy_term_reference' => array( 'label' => t('Term reference'), 'description' => t('This field stores a reference to a taxonomy term.'), 'default_widget' => 'options_select', 'default_formatter' => 'taxonomy_term_reference_link', 'settings' => array( 'allowed_values' => array( array( 'vocabulary' => '', 'parent' => '0', ), ), ), ), ); } /** * Implements hook_field_widget_info(). */ function taxonomy_field_widget_info() { return array( 'taxonomy_autocomplete' => array( 'label' => t('Autocomplete term widget (tagging)'), 'field types' => array('taxonomy_term_reference'), 'settings' => array( 'size' => 60, 'autocomplete_path' => 'taxonomy/autocomplete', ), 'behaviors' => array( 'multiple values' => FIELD_BEHAVIOR_CUSTOM, ), ), ); } /** * Implements hook_field_widget_info_alter(). */ function taxonomy_field_widget_info_alter(&$info) { $info['options_select']['field types'][] = 'taxonomy_term_reference'; $info['options_buttons']['field types'][] = 'taxonomy_term_reference'; } /** * Implements hook_options_list(). */ function taxonomy_options_list($field) { $function = !empty($field['settings']['options_list_callback']) ? $field['settings']['options_list_callback'] : 'taxonomy_allowed_values'; return $function($field); } /** * Implements hook_field_validate(). * * Taxonomy field settings allow for either a single vocabulary ID, multiple * vocabulary IDs, or sub-trees of a vocabulary to be specified as allowed * values, although only the first of these is supported via the field UI. * Confirm that terms entered as values meet at least one of these conditions. * * Possible error codes: * - 'taxonomy_term_illegal_value': The value is not part of the list of allowed values. */ function taxonomy_field_validate($entity_type, $entity, $field, $instance, $langcode, $items, &$errors) { // Build an array of existing term IDs so they can be loaded with // taxonomy_term_load_multiple(); foreach ($items as $delta => $item) { if (!empty($item['tid']) && $item['tid'] != 'autocreate') { $tids[] = $item['tid']; } } if (!empty($tids)) { $terms = taxonomy_term_load_multiple($tids); // Check each existing item to ensure it can be found in the // allowed values for this field. foreach ($items as $delta => $item) { $validate = TRUE; if (!empty($item['tid']) && $item['tid'] != 'autocreate') { $validate = FALSE; foreach ($field['settings']['allowed_values'] as $settings) { // If no parent is specified, check if the term is in the vocabulary. if (isset($settings['vocabulary']) && empty($settings['parent'])) { if ($settings['vocabulary'] == $terms[$item['tid']]->vocabulary_machine_name) { $validate = TRUE; break; } } // If a parent is specified, then to validate it must appear in the // array returned by taxonomy_get_parents_all(). elseif (!empty($settings['parent'])) { $ancestors = taxonomy_get_parents_all($item['tid']); foreach ($ancestors as $ancestor) { if ($ancestor->tid == $settings['parent']) { $validate = TRUE; break 2; } } } } } if (!$validate) { $errors[$field['field_name']][$langcode][$delta][] = array( 'error' => 'taxonomy_term_reference_illegal_value', 'message' => t('%name: illegal value.', array('%name' => $instance['label'])), ); } } } } /** * Implements hook_field_is_empty(). */ function taxonomy_field_is_empty($item, $field) { if (!is_array($item) || (empty($item['tid']) && (string) $item['tid'] !== '0')) { return TRUE; } return FALSE; } /** * Implements hook_field_formatter_info(). */ function taxonomy_field_formatter_info() { return array( 'taxonomy_term_reference_link' => array( 'label' => t('Link'), 'field types' => array('taxonomy_term_reference'), ), 'taxonomy_term_reference_plain' => array( 'label' => t('Plain text'), 'field types' => array('taxonomy_term_reference'), ), ); } /** * Implements hook_field_formatter_view(). */ function taxonomy_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) { $element = array(); // Terms whose tid is 'autocreate' do not exist // yet and $item['taxonomy_term'] is not set. Theme such terms as // just their name. switch ($display['type']) { case 'taxonomy_term_reference_link': foreach ($items as $delta => $item) { if ($item['tid'] == 'autocreate') { $element[$delta] = array( '#markup' => check_plain($item['name']), ); } else { $term = $item['taxonomy_term']; $uri = entity_uri('taxonomy_term', $term); $element[$delta] = array( '#type' => 'link', '#title' => $term->name, '#href' => $uri['path'], '#options' => $uri['options'], ); } } break; case 'taxonomy_term_reference_plain': foreach ($items as $delta => $item) { $name = ($item['tid'] != 'autocreate' ? $item['taxonomy_term']->name : $item['name']); $element[$delta] = array( '#markup' => check_plain($name), ); } break; } return $element; } /** * Returns the set of valid terms for a taxonomy field. * * @param $field * The field definition. * @return * The array of valid terms for this field, keyed by term id. */ function taxonomy_allowed_values($field) { $options = array(); foreach ($field['settings']['allowed_values'] as $tree) { if ($vocabulary = taxonomy_vocabulary_machine_name_load($tree['vocabulary'])) { if ($terms = taxonomy_get_tree($vocabulary->vid, $tree['parent'])) { foreach ($terms as $term) { $options[$term->tid] = str_repeat('-', $term->depth) . $term->name; } } } } return $options; } /** * Implements hook_field_formatter_prepare_view(). * * This preloads all taxonomy terms for multiple loaded objects at once and * unsets values for invalid terms that do not exist. */ function taxonomy_field_formatter_prepare_view($entity_type, $entities, $field, $instances, $langcode, &$items, $displays) { $tids = array(); // Collect every possible term attached to any of the fieldable entities. foreach ($entities as $id => $entity) { foreach ($items[$id] as $delta => $item) { // Force the array key to prevent duplicates. if ($item['tid'] != 'autocreate') { $tids[$item['tid']] = $item['tid']; } } } if ($tids) { $terms = taxonomy_term_load_multiple($tids); // Iterate through the fieldable entities again to attach the loaded term data. foreach ($entities as $id => $entity) { $rekey = FALSE; foreach ($items[$id] as $delta => $item) { // Check whether the taxonomy term field instance value could be loaded. if (isset($terms[$item['tid']])) { // Replace the instance value with the term data. $items[$id][$delta]['taxonomy_term'] = $terms[$item['tid']]; } // Terms to be created are not in $terms, but are still legitimate. else if ($item['tid'] == 'autocreate') { // Leave the item in place. } // Otherwise, unset the instance value, since the term does not exist. else { unset($items[$id][$delta]); $rekey = TRUE; } } if ($rekey) { // Rekey the items array. $items[$id] = array_values($items[$id]); } } } } /** * Title callback for term pages. * * @param $term * A term object. * * @return * The term name to be used as the page title. */ function taxonomy_term_title($term) { return $term->name; } /** * Implements hook_field_widget_form(). */ function taxonomy_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) { $tags = array(); foreach ($items as $item) { $tags[$item['tid']] = isset($item['taxonomy_term']) ? $item['taxonomy_term'] : taxonomy_term_load($item['tid']); } $element += array( '#type' => 'textfield', '#default_value' => taxonomy_implode_tags($tags), '#autocomplete_path' => $instance['widget']['settings']['autocomplete_path'] . '/' . $field['field_name'], '#size' => $instance['widget']['settings']['size'], '#maxlength' => 1024, '#element_validate' => array('taxonomy_autocomplete_validate'), ); return $element; } /** * Form element validate handler for taxonomy term autocomplete element. */ function taxonomy_autocomplete_validate($element, &$form_state) { // Autocomplete widgets do not send their tids in the form, so we must detect // them here and process them independently. $value = array(); if ($tags = $element['#value']) { // Collect candidate vocabularies. $field = field_widget_field($element, $form_state); $vocabularies = array(); foreach ($field['settings']['allowed_values'] as $tree) { if ($vocabulary = taxonomy_vocabulary_machine_name_load($tree['vocabulary'])) { $vocabularies[$vocabulary->vid] = $vocabulary; } } // Translate term names into actual terms. $typed_terms = drupal_explode_tags($tags); foreach ($typed_terms as $typed_term) { // See if the term exists in the chosen vocabulary and return the tid; // otherwise, create a new 'autocreate' term for insert/update. if ($possibilities = taxonomy_term_load_multiple(array(), array('name' => trim($typed_term), 'vid' => array_keys($vocabularies)))) { $term = array_pop($possibilities); } else { $vocabulary = reset($vocabularies); $term = array( 'tid' => 'autocreate', 'vid' => $vocabulary->vid, 'name' => $typed_term, 'vocabulary_machine_name' => $vocabulary->machine_name, ); } $value[] = (array)$term; } } form_set_value($element, $value, $form_state); } /** * Implements hook_field_widget_error(). */ function taxonomy_field_widget_error($element, $error, $form, &$form_state) { form_error($element, $error['message']); } /** * Implements hook_field_settings_form(). */ function taxonomy_field_settings_form($field, $instance, $has_data) { // Get proper values for 'allowed_values_function', which is a core setting. $vocabularies = taxonomy_get_vocabularies(); $options = array(); foreach ($vocabularies as $vocabulary) { $options[$vocabulary->machine_name] = $vocabulary->name; } $form['allowed_values'] = array( '#tree' => TRUE, ); foreach ($field['settings']['allowed_values'] as $delta => $tree) { $form['allowed_values'][$delta]['vocabulary'] = array( '#type' => 'select', '#title' => t('Vocabulary'), '#default_value' => $tree['vocabulary'], '#options' => $options, '#required' => TRUE, '#description' => t('The vocabulary which supplies the options for this field.'), '#disabled' => $has_data, ); $form['allowed_values'][$delta]['parent'] = array( '#type' => 'value', '#value' => $tree['parent'], ); } return $form; } /** * Implements hook_rdf_mapping(). * * @return array * The rdf mapping for vocabularies and terms. */ function taxonomy_rdf_mapping() { return array( array( 'type' => 'taxonomy_term', 'bundle' => RDF_DEFAULT_BUNDLE, 'mapping' => array( 'rdftype' => array('skos:Concept'), 'name' => array( 'predicates' => array('rdfs:label', 'skos:prefLabel'), ), 'description' => array( 'predicates' => array('skos:definition'), ), 'vid' => array( 'predicates' => array('skos:inScheme'), 'type' => 'rel', ), 'parent' => array( 'predicates' => array('skos:broader'), 'type' => 'rel', ), ), ), array( 'type' => 'taxonomy_vocabulary', 'bundle' => RDF_DEFAULT_BUNDLE, 'mapping' => array( 'rdftype' => array('skos:ConceptScheme'), 'name' => array( 'predicates' => array('dc:title'), ), 'description' => array( 'predicates' => array('rdfs:comment'), ), ), ), ); } /** * @defgroup taxonomy_index Taxonomy indexing * @{ * Functions to maintain taxonomy indexing. * * Taxonomy uses default field storage to store canonical relationships * between terms and fieldable entities. However its most common use case * requires listing all content associated with a term or group of terms * sorted by creation date. To avoid slow queries due to joining across * multiple node and field tables with various conditions and order by criteria, * we maintain a denormalized table with all relationships between terms, * published nodes and common sort criteria such as sticky and created. * This is used as a lookup table by taxonomy_select_nodes(). When using other * field storage engines or alternative methods of denormalizing this data * you should set the variable 'taxonomy_maintain_index_table' to FALSE * to avoid unnecessary writes in SQL. */ /** * Implements hook_field_presave(). * * Create any new terms defined in a freetagging vocabulary. */ function taxonomy_field_presave($entity_type, $entity, $field, $instance, $langcode, &$items) { foreach ($items as $delta => $item) { if ($item['tid'] == 'autocreate') { $term = (object) $item; unset($term->tid); taxonomy_term_save($term); $items[$delta]['tid'] = $term->tid; } } } /** * Implements hook_field_insert(). */ function taxonomy_field_insert($entity_type, $entity, $field, $instance, $langcode, &$items) { // We maintain a denor malicious attempt to alter protected user fields.', array(), WATCHDOG_WARNING); return; } } } return $operations; } /** * Callback function for admin mass unblocking users. */ function user_user_operations_unblock($accounts) { $accounts = user_load_multiple($accounts); foreach ($accounts as $account) { // Skip unblocking user if they are already unblocked. if ($account !== FALSE && $account->status == 0) { user_save($account, array('status' => 1)); } } } /** * Callback function for admin mass blocking users. */ function user_user_operations_block($accounts) { $accounts = user_load_multiple($accounts); foreach ($accounts as $account) { // Skip blocking user if they are already blocked. if ($account !== FALSE && $account->status == 1) { // For efficiency manually save the original account before applying any // changes. $account->original = clone $account; user_save($account, array('status' => 0)); } } } /** * Callback function for admin mass adding/deleting a user role. */ function user_multiple_role_edit($accounts, $operation, $rid) { // The role name is not necessary as user_save() will reload the user // object, but some modules' hook_user() may look at this first. $role_name = db_query('SELECT name FROM {role} WHERE rid = :rid', array(':rid' => $rid))->fetchField(); switch ($operation) { case 'add_role': $accounts = user_load_multiple($accounts); foreach ($accounts as $account) { // Skip adding the role to the user if they already have it. if ($account !== FALSE && !isset($account->roles[$rid])) { $roles = $account->roles + array($rid => $role_name); // For efficiency manually save the original account before applying // any changes. $account->original = clone $account; user_save($account, array('roles' => $roles)); } } break; case 'remove_role': $accounts = user_load_multiple($accounts); foreach ($accounts as $account) { // Skip removing the role from the user if they already don't have it. if ($account !== FALSE && isset($account->roles[$rid])) { $roles = array_diff($account->roles, array($rid => $role_name)); // For efficiency manually save the original account before applying // any changes. $account->original = clone $account; user_save($account, array('roles' => $roles)); } } break; } } function user_multiple_cancel_confirm($form, &$form_state) { $edit = $form_state['input']; $form['accounts'] = array('#prefix' => '', '#tree' => TRUE); $accounts = user_load_multiple(array_keys(array_filter($edit['accounts']))); foreach ($accounts as $uid => $account) { // Prevent user 1 from being canceled. if ($uid <= 1) { continue; } $form['accounts'][$uid] = array( '#type' => 'hidden', '#value' => $uid, '#prefix' => '
  • ', '#suffix' => check_plain($account->name) . "
  • \n", ); } // Output a notice that user 1 cannot be canceled. if (isset($accounts[1])) { $redirect = (count($accounts) == 1); $message = t('The user account %name cannot be cancelled.', array('%name' => $accounts[1]->name)); drupal_set_message($message, $redirect ? 'error' : 'warning'); // If only user 1 was selected, redirect to the overview. if ($redirect) { drupal_goto('admin/people'); } } $form['operation'] = array('#type' => 'hidden', '#value' => 'cancel'); module_load_include('inc', 'user', 'user.pages'); $form['user_cancel_method'] = array( '#type' => 'item', '#title' => t('When cancelling these accounts'), ); $form['user_cancel_method'] += user_cancel_methods(); // Remove method descriptions. foreach (element_children($form['user_cancel_method']) as $element) { unset($form['user_cancel_method'][$element]['#description']); } // Allow to send the account cancellation confirmation mail. $form['user_cancel_confirm'] = array( '#type' => 'checkbox', '#title' => t('Require e-mail confirmation to cancel account.'), '#default_value' => FALSE, '#description' => t('When enabled, the user must confirm the account cancellation via e-mail.'), ); // Also allow to send account canceled notification mail, if enabled. $form['user_cancel_notify'] = array( '#type' => 'checkbox', '#title' => t('Notify user when account is canceled.'), '#default_value' => FALSE, '#access' => variable_get('user_mail_status_canceled_notify', FALSE), '#description' => t('When enabled, the user will receive an e-mail notification after the account has been cancelled.'), ); return confirm_form($form, t('Are you sure you want to cancel these user accounts?'), 'admin/people', t('This action cannot be undone.'), t('Cancel accounts'), t('Cancel')); } /** * Submit handler for mass-account cancellation form. * * @see user_multiple_cancel_confirm() * @see user_cancel_confirm_form_submit() */ function user_multiple_cancel_confirm_submit($form, &$form_state) { global $user; if ($form_state['values']['confirm']) { foreach ($form_state['values']['accounts'] as $uid => $value) { // Prevent programmatic form submissions from cancelling user 1. if ($uid <= 1) { continue; } // Prevent user administrators from deleting themselves without confirmation. if ($uid == $user->uid) { $admin_form_state = $form_state; unset($admin_form_state['values']['user_cancel_confirm']); $admin_form_state['values']['_account'] = $user; user_cancel_confirm_form_submit(array(), $admin_form_state); } else { user_cancel($form_state['values'], $uid, $form_state['values']['user_cancel_method']); } } } $form_state['redirect'] = 'admin/people'; } /** * Retrieve a list of all user setting/information categories and sort them by weight. */ function _user_categories() { $categories = module_invoke_all('user_categories'); usort($categories, '_user_sort'); return $categories; } function _user_sort($a, $b) { $a = (array) $a + array('weight' => 0, 'title' => ''); $b = (array) $b + array('weight' => 0, 'title' => ''); return $a['weight'] < $b['weight'] ? -1 : ($a['weight'] > $b['weight'] ? 1 : ($a['title'] < $b['title'] ? -1 : 1)); } /** * List user administration filters that can be applied. */ function user_filters() { // Regular filters $filters = array(); $roles = user_roles(TRUE); unset($roles[DRUPAL_AUTHENTICATED_RID]); // Don't list authorized role. if (count($roles)) { $filters['role'] = array( 'title' => t('role'), 'field' => 'ur.rid', 'options' => array( '[any]' => t('any'), ) + $roles, ); } $options = array(); foreach (module_implements('permission') as $module) { $function = $module . '_permission'; if ($permissions = $function('permission')) { asort($permissions); foreach ($permissions as $permission => $description) { $options[t('@module module', array('@module' => $module))][$permission] = t($permission); } } } ksort($options); $filters['permission'] = array( 'title' => t('permission'), 'options' => array( '[any]' => t('any'), ) + $options, ); $filters['status'] = array( 'title' => t('status'), 'field' => 'u.status', 'options' => array( '[any]' => t('any'), 1 => t('active'), 0 => t('blocked'), ), ); return $filters; } /** * Extends a query object for user administration filters based on session. * * @param $query * Query object that should be filtered. */ function user_build_filter_query(SelectQuery $query) { $filters = user_filters(); // Extend Query with filter conditions. foreach (isset($_SESSION['user_overview_filter']) ? $_SESSION['user_overview_filter'] : array() as $filter) { list($key, $value) = $filter; // This checks to see if this permission fig the view. */ function views_ui_cache_load($name) { ctools_include('object-cache'); views_include('view'); $view = ctools_object_cache_get('view', $name); $original_view = views_get_view($name); if (empty($view)) { $view = $original_view; if (!empty($view)) { // Check to see if someone else is already editing this view. $view->locked = ctools_object_cache_test('view', $view->name); // Set a flag to indicate that this view is being edited. // This flag will be used e.g. to determine whether strings // should be localized. $view->editing = TRUE; } } else { // Keep disabled/enabled status real. if ($original_view) { $view->disabled = !empty($original_view->disabled); } } if (empty($view)) { return FALSE; } else { return $view; } } /** * Specialized cache function to add a flag to our view, include an appropriate * include, and cache more easily. */ function views_ui_cache_set(&$view) { if (!empty($view->locked)) { drupal_set_message(t('Changes cannot be made to a locked view.'), 'error'); return; } ctools_include('object-cache'); $view->changed = TRUE; // let any future object know that this view has changed. if (isset($view->current_display)) { // Add the knowledge of the changed display, too. $view->changed_display[$view->current_display] = TRUE; unset($view->current_display); } // Unset handlers; we don't want to write these into the cache unset($view->display_handler); unset($view->default_display); $view->query = NULL; foreach (array_keys($view->display) as $id) { unset($view->display[$id]->handler); unset($view->display[$id]->default_display); } ctools_object_cache_set('view', $view->name, $view); } /** * Specialized menu callback to load a view that is only a default * view. */ function views_ui_default_load($name) { $view = views_get_view($name); if ($view->type == t('Default')) { return $view; } return FALSE; } /** * Theme preprocess for views-view.tpl.php. */ function views_ui_preprocess_views_view(&$vars) { $view = $vars['view']; if (!empty($view->views_ui_context) && module_exists('contextual')) { $view->hide_admin_links = TRUE; foreach (array('title', 'header', 'exposed', 'rows', 'pager', 'more', 'footer', 'empty', 'attachment_after', 'attachment_before') as $section) { if (!empty($vars[$section])) { $vars[$section] = array( '#theme' => 'views_ui_view_preview_section', '#view' => $view, '#section' => $section, '#content' => $vars[$section], '#theme_wrappers' => array('views_container'), '#attributes' => array('class' => 'contextual-links-region'), ); $vars[$section] = drupal_render($vars[$section]); } } } } /** * Theme preprocess for theme_views_ui_view_preview_section(). * * @TODO * Perhaps move this to includes/admin.inc or theme/theme.inc */ function template_preprocess_views_ui_view_preview_section(&$vars) { switch ($vars['section']) { case 'title': $vars['title'] = t('Title'); $links = views_ui_view_preview_section_display_category_links($vars['view'], 'title', $vars['title']); break; case 'header': $vars['title'] = t('Header'); $links = views_ui_view_preview_section_handler_links($vars['view'], $vars['section']); break; case 'empty': $vars['title'] = t('No results behavior'); $links = views_ui_view_preview_section_handler_links($vars['view'], $vars['section']); break; case 'exposed': // @todo Sorts can be exposed too, so we may need a better title. $vars['title'] = t('Exposed Filters'); $links = views_ui_view_preview_section_display_category_links($vars['view'], 'exposed_form_options', $vars['title']); break; case 'rows': // @todo The title needs to depend on what is being viewed. $vars['title'] = t('Content'); $links = views_ui_view_preview_section_rows_links($vars['view']); break;p, $account, $language = NULL) { // By default, we always notify except for canceled and blocked. $default_notify = ($op != 'status_canceled' && $op != 'status_blocked'); $notify = variable_get('user_mail_' . $op . '_notify', $default_notify); if ($notify) { $params['account'] = $account; $language = $language ? $language : user_preferred_language($account); $mail = drupal_mail('user', $op, $account->mail, $language, $params); if ($op == 'register_pending_approval') { // If a user registered requiring admin approval, notify the admin, too. // We use the site default language for this. drupal_mail('user', 'register_pending_approval_admin', variable_get('site_mail', ini_get('sendmail_from')), language_default(), $params); } } return empty($mail) ? NULL : $mail['result']; } /** * Form element process handler for client-side password validation. * * This #process handler is automatically invoked for 'password_confirm' form * elements to add the JavaScript and string translations for dynamic password * validation. * * @see system_element_info() */ function user_form_process_password_confirm($element) { global $user; $js_settings = array( 'password' => array( 'strengthTitle' => t('Password strength:'), 'hasWeaknesses' => t('To make your password stronger:'), 'tooShort' => t('Make it at least 6 characters'), 'addLowerCase' => t('Add lowercase letters'), 'addUpperCase' => t('Add uppercase letters'), 'addNumbers' => t('Add numbers'), 'addPunctuation' => t('Add punctuation'), 'sameAsUsername' => t('Make it different from your username'), 'confirmSuccess' => t('yes'), 'confirmFailure' => t('no'), 'weak' => t('Weak'), 'fair' => t('Fair'), 'good' => t('Good'), 'strong' => t('Strong'), 'confirmTitle' => t('Passwords match:'), 'username' => (isset($user->name) ? $user->name : ''), ), ); $element['#attached']['js'][] = drupal_get_path('module', 'user') . '/user.js'; // Ensure settings are only added once per page. static $already_added = FALSE; if (!$already_added) { $already_added = TRUE; $element['#attached']['js'][] = array('data' => $js_settings, 'type' => 'setting'); } return $element; } /** * Implements hook_node_load(). */ function user_node_load($nodes, $types) { // Build an array of all uids for node authors, keyed by nid. $uids = array(); foreach ($nodes as $nid => $node) { $uids[$nid] = $node->uid; } // Fetch name, picture, and data for these users. $user_fields = db_query("SELECT uid, name, picture, data FROM {users} WHERE uid IN (:uids)", array(':uids' => $uids))->fetchAllAssoc('uid'); // Add these values back into the node objects. foreach ($uids as $nid => $uid) { $nodes[$nid]->name = $user_fields[$uid]->name; $nodes[$nid]->picture = $user_fields[$uid]->picture; $nodes[$nid]->data = $user_fields[$uid]->data; } } /** * Implements hook_image_style_delete(). */ function user_image_style_delete($style) { // If a style is deleted, update the variables. // Administrators choose a replacement style when deleting. user_image_style_save($style); } /** * Implements hook_image_style_save(). */ function user_image_style_save($style) { // If a style is renamed, update the variables that use it. if (isset($style['old_name']) && $style['old_name'] == variable_get('user_picture_style', '')) { variable_set('user_picture_style', $style['name']); } } /** * Implements hook_action_info(). */ function user_action_info() { return array( 'user_block_user_action' => array( 'label' => t('Block current user'), 'type' => 'user', 'configurable' => FALSE, 'triggers' => array('any'), ), ); } /** * Blocks the current user. * * @ingroup actions */ function user_block_user_action(&$entity, $context = array()) { // First priority: If there is a $entity->uid, block that user. // This is most likely a user object or the author if a node or comment. if (isset($entity->uid)) { $uid = $entity->uid; } elseif (isset($context['uid'])) { $uid = $context['uid']; } // If neither of those are valid, then block the current user. else { $uid = $GLOBALS['user']->uid; } $account = user_load($uid); $account = user_save($account, array('status' => 0)); watchdog('action', 'Blocked user %name.', array('%name' => $account->name)); } /** * Implements hook_form_FORM_ID_alter(). * * Add a checkbox for the 'user_register_form' instance settings on the 'Edit * field instance' form. */ function user_form_field_ui_field_edit_form_alter(&$form, &$form_state, $form_id) { $instance = $form['#instance']; if ($instance['entity_type'] == 'user') { $form['instance']['settings']['user_register_form'] = array( '#type' => 'checkbox', '#title' => t('Display on user registration form.'), '#description' => t("This is compulsory for 'required' fields."), // Field instances created in D7 beta releases before the setting was // introduced might be set as 'required' and 'not shown on user_register // form'. We make sure the checkbox comes as 'checked' for those. '#default_value' => $instance['settings']['user_register_form'] || $instance['required'], // Display just below the 'required' checkbox. '#weight' => $form['instance']['required']['#weight'] + .1, // Disabled when the 'required' checkbox is checked. '#states' => array( 'enabled' => array('input[name="instance[required]"]' => array('checked' => FALSE)), ), // Checked when the 'required' checkbox is checked. This is done through // a custom behavior, since the #states system would also synchronize on // uncheck. '#attached' => array( 'js' => array(drupal_get_path('module', 'user') . '/user.js'), ), ); array_unshift($form['#submit'], 'user_form_field_ui_field_edit_form_submit'); } } /** * Additional submit handler for the 'Edit field instance' form. * * Make sure the 'user_register_form' setting is set for required fields. */ function user_form_field_ui_field_edit_form_submit($form, &$form_state) { $instance = $form_state['values']['instance']; if (!empty($instance['required'])) { form_set_value($form['instance']['settings']['user_register_form'], 1, $form_state); } } /** * Form builder; the user registration form. * * @ingroup forms * @see user_account_form() * @see user_account_form_validate() * @see user_register_submit() */ function user_register_form($form, &$form_state) { global $user; $admin = user_access('administer users'); // If we aren't admin but already logged on, go to the user page instead. if (!$admin && $user->uid) { drupal_goto('user/' . $user->uid); } $form['#user'] = drupal_anonymous_user(); $form['#user_category'] = 'register'; $form['#attached']['library'][] = array('system', 'jquery.cookie'); $form['#attributes']['class'][] = 'user-info-from-cookie'; // Start with the default user account fields. user_account_form($form, $form_state); // Attach field widgets, and hide the ones where the 'user_register_form' // setting is not on. field_attach_form('user', $form['#user'], $form, $form_state); foreach (field_info_instances('user', 'user') as $field_name => $instance) { if (empty($instance['settings']['user_register_form'])) { $form[$field_name]['#access'] = FALSE; } } if ($admin) { // Redirect back to page which initiated the create request; // usually admin/people/create. $form_state['redirect'] = $_GET['q']; } $form['actions'] = array('#type' => 'actions'); $form['actions']['submit'] = array( '#type' => 'submit', '#value' => t('Create new account'), ); $form['#validate'][] = 'user_register_validate'; // Add the final user registration form submit handler. $form['#submit'][] = 'user_register_submit'; return $form; } /** * Validation function for the user registration form. */ function user_register_validate($form, &$form_state) { entity_form_field_validate('user', $form, $form_state); } /** * Submit handler for the user registration form. * * This function is shared by the installation form and the normal registration form, * which is why it can't be in the user.pages.inc file. * * @see user_register_form() */ function user_register_submit($form, &$form_state) { $admin = user_access('administer users'); if (!variable_get('user_email_verification', TRUE) || $admin) { $pass = $form_state['values']['pass']; } else { $pass = user_password(); } $notify = !empty($form_state['values']['notify']); // Remove unneeded values. form_state_values_clean($form_state); $form_state['values']['pass'] = $pass; $form_state['values']['init'] = $form_state['values']['mail']; $account = $form['#user']; entity_form_submit_build_entity('user', $account, $form, $form_state); // Populate $edit with the properties of $account, which have been edited on // this form by taking over all values, which appear in the form values too. $edit = array_intersect_key((array) $account, $form_state['values']); $account = user_save($account, $edit); // Terminate if an error occurred during user_save(). if (!$account) { drupal_set_message(t("Error saving user account."), 'error'); $form_state['redirect'] = ''; return; } $form_state['user'] = $account; $form_state['values']['uid'] = $account->uid; watchdog('user', 'New user: %name (%email).', array('%name' => $form_state['values']['name'], '%email' => $form_state['values']['mail']), WATCHDOG_NOTICE, l(t('edit'), 'user/' . $account->uid . '/edit')); // Add plain text password into user account to generate mail tokens. $account->password = $pass; // New administrative account without notification. $uri = entity_uri('user', $account); if ($admin && !$notify) { drupal_set_message(t('Created a new user account for %name. No e-mail has been sent.', array('@url' => url($uri['path'], $uri['options']), '%name' => $account->name))); } // No e-mail verification required; log in user immediately. elseif (!$admin && !variable_get('user_email_verification', TRUE) && $account->status) { _user_mail_notify('register_no_approval_required', $account); $form_state['uid'] = $account->uid; user_login_submit(array(), $form_state); drupal_set_message(t('Registration successful. You are now logged in.')); $form_state['redirect'] = ''; } // No administrator approval required. elseif ($account->status || $notify) { $op = $notify ? 'register_admin_created' : 'register_no_approval_required'; _user_mail_notify($op, $account); if ($notify) { drupal_set_message(t('A welcome message with further instructions has been e-mailed to the new user %name.', array('@url' => url($uri['path'], $uri['options']), '%name' => $account->name))); } else { drupal_set_message(t('A welcome message with further instructions has been sent to your e-mail address.')); $form_state['redirect'] = ''; } } // Administrator approval required. else { _user_mail_notify('register_pending_approval', $account); drupal_set_message(t('Thank you for applying for an account. Your account is currently pending approval by the site administrator.
    In the meantime, a welcome message with further instructions has been sent to your e-mail address.')); $form_state['redirect'] = ''; } } /** * Implements hook_modules_installed(). */ function user_modules_installed($modules) { // Assign all available permissions to the administrator role. $rid = variable_get('user_admin_role', 0); if ($rid) { $permissions = array(); foreach ($modules as $module) { if ($module_permissions = module_invoke($module, 'permission')) { $permissions = array_merge($permissions, array_keys($module_permissions)); } } if (!empty($permissions)) { user_role_grant_permissions($rid, $permissions); } } } /** * Implements hook_modules_uninstalled(). */ function user_modules_uninstalled($modules) { db_delete('role_permission') ->condition('module', $modules, 'IN') ->execute(); } /** * Helper function to rewrite the destination to avoid redirecting to login page after login. * * Third-party authentication modules may use this function to determine the * proper destination after a user has been properly logged in. */ function user_login_destination() { $destination = drupal_get_destination(); if ($destination['destination'] == 'user/login') { $destination['destination'] = 'user'; } return $destination; } /** * Saves visitor information as a cookie so it can be reused. * * @param $values * An array of key/value pairs to be saved into a cookie. */ function user_cookie_save(array $values) { foreach ($values as $field => $value) { // Set cookie for 365 days. setrawcookie('Drupal.visitor.' . $field, rawurlencode($value), REQUEST_TIME + 31536000, '/'); } } /** * Delete a visitor information cookie. * * @param $cookie_name * A cookie name such as 'homepage'. */ function user_cookie_delete($cookie_name) { setrawcookie('Drupal.visitor.' . $cookie_name, '', REQUEST_TIME - 3600, '/'); } /** * Implements hook_rdf_mapping(). */ function user_rdf_mapping() { return array( array( 'type' => 'user', 'bundle' => RDF_DEFAULT_BUNDLE, 'mapping' => array( 'rdftype' => array('sioc:UserAccount'), 'name' => array( 'predicates' => array('foaf:name'), ), 'homepage' => array( 'predicates' => array('foaf:page'), 'type' => 'rel', ), ), ), ); } /** * Implements hook_file_download_access(). */ function user_file_download_access($field, $entity_type, $entity) { if ($entity_type == 'user') { return user_view_access($entity); } } /** * Implements hook_system_info_alter(). * * Drupal 7 ships with two methods to add additional fields to users: Profile * module, a legacy module dating back from 2002, and Field API integration * with users. While Field API support for users currently provides less end * user features, the inefficient data storage mechanism of Profile module, as * well as its lack of consistency with the rest of the entity / field based * systems in Drupal 7, make this a sub-optimal solution to those who were not * using it in previous releases of Drupal. * * To prevent new Drupal 7 sites from installing Profile module, and * unwittingly ending up with two completely different and incompatible methods * of extending users, only make the Profile module available if the profile_* * tables are present. * * @todo: Remove in D8, pending upgrade path. */ function user_system_info_alter(&$info, $file, $type) { if orm['user_cancel_confirm'] = array( '#type' => 'checkbox', '#title' => t('Require e-mail confirmation to cancel account.'), '#default_value' => FALSE, '#description' => t('When enabled, the user must confirm the account cancellation via e-mail.'), ); // Also allow to send account canceled notification mail, if enabled. $form['user_cancel_notify'] = array( '#type' => 'checkbox', '#title' => t('Notify user when account is canceled.'), '#default_value' => FALSE, '#access' => variable_get('user_mail_status_canceled_notify', FALSE), '#description' => t('When enabled, the user will receive an e-mail notification after the account has been cancelled.'), ); return confirm_form($form, t('Are you sure you want to cancel these user accounts?'), 'admin/people', t('This action cannot be undone.'), t('Cancel accounts'), t('Cancel')); } /** * Submit handler for mass-account cancellation form. * * @see user_multiple_cancel_confirm() * @see user_cancel_confirm_form_submit() */ function user_multiple_cancel_confirm_submit($form, &$form_state) { global $user; if ($form_state['values']['confirm']) { foreach ($form_state['values']['accounts'] as $uid => $value) { // Prevent programmatic form submissions from cancelling user 1. if ($uid <= 1) { continue; } // Prevent user administrators from deleting themselves without confirmation. if ($uid == $user->uid) { $admin_form_state = $form_state; unset($admin_form_state['values']['user_cancel_confirm']); $admin_form_state['values']['_account'] = $user; user_cancel_confirm_form_submit(array(), $admin_form_state); } else { user_cancel($form_state['values'], $uid, $form_state['values']['user_cancel_method']); } } } $form_state['redirect'] = 'admin/people'; } /** * Retrieve a list of all user setting/information categories and sort them by weight. */ function _user_categories() { $categories = module_invoke_all('user_categories'); usort($categories, '_user_sort'); return $categories; } function _user_sort($a, $b) { $a = (array) $a + array('weight' => 0, 'title' => ''); $b = (array) $b + array('weight' => 0, 'title' => ''); return $a['weight'] < $b['weight'] ? -1 : ($a['weight'] > $b['weight'] ? 1 : ($a['title'] < $b['title'] ? -1 : 1)); } /** * List user administration filters that can be applied. */ function user_filters() { // Regular filters $filters = array(); $roles = user_roles(TRUE); unset($roles[DRUPAL_AUTHENTICATED_RID]); // Don't list authorized role. if (count($roles)) { $filters['role'] = array( 'title' => t('role'), 'field' => 'ur.rid', 'options' => array( '[any]' => t('any'), ) + $roles, ); } $options = array(); foreach (module_implements('permission') as $module) { $function = $module . '_permission'; if ($permissions = $function('permission')) { asort($permissions); foreach ($permissions as $permission => $description) { $options[t('@module module', array('@module' => $module))][$permission] = t($permission); } } } ksort($options); $filters['permission'] = array( 'title' => t('permission'), 'options' => array( '[any]' => t('any'), ) + $options, ); $filters['status'] = array( 'title' => t('status'), 'field' => 'u.status', 'options' => array( '[any]' => t('any'), 1 => t('active'), 0 => t('blocked'), ), ); return $filters; } /** * Extends a query object for user administration filters based on session. * * @param $query * Query object that should be filtered. */ function user_build_filter_query(SelectQuery $query) { $filters = user_filters(); // Extend Query with filter conditions. foreach (isset($_SESSION['user_overview_filter']) ? $_SESSION['user_overview_filter'] : array() as $filter) { list($key, $value) = $filter; // This checks to see if this permission fig the view. */ function views_ui_cache_load($name) { ctools_include('object-cache'); views_include('view'); $view = ctools_object_cache_get('view', $name); $original_view = views_get_view($name); if (empty($view)) { $view = $original_view; if (!empty($view)) { // Check to see if someone else is already editing this view. $view->locked = ctools_object_cache_test('view', $view->name); // Set a flag to indicate that this view is being edited. // This flag will be used e.g. to determine whether strings // should be localized. $view->editing = TRUE; } } else { // Keep disabled/enabled status real. if ($original_view) { $view->disabled = !empty($original_view->disabled); } } if (empty($view)) { return FALSE; } else { return $view; } } /** * Specialized cache function to add a flag to our view, include an appropriate * include, and cache more easily. */ function views_ui_cache_set(&$view) { if (!empty($view->locked)) { drupal_set_message(t('Changes cannot be made to a locked view.'), 'error'); return; } ctools_include('object-cache'); $view->changed = TRUE; // let any future object know that this view has changed. if (isset($view->current_display)) { // Add the knowledge of the changed display, too. $view->changed_display[$view->current_display] = TRUE; unset($view->current_display); } // Unset handlers; we don't want to write these into the cache unset($view->display_handler); unset($view->default_display); $view->query = NULL; foreach (array_keys($view->display) as $id) { unset($view->display[$id]->handler); unset($view->display[$id]->default_display); } ctools_object_cache_set('view', $view->name, $view); } /** * Specialized menu callback to load a view that is only a default * view. */ function views_ui_default_load($name) { $view = views_get_view($name); if ($view->type == t('Default')) { return $view; } return FALSE; } /** * Theme preprocess for views-view.tpl.php. */ function views_ui_preprocess_views_view(&$vars) { $view = $vars['view']; if (!empty($view->views_ui_context) && module_exists('contextual')) { $view->hide_admin_links = TRUE; foreach (array('title', 'header', 'exposed', 'rows', 'pager', 'more', 'footer', 'empty', 'attachment_after', 'attachment_before') as $section) { if (!empty($vars[$section])) { $vars[$section] = array( '#theme' => 'views_ui_view_preview_section', '#view' => $view, '#section' => $section, '#content' => $vars[$section], '#theme_wrappers' => array('views_container'), '#attributes' => array('class' => 'contextual-links-region'), ); $vars[$section] = drupal_render($vars[$section]); } } } } /** * Theme preprocess for theme_views_ui_view_preview_section(). * * @TODO * Perhaps move this to includes/admin.inc or theme/theme.inc */ function template_preprocess_views_ui_view_preview_section(&$vars) { switch ($vars['section']) { case 'title': $vars['title'] = t('Title'); $links = views_ui_view_preview_section_display_category_links($vars['view'], 'title', $vars['title']); break; case 'header': $vars['title'] = t('Header'); $links = views_ui_view_preview_section_handler_links($vars['view'], $vars['section']); break; case 'empty': $vars['title'] = t('No results behavior'); $links = views_ui_view_preview_section_handler_links($vars['view'], $vars['section']); break; case 'exposed': // @todo Sorts can be exposed too, so we may need a better title. $vars['title'] = t('Exposed Filters'); $links = views_ui_view_preview_section_display_category_links($vars['view'], 'exposed_form_options', $vars['title']); break; case 'rows': // @todo The title needs to depend on what is being viewed. $vars['title'] = t('Content'); $links = views_ui_view_preview_section_rows_links($vars['view']); break;p, $account, $language = NULL) { // By default, we always notify except for canceled and blocked. $default_notify = ($op != 'status_canceled' && $op != 'status_blocked'); $notify = variable_get('user_mail_' . $op . '_notify', $default_notify); if ($notify) { $params['account'] = $account; $language = $language ? $language : user_preferred_language($account); $mail = drupal_mail('user', $op, $account->mail, $language, $params); if ($op == 'register_pending_approval') { // If a user registered requiring admin approval, notify the admin, too. // We use the site default language for this. drupal_mail('user', 'register_pending_approval_admin', variable_get('site_mail', ini_get('sendmail_from')), language_default(), $params); } } return empty($mail) ? NULL : $mail['result']; } /** * Form element process handler for client-side password validation. * * This #process handler is automatically invoked for 'password_confirm' form * elements to add the JavaScript and string translations for dynamic password * validation. * * @see system_element_info() */ function user_form_process_password_confirm($element) { global $user; $js_settings = array( 'password' => array( 'strengthTitle' => t('Password strength:'), 'hasWeaknesses' => t('To make your password stronger:'), 'tooShort' => t('Make it at least 6 characters'), 'addLowerCase' => t('Add lowercase letters'), 'addUpperCase' => t('Add uppercase letters'), 'addNumbers' => t('Add numbers'), 'addPunctuation' => t('Add punctuation'), 'sameAsUsername' => t('Make it different from your username'), 'confirmSuccess' => t('yes'), 'confirmFailure' => t('no'), 'weak' => t('Weak'), 'fair' => t('Fair'), 'good' => t('Good'), 'strong' => t('Strong'), 'confirmTitle' => t('Passwords match:'), 'username' => (isset($user->name) ? $user->name : ''), ), ); $element['#attached']['js'][] = drupal_get_path('module', 'user') . '/user.js'; // Ensure settings are only added once per page. static $already_added = FALSE; if (!$already_added) { $already_added = TRUE; $element['#attached']['js'][] = array('data' => $js_settings, 'type' => 'setting'); } return $element; } /** * Implements hook_node_load(). */ function user_node_load($nodes, $types) { // Build an array of all uids for node authors, keyed by nid. $uids = array(); foreach ($nodes as $nid => $node) { $uids[$nid] = $node->uid; } // Fetch name, picture, and data for these users. $user_fields = db_query("SELECT uid, name, picture, data FROM {users} WHERE uid IN (:uids)", array(':uids' => $uids))->fetchAllAssoc('uid'); // Add these values back into the node objects. foreach ($uids as $nid => $uid) { $nodes[$nid]->name = $user_fields[$uid]->name; $nodes[$nid]->picture = $user_fields[$uid]->picture; $nodes[$nid]->data = $user_fields[$uid]->data; } } /** * Implements hook_image_style_delete(). */ function user_image_style_delete($style) { // If a style is deleted, update the variables. // Administrators choose a replacement style when deleting. user_image_style_save($style); } /** * Implements hook_image_style_save(). */ function user_image_style_save($style) { // If a style is renamed, update the variables that use it. if (isset($style['old_name']) && $style['old_name'] == variable_get('user_picture_style', '')) { variable_set('user_picture_style', $style['name']); } } /** * Implements hook_action_info(). */ function user_action_info() { return array( 'user_block_user_action' => array( 'label' => t('Block current user'), 'type' => 'user', 'configurable' => FALSE, 'triggers' => array('any'), ), ); } /** * Blocks the current user. * * @ingroup actions */ function user_block_user_action(&$entity, $context = array()) { // First priority: If there is a $entity->uid, block that user. // This is most likely a user object or the author if a node or comment. if (isset($entity->uid)) { $uid = $entity->uid; } elseif (isset($context['uid'])) { $uid = $context['uid']; } // If neither of those are valid, then block the current user. else { $uid = $GLOBALS['user']->uid; } $account = user_load($uid); $account = user_save($account, array('status' => 0)); watchdog('action', 'Blocked user %name.', array('%name' => $account->name)); } /** * Implements hook_form_FORM_ID_alter(). * * Add a checkbox for the 'user_register_form' instance settings on the 'Edit * field instance' form. */ function user_form_field_ui_field_edit_form_alter(&$form, &$form_state, $form_id) { $instance = $form['#instance']; if ($instance['entity_type'] == 'user') { $form['instance']['settings']['user_register_form'] = array( '#type' => 'checkbox', '#title' => t('Display on user registration form.'), '#description' => t("This is compulsory for 'required' fields."), // Field instances created in D7 beta releases before the setting was // introduced might be set as 'required' and 'not shown on user_register // form'. We make sure the checkbox comes as 'checked' for those. '#default_value' => $instance['settings']['user_register_form'] || $instance['required'], // Display just below the 'required' checkbox. '#weight' => $form['instance']['required']['#weight'] + .1, // Disabled when the 'required' checkbox is checked. '#states' => array( 'enabled' => array('input[name="instance[required]"]' => array('checked' => FALSE)), ), // Checked when the 'required' checkbox is checked. This is done through // a custom behavior, since the #states system would also synchronize on // uncheck. '#attached' => array( 'js' => array(drupal_get_path('module', 'user') . '/user.js'), ), ); array_unshift($form['#submit'], 'user_form_field_ui_field_edit_form_submit'); } } /** * Additional submit handler for the 'Edit field instance' form. * * Make sure the 'user_register_form' setting is set for required fields. */ function user_form_field_ui_field_edit_form_submit($form, &$form_state) { $instance = $form_state['values']['instance']; if (!empty($instance['required'])) { form_set_value($form['instance']['settings']['user_register_form'], 1, $form_state); } } /** * Form builder; the user registration form. * * @ingroup forms * @see user_account_form() * @see user_account_form_validate() * @see user_register_submit() */ function user_register_form($form, &$form_state) { global $user; $admin = user_access('administer users'); // If we aren't admin but already logged on, go to the user page instead. if (!$admin && $user->uid) { drupal_goto('user/' . $user->uid); } $form['#user'] = drupal_anonymous_user(); $form['#user_category'] = 'register'; $form['#attached']['library'][] = array('system', 'jquery.cookie'); $form['#attributes']['class'][] = 'user-info-from-cookie'; // Start with the default user account fields. user_account_form($form, $form_state); // Attach field widgets, and hide the ones where the 'user_register_form' // setting is not on. field_attach_form('user', $form['#user'], $form, $form_state); foreach (field_info_instances('user', 'user') as $field_name => $instance) { if (empty($instance['settings']['user_register_form'])) { $form[$field_name]['#access'] = FALSE; } } if ($admin) { // Redirect back to page which initiated the create request; // usually admin/people/create. $form_state['redirect'] = $_GET['q']; } $form['actions'] = array('#type' => 'actions'); $form['actions']['submit'] = array( '#type' => 'submit', '#value' => t('Create new account'), ); $form['#validate'][] = 'user_register_validate'; // Add the final user registration form submit handler. $form['#submit'][] = 'user_register_submit'; return $form; } /** * Validation function for the user registration form. */ function user_register_validate($form, &$form_state) { entity_form_field_validate('user', $form, $form_state); } /** * Submit handler for the user registration form. * * This function is shared by the installation form and the normal registration form, * which is why it can't be in the user.pages.inc file. * * @see user_register_form() */ function user_register_submit($form, &$form_state) { $admin = user_access('administer users'); if (!variable_get('user_email_verification', TRUE) || $admin) { $pass = $form_state['values']['pass']; } else { $pass = user_password(); } $notify = !empty($form_state['values']['notify']); // Remove unneeded values. form_state_values_clean($form_state); $form_state['values']['pass'] = $pass; $form_state['values']['init'] = $form_state['values']['mail']; $account = $form['#user']; entity_form_submit_build_entity('user', $account, $form, $form_state); // Populate $edit with the properties of $account, which have been edited on // this form by taking over all values, which appear in the form values too. $edit = array_intersect_key((array) $account, $form_state['values']); $account = user_save($account, $edit); // Terminate if an error occurred during user_save(). if (!$account) { drupal_set_message(t("Error saving user account."), 'error'); $form_state['redirect'] = ''; return; } $form_state['user'] = $account; $form_state['values']['uid'] = $account->uid; watchdog('user', 'New user: %name (%email).', array('%name' => $form_state['values']['name'], '%email' => $form_state['values']['mail']), WATCHDOG_NOTICE, l(t('edit'), 'user/' . $account->uid . '/edit')); // Add plain text password into user account to generate mail tokens. $account->password = $pass; // New administrative account without notification. $uri = entity_uri('user', $account); if ($admin && !$notify) { drupal_set_message(t('Created a new user account for %name. No e-mail has been sent.', array('@url' => url($uri['path'], $uri['options']), '%name' => $account->name))); } // No e-mail verification required; log in user immediately. elseif (!$admin && !variable_get('user_email_verification', TRUE) && $account->status) { _user_mail_notify('register_no_approval_required', $account); $form_state['uid'] = $account->uid; user_login_submit(array(), $form_state); drupal_set_message(t('Registration successful. You are now logged in.')); $form_state['redirect'] = ''; } // No administrator approval required. elseif ($account->status || $notify) { $op = $notify ? 'register_admin_created' : 'register_no_approval_required'; _user_mail_notify($op, $account); if ($notify) { drupal_set_message(t('A welcome message with further instructions has been e-mailed to the new user %name.', array('@url' => url($uri['path'], $uri['options']), '%name' => $account->name))); } else { drupal_set_message(t('A welcome message with further instructions has been sent to your e-mail address.')); $form_state['redirect'] = ''; } } // Administrator approval required. else { _user_mail_notify('register_pending_approval', $account); drupal_set_message(t('Thank you for applying for an account. Your account is currently pending approval by the site administrator.
    In the meantime, a welcome message with further instructions has been sent to your e-mail address.')); $form_state['redirect'] = ''; } } /** * Implements hook_modules_installed(). */ function user_modules_installed($modules) { // Assign all available permissions to the administrator role. $rid = variable_get('user_admin_role', 0); if ($rid) { $permissions = array(); foreach ($modules as $module) { if ($module_permissions = module_invoke($module, 'permission')) { $permissions = array_merge($permissions, array_keys($module_permissions)); } } if (!empty($permissions)) { user_role_grant_permissions($rid, $permissions); } } } /** * Implements hook_modules_uninstalled(). */ function user_modules_uninstalled($modules) { db_delete('role_permission') ->condition('module', $modules, 'IN') ->execute(); } /** * Helper function to rewrite the destination to avoid redirecting to login page after login. * * Third-party authentication modules may use this function to determine the * proper destination after a user has been properly logged in. */ function user_login_destination() { $destination = drupal_get_destination(); if ($destination['destination'] == 'user/login') { $destination['destination'] = 'user'; } return $destination; } /** * Saves visitor information as a cookie so it can be reused. * * @param $values * An array of key/value pairs to be saved into a cookie. */ function user_cookie_save(array $values) { foreach ($values as $field => $value) { // Set cookie for 365 days. setrawcookie('Drupal.visitor.' . $field, rawurlencode($value), REQUEST_TIME + 31536000, '/'); } } /** * Delete a visitor information cookie. * * @param $cookie_name * A cookie name such as 'homepage'. */ function user_cookie_delete($cookie_name) { setrawcookie('Drupal.visitor.' . $cookie_name, '', REQUEST_TIME - 3600, '/'); } /** * Implements hook_rdf_mapping(). */ function user_rdf_mapping() { return array( array( 'type' => 'user', 'bundle' => RDF_DEFAULT_BUNDLE, 'mapping' => array( 'rdftype' => array('sioc:UserAccount'), 'name' => array( 'predicates' => array('foaf:name'), ), 'homepage' => array( 'predicates' => array('foaf:page'), 'type' => 'rel', ), ), ), ); } /** * Implements hook_file_download_access(). */ function user_file_download_access($field, $entity_type, $entity) { if ($entity_type == 'user') { return user_view_access($entity); } } /** * Implements hook_system_info_alter(). * * Drupal 7 ships with two methods to add additional fields to users: Profile * module, a legacy module dating back from 2002, and Field API integration * with users. While Field API support for users currently provides less end * user features, the inefficient data storage mechanism of Profile module, as * well as its lack of consistency with the rest of the entity / field based * systems in Drupal 7, make this a sub-optimal solution to those who were not * using it in previous releases of Drupal. * * To prevent new Drupal 7 sites from installing Profile module, and * unwittingly ending up with two completely different and incompatible methods * of extending users, only make the Profile module available if the profile_* * tables are present. * * @todo: Remove in D8, pending upgrade path. */ function user_system_info_alter(&$info, $file, $type) { if ($type == 'module' && $file->name == 'profile' && db_table_exists('profile_field')) { $info['hidden'] = FALSE; } } type' => MENU_DEFAULT_LOCAL_TASK, 'load arguments' => array('%map', '%index'), ); if (($categories = _user_categories()) && (count($categories) > 1)) { foreach ($categories as $key => $category) { // 'account' is already handled by the MENU_DEFAULT_LOCAL_TASK. if ($category['name'] != 'account') { $items['user/%user_category/edit/' . $category['name']] = array( 'title callback' => 'check_plain', 'title arguments' => array($category['title']), 'page callback' => 'drupal_get_form', 'page arguments' => array('user_profile_form', 1, 3), 'access callback' => isset($category['access callback']) ? $category['access callback'] : 'user_edit_access', 'access arguments' => isset($category['access arguments']) ? $category['access arguments'] : array(1), 'type' => MENU_LOCAL_TASK, 'weight' => $category['weight'], 'load arguments' => array('%map', '%index'), 'tab_parent' => 'user/%/edit', 'file' => 'user.pages.inc', ); } } } return $items; } /** * Implements hook_menu_site_status_alter(). */ function user_menu_site_status_alter(&$menu_site_status, $path) { if ($menu_site_status == MENU_SITE_OFFLINE) { // If the site is offline, log out unprivileged users. if (user_is_logged_in() && !user_access('access site in maintenance mode')) { module_load_include('pages.inc', 'user', 'user'); user_logout(); } if (user_is_anonymous()) { switch ($path) { case 'user': // Forward anonymous user to login page. drupal_goto('user/login'); case 'user/login': case 'user/password': // Disable offline mode. $menu_site_status = MENU_SITE_ONLINE; break; default: if (strpos($path, 'user/reset/') === 0) { // Disable offline mode. $menu_site_status = MENU_SITE_ONLINE; } break; } } } if (user_is_logged_in()) { if ($path == 'user/login') { // If user is logged in, redirect to 'user' instead of giving 403. drupal_goto('user'); } if ($path == 'user/register') { // Authenticated user should be redirected to user edit page. drupal_goto('user/' . $GLOBALS['user']->uid . '/edit'); } } } /** * Implements hook_menu_link_alter(). */ function user_menu_link_alter(&$link) { // The path 'user' must be accessible for anonymous users, but only visible // for authenticated users. Authenticated users should see "My account", but // anonymous users should not see it at all. Therefore, invoke // user_translated_menu_link_alter() to conditionally hide the link. if ($link['link_path'] == 'user' && $link['module'] == 'system') { $link['options']['alter'] = TRUE; } // Force the Logout link to appear on the top-level of 'user-menu' menu by // default (i.e., unless it has been customized). if ($link['link_path'] == 'user/logout' && $link['module'] == 'system' && empty($link['customized'])) { $link['plid'] = 0; } } /** * Implements hook_translated_menu_link_alter(). */ function user_translated_menu_link_alter(&$link) { // Hide the "User account" link for anonymous users. if ($link['link_path'] == 'user' && $link['module'] == 'system' && user_is_anonymous()) { $link['hidden'] = 1; } } /** * Implements hook_admin_paths(). */ function user_admin_paths() { $paths = array( 'user/*/cancel' => TRUE, 'user/*/edit' => TRUE, 'user/*/edit/*' => TRUE, ); return $paths; } /** * Returns $arg or the user ID of the current user if $arg is '%' or empty. * * Deprecated. Use %user_uid_optional instead. * * @todo D8: Remove. */ function user_uid_only_optional_to_arg($arg) { return user_uid_optional_to_arg($arg); } /** * Load either a specified or the current user account. * * @param $uid * An optional user ID of the user to load. If not provided, the current * user's ID will be used. * @return * A fully-loaded $user object upon sucorm', 'pattern' => 'views_exposed_form__', 'render element' => 'form', ); $hooks['views_more'] = $base + array( 'template' => 'views-more', 'pattern' => 'views_more__', 'variables' => array('more_url' => NULL, 'link_text' => 'more'), ); // Add theme suggestions which are part of modules. foreach (views_get_module_apis() as $info) { if (isset($info['template path'])) { $hooks += _views_find_module_templates($hooks, $info['template path']); } } return $hooks; } /** * Scans a directory of a module for template files. * * @param $cache * The existing cache of theme hooks to test against. * @param $path * The path to search. * * @see drupal_find_theme_templates */ function _views_find_module_templates($cache, $path) { $regex = '/' . '\.tpl\.php' . '$' . '/'; // Because drupal_system_listing works the way it does, we check for real // templates separately from checking for patterns. $files = drupal_system_listing($regex, $path, 'name', 0); foreach ($files as $template => $file) { // Chop off the remaining extensions if there are any. $template already // has the rightmost extension removed, but there might still be more, // such as with .tpl.php, which still has .tpl in $template at this point. if (($pos = strpos($template, '.')) !== FALSE) { $template = substr($template, 0, $pos); } // Transform - in filenames to _ to match function naming scheme // for the purposes of searching. $hook = strtr($template, '-', '_'); if (isset($cache[$hook])) { $templates[$hook] = array( 'template' => $template, 'path' => dirname($file->filename), 'includes' => isset($cache[$hook]['includes']) ? $cache[$hook]['includes'] : NULL, ); } // Ensure that the pattern is maintained from base themes to its sub-themes. // Each sub-theme will have their templates scanned so the pattern must be // held for subsequent runs. if (isset($cache[$hook]['pattern'])) { $templates[$hook]['pattern'] = $cache[$hook]['pattern']; } } $patterns = array_keys($files); foreach ($cache as $hook => $info) { if (!empty($info['pattern'])) { // Transform _ in pattern to - to match file naming scheme // for the purposes of searching. $pattern = strtr($info['pattern'], '_', '-'); $matches = preg_grep('/^'. $pattern .'/', $patterns); if ($matches) { foreach ($matches as $match) { $file = substr($match, 0, strpos($match, '.')); // Put the underscores back in for the hook name and register this pattern. $templates[strtr($file, '-', '_')] = array( 'template' => $file, 'path' => dirname($files[$match]->uri), 'variables' => $info['variables'], 'base hook' => $hook, 'includes' => isset($info['includes']) ? $info['includes'] : NULL, ); } } } } return $templates; } /** * A theme preprocess function to automatically allow view-based node * templates if called from a view. * * The 'modules/node.views.inc' file is a better place for this, but * we haven't got a chance to load that file before Drupal builds the * node portion of the theme registry. */ function views_preprocess_node(&$vars) { // The 'view' attribute of the node is added in views_preprocess_node() if (!empty($vars['node']->view) && !empty($vars['node']->view->name)) { $vars['view'] = $vars['node']->view; $vars['theme_hook_suggestions'][] = 'node__view__' . $vars['node']->view->name; if (!empty($vars['node']->view->current_display)) { $vars['theme_hook_suggestions'][] = 'node__view__' . $vars['node']->view->name . '__' . $vars['node']->view->current_display; // If a node is being rendered in a view, and the view does not have a path, // prevent drupal from accidentally setting the $page variable: if ($vars['page'] && $vars['view_mode'] == 'full' && !$vars['view']->display_handler->has_path()) { $vars['page'] = FALSE; to the user page instead. if ($user->uid) { drupal_goto('user/' . $user->uid); } // Display login form: $form['name'] = array('#type' => 'textfield', '#title' => t('Username'), '#size' => 60, '#maxlength' => USERNAME_MAX_LENGTH, '#required' => TRUE, ); $form['name']['#description'] = t('Enter your @s username.', array('@s' => variable_get('site_name', 'Drupal'))); $form['pass'] = array('#type' => 'password', '#title' => t('Password'), '#description' => t('Enter the password that accompanies your username.'), '#required' => TRUE, ); $form['#validate'] = user_login_default_validators(); $form['actions'] = array('#type' => 'actions'); $form['actions']['submit'] = array('#type' => 'submit', '#value' => t('Log in')); return $form; } /** * Set up a series for validators which check for blocked users, * then authenticate against local database, then return an error if * authentication fails. Distributed authentication modules are welcome * to use hook_form_alter() to change this series in order to * authenticate against their user database instead of the local users * table. If a distributed authentication module is successful, it * should set $form_state['uid'] to a user ID. * * We use three validators instead of one since external authentication * modules usually only need to alter the second validator. * * @see user_login_name_validate() * @see user_login_authenticate_validate() * @see user_login_final_validate() * @return array * A simple list of validate functions. */ function user_login_default_validators() { return array('user_login_name_validate', 'user_login_authenticate_validate', 'user_login_final_validate'); } /** * A FAPI validate handler. Sets an error if supplied username has been blocked. */ function user_login_name_validate($form, &$form_state) { if (isset($form_state['values']['name']) && user_is_blocked($form_state['values']['name'])) { // Blocked in user administration. form_set_error('name', t('The username %name has not been activated or is blocked.', array('%name' => $form_state['values']['name']))); } } /** * A validate handler on the login form. Check supplied username/password * against local users table. If successful, $form_state['uid'] * is set to the matching user ID. */ function user_login_authenticate_validate($form, &$form_state) { $password = trim($form_state['values']['pass']); if (!empty($form_state['values']['name']) && !empty($password)) { // Do not allow any login from the current user's IP if the limit has been // reached. Default is 50 failed attempts allowed in one hour. This is // independent of the per-user limit to catch attempts from one IP to log // in to many different user accounts. We have a reasonably high limit // since there may be only one apparent IP for all users at an institution. if (!flood_is_allowed('failed_login_attempt_ip', variable_get('user_failed_login_ip_limit', 50), variable_get('user_failed_login_ip_window', 3600))) { $form_state['flood_control_triggered'] = 'ip'; return; } $account = db_query("SELECT * FROM {users} WHERE name = :name AND status = 1", array(':name' => $form_state['values']['name']))->fetchObject(); if ($account) { if (variable_get('user_failed_login_identifier_uid_only', FALSE)) { // Register flood events based on the uid only, so they apply for any // IP address. This is the most secure option. $identifier = $account->uid; } else { // The default identifier is a combination of uid and IP address. This // is less secure but more resistant to denial-of-service attacks that // could lock out all users with public user names. $identifier = $account->uid . '-' . ip_address(); } $form_state['flood_control_user_identifier'] = $identifier; // Don't allow login if the limit for this user has been reached. // Default is to allow 5 failed attempts every 6 hours. if (!flood_is_allowed('failed_login_attempt_user', variable_get('user_failed_login_user_limit', 5), variable_get('user_failed_login_user_window', 21600), $identifier)) { $form_state['flood_control_triggered'] = 'user'; return; } } // We are not limited by flood control, so try to authenticate. // Set $form_state['uid'] as a flag for user_login_final_validate(). $form_state['uid'] = user_authenticate($form_state['values']['name'], $password); } } /** * The final validation handler on the login form. * * Sets a form error if user has not been authenticated, or if too many * logins have been attempted. This validation function should always * be the last one. */ function user_login_final_validate($form, &$form_state) { if (empty($form_state['uid'])) { // Always register an IP-based failed login event. flood_register_event('failed_login_attempt_ip', variable_get('user_failed_login_ip_window', 3600)); // Register a per-user failed login event. if (isset($form_state['flood_control_user_identifier'])) { flood_register_event('failed_login_attempt_user', variable_get('user_failed_login_user_window', 21600), $form_state['flood_control_user_identifier']); } if (isset($form_state['flood_control_triggered'])) { if ($form_state['flood_control_triggered'] == 'user') { form_set_error('name', format_plural(variable_get('user_failed_login_user_limit', 5), 'Sorry, there has been more than one failed login attempt for this account. It is temporarily blocked. Try again later or request a new password.', 'Sorry, there have been more than @count failed login attempts for this account. It is temporarily blocked. Try again later or request a new password.', array('@url' => url('user/password')))); } else { // We did not find a uid, so the limit is IP-based. form_set_error('name', t('Sorry, too many failed login attempts from your IP address. This IP address is temporarily blocked. Try again later or request a new password.', array('@url' => url('user/password')))); } } else { form_set_error('name', t('Sorry, unrecognized username or password. Have you forgotten your password?', array('@password' => url('user/password')))); watchdog('user', 'Login attempt failed for %user.', array('%user' => $form_state['values']['name'])); } } elseif (isset($form_state['flood_control_user_identifier'])) { // Clear past failures for this user so as not to block a user who might // log in and out more than once in an hour. flood_clear_event('failed_login_attempt_user', $form_state['flood_control_user_identifier']); } } /** * Try to validate the user's login credentials locally. * * @param $name * User name to authenticate. * @param $password * A plain-text password, such as trimmed text from form values. * @return * The user's uid on success, or FALSE on failure to authenticate. */ function user_authenticate($name, $password) { $uid = FALSE; if (!empty($name) && !empty($password)) { $account = user_load_by_name($name); if ($account) { // Allow alternate password hashing schemes. require_once DRUPAL_ROOT . '/' . variable_get('password_inc', 'includes/password.inc'); if (user_check_password($password, $account)) { // Successful authentication. $uid = $account->uid; // Update user to new password scheme if needed. if (user_needs_new_hash($account)) { user_save($account, array('pass' => $password)); } } } } return $uid; } /** * Finalize the login process. Must be called when logging in a user. * * The function records a watchdog message about the new session, saves the * login timestamp, calls hook_user op 'login' and generates a new session. * */ function user_login_finalize(&$edit = array()) { global $user; watchdog('user', 'Session opened for %name.', array('%name' => $user->name)); // Update the user table timestamp noting user has logged in. // This is also used to invalidate one-time login links. $user->login = REQUEST_TIME; db_update('users') ->fields(array('login' => $user->login)) ->condition('uid', $user->uid) ->execute(); // Regenerate the session ID to prevent against session fixation attacks. // This is called before hook_user in case one of those functions fails // or incorrectly does a redirect which would leave the old session in place. drupal_session_regenerate(); user_module_invoke('login', $edit, $user); } /** * Submit handler for the login form. Load $user object and perform standard login * tasks. The user is then redirected to the My Account page. Setting the * destination in the query string overrides the redirect. */ function user_login_submit($form, &$form_state) { global $user; $user = user_load($form_state['uid']); $form_state['redirect'] = 'user/' . $user->uid; user_login_finalize($form_state); } /** * Helper function for authentication modules. Either logs in or registers * the current user, based on username. Either way, the global $user object is * populated and login tasks are performed. */ function user_external_login_register($name, $module) { $account = user_external_load($name); if (!$account) { // Register this new user. $userinfo = array( 'name' => $name, 'pass' => user_password(), 'init' => $name, 'status' => 1, 'access' => REQUEST_TIME ); $account = user_save(drupal_anonymous_user(), $userinfo); // Terminate if an error occurred during user_save(). if (!$account) { drupal_set_message(t("Error saving user account."), 'error'); return; } user_set_authmaps($account, array("authname_$module" => $name)); } // Log user in. $form_state['uid'] = $account->uid; user_login_submit(array(), $form_state); } /** * Generates a unique URL for a user to login and reset their password. * * @param object $account * An object containing the user account. * * @return * A unique URL that provides a one-time log in for the user, from which * they can change their password. */ function user_pass_reset_url($account) { $timestamp = REQUEST_TIME; return url("user/reset/$account->uid/$timestamp/" . user_pass_rehash($account->pass, $timestamp, $account->login), array('absolute' => TRUE)); } /** * Generates a URL to confirm an account cancellation request. * * @param object $account * The user account object, which must contain at least the following * properties: * - uid: The user uid number. * - pass: The hashed user password string. * - login: The user login name. * * @return * A unique URL that may be used to confirm the cancellation of the user * account. * * @see user_mail_tokens() * @see user_cancel_confirm() */ function user_cancel_url($account) { $timestamp = REQUEST_TIME; return url("user/$account->uid/cancel/confirm/$timestamp/" . user_pass_rehash($account->pass, $timestamp, $account->login), array('absolute' => TRUE)); } /** * Creates a unique hash value for use in time-dependent per-user URLs. * * This hash is normally used to build a unique and secure URL that is sent to * the user by email for purposes such as resetting the user's password. In * order to validate the URL, the same hash can be generated again, from the * same information, and compared to the hash value from the URL. The URL * normally contains both the time stamp and the numeric user ID. The login * name and hashed password are retrieved from the database as necessary. For a * usage example, see user_cancel_url() and user_cancel_confirm(). * * @param $password * The hashed user account password value. * @param $timestamp * A unix timestamp. * @param $login * The user account login name. * * @return * A string that is safe for use in URLs and SQL statements. */ function user_pass_rehash($password, $timestamp, $login) { return drupal_hmac_base64($timestamp . $login, drupal_get_hash_salt() . $password); } /** * Cancel a user account. * * Since the user cancellation process needs to be run in a batch, either * Form API will invoke it, or batch_process() needs to be invoked after calling * this function and should define the path to redirect to. * * @param $edit * An array of submitted form values. * @param $uid * The user ID of the user account to cancel. * @param $method * The account cancellation method to use. * * @see _user_cancel() */ function user_cancel($edit, $uid, $method) { global $user; $account = user_load($uid); if (!$account) { drupal_set_message(t('The user account %id does not exist.', array('%id' => $uid)), 'error'); watchdog('user', 'Attempted to cancel non-existing user account: %id.', array('%id' => $uid), WATCHDOG_ERROR); return; } // Initialize batch (to set title). $batch = array( 'title' => t('Cancelling account'), 'operations' => array(), ); batch_set($batch); // Modules use hook_user_delete() to respond to deletion. if ($method != 'user_cancel_delete') { // Allow modules to add further sets to this batch. module_invoke_all('user_cancel', $edit, $account, $method); } // Finish the batch and actually cancel the account. $batch = array( 'title' => t('Cancelling user account'), 'operations' => array( array('_user_cancel', array($edit, $account, $method)), ), ); batch_set($batch); // Batch processing is either handled via Form API or has to be invoked // manually. } /** * Last batch processing step for cancelling a user account. * * Since batch and session API require a valid user account, the actual * cancellation of a user account needs to happen last. * * @see user_cancel() */ function _user_cancel($edit, $account, $method) { global $user; switch ($method) { case 'user_cancel_block': case 'user_cancel_block_unpublish': default: // Send account blocked notification if option was checked. if (!empty($edit['user_cancel_notify'])) { _user_mail_notify('status_blocked', $account); } user_save($account, array('status' => 0)); drupal_set_message(t('%name has been disabled.', array('%name' => $account->name))); watchdog('user', 'Blocked user: %name %email.', array('%name' => $account->name, '%email' => '<' . $account->mail . '>'), WATCHDOG_NOTICE); break; case 'user_cancel_reassign': case 'user_cancel_delete': // Send account canceled notification if option was checked. if (!empty($edit['user_cancel_notify'])) { _user_mail_notify('status_canceled', $account); } user_delete($account->uid); drupal_set_message(t('%name has been deleted.', array('%name' => $account->name))); watchdog('user', 'Deleted user: %name %email.', array('%name' => $account->name, '%email' => '<' . $account->mail . '>'), WATCHDOG_NOTICE); break; } // After cancelling account, ensure that user is logged out. if ($account->uid == $user->uid) { // Destroy the current session, and reset $user to the anonymous user. session_destroy(); } // Clear the cache for anonymous users. cache_clear_all(); } /** * Delete a user. * * @param $uid * A user ID. */ function user_delete($uid) { user_delete_multiple(array($uid)); } /** * Delete multiple user accounts. * * @param $uids * An array of user IDs. */ function user_delete_multiple(array $uids) { if (!empty($uids)) { $accounts = user_load_multiple($uids, array()); $transaction = db_transaction(); try { foreach ($accounts as $uid => $account) { module_invoke_all('user_delete', $account); module_invoke_all('entity_delete', $account, 'user'); field_attach_delete('user', $account); drupal_session_destroy_uid($account->uid); } db_delete('users') ->condition('uid', $uids, 'IN') ->execute(); db_delete('users_roles') ->condition('uid', $uids, 'IN') ->execute(); db_delete('authmap') ->condition('uid', $uids, 'IN') ->execute(); } catch (Exception $e) { $transaction->rollback(); watchdog_exception('user', $e); throw $e; } entity_get_controller('user')->resetCache(); } } /** * Page callback wrapper for user_view(). */ function user_view_page($account) { // An administrator may try to view a non-existent account, // so we give them a 404 (versus a 403 for non-admins). return is_object($account) ? user_view($account) : MENU_NOT_FOUND; } /** * Generate an array for rendering the given user. * * When viewing a user profile, the $page array contains: * * - $page['content']['Profile Category']: * Profile categories keyed by their human-readable names. * - $page['content']['Profile Category']['profile_machine_name']: * Profile fields keyed by their machine-readable names. * - $page['content']['user_picture']: * User's rendered picture. * - $page['content']['summary']: * Contains the default "History" profile data for a user. * - $page['content']['#account']: * The user account of the profile being viewed. * * To theme user profiles, copy modules/user/user-profile.tpl.php * to your theme directory, and edit it as instructed in that file's comments. * * @param $account * A user object. * @param $view_mode * View mode, e.g. 'full'. * @param $langcode * (optional) A language code to use for rendering. Defaults to the global * content language of the current request. * * @return * An array as expected by drupal_render(). */ function user_view($account, $view_mode = 'full', $langcode = NULL) { if (!isset($langcode)) { $langcode = $GLOBALS['language_content']->language; } // Retrieve all profile fields and attach to $account->content. user_build_content($account, $view_mode, $langcode); $build = $account->content; // We don't need duplicate rendering info in account->content. unset($account->content); $build += array( '#theme' => 'user_profile', '#account' => $account, '#view_mode' => $view_mode, '#language' => $langcode, ); // Allow modules to modify the structured user. $type = 'user'; drupal_alter(array('user_view', 'entity_view'), $build, $type); return $build; } /** * Builds a structured array representing the profile content. * * @param $account * A user object. * @param $view_mode * View mode, e.g. 'full'. * @param $langcode * (optional) A language code to use for rendering. Defaults to the global * content language of the current request. */ function user_build_content($account, $view_mode = 'full', $langcode = NULL) { if (!isset($langcode)) { $langcode = $GLOBALS['language_content']->language; } // Remove previously built content, if exists. $account->content = array(); // Build fields content. field_attach_prepare_view('user', array($account->uid => $account), $view_mode, $langcode); entity_prepare_view('user', array($account->uid => $account), $langcode); $account->content += field_attach_view('user', $account, $view_mode, $langcode); // Populate $account->content with a render() array. module_invoke_all('user_view', $account, $view_mode, $langcode); module_invoke_all('entity_view', $account, 'user', $view_mode, $langcode); } /** * Implements hook_mail(). */ function user_mail($key, &$message, $params) { $language = $message['language']; $variables = array('user' => $params['account']); $message['subject'] .= _user_mail_text($key . '_subject', $language, $variables); $message['body'][] = _user_mail_text($key . '_body', $language, $variables); } /** * Returns a mail string for a variable name. * * Used by user_mail() and the settings forms to retrieve strings. */ function _user_mail_text($key, $language = NULL, $variables = array(), $replace = TRUE) { $langcode = isset($language) ? $language->language : NULL; if ($admin_setting = variable_get('user_mail_' . $key, FALSE)) { // An admin setting overrides the default string. $text = $admin_setting; } else { // No override, return default string. switch ($key) { case 'register_no_approval_required_subject': $text = t('Account details for [user:name] at [site:name]', array(), array('langcode' => $langcode)); break; case 'register_no_approval_required_body': $text = t("[user:name], Thank you for registering at [site:name]. You may now log in by clicking this link or copying and pasting it to your browser: [user:one-time-login-url] This link can only be used once to log in and will lead you to a page where you can set your password. After setting your password, you will be able to log in at [site:login-url] in the future using: username: [user:name] password: Your password -- [site:name] team", array(), array('langcode' => $langcode)); break; case 'register_admin_created_subject': $text = t('An administrator created an account for you at [site:name]', array(), array('langcode' => $langcode)); break; case 'register_admin_created_body': $text = t("[user:name], A site administrator at [site:name] has created an account for you. You may now log in by clicking this link or copying and pasting it to your browser: [user:one-time-login-url] This link can only be used once to log in and will lead you to a page where you can set your password. After setting your password, you will be able to log in at [site:login-url] in the future using: username: [user:name] password: Your password -- [site:name] team", array(), array('langcode' => $langcode)); break; case 'register_pending_approval_subject': case 'register_pending_approval_admin_subject': $text = t('Account details for [user:name] at [site:name] (pending admin approval)', array(), array('langcode' => $langcode)); break; case 'register_pending_approval_body': $text = t("[user:name], Thank you for registering at [site:name]. Your application for an account is currently pending approval. Once it has been approved, you will receive another e-mail containing information about how to log in, set your password, and other details. -- [site:name] team", array(), array('langcode' => $langcode)); break; case 'register_pending_approval_admin_body': $text = t("[user:name] has applied for an account. [user:edit-url]", array(), array('langcode' => $langcode)); break; case 'password_reset_subject': $text = t('Replacement login information for [user:name] at [site:name]', array(), array('langcode' => $langcode)); break; case 'password_reset_body': $text = t("[user:name], A request to reset the password for your account has been made at [site:name]. You may now log in by clicking this link or copying and pasting it to your browser: [user:one-time-login-url] This link can only be used once to log in and will lead you to a page where you can set your password. It expires after one day and nothing will happen if it's not used. -- [site:name] team", array(), array('langcode' => $langcode)); break; case 'status_activated_subject': $text = t('Account details for [user:name] at [site:name] (approved)', array(), array('langcode' => $langcode)); break; case 'status_activated_body': $text = t("[user:name], Your account at [site:name] has been activated. You may now log in by clicking this link or copying and pasting it into your browser: [user:one-time-login-url] This link can only be used once to log in and will lead you to a page where you can set your password. After setting your password, you will be able to log in at [site:login-url] in the future using: username: [user:name] password: Your password -- [site:name] team", array(), array('langcode' => $langcode)); break; case 'status_blocked_subject': $text = t('Account details for [user:name] at [site:name] (blocked)', array(), array('langcode' => $langcode)); break; case 'status_blocked_body': $text = t("[user:name], Your account on [site:name] has been blocked. -- [site:name] team", array(), array('langcode' => $langcode)); note that * this returns a reference, so be careful! You can unintentionally modify the * $view object. */ function &views_get_page_view() { return views_set_page_view(); } /** * Set the current 'current view' that is being built/rendered so that it is * easy for other modules or items in drupal_eval to identify */ function &views_set_current_view($view = NULL) { static $cache = NULL; if (isset($view)) { $cache = $view; } return $cache; } /** * Find out what, if any, current view is currently in use. Please note that * this returns a reference, so be careful! You can unintentionally modify the * $view object. */ function &views_get_current_view() { return views_set_current_view(); } // ------------------------------------------------------------------ // Include file helpers /** * Include views .inc files as necessary. */ function views_include($file) { ctools_include($file, 'views'); } /** * Load views files on behalf of modules. */ function views_module_include($api, $reset = FALSE) { if ($reset) { $cache = &drupal_static('ctools_plugin_api_info'); if (isset($cache['views']['views'])) { unset($cache['views']['views']); } } ctools_include('plugins'); return ctools_plugin_api_include('views', $api, views_api_minimum_version(), views_api_version()); } /** * Get a list of modules that support the current views API. */ function views_get_module_apis($api = 'views', $reset = FALSE) { if ($reset) { $cache = &drupal_static('ctools_plugin_api_info'); if (isset($cache['views']['views'])) { unset($cache['views']['views']); } } ctools_include('plugins'); return ctools_plugin_api_info('views', $api, views_api_minimum_version(), views_api_version()); } /** * Include views .css files. */ function views_add_css($file) { // We set preprocess to FALSE because we are adding the files conditionally, // and we don't want to generate duplicate cache files. // TODO: at some point investigate adding some files unconditionally and // allowing preprocess. drupal_add_css(drupal_get_path('module', 'views') . "/css/$file.css", array('preprocess' => FALSE)); } /** * Include views .js files. */ function views_add_js($file) { // If javascript has been disabled by the user, never add js files. if (variable_get('views_no_javascript', FALSE)) { return; } static $base = TRUE, $ajax = TRUE; if ($base) { drupal_add_js(drupal_get_path('module', 'views') . "/js/base.js"); $base = FALSE; } if ($ajax && in_array($file, array('ajax', 'ajax_view'))) { drupal_add_library('system', 'drupal.ajax'); drupal_add_library('system', 'jquery.form'); $ajax = FALSE; } ctools_add_js($file, 'views'); } /** * Load views files on behalf of modules. */ function views_include_handlers($reset = FALSE) { static $finished = FALSE; // Ensure this only gets run once. if ($finished && !$reset) { return; } views_include('base'); views_include('handlers'); views_include('cache'); views_include('plugins'); views_module_include('views', $reset); $finished = TRUE; } // ----------------------------------------------------------------------- // Views handler functions /** * Fetch a handler from the data cache. * * @param $table * The name of the table this handler is from. * @param $field * The name of the field this handler is from. * @param $key * The type of handler. i.e, sort, field, argument, filter, relationship * @param $override * Override the actual handler object with this class. Used for * aggregation when the handler is redirected to the aggregation * handler. * * @return views_handler * An instance of a handler object. May be views_handler_broken. */ function views_get_handler($table, $field, $key, $override = NULL) { static $recursion_protection = array(); $data = views_fetch_data($table, FALSE); $handler = NULL; // Support old views_data entries conversion. // Support conversion on table level. if (isset($data['moved to'])) { $moved = array($data['moved to'], $field); } // Support conversion on datafield level. if (isset($data[$field]['moved to'])) { $moved = $data[$field]['moved to']; } // Support conversion on handler level. if (isset($data[$field][$key]['moved to'])) { $moved = $data[$field][$key]['moved to']; } if (isset($data[$field][$key]) || !empty($moved)) { if (!empty($moved)) { list($moved_table, $moved_field) = $moved; if (!empty($recursion_protection[$moved_table][$moved_field])) { // recursion detected! return NULL; } $recursion_protection[$moved_table][$moved_field] = TRUE; $handler = views_get_handler($moved_table, $moved_field, $key, $override); $recursion_protection = array(); if ($handler) { // store these values so we know what we were originally called. $handler->original_table = $table; $handler->original_field = $field; if (empty($handler->actual_table)) { $handler->actual_table = $moved_table; $handler->actual_field = $moved_field; } } return $handler; } // Set up a default handler: if (empty($data[$field][$key]['handler'])) { $data[$field][$key]['handler'] = 'views_handler_' . $key; } if ($override) { $data[$field][$key]['override handler'] = $override; } $handler = _views_prepare_handler($data[$field][$key], $data, $field, $key); } if ($handler) { return $handler; } // DEBUG -- identify missing handlers vpr("Missing handler: $table $field $key"); $broken = array( 'title' => t('Broken handler @table.@field', array('@table' => $table, '@field' => $field)), 'handler' => 'views_handler_' . $key . '_broken', 'table' => $table, 'field' => $field, ); return _views_create_handler($broken, 'handler', $key); } /** * Fetch Views' data from the cache */ function views_fetch_data($table = NULL, $move = TRUE, $reset = FALSE) { views_include('cache'); return _views_fetch_data($table, $move, $reset); } // ----------------------------------------------------------------------- // Views plugin functions /** * Fetch the plugin data from cache. */ function views_fetch_plugin_data($type = NULL, $plugin = NULL, $reset = FALSE) { views_include('cache'); return _views_fetch_plugin_data($type, $plugin, $reset); } /** * Fetch a list of all base tables available * * @param $type * Either 'display', 'style' or 'row' * @param $key * For style plugins, this is an optional type to restrict to. May be 'normal', * 'summary', 'feed' or others based on the neds of the display. * @param $base * An array of possible base tables. * * @return * A keyed array of in the form of 'base_table' => 'Description'. */ function views_fetch_plugin_names($type, $key = NULL, $base = array()) { $data = views_fetch_plugin_data(); $plugins[$type] = array(); foreach ($data[$type] as $id => $plugin) { // Skip plugins that don't conform to our key. if ($key && (empty($plugin['type']) || $plugin['type'] != $key)) { continue; } if (empty($plugin['no ui']) && (empty($base) || empty($plugin['base']) || array_intersect($base, $plugin['base']))) { $plugins[$type][$id] = $plugin['title']; } } if (!empty($plugins[$type])) { asort($plugins[$type]); return $plugins[$type]; } // fall-through return array(); } /** * Get a handler for a plugin * * @return views_plugin * * The created plugin object. */ function views_get_plugin($type, $plugin, $reset = FALSE) { views_include('handlers'); $definition = views_fetch_plugin_data($type, $plugin, $reset); if (!empty($definition)) { return _views_create_handler($definition, $type); } } /** * Load the current enabled localization plugin. * * @return The name of the localization plugin. */ function views_get_localization_plugin() { $plugin = variable_get('views_localization_plugin', ''); // Provide sane default values for the localization plugin. if (empty($plugin)) { if (module_exists('locale')) { $p 'access user profiles' => 1, // Grant 'access user profiles' * 'access content' => TRUE, // Grant 'access content' * 'access comments' => 'access comments', // Grant 'access comments' * ) * @endcode * Existing permissions are not changed, unless specified in $permissions. * * @see user_role_grant_permissions() * @see user_role_revoke_permissions() */ function user_role_change_permissions($rid, array $permissions = array()) { // Grant new permissions for the role. $grant = array_filter($permissions); if (!empty($grant)) { user_role_grant_permissions($rid, array_keys($grant)); } // Revoke permissions for the role. $revoke = array_diff_assoc($permissions, $grant); if (!empty($revoke)) { user_role_revoke_permissions($rid, array_keys($revoke)); } } /** * Grant permissions to a user role. * * @param $rid * The ID of a user role to alter. * @param $permissions * A list of permission names to grant. * * @see user_role_change_permissions() * @see user_role_revoke_permissions() */ function user_role_grant_permissions($rid, array $permissions = array()) { $modules = user_permission_get_modules(); // Grant new permissions for the role. foreach ($permissions as $name) { db_merge('role_permission') ->key(array( 'rid' => $rid, 'permission' => $name, )) ->fields(array( 'module' => $modules[$name], )) ->execute(); } // Clear the user access cache. drupal_static_reset('user_access'); drupal_static_reset('user_role_permissions'); } /** * Revoke permissions from a user role. * * @param $rid * The ID of a user role to alter. * @param $permissions * A list of permission names to revoke. * * @see user_role_change_permissions() * @see user_role_grant_permissions() */ function user_role_revoke_permissions($rid, array $permissions = array()) { // Revoke permissions for the role. db_delete('role_permission') ->condition('rid', $rid) ->condition('permission', $permissions, 'IN') ->execute(); // Clear the user access cache. drupal_static_reset('user_access'); drupal_static_reset('user_role_permissions'); } /** * Implements hook_user_operations(). */ function user_user_operations($form = array(), $form_state = array()) { $operations = array( 'unblock' => array( 'label' => t('Unblock the selected users'), 'callback' => 'user_user_operations_unblock', ), 'block' => array( 'label' => t('Block the selected users'), 'callback' => 'user_user_operations_block', ), 'cancel' => array( 'label' => t('Cancel the selected user accounts'), ), ); if (user_access('administer permissions')) { $roles = user_roles(TRUE); unset($roles[DRUPAL_AUTHENTICATED_RID]); // Can't edit authenticated role. $add_roles = array(); foreach ($roles as $key => $value) { $add_roles['add_role-' . $key] = $value; } $remove_roles = array(); foreach ($roles as $key => $value) { $remove_roles['remove_role-' . $key] = $value; } if (count($roles)) { $role_operations = array( t('Add a role to the selected users') => array( 'label' => $add_roles, ), t('Remove a role from the selected users') => array( 'label' => $remove_roles, ), ); $operations += $role_operations; } } // If the form has been posted, we need to insert the proper data for // role editing if necessary. if (!empty($form_state['submitted'])) { $operation_rid = explode('-', $form_state['values']['operation']); $operation = $operation_rid[0]; if ($operation == 'add_role' || $operation == 'remove_role') { $rid = $operation_rid[1]; if (user_access('administer permissions')) { $operations[$form_state['values']['operation']] = array( 'callback' => 'user_multiple_role_edit', 'callback arguments' => array($operation, $rid), ); } else { watchdog('security', 'Detected malicious attempt to alter protected user fields.', array(), WATCHDOG_WARNING); return; } } } return $operations; } /** * Callback function for admin mass unblocking users. */ function user_user_operations_unblock($accounts) { $accounts = user_load_multiple($accounts); foreach ($accounts as $account) { // Skip unblocking user if they are already unblocked. if ($account !== FALSE && $account->status == 0) { user_save($account, array('status' => 1)); } } } /** * Callback function for admin mass blocking users. */ function user_user_operations_block($accounts) { $accounts = user_load_multiple($accounts); foreach ($accounts as $account) { // Skip blocking user if they are already blocked. if ($account !== FALSE && $account->status == 1) { // For efficiency manually save the original account before applying any // changes. $account->original = clone $account; user_save($account, array('status' => 0)); } } } /** * Callback function for admin mass adding/deleting a user role. */ function user_multiple_role_edit($accounts, $operation, $rid) { // The role name is not necessary as user_save() will reload the user // object, but some modules' hook_user() may look at this first. $role_name = db_query('SELECT name FROM {role} WHERE rid = :rid', array(':rid' => $rid))->fetchField(); switch ($operation) { case 'add_role': $accounts = user_load_multiple($accounts); foreach ($accounts as $account) { // Skip adding the role to the user if they already have it. if ($account !== FALSE && !isset($account->roles[$rid])) { $roles = $account->roles + array($rid => $role_name); // For efficiency manually save the original account before applying // any changes. $account->original = clone $account; user_save($account, array('roles' => $roles)); } } break; case 'remove_role': $accounts = user_load_multiple($accounts); foreach ($accounts as $account) { // Skip removing the role from the user if they already don't have it. if ($account !== FALSE && isset($account->roles[$rid])) { $roles = array_diff($account->roles, array($rid => $role_name)); // For efficiency manually save the original account before applying // any changes. $account->original = clone $account; user_save($account, array('roles' => $roles)); } } break; } } function user_multiple_cancel_confirm($form, &$form_state) { $edit = $form_state['input']; $form['accounts'] = array('#prefix' => '', '#tree' => TRUE); $accounts = user_load_multiple(array_keys(array_filter($edit['accounts']))); foreach ($accounts as $uid => $account) { // Prevent user 1 from being canceled. if ($uid <= 1) { continue; } $form['accounts'][$uid] = array( '#type' => 'hidden', '#value' => $uid, '#prefix' => '
  • ', '#suffix' => check_plain($account->name) . "
  • \n", ); } // Output a notice that user 1 cannot be canceled. if (isset($accounts[1])) { $redirect = (count($accounts) == 1); $message = t('The user account %name cannot be cancelled.', array('%name' => $accounts[1]->name)); drupal_set_message($message, $redirect ? 'error' : 'warning'); // If only user 1 was selected, redirect to the overview. if ($redirect) { drupal_goto('admin/people'); } } $form['operation'] = array('#type' => 'hidden', '#value' => 'cancel'); module_load_include('inc', 'user', 'user.pages'); $form['user_cancel_method'] = array( '#type' => 'item', '#title' => t('When cancelling these accounts'), ); $form['user_cancel_method'] += user_cancel_methods(); // Remove method descriptions. foreach (element_children($form['user_cancel_method']) as $element) { unset($form['user_cancel_method'][$element]['#description']); } // Allow to send the account cancellation confirmation mail. $form['user_cancel_confirm'] = array( '#type' => 'checkbox', '#title' => t('Require e-mail confirmation to cancel account.'), '#default_value' => FALSE, '#description' => t('When enabled, the user must confirm the account cancellation via e-mail.'), ); // Also allow to send account canceled notification mail, if enabled. $form['user_cancel_notify'] = array( '#type' => 'checkbox', '#title' => t('Notify user when account is canceled.'), '#default_value' => FALSE, '#access' => variable_get('user_mail_status_canceled_notify', FALSE), '#description' => t('When enabled, the user will receive an e-mail notification after the account has been cancelled.'), ); return confirm_form($form, t('Are you sure you want to cancel these user accounts?'), 'admin/people', t('This action cannot be undone.'), t('Cancel accounts'), t('Cancel')); } /** * Submit handler for mass-account cancellation form. * * @see user_multiple_cancel_confirm() * @see user_cancel_confirm_form_submit() */ function user_multiple_cancel_confirm_submit($form, &$form_state) { global $user; if ($form_state['values']['confirm']) { foreach ($form_state['values']['accounts'] as $uid => $value) { // Prevent programmatic form submissions from cancelling user 1. if ($uid <= 1) { continue; } // Prevent user administrators from deleting themselves without confirmation. if ($uid == $user->uid) { $admin_form_state = $form_state; unset($admin_form_state['values']['user_cancel_confirm']); $admin_form_state['values']['_account'] = $user; user_cancel_confirm_form_submit(array(), $admin_form_state); } else { user_cancel($form_state['values'], $uid, $form_state['values']['user_cancel_method']); } } } $form_state['redirect'] = 'admin/people'; } /** * Retrieve a list of all user setting/information categories and sort them by weight. */ function _user_categories() { $categories = module_invoke_all('user_categories'); usort($categories, '_user_sort'); return $categories; } function _user_sort($a, $b) { $a = (array) $a + array('weight' => 0, 'title' => ''); $b = (array) $b + array('weight' => 0, 'title' => ''); return $a['weight'] < $b['weight'] ? -1 : ($a['weight'] > $b['weight'] ? 1 : ($a['title'] < $b['title'] ? -1 : 1)); } /** * List user administration filters that can be applied. */ function user_filters() { // Regular filters $filters = array(); $roles = user_roles(TRUE); unset($roles[DRUPAL_AUTHENTICATED_RID]); // Don't list authorized role. if (count($roles)) { $filters['role'] = array( 'title' => t('role'), 'field' => 'ur.rid', 'options' => array( '[any]' => t('any'), ) + $roles, ); } $options = array(); foreach (module_implements('permission') as $module) { $function = $module . '_permission'; if ($permissions = $function('permission')) { asort($permissions); foreach ($permissions as $permission => $description) { $options[t('@module module', array('@module' => $module))][$permission] = t($permission); } } } ksort($options); $filters['permission'] = array( 'title' => t('permission'), 'options' => array( '[any]' => t('any'), ) + $options, ); $filters['status'] = array( 'title' => t('status'), 'field' => 'u.status', 'options' => array( '[any]' => t('any'), 1 => t('active'), 0 => t('blocked'), ), ); return $filters; } /** * Extends a query object for user administration filters based on session. * * @param $query * Query object that should be filtered. */ function user_build_filter_query(SelectQuery $query) { $filters = user_filters(); // Extend Query with filter conditions. foreach (isset($_SESSION['user_overview_filter']) ? $_SESSION['user_overview_filter'] : array() as $filter) { list($key, $value) = $filter; // This checks to see if this permission fig the view. */ function views_ui_cache_load($name) { ctools_include('object-cache'); views_include('view'); $view = ctools_object_cache_get('view', $name); $original_view = views_get_view($name); if (empty($view)) { $view = $original_view; if (!empty($view)) { // Check to see if someone else is already editing this view. $view->locked = ctools_object_cache_test('view', $view->name); // Set a flag to indicate that this view is being edited. // This flag will be used e.g. to determine whether strings // should be localized. $view->editing = TRUE; } } else { // Keep disabled/enabled status real. if ($original_view) { $view->disabled = !empty($original_view->disabled); } } if (empty($view)) { return FALSE; } else { return $view; } } /** * Specialized cache function to add a flag to our view, include an appropriate * include, and cache more easily. */ function views_ui_cache_set(&$view) { if (!empty($view->locked)) { drupal_set_message(t('Changes cannot be made to a locked view.'), 'error'); return; } ctools_include('object-cache'); $view->changed = TRUE; // let any future object know that this view has changed. if (isset($view->current_display)) { // Add the knowledge of the changed display, too. $view->changed_display[$view->current_display] = TRUE; unset($view->current_display); } // Unset handlers; we don't want to write these into the cache unset($view->display_handler); unset($view->default_display); $view->query = NULL; foreach (array_keys($view->display) as $id) { unset($view->display[$id]->handler); unset($view->display[$id]->default_display); } ctools_object_cache_set('view', $view->name, $view); } /** * Specialized menu callback to load a view that is only a default * view. */ function views_ui_default_load($name) { $view = views_get_view($name); if ($view->type == t('Default')) { return $view; } return FALSE; } /** * Theme preprocess for views-view.tpl.php. */ function views_ui_preprocess_views_view(&$vars) { $view = $vars['view']; if (!empty($view->views_ui_context) && module_exists('contextual')) { $view->hide_admin_links = TRUE; foreach (array('title', 'header', 'exposed', 'rows', 'pager', 'more', 'footer', 'empty', 'attachment_after', 'attachment_before') as $section) { if (!empty($vars[$section])) { $vars[$section] = array( '#theme' => 'views_ui_view_preview_section', '#view' => $view, '#section' => $section, '#content' => $vars[$section], '#theme_wrappers' => array('views_container'), '#attributes' => array('class' => 'contextual-links-region'), ); $vars[$section] = drupal_render($vars[$section]); } } } } /** * Theme preprocess for theme_views_ui_view_preview_section(). * * @TODO * Perhaps move this to includes/admin.inc or theme/theme.inc */ function template_preprocess_views_ui_view_preview_section(&$vars) { switch ($vars['section']) { case 'title': $vars['title'] = t('Title'); $links = views_ui_view_preview_section_display_category_links($vars['view'], 'title', $vars['title']); break; case 'header': $vars['title'] = t('Header'); $links = views_ui_view_preview_section_handler_links($vars['view'], $vars['section']); break; case 'empty': $vars['title'] = t('No results behavior'); $links = views_ui_view_preview_section_handler_links($vars['view'], $vars['section']); break; case 'exposed': // @todo Sorts can be exposed too, so we may need a better title. $vars['title'] = t('Exposed Filters'); $links = views_ui_view_preview_section_display_category_links($vars['view'], 'exposed_form_options', $vars['title']); break; case 'rows': // @todo The title needs to depend on what is being viewed. $vars['title'] = t('Content'); $links = views_ui_view_preview_section_rows_links($vars['view']); break;p, $account, $language = NULL) { // By default, we always notify except for canceled and blocked. $default_notify = ($op != 'status_canceled' && $op != 'status_blocked'); $notify = variable_get('user_mail_' . $op . '_notify', $default_notify); if ($notify) { $params['account'] = $account; $language = $language ? $language : user_preferred_language($account); $mail = drupal_mail('user', $op, $account->mail, $language, $params); if ($op == 'register_pending_approval') { // If a user registered requiring admin approval, notify the admin, too. // We use the site default language for this. drupal_mail('user', 'register_pending_approval_admin', variable_get('site_mail', ini_get('sendmail_from')), language_default(), $params); } } return empty($mail) ? NULL : $mail['result']; } /** * Form element process handler for client-side password validation. * * This #process handler is automatically invoked for 'password_confirm' form * elements to add the JavaScript and string translations for dynamic password * validation. * * @see system_element_info() */ function user_form_process_password_confirm($element) { global $user; $js_settings = array( 'password' => array( 'strengthTitle' => t('Password strength:'), 'hasWeaknesses' => t('To make your password stronger:'), 'tooShort' => t('Make it at least 6 characters'), 'addLowerCase' => t('Add lowercase letters'), 'addUpperCase' => t('Add uppercase letters'), 'addNumbers' => t('Add numbers'), 'addPunctuation' => t('Add punctuation'), 'sameAsUsername' => t('Make it different from your username'), 'confirmSuccess' => t('yes'), 'confirmFailure' => t('no'), 'weak' => t('Weak'), 'fair' => t('Fair'), 'good' => t('Good'), 'strong' => t('Strong'), 'confirmTitle' => t('Passwords match:'), 'username' => (isset($user->name) ? $user->name : ''), ), ); $element['#attached']['js'][] = drupal_get_path('module', 'user') . '/user.js'; // Ensure settings are only added once per page. static $already_added = FALSE; if (!$already_added) { $already_added = TRUE; $element['#attached']['js'][] = array('data' => $js_settings, 'type' => 'setting'); } return $element; } /** * Implements hook_node_load(). */ function user_node_load($nodes, $types) { // Build an array of all uids for node authors, keyed by nid. $uids = array(); foreach ($nodes as $nid => $node) { $uids[$nid] = $node->uid; } // Fetch name, picture, and data for these users. $user_fields = db_query("SELECT uid, name, picture, data FROM {users} WHERE uid IN (:uids)", array(':uids' => $uids))->fetchAllAssoc('uid'); // Add these values back into the node objects. foreach ($uids as $nid => $uid) { $nodes[$nid]->name = $user_fields[$uid]->name; $nodes[$nid]->picture = $user_fields[$uid]->picture; $nodes[$nid]->data = $user_fields[$uid]->data; } } /** * Implements hook_image_style_delete(). */ function user_image_style_delete($style) { // If a style is deleted, update the variables. // Administrators choose a replacement style when deleting. user_image_style_save($style); } /** * Implements hook_image_style_save(). */ function user_image_style_save($style) { // If a style is renamed, update the variables that use it. if (isset($style['old_name']) && $style['old_name'] == variable_get('user_picture_style', '')) { variable_set('user_picture_style', $style['name']); } } /** * Implements hook_action_info(). */ function user_action_info() { return array( 'user_block_user_action' => array( 'label' => t('Block current user'), 'type' => 'user', 'configurable' => FALSE, 'triggers' => array('any'), ), ); } /** * Blocks the current user. * * @ingroup actions */ function user_block_user_action(&$entity, $context = array()) { // First priority: If there is a $entity->uid, block that user. // This is most likely a user object or the author if a node or comment. if (isset($entity->uid)) { $uid = $entity->uid; } elseif (isset($context['uid'])) { $uid = $context['uid']; } // If neither of those are valid, then block the current user. else { $uid = $GLOBALS['user']->uid; } $account = user_load($uid); $account = user_save($account, array('status' => 0)); watchdog('action', 'Blocked user %name.', array('%name' => $account->name)); } /** * Implements hook_form_FORM_ID_alter(). * * Add a checkbox for the 'user_register_form' instance settings on the 'Edit * field instance' form. */ function user_form_field_ui_field_edit_form_alter(&$form, &$form_state, $form_id) { $instance = $form['#instance']; if ($instance['entity_type'] == 'user') { $form['instance']['settings']['user_register_form'] = array( '#type' => 'checkbox', '#title' => t('Display on user registration form.'), '#description' => t("This is compulsory for 'required' fields."), // Field instances created in D7 beta releases before the setting was // introduced might be set as 'required' and 'not shown on user_register // form'. We make sure the checkbox comes as 'checked' for those. '#default_value' => $instance['settings']['user_register_form'] || $instance['required'], // Display just below the 'required' checkbox. '#weight' => $form['instance']['required']['#weight'] + .1, // Disabled when the 'required' checkbox is checked. '#states' => array( 'enabled' => array('input[name="instance[required]"]' => array('checked' => FALSE)), ), // Checked when the 'required' checkbox is checked. This is done through // a custom behavior, since the #states system would also synchronize on // uncheck. '#attached' => array( 'js' => array(drupal_get_path('module', 'user') . '/user.js'), ), ); array_unshift($form['#submit'], 'user_form_field_ui_field_edit_form_submit'); } } /** * Additional submit handler for the 'Edit field instance' form. * * Make sure the 'user_register_form' setting is set for required fields. */ function user_form_field_ui_field_edit_form_submit($form, &$form_state) { $instance = $form_state['values']['instance']; if (!empty($instance['required'])) { form_set_value($form['instance']['settings']['user_register_form'], 1, $form_state); } } /** * Form builder; the user registration form. * * @ingroup forms * @see user_account_form() * @see user_account_form_validate() * @see user_register_submit() */ function user_register_form($form, &$form_state) { global $user; $admin = user_access('administer users'); // If we aren't admin but already logged on, go to the user page instead. if (!$admin && $user->uid) { drupal_goto('user/' . $user->uid); } $form['#user'] = drupal_anonymous_user(); $form['#user_category'] = 'register'; $form['#attached']['library'][] = array('system', 'jquery.cookie'); $form['#attributes']['class'][] = 'user-info-from-cookie'; // Start with the default user account fields. user_account_form($form, $form_state); // Attach field widgets, and hide the ones where the 'user_register_form' // setting is not on. field_attach_form('user', $form['#user'], $form, $form_state); foreach (field_info_instances('user', 'user') as $field_name => $instance) { if (empty($instance['settings']['user_register_form'])) { $form[$field_name]['#access'] = FALSE; } } if ($admin) { // Redirect back to page which initiated the create request; // usually admin/people/create. $form_state['redirect'] = $_GET['q']; } $form['actions'] = array('#type' => 'actions'); $form['actions']['submit'] = array( '#type' => 'submit', '#value' => t('Create new account'), ); $form['#validate'][] = 'user_register_validate'; // Add the final user registration form submit handler. $form['#submit'][] = 'user_register_submit'; return $form; } /** * Validation function for the user registration form. */ function user_register_validate($form, &$form_state) { entity_form_field_validate('user', $form, $form_state); } /** * Submit handler for the user registration form. * * This function is shared by the installation form and the normal registration form, * which is why it can't be in the user.pages.inc file. * * @see user_register_form() */ function user_register_submit($form, &$form_state) { $admin = user_access('administer users'); if (!variable_get('user_email_verification', TRUE) || $admin) { $pass = $form_state['values']['pass']; } else { $pass = user_password(); } $notify = !empty($form_state['values']['notify']); // Remove unneeded values. form_state_values_clean($form_state); $form_state['values']['pass'] = $pass; $form_state['values']['init'] = $form_state['values']['mail']; $account = $form['#user']; entity_form_submit_build_entity('user', $account, $form, $form_state); // Populate $edit with the properties of $account, which have been edited on // this form by taking over all values, which appear in the form values too. $edit = array_intersect_key((array) $account, $form_state['values']); $account = user_save($account, $edit); // Terminate if an error occurred during user_save(). if (!$account) { drupal_set_message(t("Error saving user account."), 'error'); $form_state['redirect'] = ''; return; } $form_state['user'] = $account; $form_state['values']['uid'] = $account->uid; watchdog('user', 'New user: %name (%email).', array('%name' => $form_state['values']['name'], '%email' => $form_state['values']['mail']), WATCHDOG_NOTICE, l(t('edit'), 'user/' . $account->uid . '/edit')); // Add plain text password into user account to generate mail tokens. $account->password = $pass; // New administrative account without notification. $uri = entity_uri('user', $account); if ($admin && !$notify) { drupal_set_message(t('Created a new user account for %name. No e-mail has been sent.', array('@url' => url($uri['path'], $uri['options']), '%name' => $account->name))); } // No e-mail verification required; log in user immediately. elseif (!$admin && !variable_get('user_email_verification', TRUE) && $account->status) { _user_mail_notify('register_no_approval_required', $account); $form_state['uid'] = $account->uid; user_login_submit(array(), $form_state); drupal_set_message(t('Registration successful. You are now logged in.')); $form_state['redirect'] = ''; } // No administrator approval required. elseif ($account->status || $notify) { $op = $notify ? 'register_admin_created' : 'register_no_approval_required'; _user_mail_notify($op, $account); if ($notify) { drupal_set_message(t('A welcome message with further instructions has been e-mailed to the new user %name.', array('@url' => url($uri['path'], $uri['options']), '%name' => $account->name))); } else { drupal_set_message(t('A welcome message with further instructions has been sent to your e-mail address.')); $form_state['redirect'] = ''; } } // Administrator approval required. else { _user_mail_notify('register_pending_approval', $account); drupal_set_message(t('Thank you for applying for an account. Your account is currently pending approval by the site administrator.
    In the meantime, a welcome message with further instructions has been sent to your e-mail address.')); $form_state['redirect'] = ''; } } /** * Implements hook_modules_installed(). */ function user_modules_installed($modules) { // Assign all available permissions to the administrator role. $rid = variable_get('user_admin_role', 0); if ($rid) { $permissions = array(); foreach ($modules as $module) { if ($module_permissions = module_invoke($module, 'permission')) { $permissions = array_merge($permissions, array_keys($module_permissions)); } } if (!empty($permissions)) { user_role_grant_permissions($rid, $permissions); } } } /** * Implements hook_modules_uninstalled(). */ function user_modules_uninstalled($modules) { db_delete('role_permission') ->condition('module', $modules, 'IN') ->execute(); } /** * Helper function to rewrite the destination to avoid redirecting to login page after login. * * Third-party authentication modules may use this function to determine the * proper destination after a user has been properly logged in. */ function user_login_destination() { $destination = drupal_get_destination(); if ($destination['destination'] == 'user/login') { $destination['destination'] = 'user'; } return $destination; } /** * Saves visitor information as a cookie so it can be reused. * * @param $values * An array of key/value pairs to be saved into a cookie. */ function user_cookie_save(array $values) { foreach ($values as $field => $value) { // Set cookie for 365 days. setrawcookie('Drupal.visitor.' . $field, rawurlencode($value), REQUEST_TIME + 31536000, '/'); } } /** * Delete a visitor information cookie. * * @param $cookie_name * A cookie name such as 'homepage'. */ function user_cookie_delete($cookie_name) { setrawcookie('Drupal.visitor.' . $cookie_name, '', REQUEST_TIME - 3600, '/'); } /** * Implements hook_rdf_mapping(). */ function user_rdf_mapping() { return array( array( 'type' => 'user', 'bundle' => RDF_DEFAULT_BUNDLE, 'mapping' => array( 'rdftype' => array('sioc:UserAccount'), 'name' => array( 'predicates' => array('foaf:name'), ), 'homepage' => array( 'predicates' => array('foaf:page'), 'type' => 'rel', ), ), ), ); } /** * Implements hook_file_download_access(). */ function user_file_download_access($field, $entity_type, $entity) { if ($entity_type == 'user') { return user_view_access($entity); } } /** * Implements hook_system_info_alter(). * * Drupal 7 ships with two methods to add additional fields to users: Profile * module, a legacy module dating back from 2002, and Field API integration * with users. While Field API support for users currently provides less end * user features, the inefficient data storage mechanism of Profile module, as * well as its lack of consistency with the rest of the entity / field based * systems in Drupal 7, make this a sub-optimal solution to those who were not * using it in previous releases of Drupal. * * To prevent new Drupal 7 sites from installing Profile module, and * unwittingly ending up with two completely different and incompatible methods * of extending users, only make the Profile module available if the profile_* * tables are present. * * @todo: Remove in D8, pending upgrade path. */ function user_system_info_alter(&$info, $file, $type) { if ($type == 'module' && $file->name == 'profile' && db_table_exists('profile_fi Detailed But Not Overwhelming | Kem Knapp Sawyer

    Blog

    Detailed But Not Overwhelming