' . (!empty($element['#children']) ? $element['#children'] : '') . '
';
+}
+
+/**
+ * Expand a password_confirm field into two text boxes.
+ */
+function form_process_password_confirm($element) {
+ $element['pass1'] = array(
+ '#type' => 'password',
+ '#title' => t('Password'),
+ '#value' => empty($element['#value']) ? NULL : $element['#value']['pass1'],
+ '#required' => $element['#required'],
+ '#attributes' => array('class' => array('password-field')),
+ );
+ $element['pass2'] = array(
+ '#type' => 'password',
+ '#title' => t('Confirm password'),
+ '#value' => empty($element['#value']) ? NULL : $element['#value']['pass2'],
+ '#required' => $element['#required'],
+ '#attributes' => array('class' => array('password-confirm')),
+ );
+ $element['#element_validate'] = array('password_confirm_validate');
+ $element['#tree'] = TRUE;
+
+ if (isset($element['#size'])) {
+ $element['pass1']['#size'] = $element['pass2']['#size'] = $element['#size'];
+ }
+
+ return $element;
+}
+
+/**
+ * Validate password_confirm element.
+ */
+function password_confirm_validate($element, &$element_state) {
+ $pass1 = trim($element['pass1']['#value']);
+ $pass2 = trim($element['pass2']['#value']);
+ if (!empty($pass1) || !empty($pass2)) {
+ if (strcmp($pass1, $pass2)) {
+ form_error($element, t('The specified passwords do not match.'));
+ }
+ }
+ elseif ($element['#required'] && !empty($element_state['input'])) {
+ form_error($element, t('Password field is required.'));
+ }
+
+ // Password field must be converted from a two-element array into a single
+ // string regardless of validation results.
+ form_set_value($element['pass1'], NULL, $element_state);
+ form_set_value($element['pass2'], NULL, $element_state);
+ form_set_value($element, $pass1, $element_state);
+
+ return $element;
+
+}
+
+/**
+ * Returns HTML for a date selection form element.
+ *
+ * @param $variables
+ * An associative array containing:
+ * - element: An associative array containing the properties of the element.
+ * Properties used: #title, #value, #options, #description, #required,
+ * #attributes.
+ *
+ * @ingroup themeable
+ */
+function theme_date($variables) {
+ $element = $variables['element'];
+ return '' . drupal_render_children($element) . '
';
+}
+
+/**
+ * Roll out a single date element.
+ */
+function form_process_date($element) {
+ // Default to current date
+ if (empty($element['#value'])) {
+ $element['#value'] = array(
+ 'day' => format_date(REQUEST_TIME, 'custom', 'j'),
+ 'month' => format_date(REQUEST_TIME, 'custom', 'n'),
+ 'year' => format_date(REQUEST_TIME, 'custom', 'Y'),
+ );
+ }
+
+ $element['#tree'] = TRUE;
+
+ // Determine the order of day, month, year in the site's chosen date format.
+ $format = variable_get('date_format_short', 'm/d/Y - H:i');
+ $sort = array();
+ $sort['day'] = max(strpos($format, 'd'), strpos($format, 'j'));
+ $sort['month'] = max(strpos($format, 'm'), strpos($format, 'M'));
+ $sort['year'] = strpos($format, 'Y');
+ asort($sort);
+ $order = array_keys($sort);
+
+ // Output multi-selector for date.
+ foreach ($order as $type) {
+ switch ($type) {
+ case 'day':
+ $options = drupal_map_assoc(range(1, 31));
+ $title = t('Day');
+ break;
+
+ case 'month':
+ $options = drupal_map_assoc(range(1, 12), 'map_month');
+ $title = t('Month');
+ break;
+
+ case 'year':
+ $options = drupal_map_assoc(range(1900, 2050));
+ $title = t('Year');
+ break;
+ }
+
+ $element[$type] = array(
+ '#type' => 'select',
+ '#title' => $title,
+ '#title_display' => 'invisible',
+ '#value' => $element['#value'][$type],
+ '#attributes' => $element['#attributes'],
+ '#options' => $options,
+ );
+ }
+
+ return $element;
+}
+
+/**
+ * Validates the date type to stop dates like February 30, 2006.
+ */
+function date_validate($form) {
+ if (!checkdate($form['#value']['month'], $form['#value']['day'], $form['#value']['year'])) {
+ form_error($form, t('The specified date is invalid.'));
+ }
+}
+
+/**
+ * Helper function for usage with drupal_map_assoc to display month names.
+ */
+function map_month($month) {
+ $months = &drupal_static(__FUNCTION__, array(
+ 1 => 'Jan',
+ 2 => 'Feb',
+ 3 => 'Mar',
+ 4 => 'Apr',
+ 5 => 'May',
+ 6 => 'Jun',
+ 7 => 'Jul',
+ 8 => 'Aug',
+ 9 => 'Sep',
+ 10 => 'Oct',
+ 11 => 'Nov',
+ 12 => 'Dec',
+ ));
+ return t($months[$month]);
+}
+
+/**
+ * If no default value is set for weight select boxes, use 0.
+ */
+function weight_value(&$form) {
+ if (isset($form['#default_value'])) {
+ $form['#value'] = $form['#default_value'];
+ }
+ else {
+ $form['#value'] = 0;
+ }
+}
+
+/**
+ * Roll out a single radios element to a list of radios,
+ * using the options array as index.
+ */
+function form_process_radios($element) {
+ if (count($element['#options']) > 0) {
+ $weight = 0;
+ foreach ($element['#options'] as $key => $choice) {
+ // Maintain order of options as defined in #options, in case the element
+ // defines custom option sub-elements, but does not define all option
+ // sub-elements.
+ $weight += 0.001;
+
+ $element += array($key => array());
+ // Generate the parents as the autogenerator does, so we will have a
+ // unique id for each radio button.
+ $parents_for_id = array_merge($element['#parents'], array($key));
+ $element[$key] += array(
+ '#type' => 'radio',
+ '#title' => $choice,
+ // The key is sanitized in drupal_attributes() during output from the
+ // theme function.
+ '#return_value' => $key,
+ '#default_value' => isset($element['#default_value']) ? $element['#default_value'] : NULL,
+ '#attributes' => $element['#attributes'],
+ '#parents' => $element['#parents'],
+ '#id' => drupal_html_id('edit-' . implode('-', $parents_for_id)),
+ '#ajax' => isset($element['#ajax']) ? $element['#ajax'] : NULL,
+ '#weight' => $weight,
+ );
+ }
+ }
+ return $element;
+}
+
+/**
+ * Returns HTML for a checkbox form element.
+ *
+ * @param $variables
+ * An associative array containing:
+ * - element: An associative array containing the properties of the element.
+ * Properties used: #title, #value, #return_value, #description, #required,
+ * #attributes.
+ *
+ * @ingroup themeable
+ */
+function theme_checkbox($variables) {
+ $element = $variables['element'];
+ $t = get_t();
+ $element['#attributes']['type'] = 'checkbox';
+ element_set_attributes($element, array('id', 'name', '#return_value' => 'value'));
+
+ // Unchecked checkbox has #value of integer 0.
+ if (!empty($element['#checked'])) {
+ $element['#attributes']['checked'] = 'checked';
+ }
+ _form_set_class($element, array('form-checkbox'));
+
+ return '' . (!empty($element['#children']) ? $element['#children'] : '') . '
';
+}
+
+/**
+ * Add form_element theming to an element if title or description is set.
+ *
+ * This is used as a pre render function for checkboxes and radios.
+ */
+function form_pre_render_conditional_form_element($element) {
+ // Set the element's title attribute to show #title as a tooltip, if needed.
+ if (isset($element['#title']) && $element['#title_display'] == 'attribute') {
+ $element['#attributes']['title'] = $element['#title'];
+ if (!empty($element['#required'])) {
+ // Append an indication that this field is required.
+ $element['#attributes']['title'] .= ' (' . $t('Required') . ')';
+ }
+ }
+
+ if (isset($element['#title']) || isset($element['#description'])) {
+ $element['#theme_wrappers'][] = 'form_element';
+ }
+ return $element;
+}
+
+/**
+ * Sets the #checked property of a checkbox element.
+ */
+function form_process_checkbox($element, $form_state) {
+ $value = $element['#value'];
+ $return_value = $element['#return_value'];
+ // On form submission, the #value of an available and enabled checked
+ // checkbox is #return_value, and the #value of an available and enabled
+ // unchecked checkbox is integer 0. On not submitted forms, and for
+ // checkboxes with #access=FALSE or #disabled=TRUE, the #value is
+ // #default_value (integer 0 if #default_value is NULL). Most of the time,
+ // a string comparison of #value and #return_value is sufficient for
+ // determining the "checked" state, but a value of TRUE always means checked
+ // (even if #return_value is 'foo'), and a value of FALSE or integer 0 always
+ // means unchecked (even if #return_value is '' or '0').
+ if ($value === TRUE || $value === FALSE || $value === 0) {
+ $element['#checked'] = (bool) $value;
+ }
+ else {
+ // Compare as strings, so that 15 is not considered equal to '15foo', but 1
+ // is considered equal to '1'. This cast does not imply that either #value
+ // or #return_value is expected to be a string.
+ $element['#checked'] = ((string) $value === (string) $return_value);
+ }
+ return $element;
+}
+
+function form_process_checkboxes($element) {
+ $value = is_array($element['#value']) ? $element['#value'] : array();
+ $element['#tree'] = TRUE;
+ if (count($element['#options']) > 0) {
+ if (!isset($element['#default_value']) || $element['#default_value'] == 0) {
+ $element['#default_value'] = array();
+ }
+ $weight = 0;
+ foreach ($element['#options'] as $key => $choice) {
+ // Integer 0 is not a valid #return_value, so use '0' instead.
+ // @see form_type_checkbox_value().
+ // @todo For Drupal 8, cast all integer keys to strings for consistency
+ // with form_process_radios().
+ if ($key === 0) {
+ $key = '0';
+ }
+ // Maintain order of options as defined in #options, in case the element
+ // defines custom option sub-elements, but does not define all option
+ // sub-elements.
+ $weight += 0.001;
+
+ $element += array($key => array());
+ $element[$key] += array(
+ '#type' => 'checkbox',
+ '#title' => $choice,
+ '#return_value' => $key,
+ '#default_value' => isset($value[$key]) ? $key : NULL,
+ '#attributes' => $element['#attributes'],
+ '#ajax' => isset($element['#ajax']) ? $element['#ajax'] : NULL,
+ '#weight' => $weight,
+ );
+ }
+ }
+ return $element;
+}
+
+/**
+ * Processes a form actions container element.
+ *
+ * @param $element
+ * An associative array containing the properties and children of the
+ * form actions container.
+ * @param $form_state
+ * The $form_state array for the form this element belongs to.
+ *
+ * @return
+ * The processed element.
+ */
+function form_process_actions($element, &$form_state) {
+ $element['#attributes']['class'][] = 'form-actions';
+ return $element;
+}
+
+/**
+ * Processes a container element.
+ *
+ * @param $element
+ * An associative array containing the properties and children of the
+ * container.
+ * @param $form_state
+ * The $form_state array for the form this element belongs to.
+ * @return
+ * The processed element.
+ */
+function form_process_container($element, &$form_state) {
+ // Generate the ID of the element if it's not explicitly given.
+ if (!isset($element['#id'])) {
+ $element['#id'] = drupal_html_id(implode('-', $element['#parents']) . '-wrapper');
+ }
+ return $element;
+}
+
+/**
+ * Returns HTML to wrap child elements in a container.
+ *
+ * Used for grouped form items. Can also be used as a #theme_wrapper for any
+ * renderable element, to surround it with a and add attributes such as
+ * classes or an HTML id.
+ *
+ * @param $variables
+ * An associative array containing:
+ * - element: An associative array containing the properties of the element.
+ * Properties used: #id, #attributes, #children.
+ *
+ * @ingroup themeable
+ */
+function theme_container($variables) {
+ $element = $variables['element'];
+
+ // Special handling for form elements.
+ if (isset($element['#array_parents'])) {
+ // Assign an html ID.
+ if (!isset($element['#attributes']['id'])) {
+ $element['#attributes']['id'] = $element['#id'];
+ }
+ // Add the 'form-wrapper' class.
+ $element['#attributes']['class'][] = 'form-wrapper';
+ }
+
+ return '
' . $element['#children'] . '
';
+}
+
+/**
+ * Returns HTML for a table with radio buttons or checkboxes.
+ *
+ * An example of per-row options:
+ * @code
+ * $options = array();
+ * $options[0]['title'] = "A red row"
+ * $options[0]['#attributes'] = array ('class' => array('red-row'));
+ * $options[1]['title'] = "A blue row"
+ * $options[1]['#attributes'] = array ('class' => array('blue-row'));
+ *
+ * $form['myselector'] = array (
+ * '#type' => 'tableselect',
+ * '#title' => 'My Selector'
+ * '#options' => $options,
+ * );
+ * @endcode
+ *
+ * @param $variables
+ * An associative array containing:
+ * - element: An associative array containing the properties and children of
+ * the tableselect element. Properties used: #header, #options, #empty,
+ * and #js_select. The #options property is an array of selection options;
+ * each array element of #options is an array of properties. These
+ * properties can include #attributes, which is added to the
+ * table row's HTML attributes; see theme_table().
+ *
+ * @ingroup themeable
+ */
+function theme_tableselect($variables) {
+ $element = $variables['element'];
+ $rows = array();
+ $header = $element['#header'];
+ if (!empty($element['#options'])) {
+ // Generate a table row for each selectable item in #options.
+ foreach (element_children($element) as $key) {
+ $row = array();
+
+ $row['data'] = array();
+ if (isset($element['#options'][$key]['#attributes'])) {
+ $row += $element['#options'][$key]['#attributes'];
+ }
+ // Render the checkbox / radio element.
+ $row['data'][] = drupal_render($element[$key]);
+
+ // As theme_table only maps header and row columns by order, create the
+ // correct order by iterating over the header fields.
+ foreach ($element['#header'] as $fieldname => $title) {
+ $row['data'][] = $element['#options'][$key][$fieldname];
+ }
+ $rows[] = $row;
+ }
+ // Add an empty header or a "Select all" checkbox to provide room for the
+ // checkboxes/radios in the first table column.
+ if ($element['#js_select']) {
+ // Add a "Select all" checkbox.
+ drupal_add_js('misc/tableselect.js');
+ array_unshift($header, array('class' => array('select-all')));
+ }
+ else {
+ // Add an empty header when radio buttons are displayed or a "Select all"
+ // checkbox is not desired.
+ array_unshift($header, '');
+ }
+ }
+ return theme('table', array('header' => $header, 'rows' => $rows, 'empty' => $element['#empty'], 'attributes' => $element['#attributes']));
+}
+
+/**
+ * Create the correct amount of checkbox or radio elements to populate the table.
+ *
+ * @param $element
+ * An associative array containing the properties and children of the
+ * tableselect element.
+ * @return
+ * The processed element.
+ */
+function form_process_tableselect($element) {
+
+ if ($element['#multiple']) {
+ $value = is_array($element['#value']) ? $element['#value'] : array();
+ }
+ else {
+ // Advanced selection behaviour make no sense for radios.
+ $element['#js_select'] = FALSE;
+ }
+
+ $element['#tree'] = TRUE;
+
+ if (count($element['#options']) > 0) {
+ if (!isset($element['#default_value']) || $element['#default_value'] === 0) {
+ $element['#default_value'] = array();
+ }
+
+ // Create a checkbox or radio for each item in #options in such a way that
+ // the value of the tableselect element behaves as if it had been of type
+ // checkboxes or radios.
+ foreach ($element['#options'] as $key => $choice) {
+ // Do not overwrite manually created children.
+ if (!isset($element[$key])) {
+ if ($element['#multiple']) {
+ $title = '';
+ if (!empty($element['#options'][$key]['title']['data']['#title'])) {
+ $title = t('Update @title', array(
+ '@title' => $element['#options'][$key]['title']['data']['#title'],
+ ));
+ }
+ $element[$key] = array(
+ '#type' => 'checkbox',
+ '#title' => $title,
+ '#title_display' => 'invisible',
+ '#return_value' => $key,
+ '#default_value' => isset($value[$key]) ? $key : NULL,
+ '#attributes' => $element['#attributes'],
+ );
+ }
+ else {
+ // Generate the parents as the autogenerator does, so we will have a
+ // unique id for each radio button.
+ $parents_for_id = array_merge($element['#parents'], array($key));
+ $element[$key] = array(
+ '#type' => 'radio',
+ '#title' => '',
+ '#return_value' => $key,
+ '#default_value' => ($element['#default_value'] == $key) ? $key : NULL,
+ '#attributes' => $element['#attributes'],
+ '#parents' => $element['#parents'],
+ '#id' => drupal_html_id('edit-' . implode('-', $parents_for_id)),
+ '#ajax' => isset($element['#ajax']) ? $element['#ajax'] : NULL,
+ );
+ }
+ if (isset($element['#options'][$key]['#weight'])) {
+ $element[$key]['#weight'] = $element['#options'][$key]['#weight'];
+ }
+ }
+ }
+ }
+ else {
+ $element['#value'] = array();
+ }
+ return $element;
+}
+
+/**
+ * Processes a machine-readable name form element.
+ *
+ * @param $element
+ * The form element to process. Properties used:
+ * - #machine_name: An associative array containing:
+ * - exists: A function name to invoke for checking whether a submitted
+ * machine name value already exists. The submitted value is passed as
+ * argument. In most cases, an existing API or menu argument loader
+ * function can be re-used. The callback is only invoked, if the submitted
+ * value differs from the element's #default_value.
+ * - source: (optional) The #array_parents of the form element containing
+ * the human-readable name (i.e., as contained in the $form structure) to
+ * use as source for the machine name. Defaults to array('name').
+ * - label: (optional) A text to display as label for the machine name value
+ * after the human-readable name form element. Defaults to "Machine name".
+ * - replace_pattern: (optional) A regular expression (without delimiters)
+ * matching disallowed characters in the machine name. Defaults to
+ * '[^a-z0-9_]+'.
+ * - replace: (optional) A character to replace disallowed characters in the
+ * machine name via JavaScript. Defaults to '_' (underscore). When using a
+ * different character, 'replace_pattern' needs to be set accordingly.
+ * - error: (optional) A custom form error message string to show, if the
+ * machine name contains disallowed characters.
+ * - #maxlength: (optional) Should be set to the maximum allowed length of the
+ * machine name. Defaults to 64.
+ * - #disabled: (optional) Should be set to TRUE in case an existing machine
+ * name must not be changed after initial creation.
+ */
+function form_process_machine_name($element, &$form_state) {
+ // Apply default form element properties.
+ $element += array(
+ '#title' => t('Machine-readable name'),
+ '#description' => t('A unique machine-readable name. Can only contain lowercase letters, numbers, and underscores.'),
+ '#machine_name' => array(),
+ );
+ // A form element that only wants to set one #machine_name property (usually
+ // 'source' only) would leave all other properties undefined, if the defaults
+ // were defined in hook_element_info(). Therefore, we apply the defaults here.
+ $element['#machine_name'] += array(
+ 'source' => array('name'),
+ 'target' => '#' . $element['#id'],
+ 'label' => t('Machine name'),
+ 'replace_pattern' => '[^a-z0-9_]+',
+ 'replace' => '_',
+ );
+
+ // The source element defaults to array('name'), but may have been overidden.
+ if (empty($element['#machine_name']['source'])) {
+ return $element;
+ }
+
+ // Retrieve the form element containing the human-readable name from the
+ // complete form in $form_state. By reference, because we need to append
+ // a #field_suffix that will hold the live preview.
+ $key_exists = NULL;
+ $source = drupal_array_get_nested_value($form_state['complete form'], $element['#machine_name']['source'], $key_exists);
+ if (!$key_exists) {
+ return $element;
+ }
+
+ // Append a field suffix to the source form element, which will contain
+ // the live preview of the machine name.
+ $suffix_id = $source['#id'] . '-machine-name-suffix';
+ $source += array('#field_suffix' => '');
+ $source['#field_suffix'] .= '
';
+
+ $parents = array_merge($element['#machine_name']['source'], array('#field_suffix'));
+ drupal_array_set_nested_value($form_state['complete form'], $parents, $source['#field_suffix']);
+
+ $element['#machine_name']['suffix'] = '#' . $suffix_id;
+
+ $js_settings = array(
+ 'type' => 'setting',
+ 'data' => array(
+ 'machineName' => array(
+ '#' . $source['#id'] => $element['#machine_name'],
+ ),
+ ),
+ );
+ $element['#attached']['js'][] = 'misc/machine-name.js';
+ $element['#attached']['js'][] = $js_settings;
+
+ return $element;
+}
+
+/**
+ * Form element validation handler for #type 'machine_name'.
+ *
+ * Note that #maxlength is validated by _form_validate() already.
+ */
+function form_validate_machine_name(&$element, &$form_state) {
+ // Verify that the machine name not only consists of replacement tokens.
+ if (preg_match('@^' . $element['#machine_name']['replace'] . '+$@', $element['#value'])) {
+ form_error($element, t('The machine-readable name must contain unique characters.'));
+ }
+
+ // Verify that the machine name contains no disallowed characters.
+ if (preg_match('@' . $element['#machine_name']['replace_pattern'] . '@', $element['#value'])) {
+ if (!isset($element['#machine_name']['error'])) {
+ // Since a hyphen is the most common alternative replacement character,
+ // a corresponding validation error message is supported here.
+ if ($element['#machine_name']['replace'] == '-') {
+ form_error($element, t('The machine-readable name must contain only lowercase letters, numbers, and hyphens.'));
+ }
+ // Otherwise, we assume the default (underscore).
+ else {
+ form_error($element, t('The machine-readable name must contain only lowercase letters, numbers, and underscores.'));
+ }
+ }
+ else {
+ form_error($element, $element['#machine_name']['error']);
+ }
+ }
+
+ // Verify that the machine name is unique.
+ if ($element['#default_value'] !== $element['#value']) {
+ $function = $element['#machine_name']['exists'];
+ if ($function($element['#value'], $element, $form_state)) {
+ form_error($element, t('The machine-readable name is already in use. It must be unique.'));
+ }
+ }
+}
+
+/**
+ * Adds fieldsets to the specified group or adds group members to this
+ * fieldset.
+ *
+ * @param &$element
+ * An associative array containing the properties and children of the
+ * fieldset. Note that $element must be taken by reference here, so processed
+ * child elements are taken over into $form_state.
+ * @param $form_state
+ * The $form_state array for the form this fieldset belongs to.
+ * @return
+ * The processed element.
+ */
+function form_process_fieldset(&$element, &$form_state) {
+ $parents = implode('][', $element['#parents']);
+
+ // Each fieldset forms a new group. The #type 'vertical_tabs' basically only
+ // injects a new fieldset.
+ $form_state['groups'][$parents]['#group_exists'] = TRUE;
+ $element['#groups'] = &$form_state['groups'];
+
+ // Process vertical tabs group member fieldsets.
+ if (isset($element['#group'])) {
+ // Add this fieldset to the defined group (by reference).
+ $group = $element['#group'];
+ $form_state['groups'][$group][] = &$element;
+ }
+
+ // Contains form element summary functionalities.
+ $element['#attached']['library'][] = array('system', 'drupal.form');
+
+ // The .form-wrapper class is required for #states to treat fieldsets like
+ // containers.
+ if (!isset($element['#attributes']['class'])) {
+ $element['#attributes']['class'] = array();
+ }
+
+ // Collapsible fieldsets
+ if (!empty($element['#collapsible'])) {
+ $element['#attached']['library'][] = array('system', 'drupal.collapse');
+ $element['#attributes']['class'][] = 'collapsible';
+ if (!empty($element['#collapsed'])) {
+ $element['#attributes']['class'][] = 'collapsed';
+ }
+ }
+
+ return $element;
+}
+
+/**
+ * Adds members of this group as actual elements for rendering.
+ *
+ * @param $element
+ * An associative array containing the properties and children of the
+ * fieldset.
+ *
+ * @return
+ * The modified element with all group members.
+ */
+function form_pre_render_fieldset($element) {
+ // Fieldsets may be rendered outside of a Form API context.
+ if (!isset($element['#parents']) || !isset($element['#groups'])) {
+ return $element;
+ }
+ // Inject group member elements belonging to this group.
+ $parents = implode('][', $element['#parents']);
+ $children = element_children($element['#groups'][$parents]);
+ if (!empty($children)) {
+ foreach ($children as $key) {
+ // Break references and indicate that the element should be rendered as
+ // group member.
+ $child = (array) $element['#groups'][$parents][$key];
+ $child['#group_fieldset'] = TRUE;
+ // Inject the element as new child element.
+ $element[] = $child;
+
+ $sort = TRUE;
+ }
+ // Re-sort the element's children if we injected group member elements.
+ if (isset($sort)) {
+ $element['#sorted'] = FALSE;
+ }
+ }
+
+ if (isset($element['#group'])) {
+ $group = $element['#group'];
+ // If this element belongs to a group, but the group-holding element does
+ // not exist, we need to render it (at its original location).
+ if (!isset($element['#groups'][$group]['#group_exists'])) {
+ // Intentionally empty to clarify the flow; we simply return $element.
+ }
+ // If we injected this element into the group, then we want to render it.
+ elseif (!empty($element['#group_fieldset'])) {
+ // Intentionally empty to clarify the flow; we simply return $element.
+ }
+ // Otherwise, this element belongs to a group and the group exists, so we do
+ // not render it.
+ elseif (element_children($element['#groups'][$group])) {
+ $element['#printed'] = TRUE;
+ }
+ }
+
+ return $element;
+}
+
+/**
+ * Creates a group formatted as vertical tabs.
+ *
+ * @param $element
+ * An associative array containing the properties and children of the
+ * fieldset.
+ * @param $form_state
+ * The $form_state array for the form this vertical tab widget belongs to.
+ * @return
+ * The processed element.
+ */
+function form_process_vertical_tabs($element, &$form_state) {
+ // Inject a new fieldset as child, so that form_process_fieldset() processes
+ // this fieldset like any other fieldset.
+ $element['group'] = array(
+ '#type' => 'fieldset',
+ '#theme_wrappers' => array(),
+ '#parents' => $element['#parents'],
+ );
+
+ // The JavaScript stores the currently selected tab in this hidden
+ // field so that the active tab can be restored the next time the
+ // form is rendered, e.g. on preview pages or when form validation
+ // fails.
+ $name = implode('__', $element['#parents']);
+ if (isset($form_state['values'][$name . '__active_tab'])) {
+ $element['#default_tab'] = $form_state['values'][$name . '__active_tab'];
+ }
+ $element[$name . '__active_tab'] = array(
+ '#type' => 'hidden',
+ '#default_value' => $element['#default_tab'],
+ '#attributes' => array('class' => array('vertical-tabs-active-tab')),
+ );
+
+ return $element;
+}
+
+/**
+ * Returns HTML for an element's children fieldsets as vertical tabs.
+ *
+ * @param $variables
+ * An associative array containing:
+ * - element: An associative array containing the properties and children of the
+ * fieldset. Properties used: #children.
+ *
+ * @ingroup themeable
+ */
+function theme_vertical_tabs($variables) {
+ $element = $variables['element'];
+ // Add required JavaScript and Stylesheet.
+ drupal_add_library('system', 'drupal.vertical-tabs');
+
+ $output = '
' . t('Vertical Tabs') . ' ';
+ $output .= '
' . $element['#children'] . '
';
+ return $output;
+}
+
+/**
+ * Returns HTML for a submit button form element.
+ *
+ * @param $variables
+ * An associative array containing:
+ * - element: An associative array containing the properties of the element.
+ * Properties used: #attributes, #button_type, #name, #value.
+ *
+ * @ingroup themeable
+ */
+function theme_submit($variables) {
+ return theme('button', $variables['element']);
+}
+
+/**
+ * Returns HTML for a button form element.
+ *
+ * @param $variables
+ * An associative array containing:
+ * - element: An associative array containing the properties of the element.
+ * Properties used: #attributes, #button_type, #name, #value.
+ *
+ * @ingroup themeable
+ */
+function theme_button($variables) {
+ $element = $variables['element'];
+ $element['#attributes']['type'] = 'submit';
+ element_set_attributes($element, array('id', 'name', 'value'));
+
+ $element['#attributes']['class'][] = 'form-' . $element['#button_type'];
+ if (!empty($element['#attributes']['disabled'])) {
+ $element['#attributes']['class'][] = 'form-button-disabled';
+ }
+
+ return '
';
+}
+
+/**
+ * Returns HTML for an image button form element.
+ *
+ * @param $variables
+ * An associative array containing:
+ * - element: An associative array containing the properties of the element.
+ * Properties used: #attributes, #button_type, #name, #value, #title, #src.
+ *
+ * @ingroup themeable
+ */
+function theme_image_button($variables) {
+ $element = $variables['element'];
+ $element['#attributes']['type'] = 'image';
+ element_set_attributes($element, array('id', 'name', 'value'));
+
+ $element['#attributes']['src'] = file_create_url($element['#src']);
+ if (!empty($element['#title'])) {
+ $element['#attributes']['alt'] = $element['#title'];
+ $element['#attributes']['title'] = $element['#title'];
+ }
+
+ $element['#attributes']['class'][] = 'form-' . $element['#button_type'];
+ if (!empty($element['#attributes']['disabled'])) {
+ $element['#attributes']['class'][] = 'form-button-disabled';
+ }
+
+ return '
';
+}
+
+/**
+ * Returns HTML for a hidden form element.
+ *
+ * @param $variables
+ * An associative array containing:
+ * - element: An associative array containing the properties of the element.
+ * Properties used: #name, #value, #attributes.
+ *
+ * @ingroup themeable
+ */
+function theme_hidden($variables) {
+ $element = $variables['element'];
+ $element['#attributes']['type'] = 'hidden';
+ element_set_attributes($element, array('name', 'value'));
+ return '
\n";
+}
+
+/**
+ * Returns HTML for a textfield form element.
+ *
+ * @param $variables
+ * An associative array containing:
+ * - element: An associative array containing the properties of the element.
+ * Properties used: #title, #value, #description, #size, #maxlength,
+ * #required, #attributes, #autocomplete_path.
+ *
+ * @ingroup themeable
+ */
+function theme_textfield($variables) {
+ $element = $variables['element'];
+ $element['#attributes']['type'] = 'text';
+ element_set_attributes($element, array('id', 'name', 'value', 'size', 'maxlength'));
+ _form_set_class($element, array('form-text'));
+
+ $extra = '';
+ if ($element['#autocomplete_path'] && drupal_valid_path($element['#autocomplete_path'])) {
+ drupal_add_library('system', 'drupal.autocomplete');
+ $element['#attributes']['class'][] = 'form-autocomplete';
+
+ $attributes = array();
+ $attributes['type'] = 'hidden';
+ $attributes['id'] = $element['#attributes']['id'] . '-autocomplete';
+ $attributes['value'] = url($element['#autocomplete_path'], array('absolute' => TRUE));
+ $attributes['disabled'] = 'disabled';
+ $attributes['class'][] = 'autocomplete';
+ $extra = '
';
+ }
+
+ $output = '
';
+
+ return $output . $extra;
+}
+
+/**
+ * Returns HTML for a form.
+ *
+ * @param $variables
+ * An associative array containing:
+ * - element: An associative array containing the properties of the element.
+ * Properties used: #action, #method, #attributes, #children
+ *
+ * @ingroup themeable
+ */
+function theme_form($variables) {
+ $element = $variables['element'];
+ if (isset($element['#action'])) {
+ $element['#attributes']['action'] = drupal_strip_dangerous_protocols($element['#action']);
+ }
+ element_set_attributes($element, array('method', 'id'));
+ if (empty($element['#attributes']['accept-charset'])) {
+ $element['#attributes']['accept-charset'] = "UTF-8";
+ }
+ // Anonymous DIV to satisfy XHTML compliance.
+ return '
';
+}
+
+/**
+ * Returns HTML for a textarea form element.
+ *
+ * @param $variables
+ * An associative array containing:
+ * - element: An associative array containing the properties of the element.
+ * Properties used: #title, #value, #description, #rows, #cols, #required,
+ * #attributes
+ *
+ * @ingroup themeable
+ */
+function theme_textarea($variables) {
+ $element = $variables['element'];
+ element_set_attributes($element, array('id', 'name', 'cols', 'rows'));
+ _form_set_class($element, array('form-textarea'));
+
+ $wrapper_attributes = array(
+ 'class' => array('form-textarea-wrapper'),
+ );
+
+ // Add resizable behavior.
+ if (!empty($element['#resizable'])) {
+ drupal_add_library('system', 'drupal.textarea');
+ $wrapper_attributes['class'][] = 'resizable';
+ }
+
+ $output = '
';
+ $output .= '';
+ $output .= '
';
+ return $output;
+}
+
+/**
+ * Returns HTML for a password form element.
+ *
+ * @param $variables
+ * An associative array containing:
+ * - element: An associative array containing the properties of the element.
+ * Properties used: #title, #value, #description, #size, #maxlength,
+ * #required, #attributes.
+ *
+ * @ingroup themeable
+ */
+function theme_password($variables) {
+ $element = $variables['element'];
+ $element['#attributes']['type'] = 'password';
+ element_set_attributes($element, array('id', 'name', 'value', 'size', 'maxlength'));
+ _form_set_class($element, array('form-text'));
+
+ return '
';
+}
+
+/**
+ * Expand weight elements into selects.
+ */
+function form_process_weight($element) {
+ for ($n = (-1 * $element['#delta']); $n <= $element['#delta']; $n++) {
+ $weights[$n] = $n;
+ }
+ $element['#options'] = $weights;
+ $element['#type'] = 'select';
+ $element['#is_weight'] = TRUE;
+ $element += element_info('select');
+ return $element;
+}
+
+/**
+ * Returns HTML for a file upload form element.
+ *
+ * For assistance with handling the uploaded file correctly, see the API
+ * provided by file.inc.
+ *
+ * @param $variables
+ * An associative array containing:
+ * - element: An associative array containing the properties of the element.
+ * Properties used: #title, #name, #size, #description, #required,
+ * #attributes.
+ *
+ * @ingroup themeable
+ */
+function theme_file($variables) {
+ $element = $variables['element'];
+ $element['#attributes']['type'] = 'file';
+ element_set_attributes($element, array('id', 'name', 'size'));
+ _form_set_class($element, array('form-file'));
+
+ return '
';
+}
+
+/**
+ * Returns HTML for a form element.
+ *
+ * Each form element is wrapped in a DIV container having the following CSS
+ * classes:
+ * - form-item: Generic for all form elements.
+ * - form-type-#type: The internal element #type.
+ * - form-item-#name: The internal form element #name (usually derived from the
+ * $form structure and set via form_builder()).
+ * - form-disabled: Only set if the form element is #disabled.
+ *
+ * In addition to the element itself, the DIV contains a label for the element
+ * based on the optional #title_display property, and an optional #description.
+ *
+ * The optional #title_display property can have these values:
+ * - before: The label is output before the element. This is the default.
+ * The label includes the #title and the required marker, if #required.
+ * - after: The label is output after the element. For example, this is used
+ * for radio and checkbox #type elements as set in system_element_info().
+ * If the #title is empty but the field is #required, the label will
+ * contain only the required marker.
+ * - invisible: Labels are critical for screen readers to enable them to
+ * properly navigate through forms but can be visually distracting. This
+ * property hides the label for everyone except screen readers.
+ * - attribute: Set the title attribute on the element to create a tooltip
+ * but output no label element. This is supported only for checkboxes
+ * and radios in form_pre_render_conditional_form_element(). It is used
+ * where a visual label is not needed, such as a table of checkboxes where
+ * the row and column provide the context. The tooltip will include the
+ * title and required marker.
+ *
+ * If the #title property is not set, then the label and any required marker
+ * will not be output, regardless of the #title_display or #required values.
+ * This can be useful in cases such as the password_confirm element, which
+ * creates children elements that have their own labels and required markers,
+ * but the parent element should have neither. Use this carefully because a
+ * field without an associated label can cause accessibility challenges.
+ *
+ * @param $variables
+ * An associative array containing:
+ * - element: An associative array containing the properties of the element.
+ * Properties used: #title, #title_display, #description, #id, #required,
+ * #children, #type, #name.
+ *
+ * @ingroup themeable
+ */
+function theme_form_element($variables) {
+ $element = &$variables['element'];
+ // This is also used in the installer, pre-database setup.
+ $t = get_t();
+
+ // This function is invoked as theme wrapper, but the rendered form element
+ // may not necessarily have been processed by form_builder().
+ $element += array(
+ '#title_display' => 'before',
+ );
+
+ // Add element #id for #type 'item'.
+ if (isset($element['#markup']) && !empty($element['#id'])) {
+ $attributes['id'] = $element['#id'];
+ }
+ // Add element's #type and #name as class to aid with JS/CSS selectors.
+ $attributes['class'] = array('form-item');
+ if (!empty($element['#type'])) {
+ $attributes['class'][] = 'form-type-' . strtr($element['#type'], '_', '-');
+ }
+ if (!empty($element['#name'])) {
+ $attributes['class'][] = 'form-item-' . strtr($element['#name'], array(' ' => '-', '_' => '-', '[' => '-', ']' => ''));
+ }
+ // Add a class for disabled elements to facilitate cross-browser styling.
+ if (!empty($element['#attributes']['disabled'])) {
+ $attributes['class'][] = 'form-disabled';
+ }
+ $output = '
' . "\n";
+
+ // If #title is not set, we don't display any label or required marker.
+ if (!isset($element['#title'])) {
+ $element['#title_display'] = 'none';
+ }
+ $prefix = isset($element['#field_prefix']) ? '
' . $element['#field_prefix'] . ' ' : '';
+ $suffix = isset($element['#field_suffix']) ? '
' . $element['#field_suffix'] . ' ' : '';
+
+ switch ($element['#title_display']) {
+ case 'before':
+ case 'invisible':
+ $output .= ' ' . theme('form_element_label', $variables);
+ $output .= ' ' . $prefix . $element['#children'] . $suffix . "\n";
+ break;
+
+ case 'after':
+ $output .= ' ' . $prefix . $element['#children'] . $suffix;
+ $output .= ' ' . theme('form_element_label', $variables) . "\n";
+ break;
+
+ case 'none':
+ case 'attribute':
+ // Output no label and no required marker, only the children.
+ $output .= ' ' . $prefix . $element['#children'] . $suffix . "\n";
+ break;
+ }
+
+ if (!empty($element['#description'])) {
+ $output .= '
' . $element['#description'] . "
\n";
+ }
+
+ $output .= "
\n";
+
+ return $output;
+}
+
+/**
+ * Returns HTML for a marker for required form elements.
+ *
+ * @param $variables
+ * An associative array containing:
+ * - element: An associative array containing the properties of the element.
+ *
+ * @ingroup themeable
+ */
+function theme_form_required_marker($variables) {
+ // This is also used in the installer, pre-database setup.
+ $t = get_t();
+ $attributes = array(
+ 'class' => 'form-required',
+ 'title' => $t('This field is required.'),
+ );
+ return '
* ';
+}
+
+/**
+ * Returns HTML for a form element label and required marker.
+ *
+ * Form element labels include the #title and a #required marker. The label is
+ * associated with the element itself by the element #id. Labels may appear
+ * before or after elements, depending on theme_form_element() and #title_display.
+ *
+ * This function will not be called for elements with no labels, depending on
+ * #title_display. For elements that have an empty #title and are not required,
+ * this function will output no label (''). For required elements that have an
+ * empty #title, this will output the required marker alone within the label.
+ * The label will use the #id to associate the marker with the field that is
+ * required. That is especially important for screenreader users to know
+ * which field is required.
+ *
+ * @param $variables
+ * An associative array containing:
+ * - element: An associative array containing the properties of the element.
+ * Properties used: #required, #title, #id, #value, #description.
+ *
+ * @ingroup themeable
+ */
+function theme_form_element_label($variables) {
+ $element = $variables['element'];
+ // This is also used in the installer, pre-database setup.
+ $t = get_t();
+
+ // If title and required marker are both empty, output no label.
+ if (empty($element['#title']) && empty($element['#required'])) {
+ return '';
+ }
+
+ // If the element is required, a required marker is appended to the label.
+ $required = !empty($element['#required']) ? theme('form_required_marker', array('element' => $element)) : '';
+
+ $title = filter_xss_admin($element['#title']);
+
+ $attributes = array();
+ // Style the label as class option to display inline with the element.
+ if ($element['#title_display'] == 'after') {
+ $attributes['class'] = 'option';
+ }
+ // Show label only to screen readers to avoid disruption in visual flows.
+ elseif ($element['#title_display'] == 'invisible') {
+ $attributes['class'] = 'element-invisible';
+ }
+
+ if (!empty($element['#id'])) {
+ $attributes['for'] = $element['#id'];
+ }
+
+ // The leading whitespace helps visually separate fields from inline labels.
+ return '
' . $t('!title !required', array('!title' => $title, '!required' => $required)) . " \n";
+}
+
+/**
+ * Sets a form element's class attribute.
+ *
+ * Adds 'required' and 'error' classes as needed.
+ *
+ * @param &$element
+ * The form element.
+ * @param $name
+ * Array of new class names to be added.
+ */
+function _form_set_class(&$element, $class = array()) {
+ if (!empty($class)) {
+ if (!isset($element['#attributes']['class'])) {
+ $element['#attributes']['class'] = array();
+ }
+ $element['#attributes']['class'] = array_merge($element['#attributes']['class'], $class);
+ }
+ // This function is invoked from form element theme functions, but the
+ // rendered form element may not necessarily have been processed by
+ // form_builder().
+ if (!empty($element['#required'])) {
+ $element['#attributes']['class'][] = 'required';
+ }
+ if (isset($element['#parents']) && form_get_error($element)) {
+ $element['#attributes']['class'][] = 'error';
+ }
+}
+
+/**
+ * @} End of "defgroup form_api".
+ */
+
+/**
+ * @defgroup batch Batch operations
+ * @{
+ * Create and process batch operations.
+ *
+ * Functions allowing forms processing to be spread out over several page
+ * requests, thus ensuring that the processing does not get interrupted
+ * because of a PHP timeout, while allowing the user to receive feedback
+ * on the progress of the ongoing operations.
+ *
+ * The API is primarily designed to integrate nicely with the Form API
+ * workflow, but can also be used by non-Form API scripts (like update.php)
+ * or even simple page callbacks (which should probably be used sparingly).
+ *
+ * Example:
+ * @code
+ * $batch = array(
+ * 'title' => t('Exporting'),
+ * 'operations' => array(
+ * array('my_function_1', array($account->uid, 'story')),
+ * array('my_function_2', array()),
+ * ),
+ * 'finished' => 'my_finished_callback',
+ * 'file' => 'path_to_file_containing_myfunctions',
+ * );
+ * batch_set($batch);
+ * // only needed if not inside a form _submit handler :
+ * batch_process();
+ * @endcode
+ *
+ * Note: if the batch 'title', 'init_message', 'progress_message', or
+ * 'error_message' could contain any user input, it is the responsibility of
+ * the code calling batch_set() to sanitize them first with a function like
+ * check_plain() or filter_xss().
+ *
+ * Sample batch operations:
+ * @code
+ * // Simple and artificial: load a node of a given type for a given user
+ * function my_function_1($uid, $type, &$context) {
+ * // The $context array gathers batch context information about the execution (read),
+ * // as well as 'return values' for the current operation (write)
+ * // The following keys are provided :
+ * // 'results' (read / write): The array of results gathered so far by
+ * // the batch processing, for the current operation to append its own.
+ * // 'message' (write): A text message displayed in the progress page.
+ * // The following keys allow for multi-step operations :
+ * // 'sandbox' (read / write): An array that can be freely used to
+ * // store persistent data between iterations. It is recommended to
+ * // use this instead of $_SESSION, which is unsafe if the user
+ * // continues browsing in a separate window while the batch is processing.
+ * // 'finished' (write): A float number between 0 and 1 informing
+ * // the processing engine of the completion level for the operation.
+ * // 1 (or no value explicitly set) means the operation is finished
+ * // and the batch processing can continue to the next operation.
+ *
+ * $node = node_load(array('uid' => $uid, 'type' => $type));
+ * $context['results'][] = $node->nid . ' : ' . $node->title;
+ * $context['message'] = $node->title;
+ * }
+ *
+ * // More advanced example: multi-step operation - load all nodes, five by five
+ * function my_function_2(&$context) {
+ * if (empty($context['sandbox'])) {
+ * $context['sandbox']['progress'] = 0;
+ * $context['sandbox']['current_node'] = 0;
+ * $context['sandbox']['max'] = db_query('SELECT COUNT(DISTINCT nid) FROM {node}')->fetchField();
+ * }
+ * $limit = 5;
+ * $result = db_select('node')
+ * ->fields('node', array('nid'))
+ * ->condition('nid', $context['sandbox']['current_node'], '>')
+ * ->orderBy('nid')
+ * ->range(0, $limit)
+ * ->execute();
+ * foreach ($result as $row) {
+ * $node = node_load($row->nid, NULL, TRUE);
+ * $context['results'][] = $node->nid . ' : ' . $node->title;
+ * $context['sandbox']['progress']++;
+ * $context['sandbox']['current_node'] = $node->nid;
+ * $context['message'] = $node->title;
+ * }
+ * if ($context['sandbox']['progress'] != $context['sandbox']['max']) {
+ * $context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['max'];
+ * }
+ * }
+ * @endcode
+ *
+ * Sample 'finished' callback:
+ * @code
+ * function batch_test_finished($success, $results, $operations) {
+ * if ($success) {
+ * $message = format_plural(count($results), 'One post processed.', '@count posts processed.');
+ * }
+ * else {
+ * $message = t('Finished with an error.');
+ * }
+ * drupal_set_message($message);
+ * // Providing data for the redirected page is done through $_SESSION.
+ * foreach ($results as $result) {
+ * $items[] = t('Loaded node %title.', array('%title' => $result));
+ * }
+ * $_SESSION['my_batch_results'] = $items;
+ * }
+ * @endcode
+ */
+
+/**
+ * Opens a new batch.
+ *
+ * @param $batch
+ * An array defining the batch. The following keys can be used -- only
+ * 'operations' is required, and batch_init() provides default values for
+ * the messages.
+ * - 'operations': Array of function calls to be performed.
+ * Example:
+ * @code
+ * array(
+ * array('my_function_1', array($arg1)),
+ * array('my_function_2', array($arg2_1, $arg2_2)),
+ * )
+ * @endcode
+ * - 'title': Title for the progress page. Only safe strings should be passed.
+ * Defaults to t('Processing').
+ * - 'init_message': Message displayed while the processing is initialized.
+ * Defaults to t('Initializing.').
+ * - 'progress_message': Message displayed while processing the batch.
+ * Available placeholders are @current, @remaining, @total, @percentage,
+ * @estimate and @elapsed. Defaults to t('Completed @current of @total.').
+ * - 'error_message': Message displayed if an error occurred while processing
+ * the batch. Defaults to t('An error has occurred.').
+ * - 'finished': Name of a function to be executed after the batch has
+ * completed. This should be used to perform any result massaging that
+ * may be needed, and possibly save data in $_SESSION for display after
+ * final page redirection.
+ * - 'file': Path to the file containing the definitions of the
+ * 'operations' and 'finished' functions, for instance if they don't
+ * reside in the main .module file. The path should be relative to
+ * base_path(), and thus should be built using drupal_get_path().
+ * - 'css': Array of paths to CSS files to be used on the progress page.
+ * - 'url_options': options passed to url() when constructing redirect
+ * URLs for the batch.
+ *
+ * Operations are added as new batch sets. Batch sets are used to ensure
+ * clean code independence, ensuring that several batches submitted by
+ * different parts of the code (core / contrib modules) can be processed
+ * correctly while not interfering or having to cope with each other. Each
+ * batch set gets to specify his own UI messages, operates on its own set
+ * of operations and results, and triggers its own 'finished' callback.
+ * Batch sets are processed sequentially, with the progress bar starting
+ * fresh for every new set.
+ */
+function batch_set($batch_definition) {
+ if ($batch_definition) {
+ $batch =& batch_get();
+
+ // Initialize the batch if needed.
+ if (empty($batch)) {
+ $batch = array(
+ 'sets' => array(),
+ 'has_form_submits' => FALSE,
+ );
+ }
+
+ // Base and default properties for the batch set.
+ // Use get_t() to allow batches at install time.
+ $t = get_t();
+ $init = array(
+ 'sandbox' => array(),
+ 'results' => array(),
+ 'success' => FALSE,
+ 'start' => 0,
+ 'elapsed' => 0,
+ );
+ $defaults = array(
+ 'title' => $t('Processing'),
+ 'init_message' => $t('Initializing.'),
+ 'progress_message' => $t('Completed @current of @total.'),
+ 'error_message' => $t('An error has occurred.'),
+ 'css' => array(),
+ );
+ $batch_set = $init + $batch_definition + $defaults;
+
+ // Tweak init_message to avoid the bottom of the page flickering down after
+ // init phase.
+ $batch_set['init_message'] .= '
';
+
+ // The non-concurrent workflow of batch execution allows us to save
+ // numberOfItems() queries by handling our own counter.
+ $batch_set['total'] = count($batch_set['operations']);
+ $batch_set['count'] = $batch_set['total'];
+
+ // Add the set to the batch.
+ if (empty($batch['id'])) {
+ // The batch is not running yet. Simply add the new set.
+ $batch['sets'][] = $batch_set;
+ }
+ else {
+ // The set is being added while the batch is running. Insert the new set
+ // right after the current one to ensure execution order, and store its
+ // operations in a queue.
+ $index = $batch['current_set'] + 1;
+ $slice1 = array_slice($batch['sets'], 0, $index);
+ $slice2 = array_slice($batch['sets'], $index);
+ $batch['sets'] = array_merge($slice1, array($batch_set), $slice2);
+ _batch_populate_queue($batch, $index);
+ }
+ }
+}
+
+/**
+ * Processes the batch.
+ *
+ * Unless the batch has been marked with 'progressive' = FALSE, the function
+ * issues a drupal_goto and thus ends page execution.
+ *
+ * This function is generally not needed in form submit handlers;
+ * Form API takes care of batches that were set during form submission.
+ *
+ * @param $redirect
+ * (optional) Path to redirect to when the batch has finished processing.
+ * @param $url
+ * (optional - should only be used for separate scripts like update.php)
+ * URL of the batch processing page.
+ * @param $redirect_callback
+ * (optional) Specify a function to be called to redirect to the progressive
+ * processing page. By default drupal_goto() will be used to redirect to a
+ * page which will do the progressive page. Specifying another function will
+ * allow the progressive processing to be processed differently.
+ */
+function batch_process($redirect = NULL, $url = 'batch', $redirect_callback = 'drupal_goto') {
+ $batch =& batch_get();
+
+ drupal_theme_initialize();
+
+ if (isset($batch)) {
+ // Add process information
+ $process_info = array(
+ 'current_set' => 0,
+ 'progressive' => TRUE,
+ 'url' => $url,
+ 'url_options' => array(),
+ 'source_url' => $_GET['q'],
+ 'redirect' => $redirect,
+ 'theme' => $GLOBALS['theme_key'],
+ 'redirect_callback' => $redirect_callback,
+ );
+ $batch += $process_info;
+
+ // The batch is now completely built. Allow other modules to make changes
+ // to the batch so that it is easier to reuse batch processes in other
+ // environments.
+ drupal_alter('batch', $batch);
+
+ // Assign an arbitrary id: don't rely on a serial column in the 'batch'
+ // table, since non-progressive batches skip database storage completely.
+ $batch['id'] = db_next_id();
+
+ // Move operations to a job queue. Non-progressive batches will use a
+ // memory-based queue.
+ foreach ($batch['sets'] as $key => $batch_set) {
+ _batch_populate_queue($batch, $key);
+ }
+
+ // Initiate processing.
+ if ($batch['progressive']) {
+ // Now that we have a batch id, we can generate the redirection link in
+ // the generic error message.
+ $t = get_t();
+ $batch['error_message'] = $t('Please continue to
the error page ', array('@error_url' => url($url, array('query' => array('id' => $batch['id'], 'op' => 'finished')))));
+
+ // Clear the way for the drupal_goto() redirection to the batch processing
+ // page, by saving and unsetting the 'destination', if there is any.
+ if (isset($_GET['destination'])) {
+ $batch['destination'] = $_GET['destination'];
+ unset($_GET['destination']);
+ }
+
+ // Store the batch.
+ db_insert('batch')
+ ->fields(array(
+ 'bid' => $batch['id'],
+ 'timestamp' => REQUEST_TIME,
+ 'token' => drupal_get_token($batch['id']),
+ 'batch' => serialize($batch),
+ ))
+ ->execute();
+
+ // Set the batch number in the session to guarantee that it will stay alive.
+ $_SESSION['batches'][$batch['id']] = TRUE;
+
+ // Redirect for processing.
+ $function = $batch['redirect_callback'];
+ if (function_exists($function)) {
+ $function($batch['url'], array('query' => array('op' => 'start', 'id' => $batch['id'])));
+ }
+ }
+ else {
+ // Non-progressive execution: bypass the whole progressbar workflow
+ // and execute the batch in one pass.
+ require_once DRUPAL_ROOT . '/includes/batch.inc';
+ _batch_process();
+ }
+ }
+}
+
+/**
+ * Retrieves the current batch.
+ */
+function &batch_get() {
+ // Not drupal_static(), because Batch API operates at a lower level than most
+ // use-cases for resetting static variables, and we specifically do not want a
+ // global drupal_static_reset() resetting the batch information. Functions
+ // that are part of the Batch API and need to reset the batch information may
+ // call batch_get() and manipulate the result by reference. Functions that are
+ // not part of the Batch API can also do this, but shouldn't.
+ static $batch = array();
+ return $batch;
+}
+
+/**
+ * Populates a job queue with the operations of a batch set.
+ *
+ * Depending on whether the batch is progressive or not, the BatchQueue or
+ * BatchMemoryQueue handler classes will be used.
+ *
+ * @param $batch
+ * The batch array.
+ * @param $set_id
+ * The id of the set to process.
+ * @return
+ * The name and class of the queue are added by reference to the batch set.
+ */
+function _batch_populate_queue(&$batch, $set_id) {
+ $batch_set = &$batch['sets'][$set_id];
+
+ if (isset($batch_set['operations'])) {
+ $batch_set += array(
+ 'queue' => array(
+ 'name' => 'drupal_batch:' . $batch['id'] . ':' . $set_id,
+ 'class' => $batch['progressive'] ? 'BatchQueue' : 'BatchMemoryQueue',
+ ),
+ );
+
+ $queue = _batch_queue($batch_set);
+ $queue->createQueue();
+ foreach ($batch_set['operations'] as $operation) {
+ $queue->createItem($operation);
+ }
+
+ unset($batch_set['operations']);
+ }
+}
+
+/**
+ * Returns a queue object for a batch set.
+ *
+ * @param $batch_set
+ * The batch set.
+ * @return
+ * The queue object.
+ */
+function _batch_queue($batch_set) {
+ static $queues;
+
+ // The class autoloader is not available when running update.php, so make
+ // sure the files are manually included.
+ if (!isset($queues)) {
+ $queues = array();
+ require_once DRUPAL_ROOT . '/modules/system/system.queue.inc';
+ require_once DRUPAL_ROOT . '/includes/batch.queue.inc';
+ }
+
+ if (isset($batch_set['queue'])) {
+ $name = $batch_set['queue']['name'];
+ $class = $batch_set['queue']['class'];
+
+ if (!isset($queues[$class][$name])) {
+ $queues[$class][$name] = new $class($name);
+ }
+ return $queues[$class][$name];
+ }
+}
+
+/**
+ * @} End of "defgroup batch".
+ */
diff --git a/backup/modules/20110602035425/drupal/includes/graph.inc b/backup/modules/20110602035425/drupal/includes/graph.inc
new file mode 100644
index 00000000..01a27339
--- /dev/null
+++ b/backup/modules/20110602035425/drupal/includes/graph.inc
@@ -0,0 +1,147 @@
+ array(),
+ // The components of the graph.
+ 'components' => array(),
+ );
+ // Perform the actual sort.
+ foreach ($graph as $start => $data) {
+ _drupal_depth_first_search($graph, $state, $start);
+ }
+
+ // We do such a numbering that every component starts with 0. This is useful
+ // for module installs as we can install every 0 weighted module in one
+ // request, and then every 1 weighted etc.
+ $component_weights = array();
+
+ foreach ($state['last_visit_order'] as $vertex) {
+ $component = $graph[$vertex]['component'];
+ if (!isset($component_weights[$component])) {
+ $component_weights[$component] = 0;
+ }
+ $graph[$vertex]['weight'] = $component_weights[$component]--;
+ }
+}
+
+/**
+ * Helper function to perform a depth first sort.
+ *
+ * @param &$graph
+ * A three dimensional associated graph array.
+ * @param &$state
+ * An associative array. The key 'last_visit_order' stores a list of the
+ * vertices visited. The key components stores list of vertices belonging
+ * to the same the component.
+ * @param $start
+ * An arbitrary vertex where we started traversing the graph.
+ * @param &$component
+ * The component of the last vertex.
+ *
+ * @see drupal_depth_first_search()
+ */
+function _drupal_depth_first_search(&$graph, &$state, $start, &$component = NULL) {
+ // Assign new component for each new vertex, i.e. when not called recursively.
+ if (!isset($component)) {
+ $component = $start;
+ }
+ // Nothing to do, if we already visited this vertex.
+ if (isset($graph[$start]['paths'])) {
+ return;
+ }
+ // Mark $start as visited.
+ $graph[$start]['paths'] = array();
+
+ // Assign $start to the current component.
+ $graph[$start]['component'] = $component;
+ $state['components'][$component][] = $start;
+
+ // Visit edges of $start.
+ if (isset($graph[$start]['edges'])) {
+ foreach ($graph[$start]['edges'] as $end => $v) {
+ // Mark that $start can reach $end.
+ $graph[$start]['paths'][$end] = $v;
+
+ if (isset($graph[$end]['component']) && $component != $graph[$end]['component']) {
+ // This vertex already has a component, use that from now on and
+ // reassign all the previously explored vertices.
+ $new_component = $graph[$end]['component'];
+ foreach ($state['components'][$component] as $vertex) {
+ $graph[$vertex]['component'] = $new_component;
+ $state['components'][$new_component][] = $vertex;
+ }
+ unset($state['components'][$component]);
+ $component = $new_component;
+ }
+ // Only visit existing vertices.
+ if (isset($graph[$end])) {
+ // Visit the connected vertex.
+ _drupal_depth_first_search($graph, $state, $end, $component);
+
+ // All vertices reachable by $end are also reachable by $start.
+ $graph[$start]['paths'] += $graph[$end]['paths'];
+ }
+ }
+ }
+
+ // Now that any other subgraph has been explored, add $start to all reverse
+ // paths.
+ foreach ($graph[$start]['paths'] as $end => $v) {
+ if (isset($graph[$end])) {
+ $graph[$end]['reverse_paths'][$start] = $v;
+ }
+ }
+
+ // Record the order of the last visit. This is the reverse of the
+ // topological order if the graph is acyclic.
+ $state['last_visit_order'][] = $start;
+}
+
diff --git a/backup/modules/20110602035425/drupal/includes/image.inc b/backup/modules/20110602035425/drupal/includes/image.inc
new file mode 100644
index 00000000..ce6a5275
--- /dev/null
+++ b/backup/modules/20110602035425/drupal/includes/image.inc
@@ -0,0 +1,406 @@
+ $info) {
+ // Only allow modules that aren't marked as unavailable.
+ if ($info['available']) {
+ $output[$name] = $info['title'];
+ }
+ }
+
+ return $output;
+}
+
+/**
+ * Retrieve the name of the currently used toolkit.
+ *
+ * @return
+ * String containing the name of the selected toolkit, or FALSE on error.
+ */
+function image_get_toolkit() {
+ static $toolkit;
+
+ if (!isset($toolkit)) {
+ $toolkits = image_get_available_toolkits();
+ $toolkit = variable_get('image_toolkit', 'gd');
+ if (!isset($toolkits[$toolkit]) || !function_exists('image_' . $toolkit . '_load')) {
+ // The selected toolkit isn't available so return the first one found. If
+ // none are available this will return FALSE.
+ reset($toolkits);
+ $toolkit = key($toolkits);
+ }
+ }
+
+ return $toolkit;
+}
+
+/**
+ * Invokes the given method using the currently selected toolkit.
+ *
+ * @param $method
+ * A string containing the method to invoke.
+ * @param $image
+ * An image object returned by image_load().
+ * @param $params
+ * An optional array of parameters to pass to the toolkit method.
+ *
+ * @return
+ * Mixed values (typically Boolean indicating successful operation).
+ */
+function image_toolkit_invoke($method, stdClass $image, array $params = array()) {
+ $function = 'image_' . $image->toolkit . '_' . $method;
+ if (function_exists($function)) {
+ array_unshift($params, $image);
+ return call_user_func_array($function, $params);
+ }
+ watchdog('image', 'The selected image handling toolkit %toolkit can not correctly process %function.', array('%toolkit' => $image->toolkit, '%function' => $function), WATCHDOG_ERROR);
+ return FALSE;
+}
+
+/**
+ * Get details about an image.
+ *
+ * Drupal supports GIF, JPG and PNG file formats when used with the GD
+ * toolkit, and may support others, depending on which toolkits are
+ * installed.
+ *
+ * @param $filepath
+ * String specifying the path of the image file.
+ * @param $toolkit
+ * An optional image toolkit name to override the default.
+ *
+ * @return
+ * FALSE, if the file could not be found or is not an image. Otherwise, a
+ * keyed array containing information about the image:
+ * - "width": Width, in pixels.
+ * - "height": Height, in pixels.
+ * - "extension": Commonly used file extension for the image.
+ * - "mime_type": MIME type ('image/jpeg', 'image/gif', 'image/png').
+ * - "file_size": File size in bytes.
+ */
+function image_get_info($filepath, $toolkit = FALSE) {
+ $details = FALSE;
+ if (!is_file($filepath) && !is_uploaded_file($filepath)) {
+ return $details;
+ }
+
+ if (!$toolkit) {
+ $toolkit = image_get_toolkit();
+ }
+ if ($toolkit) {
+ $image = new stdClass();
+ $image->source = $filepath;
+ $image->toolkit = $toolkit;
+ $details = image_toolkit_invoke('get_info', $image);
+ if (isset($details) && is_array($details)) {
+ $details['file_size'] = filesize($filepath);
+ }
+ }
+
+ return $details;
+}
+
+/**
+ * Scales an image to the exact width and height given.
+ *
+ * This function achieves the target aspect ratio by cropping the original image
+ * equally on both sides, or equally on the top and bottom. This function is
+ * useful to create uniform sized avatars from larger images.
+ *
+ * The resulting image always has the exact target dimensions.
+ *
+ * @param $image
+ * An image object returned by image_load().
+ * @param $width
+ * The target width, in pixels.
+ * @param $height
+ * The target height, in pixels.
+ *
+ * @return
+ * TRUE or FALSE, based on success.
+ *
+ * @see image_load()
+ * @see image_resize()
+ * @see image_crop()
+ */
+function image_scale_and_crop(stdClass $image, $width, $height) {
+ $scale = max($width / $image->info['width'], $height / $image->info['height']);
+ $x = ($image->info['width'] * $scale - $width) / 2;
+ $y = ($image->info['height'] * $scale - $height) / 2;
+
+ if (image_resize($image, $image->info['width'] * $scale, $image->info['height'] * $scale)) {
+ return image_crop($image, $x, $y, $width, $height);
+ }
+ return FALSE;
+}
+
+/**
+ * Scales an image to the given width and height while maintaining aspect ratio.
+ *
+ * The resulting image can be smaller for one or both target dimensions.
+ *
+ * @param $image
+ * An image object returned by image_load().
+ * @param $width
+ * The target width, in pixels. This value is omitted then the scaling will
+ * based only on the height value.
+ * @param $height
+ * The target height, in pixels. This value is omitted then the scaling will
+ * based only on the width value.
+ * @param $upscale
+ * Boolean indicating that files smaller than the dimensions will be scaled
+ * up. This generally results in a low quality image.
+ *
+ * @return
+ * TRUE or FALSE, based on success.
+ *
+ * @see image_load()
+ * @see image_scale_and_crop()
+ */
+function image_scale(stdClass $image, $width = NULL, $height = NULL, $upscale = FALSE) {
+ $aspect = $image->info['height'] / $image->info['width'];
+
+ if ($upscale) {
+ // Set width/height according to aspect ratio if either is empty.
+ $width = !empty($width) ? $width : $height / $aspect;
+ $height = !empty($height) ? $height : $width / $aspect;
+ }
+ else {
+ // Set impossibly large values if the width and height aren't set.
+ $width = !empty($width) ? $width : 9999999;
+ $height = !empty($height) ? $height : 9999999;
+
+ // Don't scale up.
+ if (round($width) >= $image->info['width'] && round($height) >= $image->info['height']) {
+ return TRUE;
+ }
+ }
+
+ if ($aspect < $height / $width) {
+ $height = $width * $aspect;
+ }
+ else {
+ $width = $height / $aspect;
+ }
+
+ return image_resize($image, $width, $height);
+}
+
+/**
+ * Resize an image to the given dimensions (ignoring aspect ratio).
+ *
+ * @param $image
+ * An image object returned by image_load().
+ * @param $width
+ * The target width, in pixels.
+ * @param $height
+ * The target height, in pixels.
+ *
+ * @return
+ * TRUE or FALSE, based on success.
+ *
+ * @see image_load()
+ * @see image_gd_resize()
+ */
+function image_resize(stdClass $image, $width, $height) {
+ $width = (int) round($width);
+ $height = (int) round($height);
+
+ return image_toolkit_invoke('resize', $image, array($width, $height));
+}
+
+/**
+ * Rotate an image by the given number of degrees.
+ *
+ * @param $image
+ * An image object returned by image_load().
+ * @param $degrees
+ * The number of (clockwise) degrees to rotate the image.
+ * @param $background
+ * An hexadecimal integer specifying the background color to use for the
+ * uncovered area of the image after the rotation. E.g. 0x000000 for black,
+ * 0xff00ff for magenta, and 0xffffff for white. For images that support
+ * transparency, this will default to transparent. Otherwise it will
+ * be white.
+ *
+ * @return
+ * TRUE or FALSE, based on success.
+ *
+ * @see image_load()
+ * @see image_gd_rotate()
+ */
+function image_rotate(stdClass $image, $degrees, $background = NULL) {
+ return image_toolkit_invoke('rotate', $image, array($degrees, $background));
+}
+
+/**
+ * Crop an image to the rectangle specified by the given rectangle.
+ *
+ * @param $image
+ * An image object returned by image_load().
+ * @param $x
+ * The top left coordinate, in pixels, of the crop area (x axis value).
+ * @param $y
+ * The top left coordinate, in pixels, of the crop area (y axis value).
+ * @param $width
+ * The target width, in pixels.
+ * @param $height
+ * The target height, in pixels.
+ *
+ * @return
+ * TRUE or FALSE, based on success.
+ *
+ * @see image_load()
+ * @see image_scale_and_crop()
+ * @see image_gd_crop()
+ */
+function image_crop(stdClass $image, $x, $y, $width, $height) {
+ $aspect = $image->info['height'] / $image->info['width'];
+ if (empty($height)) $height = $width / $aspect;
+ if (empty($width)) $width = $height * $aspect;
+
+ $width = (int) round($width);
+ $height = (int) round($height);
+
+ return image_toolkit_invoke('crop', $image, array($x, $y, $width, $height));
+}
+
+/**
+ * Convert an image to grayscale.
+ *
+ * @param $image
+ * An image object returned by image_load().
+ *
+ * @return
+ * TRUE or FALSE, based on success.
+ *
+ * @see image_load()
+ * @see image_gd_desaturate()
+ */
+function image_desaturate(stdClass $image) {
+ return image_toolkit_invoke('desaturate', $image);
+}
+
+
+/**
+ * Load an image file and return an image object.
+ *
+ * Any changes to the file are not saved until image_save() is called.
+ *
+ * @param $file
+ * Path to an image file.
+ * @param $toolkit
+ * An optional, image toolkit name to override the default.
+ *
+ * @return
+ * An image object or FALSE if there was a problem loading the file. The
+ * image object has the following properties:
+ * - 'source' - The original file path.
+ * - 'info' - The array of information returned by image_get_info()
+ * - 'toolkit' - The name of the image toolkit requested when the image was
+ * loaded.
+ * Image toolkits may add additional properties. The caller is advised not to
+ * monkey about with them.
+ *
+ * @see image_save()
+ * @see image_get_info()
+ * @see image_get_available_toolkits()
+ * @see image_gd_load()
+ */
+function image_load($file, $toolkit = FALSE) {
+ if (!$toolkit) {
+ $toolkit = image_get_toolkit();
+ }
+ if ($toolkit) {
+ $image = new stdClass();
+ $image->source = $file;
+ $image->info = image_get_info($file, $toolkit);
+ if (isset($image->info) && is_array($image->info)) {
+ $image->toolkit = $toolkit;
+ if (image_toolkit_invoke('load', $image)) {
+ return $image;
+ }
+ }
+ }
+ return FALSE;
+}
+
+/**
+ * Close the image and save the changes to a file.
+ *
+ * @param $image
+ * An image object returned by image_load(). The object's 'info' property
+ * will be updated if the file is saved successfully.
+ * @param $destination
+ * Destination path where the image should be saved. If it is empty the
+ * original image file will be overwritten.
+ *
+ * @return
+ * TRUE or FALSE, based on success.
+ *
+ * @see image_load()
+ * @see image_gd_save()
+ */
+function image_save(stdClass $image, $destination = NULL) {
+ if (empty($destination)) {
+ $destination = $image->source;
+ }
+ if ($return = image_toolkit_invoke('save', $image, array($destination))) {
+ // Clear the cached file size and refresh the image information.
+ clearstatcache();
+ $image->info = image_get_info($destination, $image->toolkit);
+
+ if (drupal_chmod($destination)) {
+ return $return;
+ }
+ }
+ return FALSE;
+}
+
+/**
+ * @} End of "defgroup image".
+ */
diff --git a/backup/modules/20110602035425/drupal/includes/install.core.inc b/backup/modules/20110602035425/drupal/includes/install.core.inc
new file mode 100644
index 00000000..52ef5935
--- /dev/null
+++ b/backup/modules/20110602035425/drupal/includes/install.core.inc
@@ -0,0 +1,1832 @@
+ $interactive) + install_state_defaults();
+ try {
+ // Begin the page request. This adds information about the current state of
+ // the Drupal installation to the passed-in array.
+ install_begin_request($install_state);
+ // Based on the installation state, run the remaining tasks for this page
+ // request, and collect any output.
+ $output = install_run_tasks($install_state);
+ }
+ catch (Exception $e) {
+ // When an installation error occurs, either send the error to the web
+ // browser or pass on the exception so the calling script can use it.
+ if ($install_state['interactive']) {
+ install_display_output($e->getMessage(), $install_state);
+ }
+ else {
+ throw $e;
+ }
+ }
+ // All available tasks for this page request are now complete. Interactive
+ // installations can send output to the browser or redirect the user to the
+ // next page.
+ if ($install_state['interactive']) {
+ if ($install_state['parameters_changed']) {
+ // Redirect to the correct page if the URL parameters have changed.
+ install_goto(install_redirect_url($install_state));
+ }
+ elseif (isset($output)) {
+ // Display a page only if some output is available. Otherwise it is
+ // possible that we are printing a JSON page and theme output should
+ // not be shown.
+ install_display_output($output, $install_state);
+ }
+ }
+}
+
+/**
+ * Returns an array of default settings for the global installation state.
+ *
+ * The installation state is initialized with these settings at the beginning
+ * of each page request. They may evolve during the page request, but they are
+ * initialized again once the next request begins.
+ *
+ * Non-interactive Drupal installations can override some of these default
+ * settings by passing in an array to the installation script, most notably
+ * 'parameters' (which contains one-time parameters such as 'profile' and
+ * 'locale' that are normally passed in via the URL) and 'forms' (which can
+ * be used to programmatically submit forms during the installation; the keys
+ * of each element indicate the name of the installation task that the form
+ * submission is for, and the values are used as the $form_state['values']
+ * array that is passed on to the form submission via drupal_form_submit()).
+ *
+ * @see drupal_form_submit()
+ */
+function install_state_defaults() {
+ $defaults = array(
+ // The current task being processed.
+ 'active_task' => NULL,
+ // The last task that was completed during the previous installation
+ // request.
+ 'completed_task' => NULL,
+ // This becomes TRUE only when Drupal's system module is installed.
+ 'database_tables_exist' => FALSE,
+ // An array of forms to be programmatically submitted during the
+ // installation. The keys of each element indicate the name of the
+ // installation task that the form submission is for, and the values are
+ // used as the $form_state['values'] array that is passed on to the form
+ // submission via drupal_form_submit().
+ 'forms' => array(),
+ // This becomes TRUE only at the end of the installation process, after
+ // all available tasks have been completed and Drupal is fully installed.
+ // It is used by the installer to store correct information in the database
+ // about the completed installation, as well as to inform theme functions
+ // that all tasks are finished (so that the task list can be displayed
+ // correctly).
+ 'installation_finished' => FALSE,
+ // Whether or not this installation is interactive. By default this will
+ // be set to FALSE if settings are passed in to install_drupal().
+ 'interactive' => TRUE,
+ // An array of available languages for the installation.
+ 'locales' => array(),
+ // An array of parameters for the installation, pre-populated by the URL
+ // or by the settings passed in to install_drupal(). This is primarily
+ // used to store 'profile' (the name of the chosen installation profile)
+ // and 'locale' (the name of the chosen installation language), since
+ // these settings need to persist from page request to page request before
+ // the database is available for storage.
+ 'parameters' => array(),
+ // Whether or not the parameters have changed during the current page
+ // request. For interactive installations, this will trigger a page
+ // redirect.
+ 'parameters_changed' => FALSE,
+ // An array of information about the chosen installation profile. This will
+ // be filled in based on the profile's .info file.
+ 'profile_info' => array(),
+ // An array of available installation profiles.
+ 'profiles' => array(),
+ // An array of server variables that will be substituted into the global
+ // $_SERVER array via drupal_override_server_variables(). Used by
+ // non-interactive installations only.
+ 'server' => array(),
+ // This becomes TRUE only when a valid database connection can be
+ // established.
+ 'settings_verified' => FALSE,
+ // Installation tasks can set this to TRUE to force the page request to
+ // end (even if there is no themable output), in the case of an interactive
+ // installation. This is needed only rarely; for example, it would be used
+ // by an installation task that prints JSON output rather than returning a
+ // themed page. The most common example of this is during batch processing,
+ // but the Drupal installer automatically takes care of setting this
+ // parameter properly in that case, so that individual installation tasks
+ // which implement the batch API do not need to set it themselves.
+ 'stop_page_request' => FALSE,
+ // Installation tasks can set this to TRUE to indicate that the task should
+ // be run again, even if it normally wouldn't be. This can be used, for
+ // example, if a single task needs to be spread out over multiple page
+ // requests, or if it needs to perform some validation before allowing
+ // itself to be marked complete. The most common examples of this are batch
+ // processing and form submissions, but the Drupal installer automatically
+ // takes care of setting this parameter properly in those cases, so that
+ // individual installation tasks which implement the batch API or form API
+ // do not need to set it themselves.
+ 'task_not_complete' => FALSE,
+ // A list of installation tasks which have already been performed during
+ // the current page request.
+ 'tasks_performed' => array(),
+ );
+ return $defaults;
+}
+
+/**
+ * Begin an installation request, modifying the installation state as needed.
+ *
+ * This function performs commands that must run at the beginning of every page
+ * request. It throws an exception if the installation should not proceed.
+ *
+ * @param $install_state
+ * An array of information about the current installation state. This is
+ * modified with information gleaned from the beginning of the page request.
+ */
+function install_begin_request(&$install_state) {
+ // Add any installation parameters passed in via the URL.
+ $install_state['parameters'] += $_GET;
+
+ // Validate certain core settings that are used throughout the installation.
+ if (!empty($install_state['parameters']['profile'])) {
+ $install_state['parameters']['profile'] = preg_replace('/[^a-zA-Z_0-9]/', '', $install_state['parameters']['profile']);
+ }
+ if (!empty($install_state['parameters']['locale'])) {
+ $install_state['parameters']['locale'] = preg_replace('/[^a-zA-Z_0-9\-]/', '', $install_state['parameters']['locale']);
+ }
+
+ // Allow command line scripts to override server variables used by Drupal.
+ require_once DRUPAL_ROOT . '/includes/bootstrap.inc';
+ if (!$install_state['interactive']) {
+ drupal_override_server_variables($install_state['server']);
+ }
+
+ // The user agent header is used to pass a database prefix in the request when
+ // running tests. However, for security reasons, it is imperative that no
+ // installation be permitted using such a prefix.
+ if (isset($_SERVER['HTTP_USER_AGENT']) && strpos($_SERVER['HTTP_USER_AGENT'], "simpletest") !== FALSE) {
+ header($_SERVER['SERVER_PROTOCOL'] . ' 403 Forbidden');
+ exit;
+ }
+
+ drupal_bootstrap(DRUPAL_BOOTSTRAP_CONFIGURATION);
+
+ // This must go after drupal_bootstrap(), which unsets globals!
+ global $conf;
+
+ require_once DRUPAL_ROOT . '/modules/system/system.install';
+ require_once DRUPAL_ROOT . '/includes/common.inc';
+ require_once DRUPAL_ROOT . '/includes/file.inc';
+ require_once DRUPAL_ROOT . '/includes/install.inc';
+ require_once DRUPAL_ROOT . '/' . variable_get('path_inc', 'includes/path.inc');
+
+ // Load module basics (needed for hook invokes).
+ include_once DRUPAL_ROOT . '/includes/module.inc';
+ include_once DRUPAL_ROOT . '/includes/session.inc';
+
+ // Set up $language, so t() caller functions will still work.
+ drupal_language_initialize();
+
+ include_once DRUPAL_ROOT . '/includes/entity.inc';
+ require_once DRUPAL_ROOT . '/includes/ajax.inc';
+ $module_list['system']['filename'] = 'modules/system/system.module';
+ $module_list['user']['filename'] = 'modules/user/user.module';
+ module_list(TRUE, FALSE, FALSE, $module_list);
+ drupal_load('module', 'system');
+ drupal_load('module', 'user');
+
+ // Load the cache infrastructure using a "fake" cache implementation that
+ // does not attempt to write to the database. We need this during the initial
+ // part of the installer because the database is not available yet. We
+ // continue to use it even when the database does become available, in order
+ // to preserve consistency between interactive and command-line installations
+ // (the latter complete in one page request and therefore are forced to
+ // continue using the cache implementation they started with) and also
+ // because any data put in the cache during the installer is inherently
+ // suspect, due to the fact that Drupal is not fully set up yet.
+ require_once DRUPAL_ROOT . '/includes/cache.inc';
+ require_once DRUPAL_ROOT . '/includes/cache-install.inc';
+ $conf['cache_default_class'] = 'DrupalFakeCache';
+
+ // Prepare for themed output. We need to run this at the beginning of the
+ // page request to avoid a different theme accidentally getting set. (We also
+ // need to run it even in the case of command-line installations, to prevent
+ // any code in the installer that happens to initialize the theme system from
+ // accessing the database before it is set up yet.)
+ drupal_maintenance_theme();
+
+ // Check existing settings.php.
+ $install_state['settings_verified'] = install_verify_settings();
+
+ if ($install_state['settings_verified']) {
+ // Initialize the database system. Note that the connection
+ // won't be initialized until it is actually requested.
+ require_once DRUPAL_ROOT . '/includes/database/database.inc';
+
+ // Verify the last completed task in the database, if there is one.
+ $task = install_verify_completed_task();
+ }
+ else {
+ $task = NULL;
+
+ // Since previous versions of Drupal stored database connection information
+ // in the 'db_url' variable, we should never let an installation proceed if
+ // this variable is defined and the settings file was not verified above
+ // (otherwise we risk installing over an existing site whose settings file
+ // has not yet been updated).
+ if (!empty($GLOBALS['db_url'])) {
+ throw new Exception(install_already_done_error());
+ }
+ }
+
+ // Modify the installation state as appropriate.
+ $install_state['completed_task'] = $task;
+ $install_state['database_tables_exist'] = !empty($task);
+}
+
+/**
+ * Runs all tasks for the current installation request.
+ *
+ * In the case of an interactive installation, all tasks will be attempted
+ * until one is reached that has output which needs to be displayed to the
+ * user, or until a page redirect is required. Otherwise, tasks will be
+ * attempted until the installation is finished.
+ *
+ * @param $install_state
+ * An array of information about the current installation state. This is
+ * passed along to each task, so it can be modified if necessary.
+ *
+ * @return
+ * HTML output from the last completed task.
+ */
+function install_run_tasks(&$install_state) {
+ do {
+ // Obtain a list of tasks to perform. The list of tasks itself can be
+ // dynamic (e.g., some might be defined by the installation profile,
+ // which is not necessarily known until the earlier tasks have run),
+ // so we regenerate the remaining tasks based on the installation state,
+ // each time through the loop.
+ $tasks_to_perform = install_tasks_to_perform($install_state);
+ // Run the first task on the list.
+ reset($tasks_to_perform);
+ $task_name = key($tasks_to_perform);
+ $task = array_shift($tasks_to_perform);
+ $install_state['active_task'] = $task_name;
+ $original_parameters = $install_state['parameters'];
+ $output = install_run_task($task, $install_state);
+ $install_state['parameters_changed'] = ($install_state['parameters'] != $original_parameters);
+ // Store this task as having been performed during the current request,
+ // and save it to the database as completed, if we need to and if the
+ // database is in a state that allows us to do so. Also mark the
+ // installation as 'done' when we have run out of tasks.
+ if (!$install_state['task_not_complete']) {
+ $install_state['tasks_performed'][] = $task_name;
+ $install_state['installation_finished'] = empty($tasks_to_perform);
+ if ($install_state['database_tables_exist'] && ($task['run'] == INSTALL_TASK_RUN_IF_NOT_COMPLETED || $install_state['installation_finished'])) {
+ variable_set('install_task', $install_state['installation_finished'] ? 'done' : $task_name);
+ }
+ }
+ // Stop when there are no tasks left. In the case of an interactive
+ // installation, also stop if we have some output to send to the browser,
+ // the URL parameters have changed, or an end to the page request was
+ // specifically called for.
+ $finished = empty($tasks_to_perform) || ($install_state['interactive'] && (isset($output) || $install_state['parameters_changed'] || $install_state['stop_page_request']));
+ } while (!$finished);
+ return $output;
+}
+
+/**
+ * Runs an individual installation task.
+ *
+ * @param $task
+ * An array of information about the task to be run.
+ * @param $install_state
+ * An array of information about the current installation state. This is
+ * passed in by reference so that it can be modified by the task.
+ *
+ * @return
+ * The output of the task function, if there is any.
+ */
+function install_run_task($task, &$install_state) {
+ $function = $task['function'];
+
+ if ($task['type'] == 'form') {
+ require_once DRUPAL_ROOT . '/includes/form.inc';
+ if ($install_state['interactive']) {
+ // For interactive forms, build the form and ensure that it will not
+ // redirect, since the installer handles its own redirection only after
+ // marking the form submission task complete.
+ $form_state = array(
+ // We need to pass $install_state by reference in order for forms to
+ // modify it, since the form API will use it in call_user_func_array(),
+ // which requires that referenced variables be passed explicitly.
+ 'build_info' => array('args' => array(&$install_state)),
+ 'no_redirect' => TRUE,
+ );
+ $form = drupal_build_form($function, $form_state);
+ // If a successful form submission did not occur, the form needs to be
+ // rendered, which means the task is not complete yet.
+ if (empty($form_state['executed'])) {
+ $install_state['task_not_complete'] = TRUE;
+ return drupal_render($form);
+ }
+ // Otherwise, return nothing so the next task will run in the same
+ // request.
+ return;
+ }
+ else {
+ // For non-interactive forms, submit the form programmatically with the
+ // values taken from the installation state. Throw an exception if any
+ // errors were encountered.
+ $form_state = array(
+ 'values' => !empty($install_state['forms'][$function]) ? $install_state['forms'][$function] : array(),
+ // We need to pass $install_state by reference in order for forms to
+ // modify it, since the form API will use it in call_user_func_array(),
+ // which requires that referenced variables be passed explicitly.
+ 'build_info' => array('args' => array(&$install_state)),
+ );
+ drupal_form_submit($function, $form_state);
+ $errors = form_get_errors();
+ if (!empty($errors)) {
+ throw new Exception(implode("\n", $errors));
+ }
+ }
+ }
+
+ elseif ($task['type'] == 'batch') {
+ // Start a new batch based on the task function, if one is not running
+ // already.
+ $current_batch = variable_get('install_current_batch');
+ if (!$install_state['interactive'] || !$current_batch) {
+ $batch = $function($install_state);
+ if (empty($batch)) {
+ // If the task did some processing and decided no batch was necessary,
+ // there is nothing more to do here.
+ return;
+ }
+ batch_set($batch);
+ // For interactive batches, we need to store the fact that this batch
+ // task is currently running. Otherwise, we need to make sure the batch
+ // will complete in one page request.
+ if ($install_state['interactive']) {
+ variable_set('install_current_batch', $function);
+ }
+ else {
+ $batch =& batch_get();
+ $batch['progressive'] = FALSE;
+ }
+ // Process the batch. For progressive batches, this will redirect.
+ // Otherwise, the batch will complete.
+ batch_process(install_redirect_url($install_state), install_full_redirect_url($install_state));
+ }
+ // If we are in the middle of processing this batch, keep sending back
+ // any output from the batch process, until the task is complete.
+ elseif ($current_batch == $function) {
+ include_once DRUPAL_ROOT . '/includes/batch.inc';
+ $output = _batch_page();
+ // The task is complete when we try to access the batch page and receive
+ // FALSE in return, since this means we are at a URL where we are no
+ // longer requesting a batch ID.
+ if ($output === FALSE) {
+ // Return nothing so the next task will run in the same request.
+ variable_del('install_current_batch');
+ return;
+ }
+ else {
+ // We need to force the page request to end if the task is not
+ // complete, since the batch API sometimes prints JSON output
+ // rather than returning a themed page.
+ $install_state['task_not_complete'] = $install_state['stop_page_request'] = TRUE;
+ return $output;
+ }
+ }
+ }
+
+ else {
+ // For normal tasks, just return the function result, whatever it is.
+ return $function($install_state);
+ }
+}
+
+/**
+ * Returns a list of tasks to perform during the current installation request.
+ *
+ * Note that the list of tasks can change based on the installation state as
+ * the page request evolves (for example, if an installation profile hasn't
+ * been selected yet, we don't yet know which profile tasks need to be run).
+ *
+ * @param $install_state
+ * An array of information about the current installation state.
+ *
+ * @return
+ * A list of tasks to be performed, with associated metadata.
+ */
+function install_tasks_to_perform($install_state) {
+ // Start with a list of all currently available tasks.
+ $tasks = install_tasks($install_state);
+ foreach ($tasks as $name => $task) {
+ // Remove any tasks that were already performed or that never should run.
+ // Also, if we started this page request with an indication of the last
+ // task that was completed, skip that task and all those that come before
+ // it, unless they are marked as always needing to run.
+ if ($task['run'] == INSTALL_TASK_SKIP || in_array($name, $install_state['tasks_performed']) || (!empty($install_state['completed_task']) && empty($completed_task_found) && $task['run'] != INSTALL_TASK_RUN_IF_REACHED)) {
+ unset($tasks[$name]);
+ }
+ if (!empty($install_state['completed_task']) && $name == $install_state['completed_task']) {
+ $completed_task_found = TRUE;
+ }
+ }
+ return $tasks;
+}
+
+/**
+ * Returns a list of all tasks the installer currently knows about.
+ *
+ * This function will return tasks regardless of whether or not they are
+ * intended to run on the current page request. However, the list can change
+ * based on the installation state (for example, if an installation profile
+ * hasn't been selected yet, we don't yet know which profile tasks will be
+ * available).
+ *
+ * @param $install_state
+ * An array of information about the current installation state.
+ *
+ * @return
+ * A list of tasks, with associated metadata.
+ */
+function install_tasks($install_state) {
+ // Determine whether translation import tasks will need to be performed.
+ $needs_translations = count($install_state['locales']) > 1 && !empty($install_state['parameters']['locale']) && $install_state['parameters']['locale'] != 'en';
+
+ // Start with the core installation tasks that run before handing control
+ // to the install profile.
+ $tasks = array(
+ 'install_select_profile' => array(
+ 'display_name' => st('Choose profile'),
+ 'display' => count($install_state['profiles']) != 1,
+ 'run' => INSTALL_TASK_RUN_IF_REACHED,
+ ),
+ 'install_select_locale' => array(
+ 'display_name' => st('Choose language'),
+ 'run' => INSTALL_TASK_RUN_IF_REACHED,
+ ),
+ 'install_load_profile' => array(
+ 'run' => INSTALL_TASK_RUN_IF_REACHED,
+ ),
+ 'install_verify_requirements' => array(
+ 'display_name' => st('Verify requirements'),
+ ),
+ 'install_settings_form' => array(
+ 'display_name' => st('Set up database'),
+ 'type' => 'form',
+ 'run' => $install_state['settings_verified'] ? INSTALL_TASK_SKIP : INSTALL_TASK_RUN_IF_NOT_COMPLETED,
+ ),
+ 'install_system_module' => array(
+ ),
+ 'install_bootstrap_full' => array(
+ 'run' => INSTALL_TASK_RUN_IF_REACHED,
+ ),
+ 'install_profile_modules' => array(
+ 'display_name' => count($install_state['profiles']) == 1 ? st('Install site') : st('Install profile'),
+ 'type' => 'batch',
+ ),
+ 'install_import_locales' => array(
+ 'display_name' => st('Set up translations'),
+ 'display' => $needs_translations,
+ 'type' => 'batch',
+ 'run' => $needs_translations ? INSTALL_TASK_RUN_IF_NOT_COMPLETED : INSTALL_TASK_SKIP,
+ ),
+ 'install_configure_form' => array(
+ 'display_name' => st('Configure site'),
+ 'type' => 'form',
+ ),
+ );
+
+ // Now add any tasks defined by the installation profile.
+ if (!empty($install_state['parameters']['profile'])) {
+ $function = $install_state['parameters']['profile'] . '_install_tasks';
+ if (function_exists($function)) {
+ $result = $function($install_state);
+ if (is_array($result)) {
+ $tasks += $result;
+ }
+ }
+ }
+
+ // Finish by adding the remaining core tasks.
+ $tasks += array(
+ 'install_import_locales_remaining' => array(
+ 'display_name' => st('Finish translations'),
+ 'display' => $needs_translations,
+ 'type' => 'batch',
+ 'run' => $needs_translations ? INSTALL_TASK_RUN_IF_NOT_COMPLETED : INSTALL_TASK_SKIP,
+ ),
+ 'install_finished' => array(
+ 'display_name' => st('Finished'),
+ ),
+ );
+
+ // Allow the installation profile to modify the full list of tasks.
+ if (!empty($install_state['parameters']['profile'])) {
+ $profile_file = DRUPAL_ROOT . '/profiles/' . $install_state['parameters']['profile'] . '/' . $install_state['parameters']['profile'] . '.profile';
+ if (is_file($profile_file)) {
+ include_once $profile_file;
+ $function = $install_state['parameters']['profile'] . '_install_tasks_alter';
+ if (function_exists($function)) {
+ $function($tasks, $install_state);
+ }
+ }
+ }
+
+ // Fill in default parameters for each task before returning the list.
+ foreach ($tasks as $task_name => &$task) {
+ $task += array(
+ 'display_name' => NULL,
+ 'display' => !empty($task['display_name']),
+ 'type' => 'normal',
+ 'run' => INSTALL_TASK_RUN_IF_NOT_COMPLETED,
+ 'function' => $task_name,
+ );
+ }
+ return $tasks;
+}
+
+/**
+ * Returns a list of tasks that should be displayed to the end user.
+ *
+ * The output of this function is a list suitable for sending to
+ * theme_task_list().
+ *
+ * @param $install_state
+ * An array of information about the current installation state.
+ *
+ * @return
+ * A list of tasks, with keys equal to the machine-readable task name and
+ * values equal to the name that should be displayed.
+ *
+ * @see theme_task_list()
+ */
+function install_tasks_to_display($install_state) {
+ $displayed_tasks = array();
+ foreach (install_tasks($install_state) as $name => $task) {
+ if ($task['display']) {
+ $displayed_tasks[$name] = $task['display_name'];
+ }
+ }
+ return $displayed_tasks;
+}
+
+/**
+ * Returns the URL that should be redirected to during an installation request.
+ *
+ * The output of this function is suitable for sending to install_goto().
+ *
+ * @param $install_state
+ * An array of information about the current installation state.
+ *
+ * @return
+ * The URL to redirect to.
+ *
+ * @see install_full_redirect_url()
+ */
+function install_redirect_url($install_state) {
+ return 'install.php?' . drupal_http_build_query($install_state['parameters']);
+}
+
+/**
+ * Returns the complete URL redirected to during an installation request.
+ *
+ * @param $install_state
+ * An array of information about the current installation state.
+ *
+ * @return
+ * The complete URL to redirect to.
+ *
+ * @see install_redirect_url()
+ */
+function install_full_redirect_url($install_state) {
+ global $base_url;
+ return $base_url . '/' . install_redirect_url($install_state);
+}
+
+/**
+ * Displays themed installer output and ends the page request.
+ *
+ * Installation tasks should use drupal_set_title() to set the desired page
+ * title, but otherwise this function takes care of theming the overall page
+ * output during every step of the installation.
+ *
+ * @param $output
+ * The content to display on the main part of the page.
+ * @param $install_state
+ * An array of information about the current installation state.
+ */
+function install_display_output($output, $install_state) {
+ drupal_page_header();
+ // Only show the task list if there is an active task; otherwise, the page
+ // request has ended before tasks have even been started, so there is nothing
+ // meaningful to show.
+ if (isset($install_state['active_task'])) {
+ // Let the theming function know when every step of the installation has
+ // been completed.
+ $active_task = $install_state['installation_finished'] ? NULL : $install_state['active_task'];
+ drupal_add_region_content('sidebar_first', theme('task_list', array('items' => install_tasks_to_display($install_state), 'active' => $active_task)));
+ }
+ print theme('install_page', array('content' => $output));
+ exit;
+}
+
+/**
+ * Installation task; verify the requirements for installing Drupal.
+ *
+ * @param $install_state
+ * An array of information about the current installation state.
+ *
+ * @return
+ * A themed status report, or an exception if there are requirement errors.
+ * Otherwise, no output is returned, so that the next task can be run
+ * in the same page request.
+ */
+function install_verify_requirements(&$install_state) {
+ // Check the installation requirements for Drupal and this profile.
+ $requirements = install_check_requirements($install_state);
+
+ // Verify existence of all required modules.
+ $requirements += drupal_verify_profile($install_state);
+
+ // Check the severity of the requirements reported.
+ $severity = drupal_requirements_severity($requirements);
+
+ if ($severity == REQUIREMENT_ERROR) {
+ if ($install_state['interactive']) {
+ drupal_set_title(st('Requirements problem'));
+ $status_report = theme('status_report', array('requirements' => $requirements));
+ $status_report .= st('Check the error messages and
proceed with the installation .', array('!url' => check_url(request_uri())));
+ return $status_report;
+ }
+ else {
+ // Throw an exception showing all unmet requirements.
+ $failures = array();
+ foreach ($requirements as $requirement) {
+ if (isset($requirement['severity']) && $requirement['severity'] == REQUIREMENT_ERROR) {
+ $failures[] = $requirement['title'] . ': ' . $requirement['value'] . "\n\n" . $requirement['description'];
+ }
+ }
+ throw new Exception(implode("\n\n", $failures));
+ }
+ }
+}
+
+/**
+ * Installation task; install the Drupal system module.
+ *
+ * @param $install_state
+ * An array of information about the current installation state.
+ */
+function install_system_module(&$install_state) {
+ // Install system.module.
+ drupal_install_system();
+
+ // Enable the user module so that sessions can be recorded during the
+ // upcoming bootstrap step.
+ module_enable(array('user'), FALSE);
+
+ // Save the list of other modules to install for the upcoming tasks.
+ // variable_set() can be used now that system.module is installed.
+ $modules = $install_state['profile_info']['dependencies'];
+
+ // The install profile is also a module, which needs to be installed
+ // after all the dependencies have been installed.
+ $modules[] = drupal_get_profile();
+
+ variable_set('install_profile_modules', array_diff($modules, array('system')));
+ $install_state['database_tables_exist'] = TRUE;
+}
+
+/**
+ * Verify and return the last installation task that was completed.
+ *
+ * @return
+ * The last completed task, if there is one. An exception is thrown if Drupal
+ * is already installed.
+ */
+function install_verify_completed_task() {
+ try {
+ if ($result = db_query("SELECT value FROM {variable} WHERE name = :name", array('name' => 'install_task'))) {
+ $task = unserialize($result->fetchField());
+ }
+ }
+ // Do not trigger an error if the database query fails, since the database
+ // might not be set up yet.
+ catch (Exception $e) {
+ }
+ if (isset($task)) {
+ if ($task == 'done') {
+ throw new Exception(install_already_done_error());
+ }
+ return $task;
+ }
+}
+
+/**
+ * Verifies the existing settings in settings.php.
+ */
+function install_verify_settings() {
+ global $databases;
+
+ // Verify existing settings (if any).
+ if (!empty($databases) && install_verify_pdo()) {
+ $database = $databases['default']['default'];
+ drupal_static_reset('conf_path');
+ $settings_file = './' . conf_path(FALSE) . '/settings.php';
+ $errors = install_database_errors($database, $settings_file);
+ if (empty($errors)) {
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+/**
+ * Verify PDO library.
+ */
+function install_verify_pdo() {
+ // PDO was moved to PHP core in 5.2.0, but the old extension (targeting 5.0
+ // and 5.1) is still available from PECL, and can still be built without
+ // errors. To verify that the correct version is in use, we check the
+ // PDO::ATTR_DEFAULT_FETCH_MODE constant, which is not available in the
+ // PECL extension.
+ return extension_loaded('pdo') && defined('PDO::ATTR_DEFAULT_FETCH_MODE');
+}
+
+/**
+ * Installation task; define a form to configure and rewrite settings.php.
+ *
+ * @param $form_state
+ * An associative array containing the current state of the form.
+ * @param $install_state
+ * An array of information about the current installation state.
+ *
+ * @return
+ * The form API definition for the database configuration form.
+ */
+function install_settings_form($form, &$form_state, &$install_state) {
+ global $databases;
+ $profile = $install_state['parameters']['profile'];
+ $install_locale = $install_state['parameters']['locale'];
+
+ drupal_static_reset('conf_path');
+ $conf_path = './' . conf_path(FALSE);
+ $settings_file = $conf_path . '/settings.php';
+ $database = isset($databases['default']['default']) ? $databases['default']['default'] : array();
+
+ drupal_set_title(st('Database configuration'));
+
+ $drivers = drupal_get_database_types();
+ $drivers_keys = array_keys($drivers);
+
+ $form['driver'] = array(
+ '#type' => 'radios',
+ '#title' => st('Database type'),
+ '#required' => TRUE,
+ '#default_value' => !empty($database['driver']) ? $database['driver'] : current($drivers_keys),
+ '#description' => st('The type of database your @drupal data will be stored in.', array('@drupal' => drupal_install_profile_distribution_name())),
+ );
+ if (count($drivers) == 1) {
+ $form['driver']['#disabled'] = TRUE;
+ $form['driver']['#description'] .= ' ' . st('Your PHP configuration only supports a single database type, so it has been automatically selected.');
+ }
+
+ // Add driver specific configuration options.
+ foreach ($drivers as $key => $driver) {
+ $form['driver']['#options'][$key] = $driver->name();
+
+ $form['settings'][$key] = $driver->getFormOptions($database);
+ $form['settings'][$key]['#prefix'] = '
' . st('@driver_name settings', array('@driver_name' => $driver->name())) . ' ';
+ $form['settings'][$key]['#type'] = 'container';
+ $form['settings'][$key]['#tree'] = TRUE;
+ $form['settings'][$key]['advanced_options']['#parents'] = array($key);
+ $form['settings'][$key]['#states'] = array(
+ 'visible' => array(
+ ':input[name=driver]' => array('value' => $key),
+ )
+ );
+ }
+
+ $form['actions'] = array('#type' => 'actions');
+ $form['actions']['save'] = array(
+ '#type' => 'submit',
+ '#value' => st('Save and continue'),
+ '#limit_validation_errors' => array(
+ array('driver'),
+ array(isset($form_state['input']['driver']) ? $form_state['input']['driver'] : current($drivers_keys)),
+ ),
+ '#submit' => array('install_settings_form_submit'),
+ );
+
+ $form['errors'] = array();
+ $form['settings_file'] = array('#type' => 'value', '#value' => $settings_file);
+
+ return $form;
+}
+
+/**
+ * Form API validate for install_settings form.
+ */
+function install_settings_form_validate($form, &$form_state) {
+ $driver = $form_state['values']['driver'];
+ $database = $form_state['values'][$driver];
+ $database['driver'] = $driver;
+
+ // TODO: remove when PIFR will be updated to use 'db_prefix' instead of
+ // 'prefix' in the database settings form.
+ $database['prefix'] = $database['db_prefix'];
+ unset($database['db_prefix']);
+
+ $form_state['storage']['database'] = $database;
+ $errors = install_database_errors($database, $form_state['values']['settings_file']);
+ foreach ($errors as $name => $message) {
+ form_set_error($name, $message);
+ }
+}
+
+/**
+ * Checks a database connection and returns any errors.
+ */
+function install_database_errors($database, $settings_file) {
+ global $databases;
+ $errors = array();
+
+ // Check database type.
+ $database_types = drupal_get_database_types();
+ $driver = $database['driver'];
+ if (!isset($database_types[$driver])) {
+ $errors['driver'] = st("In your %settings_file file you have configured @drupal to use a %driver server, however your PHP installation currently does not support this database type.", array('%settings_file' => $settings_file, '@drupal' => drupal_install_profile_distribution_name(), '%driver' => $driver));
+ }
+ else {
+ // Run driver specific validation
+ $errors += $database_types[$driver]->validateDatabaseSettings($database);
+
+ // Run tasks associated with the database type. Any errors are caught in the
+ // calling function.
+ $databases['default']['default'] = $database;
+ // Just changing the global doesn't get the new information processed.
+ // We tell tell the Database class to re-parse $databases.
+ Database::parseConnectionInfo();
+
+ try {
+ db_run_tasks($driver);
+ }
+ catch (DatabaseTaskException $e) {
+ // These are generic errors, so we do not have any specific key of the
+ // database connection array to attach them to; therefore, we just put
+ // them in the error array with standard numeric keys.
+ $errors[$driver . '][0'] = $e->getMessage();
+ }
+ }
+ return $errors;
+}
+
+/**
+ * Form API submit for install_settings form.
+ */
+function install_settings_form_submit($form, &$form_state) {
+ global $install_state;
+
+ // Update global settings array and save.
+ $settings['databases'] = array(
+ 'value' => array('default' => array('default' => $form_state['storage']['database'])),
+ 'required' => TRUE,
+ );
+ $settings['drupal_hash_salt'] = array(
+ 'value' => drupal_hash_base64(drupal_random_bytes(55)),
+ 'required' => TRUE,
+ );
+ drupal_rewrite_settings($settings);
+ // Indicate that the settings file has been verified, and check the database
+ // for the last completed task, now that we have a valid connection. This
+ // last step is important since we want to trigger an error if the new
+ // database already has Drupal installed.
+ $install_state['settings_verified'] = TRUE;
+ $install_state['completed_task'] = install_verify_completed_task();
+}
+
+/**
+ * Finds all .profile files.
+ */
+function install_find_profiles() {
+ return file_scan_directory('./profiles', '/\.profile$/', array('key' => 'name'));
+}
+
+/**
+ * Installation task; select which profile to install.
+ *
+ * @param $install_state
+ * An array of information about the current installation state. The chosen
+ * profile will be added here, if it was not already selected previously, as
+ * will a list of all available profiles.
+ *
+ * @return
+ * For interactive installations, a form allowing the profile to be selected,
+ * if the user has a choice that needs to be made. Otherwise, an exception is
+ * thrown if a profile cannot be chosen automatically.
+ */
+function install_select_profile(&$install_state) {
+ $install_state['profiles'] += install_find_profiles();
+ if (empty($install_state['parameters']['profile'])) {
+ // Try to find a profile.
+ $profile = _install_select_profile($install_state['profiles']);
+ if (empty($profile)) {
+ // We still don't have a profile, so display a form for selecting one.
+ // Only do this in the case of interactive installations, since this is
+ // not a real form with submit handlers (the database isn't even set up
+ // yet), rather just a convenience method for setting parameters in the
+ // URL.
+ if ($install_state['interactive']) {
+ include_once DRUPAL_ROOT . '/includes/form.inc';
+ drupal_set_title(st('Select an installation profile'));
+ $form = drupal_get_form('install_select_profile_form', $install_state['profiles']);
+ return drupal_render($form);
+ }
+ else {
+ throw new Exception(install_no_profile_error());
+ }
+ }
+ else {
+ $install_state['parameters']['profile'] = $profile;
+ }
+ }
+}
+
+/**
+ * Helper function for automatically selecting an installation profile from a
+ * list or from a selection passed in via $_POST.
+ */
+function _install_select_profile($profiles) {
+ if (sizeof($profiles) == 0) {
+ throw new Exception(install_no_profile_error());
+ }
+ // Don't need to choose profile if only one available.
+ if (sizeof($profiles) == 1) {
+ $profile = array_pop($profiles);
+ // TODO: is this right?
+ require_once DRUPAL_ROOT . '/' . $profile->uri;
+ return $profile->name;
+ }
+ else {
+ foreach ($profiles as $profile) {
+ if (!empty($_POST['profile']) && ($_POST['profile'] == $profile->name)) {
+ return $profile->name;
+ }
+ }
+ }
+}
+
+/**
+ * Form API array definition for the profile selection form.
+ *
+ * @param $form_state
+ * Array of metadata about state of form processing.
+ * @param $profile_files
+ * Array of .profile files, as returned from file_scan_directory().
+ */
+function install_select_profile_form($form, &$form_state, $profile_files) {
+ $profiles = array();
+ $names = array();
+
+ foreach ($profile_files as $profile) {
+ // TODO: is this right?
+ include_once DRUPAL_ROOT . '/' . $profile->uri;
+
+ $details = install_profile_info($profile->name);
+ // Don't show hidden profiles. This is used by to hide the testing profile,
+ // which only exists to speed up test runs.
+ if ($details['hidden'] === TRUE) {
+ continue;
+ }
+ $profiles[$profile->name] = $details;
+
+ // Determine the name of the profile; default to file name if defined name
+ // is unspecified.
+ $name = isset($details['name']) ? $details['name'] : $profile->name;
+ $names[$profile->name] = $name;
+ }
+
+ // Display radio buttons alphabetically by human-readable name, but always
+ // put the core profiles first (if they are present in the filesystem).
+ natcasesort($names);
+ if (isset($names['minimal'])) {
+ // If the expert ("Minimal") core profile is present, put it in front of
+ // any non-core profiles rather than including it with them alphabetically,
+ // since the other profiles might be intended to group together in a
+ // particular way.
+ $names = array('minimal' => $names['minimal']) + $names;
+ }
+ if (isset($names['standard'])) {
+ // If the default ("Standard") core profile is present, put it at the very
+ // top of the list. This profile will have its radio button pre-selected,
+ // so we want it to always appear at the top.
+ $names = array('standard' => $names['standard']) + $names;
+ }
+
+ foreach ($names as $profile => $name) {
+ $form['profile'][$name] = array(
+ '#type' => 'radio',
+ '#value' => 'standard',
+ '#return_value' => $profile,
+ '#title' => $name,
+ '#description' => isset($profiles[$profile]['description']) ? $profiles[$profile]['description'] : '',
+ '#parents' => array('profile'),
+ );
+ }
+ $form['actions'] = array('#type' => 'actions');
+ $form['actions']['submit'] = array(
+ '#type' => 'submit',
+ '#value' => st('Save and continue'),
+ );
+ return $form;
+}
+
+/**
+ * Find all .po files for the current profile.
+ */
+function install_find_locales($profilename) {
+ $locales = file_scan_directory('./profiles/' . $profilename . '/translations', '/\.po$/', array('recurse' => FALSE));
+ array_unshift($locales, (object) array('name' => 'en'));
+ foreach ($locales as $key => $locale) {
+ // The locale (file name) might be drupal-7.2.cs.po instead of cs.po.
+ $locales[$key]->langcode = preg_replace('!^(.+\.)?([^\.]+)$!', '\2', $locale->name);
+ // Language codes cannot exceed 12 characters to fit into the {languages}
+ // table.
+ if (strlen($locales[$key]->langcode) > 12) {
+ unset($locales[$key]);
+ }
+ }
+ return $locales;
+}
+
+/**
+ * Installation task; select which locale to use for the current profile.
+ *
+ * @param $install_state
+ * An array of information about the current installation state. The chosen
+ * locale will be added here, if it was not already selected previously, as
+ * will a list of all available locales.
+ *
+ * @return
+ * For interactive installations, a form or other page output allowing the
+ * locale to be selected or providing information about locale selection, if
+ * a locale has not been chosen. Otherwise, an exception is thrown if a
+ * locale cannot be chosen automatically.
+ */
+function install_select_locale(&$install_state) {
+ // Find all available locales.
+ $profilename = $install_state['parameters']['profile'];
+ $locales = install_find_locales($profilename);
+ $install_state['locales'] += $locales;
+
+ if (!empty($_POST['locale'])) {
+ foreach ($locales as $locale) {
+ if ($_POST['locale'] == $locale->langcode) {
+ $install_state['parameters']['locale'] = $locale->langcode;
+ return;
+ }
+ }
+ }
+
+ if (empty($install_state['parameters']['locale'])) {
+ // If only the built-in (English) language is available, and we are
+ // performing an interactive installation, inform the user that the
+ // installer can be localized. Otherwise we assume the user knows what he
+ // is doing.
+ if (count($locales) == 1) {
+ if ($install_state['interactive']) {
+ drupal_set_title(st('Choose language'));
+ if (!empty($install_state['parameters']['localize'])) {
+ $output = '
Follow these steps to translate Drupal into your language:
';
+ $output .= '
';
+ $output .= 'Download a translation from the translation server . ';
+ $output .= 'Place it into the following directory:
+
+/profiles/' . $profilename . '/translations/
+ ';
+ $output .= ' ';
+ $output .= '
For more information on installing Drupal in different languages, visit the drupal.org handbook page .
';
+ $output .= '
How should the installation continue?
';
+ $output .= '
';
+ }
+ else {
+ include_once DRUPAL_ROOT . '/includes/form.inc';
+ $elements = drupal_get_form('install_select_locale_form', $locales, $profilename);
+ $output = drupal_render($elements);
+ }
+ return $output;
+ }
+ // One language, but not an interactive installation. Assume the user
+ // knows what he is doing.
+ $locale = current($locales);
+ $install_state['parameters']['locale'] = $locale->name;
+ return;
+ }
+ else {
+ // Allow profile to pre-select the language, skipping the selection.
+ $function = $profilename . '_profile_details';
+ if (function_exists($function)) {
+ $details = $function();
+ if (isset($details['language'])) {
+ foreach ($locales as $locale) {
+ if ($details['language'] == $locale->name) {
+ $install_state['parameters']['locale'] = $locale->name;
+ return;
+ }
+ }
+ }
+ }
+
+ // We still don't have a locale, so display a form for selecting one.
+ // Only do this in the case of interactive installations, since this is
+ // not a real form with submit handlers (the database isn't even set up
+ // yet), rather just a convenience method for setting parameters in the
+ // URL.
+ if ($install_state['interactive']) {
+ drupal_set_title(st('Choose language'));
+ include_once DRUPAL_ROOT . '/includes/form.inc';
+ $elements = drupal_get_form('install_select_locale_form', $locales, $profilename);
+ return drupal_render($elements);
+ }
+ else {
+ throw new Exception(st('Sorry, you must select a language to continue the installation.'));
+ }
+ }
+ }
+}
+
+/**
+ * Form API array definition for language selection.
+ */
+function install_select_locale_form($form, &$form_state, $locales, $profilename) {
+ include_once DRUPAL_ROOT . '/includes/iso.inc';
+ $languages = _locale_get_predefined_list();
+ foreach ($locales as $locale) {
+ $name = $locale->langcode;
+ if (isset($languages[$name])) {
+ $name = $languages[$name][0] . (isset($languages[$name][1]) ? ' ' . st('(@language)', array('@language' => $languages[$name][1])) : '');
+ }
+ $form['locale'][$locale->langcode] = array(
+ '#type' => 'radio',
+ '#return_value' => $locale->langcode,
+ '#default_value' => $locale->langcode == 'en' ? 'en' : '',
+ '#title' => $name . ($locale->langcode == 'en' ? ' ' . st('(built-in)') : ''),
+ '#parents' => array('locale')
+ );
+ }
+ if (count($locales) == 1) {
+ $form['help'] = array(
+ '#markup' => '
' . st('Learn how to install Drupal in other languages') . '
',
+ );
+ }
+ $form['actions'] = array('#type' => 'actions');
+ $form['actions']['submit'] = array(
+ '#type' => 'submit',
+ '#value' => st('Save and continue'),
+ );
+ return $form;
+}
+
+/**
+ * Indicates that there are no profiles available.
+ */
+function install_no_profile_error() {
+ drupal_set_title(st('No profiles available'));
+ return st('We were unable to find any installation profiles. Installation profiles tell us what modules to enable and what schema to install in the database. A profile is necessary to continue with the installation process.');
+}
+
+/**
+ * Indicates that Drupal has already been installed.
+ */
+function install_already_done_error() {
+ global $base_url;
+
+ drupal_set_title(st('Drupal already installed'));
+ return st('
To start over, you must empty your existing database. To install to a different database, edit the appropriate settings.php file in the sites folder. To upgrade an existing installation, proceed to the update script . View your existing site . ', array('@base-url' => $base_url));
+}
+
+/**
+ * Installation task; load information about the chosen profile.
+ *
+ * @param $install_state
+ * An array of information about the current installation state. The loaded
+ * profile information will be added here, or an exception will be thrown if
+ * the profile cannot be loaded.
+ */
+function install_load_profile(&$install_state) {
+ $profile_file = DRUPAL_ROOT . '/profiles/' . $install_state['parameters']['profile'] . '/' . $install_state['parameters']['profile'] . '.profile';
+ if (is_file($profile_file)) {
+ include_once $profile_file;
+ $install_state['profile_info'] = install_profile_info($install_state['parameters']['profile'], $install_state['parameters']['locale']);
+ }
+ else {
+ throw new Exception(st('Sorry, the profile you have chosen cannot be loaded.'));
+ }
+}
+
+/**
+ * Installation task; perform a full bootstrap of Drupal.
+ *
+ * @param $install_state
+ * An array of information about the current installation state.
+ */
+function install_bootstrap_full(&$install_state) {
+ drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
+}
+
+/**
+ * Installation task; install required modules via a batch process.
+ *
+ * @param $install_state
+ * An array of information about the current installation state.
+ *
+ * @return
+ * The batch definition.
+ */
+function install_profile_modules(&$install_state) {
+ $modules = variable_get('install_profile_modules', array());
+ $files = system_rebuild_module_data();
+ variable_del('install_profile_modules');
+
+ // Always install required modules first. Respect the dependencies between
+ // the modules.
+ $required = array();
+ $non_required = array();
+ // Although the profile module is marked as required, it needs to go after
+ // every dependency, including non-required ones. So clear its required
+ // flag for now to allow it to install late.
+ $files[$install_state['parameters']['profile']]->info['required'] = FALSE;
+ // Add modules that other modules depend on.
+ foreach ($modules as $module) {
+ if ($files[$module]->requires) {
+ $modules = array_merge($modules, array_keys($files[$module]->requires));
+ }
+ }
+ $modules = array_unique($modules);
+ foreach ($modules as $module) {
+ if (!empty($files[$module]->info['required'])) {
+ $required[$module] = $files[$module]->sort;
+ }
+ else {
+ $non_required[$module] = $files[$module]->sort;
+ }
+ }
+ arsort($required);
+ arsort($non_required);
+
+ $operations = array();
+ foreach ($required + $non_required as $module => $weight) {
+ $operations[] = array('_install_module_batch', array($module, $files[$module]->info['name']));
+ }
+ $batch = array(
+ 'operations' => $operations,
+ 'title' => st('Installing @drupal', array('@drupal' => drupal_install_profile_distribution_name())),
+ 'error_message' => st('The installation has encountered an error.'),
+ 'finished' => '_install_profile_modules_finished',
+ );
+ return $batch;
+}
+
+/**
+ * Installation task; import languages via a batch process.
+ *
+ * @param $install_state
+ * An array of information about the current installation state.
+ *
+ * @return
+ * The batch definition, if there are language files to import.
+ */
+function install_import_locales(&$install_state) {
+ include_once DRUPAL_ROOT . '/includes/locale.inc';
+ $install_locale = $install_state['parameters']['locale'];
+
+ include_once DRUPAL_ROOT . '/includes/iso.inc';
+ $predefined = _locale_get_predefined_list();
+ if (!isset($predefined[$install_locale])) {
+ // Drupal does not know about this language, so we prefill its values with
+ // our best guess. The user will be able to edit afterwards.
+ locale_add_language($install_locale, $install_locale, $install_locale, LANGUAGE_LTR, '', '', TRUE, TRUE);
+ }
+ else {
+ // A known predefined language, details will be filled in properly.
+ locale_add_language($install_locale, NULL, NULL, NULL, '', '', TRUE, TRUE);
+ }
+
+ // Collect files to import for this language.
+ $batch = locale_batch_by_language($install_locale, NULL);
+ if (!empty($batch)) {
+ // Remember components we cover in this batch set.
+ variable_set('install_locale_batch_components', $batch['#components']);
+ return $batch;
+ }
+}
+
+/**
+ * Installation task; configure settings for the new site.
+ *
+ * @param $form_state
+ * An associative array containing the current state of the form.
+ * @param $install_state
+ * An array of information about the current installation state.
+ *
+ * @return
+ * The form API definition for the site configuration form.
+ */
+function install_configure_form($form, &$form_state, &$install_state) {
+ if (variable_get('site_name', FALSE) || variable_get('site_mail', FALSE)) {
+ // Site already configured: This should never happen, means re-running the
+ // installer, possibly by an attacker after the 'install_task' variable got
+ // accidentally blown somewhere. Stop it now.
+ throw new Exception(install_already_done_error());
+ }
+
+ drupal_set_title(st('Configure site'));
+
+ // Warn about settings.php permissions risk
+ $settings_dir = conf_path();
+ $settings_file = $settings_dir . '/settings.php';
+ // Check that $_POST is empty so we only show this message when the form is
+ // first displayed, not on the next page after it is submitted. (We do not
+ // want to repeat it multiple times because it is a general warning that is
+ // not related to the rest of the installation process; it would also be
+ // especially out of place on the last page of the installer, where it would
+ // distract from the message that the Drupal installation has completed
+ // successfully.)
+ if (empty($_POST) && (!drupal_verify_install_file(DRUPAL_ROOT . '/' . $settings_file, FILE_EXIST|FILE_READABLE|FILE_NOT_WRITABLE) || !drupal_verify_install_file(DRUPAL_ROOT . '/' . $settings_dir, FILE_NOT_WRITABLE, 'dir'))) {
+ drupal_set_message(st('All necessary changes to %dir and %file have been made, so you should remove write permissions to them now in order to avoid security risks. If you are unsure how to do so, consult the
online handbook .', array('%dir' => $settings_dir, '%file' => $settings_file, '@handbook_url' => 'http://drupal.org/server-permissions')), 'warning');
+ }
+
+ drupal_add_js(drupal_get_path('module', 'system') . '/system.js');
+ // Add JavaScript time zone detection.
+ drupal_add_js('misc/timezone.js');
+ // We add these strings as settings because JavaScript translation does not
+ // work on install time.
+ drupal_add_js(array('copyFieldValue' => array('edit-site-mail' => array('edit-account-mail'))), 'setting');
+ drupal_add_js('jQuery(function () { Drupal.cleanURLsInstallCheck(); });', 'inline');
+ // Add JS to show / hide the 'Email administrator about site updates' elements
+ drupal_add_js('jQuery(function () { Drupal.hideEmailAdministratorCheckbox() });', 'inline');
+ // Build menu to allow clean URL check.
+ menu_rebuild();
+
+ // Cache a fully-built schema. This is necessary for any invocation of
+ // index.php because: (1) setting cache table entries requires schema
+ // information, (2) that occurs during bootstrap before any module are
+ // loaded, so (3) if there is no cached schema, drupal_get_schema() will
+ // try to generate one but with no loaded modules will return nothing.
+ //
+ // This logically could be done during the 'install_finished' task, but the
+ // clean URL check requires it now.
+ drupal_get_schema(NULL, TRUE);
+
+ // Return the form.
+ return _install_configure_form($form, $form_state, $install_state);
+}
+
+/**
+ * Installation task; import remaining languages via a batch process.
+ *
+ * @param $install_state
+ * An array of information about the current installation state.
+ *
+ * @return
+ * The batch definition, if there are language files to import.
+ */
+function install_import_locales_remaining(&$install_state) {
+ include_once DRUPAL_ROOT . '/includes/locale.inc';
+ // Collect files to import for this language. Skip components already covered
+ // in the initial batch set.
+ $install_locale = $install_state['parameters']['locale'];
+ $batch = locale_batch_by_language($install_locale, NULL, variable_get('install_locale_batch_components', array()));
+ // Remove temporary variable.
+ variable_del('install_locale_batch_components');
+ return $batch;
+}
+
+/**
+ * Installation task; perform final steps and display a 'finished' page.
+ *
+ * @param $install_state
+ * An array of information about the current installation state.
+ *
+ * @return
+ * A message informing the user that the installation is complete.
+ */
+function install_finished(&$install_state) {
+ drupal_set_title(st('@drupal installation complete', array('@drupal' => drupal_install_profile_distribution_name())), PASS_THROUGH);
+ $messages = drupal_set_message();
+ $output = '
' . st('Congratulations, you installed @drupal!', array('@drupal' => drupal_install_profile_distribution_name())) . '
';
+ $output .= '
' . (isset($messages['error']) ? st('Review the messages above before visiting your new site .', array('@url' => url(''))) : st('Visit your new site .', array('@url' => url('')))) . '
';
+
+ // Flush all caches to ensure that any full bootstraps during the installer
+ // do not leave stale cached data, and that any content types or other items
+ // registered by the install profile are registered correctly.
+ drupal_flush_all_caches();
+
+ // Remember the profile which was used.
+ variable_set('install_profile', drupal_get_profile());
+
+ // Install profiles are always loaded last
+ db_update('system')
+ ->fields(array('weight' => 1000))
+ ->condition('type', 'module')
+ ->condition('name', drupal_get_profile())
+ ->execute();
+
+ // Cache a fully-built schema.
+ drupal_get_schema(NULL, TRUE);
+
+ // Run cron to populate update status tables (if available) so that users
+ // will be warned if they've installed an out of date Drupal version.
+ // Will also trigger indexing of profile-supplied content or feeds.
+ drupal_cron_run();
+
+ return $output;
+}
+
+/**
+ * Batch callback for batch installation of modules.
+ */
+function _install_module_batch($module, $module_name, &$context) {
+ // Install and enable the module right away, so that the module will be
+ // loaded by drupal_bootstrap in subsequent batch requests, and other
+ // modules possibly depending on it can safely perform their installation
+ // steps.
+ module_enable(array($module), FALSE);
+ $context['results'][] = $module;
+ $context['message'] = st('Installed %module module.', array('%module' => $module_name));
+}
+
+/**
+ * 'Finished' callback for module installation batch.
+ */
+function _install_profile_modules_finished($success, $results, $operations) {
+ // Flush all caches to complete the module installation process. Subsequent
+ // installation tasks will now have full access to the profile's modules.
+ drupal_flush_all_caches();
+}
+
+/**
+ * Checks installation requirements and reports any errors.
+ */
+function install_check_requirements($install_state) {
+ $profile = $install_state['parameters']['profile'];
+
+ // Check the profile requirements.
+ $requirements = drupal_check_profile($profile);
+
+ // If Drupal is not set up already, we need to create a settings file.
+ if (!$install_state['settings_verified']) {
+ $writable = FALSE;
+ $conf_path = './' . conf_path(FALSE, TRUE);
+ $settings_file = $conf_path . '/settings.php';
+ $default_settings_file = './sites/default/default.settings.php';
+ $file = $conf_path;
+ $exists = FALSE;
+ // Verify that the directory exists.
+ if (drupal_verify_install_file($conf_path, FILE_EXIST, 'dir')) {
+ // Check if a settings.php file already exists.
+ $file = $settings_file;
+ if (drupal_verify_install_file($settings_file, FILE_EXIST)) {
+ // If it does, make sure it is writable.
+ $writable = drupal_verify_install_file($settings_file, FILE_READABLE|FILE_WRITABLE);
+ $exists = TRUE;
+ }
+ }
+
+ // If default.settings.php does not exist, or is not readable, throw an
+ // error.
+ if (!drupal_verify_install_file($default_settings_file, FILE_EXIST|FILE_READABLE)) {
+ $requirements['default settings file exists'] = array(
+ 'title' => st('Default settings file'),
+ 'value' => st('The default settings file does not exist.'),
+ 'severity' => REQUIREMENT_ERROR,
+ 'description' => st('The @drupal installer requires that the %default-file file not be modified in any way from the original download.', array('@drupal' => drupal_install_profile_distribution_name(), '%default-file' => $default_settings_file)),
+ );
+ }
+ // Otherwise, if settings.php does not exist yet, we can try to copy
+ // default.settings.php to create it.
+ elseif (!$exists) {
+ $copied = drupal_verify_install_file($conf_path, FILE_EXIST|FILE_WRITABLE, 'dir') && @copy($default_settings_file, $settings_file);
+ if ($copied) {
+ // If the new settings file has the same owner as default.settings.php,
+ // this means default.settings.php is owned by the webserver user.
+ // This is an inherent security weakness because it allows a malicious
+ // webserver process to append arbitrary PHP code and then execute it.
+ // However, it is also a common configuration on shared hosting, and
+ // there is nothing Drupal can do to prevent it. In this situation,
+ // having settings.php also owned by the webserver does not introduce
+ // any additional security risk, so we keep the file in place.
+ if (fileowner($default_settings_file) === fileowner($settings_file)) {
+ $writable = drupal_verify_install_file($settings_file, FILE_READABLE|FILE_WRITABLE);
+ $exists = TRUE;
+ }
+ // If settings.php and default.settings.php have different owners, this
+ // probably means the server is set up "securely" (with the webserver
+ // running as its own user, distinct from the user who owns all the
+ // Drupal PHP files), although with either a group or world writable
+ // sites directory. Keeping settings.php owned by the webserver would
+ // therefore introduce a security risk. It would also cause a usability
+ // problem, since site owners who do not have root access to the file
+ // system would be unable to edit their settings file later on. We
+ // therefore must delete the file we just created and force the
+ // administrator to log on to the server and create it manually.
+ else {
+ $deleted = @drupal_unlink($settings_file);
+ // We expect deleting the file to be successful (since we just
+ // created it ourselves above), but if it fails somehow, we set a
+ // variable so we can display a one-time error message to the
+ // administrator at the bottom of the requirements list. We also try
+ // to make the file writable, to eliminate any conflicting error
+ // messages in the requirements list.
+ $exists = !$deleted;
+ if ($exists) {
+ $settings_file_ownership_error = TRUE;
+ $writable = drupal_verify_install_file($settings_file, FILE_READABLE|FILE_WRITABLE);
+ }
+ }
+ }
+ }
+
+ // If settings.php does not exist, throw an error.
+ if (!$exists) {
+ $requirements['settings file exists'] = array(
+ 'title' => st('Settings file'),
+ 'value' => st('The settings file does not exist.'),
+ 'severity' => REQUIREMENT_ERROR,
+ 'description' => st('The @drupal installer requires that you create a settings file as part of the installation process. Copy the %default_file file to %file. More details about installing Drupal are available in
INSTALL.txt .', array('@drupal' => drupal_install_profile_distribution_name(), '%file' => $file, '%default_file' => $default_settings_file, '@install_txt' => base_path() . 'INSTALL.txt')),
+ );
+ }
+ else {
+ $requirements['settings file exists'] = array(
+ 'title' => st('Settings file'),
+ 'value' => st('The %file file exists.', array('%file' => $file)),
+ );
+ // If settings.php is not writable, throw an error.
+ if (!$writable) {
+ $requirements['settings file writable'] = array(
+ 'title' => st('Settings file'),
+ 'value' => st('The settings file is not writable.'),
+ 'severity' => REQUIREMENT_ERROR,
+ 'description' => st('The @drupal installer requires write permissions to %file during the installation process. If you are unsure how to grant file permissions, consult the
online handbook .', array('@drupal' => drupal_install_profile_distribution_name(), '%file' => $file, '@handbook_url' => 'http://drupal.org/server-permissions')),
+ );
+ }
+ else {
+ $requirements['settings file'] = array(
+ 'title' => st('Settings file'),
+ 'value' => st('The settings file is writable.'),
+ );
+ }
+ if (!empty($settings_file_ownership_error)) {
+ $requirements['settings file ownership'] = array(
+ 'title' => st('Settings file'),
+ 'value' => st('The settings file is owned by the web server.'),
+ 'severity' => REQUIREMENT_ERROR,
+ 'description' => st('The @drupal installer failed to create a settings file with proper file ownership. Log on to your web server, remove the existing %file file, and create a new one by copying the %default_file file to %file. More details about installing Drupal are available in
INSTALL.txt . If you have problems with the file permissions on your server, consult the
online handbook .', array('@drupal' => drupal_install_profile_distribution_name(), '%file' => $file, '%default_file' => $default_settings_file, '@install_txt' => base_path() . 'INSTALL.txt', '@handbook_url' => 'http://drupal.org/server-permissions')),
+ );
+ }
+ }
+ }
+ return $requirements;
+}
+
+/**
+ * Forms API array definition for site configuration.
+ */
+function _install_configure_form($form, &$form_state, &$install_state) {
+ include_once DRUPAL_ROOT . '/includes/locale.inc';
+
+ $form['site_information'] = array(
+ '#type' => 'fieldset',
+ '#title' => st('Site information'),
+ '#collapsible' => FALSE,
+ );
+ $form['site_information']['site_name'] = array(
+ '#type' => 'textfield',
+ '#title' => st('Site name'),
+ '#required' => TRUE,
+ '#weight' => -20,
+ );
+ $form['site_information']['site_mail'] = array(
+ '#type' => 'textfield',
+ '#title' => st('Site e-mail address'),
+ '#default_value' => ini_get('sendmail_from'),
+ '#description' => st("Automated e-mails, such as registration information, will be sent from this address. Use an address ending in your site's domain to help prevent these e-mails from being flagged as spam."),
+ '#required' => TRUE,
+ '#weight' => -15,
+ );
+ $form['admin_account'] = array(
+ '#type' => 'fieldset',
+ '#title' => st('Site maintenance account'),
+ '#collapsible' => FALSE,
+ );
+
+ $form['admin_account']['account']['#tree'] = TRUE;
+ $form['admin_account']['account']['name'] = array('#type' => 'textfield',
+ '#title' => st('Username'),
+ '#maxlength' => USERNAME_MAX_LENGTH,
+ '#description' => st('Spaces are allowed; punctuation is not allowed except for periods, hyphens, and underscores.'),
+ '#required' => TRUE,
+ '#weight' => -10,
+ '#attributes' => array('class' => array('username')),
+ );
+
+ $form['admin_account']['account']['mail'] = array('#type' => 'textfield',
+ '#title' => st('E-mail address'),
+ '#maxlength' => EMAIL_MAX_LENGTH,
+ '#required' => TRUE,
+ '#weight' => -5,
+ );
+ $form['admin_account']['account']['pass'] = array(
+ '#type' => 'password_confirm',
+ '#required' => TRUE,
+ '#size' => 25,
+ '#weight' => 0,
+ );
+
+ $form['server_settings'] = array(
+ '#type' => 'fieldset',
+ '#title' => st('Server settings'),
+ '#collapsible' => FALSE,
+ );
+
+ $countries = country_get_list();
+ $form['server_settings']['site_default_country'] = array(
+ '#type' => 'select',
+ '#title' => st('Default country'),
+ '#empty_value' => '',
+ '#default_value' => variable_get('site_default_country', NULL),
+ '#options' => $countries,
+ '#description' => st('Select the default country for the site.'),
+ '#weight' => 0,
+ );
+
+ $form['server_settings']['date_default_timezone'] = array(
+ '#type' => 'select',
+ '#title' => st('Default time zone'),
+ '#default_value' => date_default_timezone_get(),
+ '#options' => system_time_zones(),
+ '#description' => st('By default, dates in this site will be displayed in the chosen time zone.'),
+ '#weight' => 5,
+ '#attributes' => array('class' => array('timezone-detect')),
+ );
+
+ $form['server_settings']['clean_url'] = array(
+ '#type' => 'hidden',
+ '#default_value' => 0,
+ '#attributes' => array('id' => 'edit-clean-url', 'class' => array('install')),
+ );
+
+ $form['update_notifications'] = array(
+ '#type' => 'fieldset',
+ '#title' => st('Update notifications'),
+ '#collapsible' => FALSE,
+ );
+ $form['update_notifications']['update_status_module'] = array(
+ '#type' => 'checkboxes',
+ '#options' => array(
+ 1 => st('Check for updates automatically'),
+ 2 => st('Receive e-mail notifications'),
+ ),
+ '#default_value' => array(1, 2),
+ '#description' => st('The system will notify you when updates and important security releases are available for installed components. Anonymous information about your site is sent to
Drupal.org .', array('@drupal' => 'http://drupal.org')),
+ '#weight' => 15,
+ );
+
+ $form['actions'] = array('#type' => 'actions');
+ $form['actions']['submit'] = array(
+ '#type' => 'submit',
+ '#value' => st('Save and continue'),
+ '#weight' => 15,
+ );
+
+ return $form;
+}
+
+/**
+ * Forms API validate for the site configuration form.
+ */
+function install_configure_form_validate($form, &$form_state) {
+ if ($error = user_validate_name($form_state['values']['account']['name'])) {
+ form_error($form['admin_account']['account']['name'], $error);
+ }
+ if ($error = user_validate_mail($form_state['values']['account']['mail'])) {
+ form_error($form['admin_account']['account']['mail'], $error);
+ }
+ if ($error = user_validate_mail($form_state['values']['site_mail'])) {
+ form_error($form['site_information']['site_mail'], $error);
+ }
+}
+
+/**
+ * Forms API submit for the site configuration form.
+ */
+function install_configure_form_submit($form, &$form_state) {
+ global $user;
+
+ variable_set('site_name', $form_state['values']['site_name']);
+ variable_set('site_mail', $form_state['values']['site_mail']);
+ variable_set('date_default_timezone', $form_state['values']['date_default_timezone']);
+ variable_set('site_default_country', $form_state['values']['site_default_country']);
+
+ // Enable update.module if this option was selected.
+ if ($form_state['values']['update_status_module'][1]) {
+ module_enable(array('update'), FALSE);
+
+ // Add the site maintenance account's email address to the list of
+ // addresses to be notified when updates are available, if selected.
+ if ($form_state['values']['update_status_module'][2]) {
+ variable_set('update_notify_emails', array($form_state['values']['account']['mail']));
+ }
+ }
+
+ // We precreated user 1 with placeholder values. Let's save the real values.
+ $account = user_load(1);
+ $merge_data = array('init' => $form_state['values']['account']['mail'], 'roles' => !empty($account->roles) ? $account->roles : array(), 'status' => 1);
+ user_save($account, array_merge($form_state['values']['account'], $merge_data));
+ // Load global $user and perform final login tasks.
+ $user = user_load(1);
+ user_login_finalize();
+
+ if (isset($form_state['values']['clean_url'])) {
+ variable_set('clean_url', $form_state['values']['clean_url']);
+ }
+
+ // Record when this install ran.
+ variable_set('install_time', $_SERVER['REQUEST_TIME']);
+}
diff --git a/backup/modules/20110602035425/drupal/includes/install.inc b/backup/modules/20110602035425/drupal/includes/install.inc
new file mode 100644
index 00000000..3956ff9f
--- /dev/null
+++ b/backup/modules/20110602035425/drupal/includes/install.inc
@@ -0,0 +1,1269 @@
+ $schema_version) {
+ if ($schema_version > -1) {
+ module_load_install($module);
+ }
+ }
+}
+
+/**
+ * Returns an array of available schema versions for a module.
+ *
+ * @param $module
+ * A module name.
+ * @return
+ * If the module has updates, an array of available updates sorted by version.
+ * Otherwise, FALSE.
+ */
+function drupal_get_schema_versions($module) {
+ $updates = &drupal_static(__FUNCTION__, NULL);
+ if (!isset($updates[$module])) {
+ $updates = array();
+
+ foreach (module_list() as $loaded_module) {
+ $updates[$loaded_module] = array();
+ }
+
+ // Prepare regular expression to match all possible defined hook_update_N().
+ $regexp = '/^(?P
.+)_update_(?P\d+)$/';
+ $functions = get_defined_functions();
+ // Narrow this down to functions ending with an integer, since all
+ // hook_update_N() functions end this way, and there are other
+ // possible functions which match '_update_'. We use preg_grep() here
+ // instead of foreaching through all defined functions, since the loop
+ // through all PHP functions can take significant page execution time
+ // and this function is called on every administrative page via
+ // system_requirements().
+ foreach (preg_grep('/_\d+$/', $functions['user']) as $function) {
+ // If this function is a module update function, add it to the list of
+ // module updates.
+ if (preg_match($regexp, $function, $matches)) {
+ $updates[$matches['module']][] = $matches['version'];
+ }
+ }
+ // Ensure that updates are applied in numerical order.
+ foreach ($updates as &$module_updates) {
+ sort($module_updates, SORT_NUMERIC);
+ }
+ }
+ return empty($updates[$module]) ? FALSE : $updates[$module];
+}
+
+/**
+ * Returns the currently installed schema version for a module.
+ *
+ * @param $module
+ * A module name.
+ * @param $reset
+ * Set to TRUE after modifying the system table.
+ * @param $array
+ * Set to TRUE if you want to get information about all modules in the
+ * system.
+ * @return
+ * The currently installed schema version, or SCHEMA_UNINSTALLED if the
+ * module is not installed.
+ */
+function drupal_get_installed_schema_version($module, $reset = FALSE, $array = FALSE) {
+ static $versions = array();
+
+ if ($reset) {
+ $versions = array();
+ }
+
+ if (!$versions) {
+ $versions = array();
+ $result = db_query("SELECT name, schema_version FROM {system} WHERE type = :type", array(':type' => 'module'));
+ foreach ($result as $row) {
+ $versions[$row->name] = $row->schema_version;
+ }
+ }
+
+ if ($array) {
+ return $versions;
+ }
+ else {
+ return isset($versions[$module]) ? $versions[$module] : SCHEMA_UNINSTALLED;
+ }
+}
+
+/**
+ * Update the installed version information for a module.
+ *
+ * @param $module
+ * A module name.
+ * @param $version
+ * The new schema version.
+ */
+function drupal_set_installed_schema_version($module, $version) {
+ db_update('system')
+ ->fields(array('schema_version' => $version))
+ ->condition('name', $module)
+ ->execute();
+
+ // Reset the static cache of module schema versions.
+ drupal_get_installed_schema_version(NULL, TRUE);
+}
+
+/**
+ * Loads the install profile, extracting its defined distribution name.
+ *
+ * @return
+ * The distribution name defined in the profile's .info file. Defaults to
+ * "Drupal" if none is explicitly provided by the install profile.
+ *
+ * @see install_profile_info()
+ */
+function drupal_install_profile_distribution_name() {
+ // During installation, the profile information is stored in the global
+ // installation state (it might not be saved anywhere yet).
+ if (drupal_installation_attempted()) {
+ global $install_state;
+ return $install_state['profile_info']['distribution_name'];
+ }
+ // At all other times, we load the profile via standard methods.
+ else {
+ $profile = drupal_get_profile();
+ $info = install_profile_info($profile);
+ return $info['distribution_name'];
+ }
+}
+
+/**
+ * Auto detect the base_url with PHP predefined variables.
+ *
+ * @param $file
+ * The name of the file calling this function so we can strip it out of
+ * the URI when generating the base_url.
+ * @return
+ * The auto-detected $base_url that should be configured in settings.php
+ */
+function drupal_detect_baseurl($file = 'install.php') {
+ $proto = $_SERVER['HTTPS'] ? 'https://' : 'http://';
+ $host = $_SERVER['SERVER_NAME'];
+ $port = ($_SERVER['SERVER_PORT'] == 80 ? '' : ':' . $_SERVER['SERVER_PORT']);
+ $uri = preg_replace("/\?.*/", '', $_SERVER['REQUEST_URI']);
+ $dir = str_replace("/$file", '', $uri);
+
+ return "$proto$host$port$dir";
+}
+
+/**
+ * Detect all supported databases that are compiled into PHP.
+ *
+ * @return
+ * An array of database types compiled into PHP.
+ */
+function drupal_detect_database_types() {
+ $databases = drupal_get_database_types();
+
+ foreach ($databases as $driver => $installer) {
+ $databases[$driver] = $installer->name();
+ }
+
+ return $databases;
+}
+
+/**
+ * Return all supported database installer objects that are compiled into PHP.
+ *
+ * @return
+ * An array of database installer objects compiled into PHP.
+ */
+function drupal_get_database_types() {
+ $databases = array();
+
+ // We define a driver as a directory in /includes/database that in turn
+ // contains a database.inc file. That allows us to drop in additional drivers
+ // without modifying the installer.
+ // Because we have no registry yet, we need to also include the install.inc
+ // file for the driver explicitly.
+ require_once DRUPAL_ROOT . '/includes/database/database.inc';
+ foreach (file_scan_directory(DRUPAL_ROOT . '/includes/database', '/^[a-z]*$/i', array('recurse' => FALSE)) as $file) {
+ if (file_exists($file->uri . '/database.inc') && file_exists($file->uri . '/install.inc')) {
+ $drivers[$file->filename] = $file->uri;
+ }
+ }
+
+ foreach ($drivers as $driver => $file) {
+ $installer = db_installer_object($driver);
+ if ($installer->installable()) {
+ $databases[$driver] = $installer;
+ }
+ }
+
+ // Usability: unconditionally put the MySQL driver on top.
+ if (isset($databases['mysql'])) {
+ $mysql_database = $databases['mysql'];
+ unset($databases['mysql']);
+ $databases = array('mysql' => $mysql_database) + $databases;
+ }
+
+ return $databases;
+}
+
+/**
+ * Database installer structure.
+ *
+ * Defines basic Drupal requirements for databases.
+ */
+abstract class DatabaseTasks {
+
+ /**
+ * Structure that describes each task to run.
+ *
+ * @var array
+ *
+ * Each value of the tasks array is an associative array defining the function
+ * to call (optional) and any arguments to be passed to the function.
+ */
+ protected $tasks = array(
+ array(
+ 'function' => 'checkEngineVersion',
+ 'arguments' => array(),
+ ),
+ array(
+ 'arguments' => array(
+ 'CREATE TABLE {drupal_install_test} (id int NULL)',
+ 'Drupal can use CREATE TABLE database commands.',
+ 'Failed to CREATE a test table on your database server with the command %query. The server reports the following message: %error.Are you sure the configured username has the necessary permissions to create tables in the database?
',
+ TRUE,
+ ),
+ ),
+ array(
+ 'arguments' => array(
+ 'INSERT INTO {drupal_install_test} (id) VALUES (1)',
+ 'Drupal can use INSERT database commands.',
+ 'Failed to INSERT a value into a test table on your database server. We tried inserting a value with the command %query and the server reported the following error: %error.',
+ ),
+ ),
+ array(
+ 'arguments' => array(
+ 'UPDATE {drupal_install_test} SET id = 2',
+ 'Drupal can use UPDATE database commands.',
+ 'Failed to UPDATE a value in a test table on your database server. We tried updating a value with the command %query and the server reported the following error: %error.',
+ ),
+ ),
+ array(
+ 'arguments' => array(
+ 'DELETE FROM {drupal_install_test}',
+ 'Drupal can use DELETE database commands.',
+ 'Failed to DELETE a value from a test table on your database server. We tried deleting a value with the command %query and the server reported the following error: %error.',
+ ),
+ ),
+ array(
+ 'arguments' => array(
+ 'DROP TABLE {drupal_install_test}',
+ 'Drupal can use DROP TABLE database commands.',
+ 'Failed to DROP a test table from your database server. We tried dropping a table with the command %query and the server reported the following error %error.',
+ ),
+ ),
+ );
+
+ /**
+ * Results from tasks.
+ *
+ * @var array
+ */
+ protected $results = array();
+
+ /**
+ * Ensure the PDO driver is supported by the version of PHP in use.
+ */
+ protected function hasPdoDriver() {
+ return in_array($this->pdoDriver, PDO::getAvailableDrivers());
+ }
+
+ /**
+ * Assert test as failed.
+ */
+ protected function fail($message) {
+ $this->results[$message] = FALSE;
+ }
+
+ /**
+ * Assert test as a pass.
+ */
+ protected function pass($message) {
+ $this->results[$message] = TRUE;
+ }
+
+ /**
+ * Check whether Drupal is installable on the database.
+ */
+ public function installable() {
+ return $this->hasPdoDriver() && empty($this->error);
+ }
+
+ /**
+ * Return the human-readable name of the driver.
+ */
+ abstract public function name();
+
+ /**
+ * Return the minimum required version of the engine.
+ *
+ * @return
+ * A version string. If not NULL, it will be checked against the version
+ * reported by the Database engine using version_compare().
+ */
+ public function minimumVersion() {
+ return NULL;
+ }
+
+ /**
+ * Run database tasks and tests to see if Drupal can run on the database.
+ */
+ public function runTasks() {
+ // We need to establish a connection before we can run tests.
+ if ($this->connect()) {
+ foreach ($this->tasks as $task) {
+ if (!isset($task['function'])) {
+ $task['function'] = 'runTestQuery';
+ }
+ if (method_exists($this, $task['function'])) {
+ // Returning false is fatal. No other tasks can run.
+ if (FALSE === call_user_func_array(array($this, $task['function']), $task['arguments'])) {
+ break;
+ }
+ }
+ else {
+ throw new DatabaseTaskException(st("Failed to run all tasks against the database server. The task %task wasn't found.", array('%task' => $task['function'])));
+ }
+ }
+ }
+ // Check for failed results and compile message
+ $message = '';
+ foreach ($this->results as $result => $success) {
+ if (!$success) {
+ $message .= '' . $result . '
';
+ }
+ }
+ if (!empty($message)) {
+ $message = 'In order for Drupal to work, and to continue with the installation process, you must resolve all issues reported below. For more help with configuring your database server, see the installation handbook . If you are unsure what any of this means you should probably contact your hosting provider.
' . $message;
+ throw new DatabaseTaskException($message);
+ }
+ }
+
+ /**
+ * Check if we can connect to the database.
+ */
+ protected function connect() {
+ try {
+ // This doesn't actually test the connection.
+ db_set_active();
+ // Now actually do a check.
+ Database::getConnection();
+ $this->pass('Drupal can CONNECT to the database ok.');
+ }
+ catch (Exception $e) {
+ $this->fail(st('Failed to connect to your database server. The server reports the following message: %error.Is the database server running? Does the database exist, and have you entered the correct database name? Have you entered the correct username and password? Have you entered the correct database hostname? ', array('%error' => $e->getMessage())));
+ return FALSE;
+ }
+ return TRUE;
+ }
+
+ /**
+ * Run SQL tests to ensure the database can execute commands with the current user.
+ */
+ protected function runTestQuery($query, $pass, $fail, $fatal = FALSE) {
+ try {
+ db_query($query);
+ $this->pass(st($pass));
+ }
+ catch (Exception $e) {
+ $this->fail(st($fail, array('%query' => $query, '%error' => $e->getMessage(), '%name' => $this->name())));
+ return !$fatal;
+ }
+ }
+
+ /**
+ * Check the engine version.
+ */
+ protected function checkEngineVersion() {
+ if ($this->minimumVersion() && version_compare(Database::getConnection()->version(), $this->minimumVersion(), '<')) {
+ $this->fail(st("The database version %version is less than the minimum required version %minimum_version.", array('%version' => Database::getConnection()->version(), '%minimum_version' => $this->minimumVersion())));
+ }
+ }
+
+ /**
+ * Return driver specific configuration options.
+ *
+ * @param $database
+ * An array of driver specific configuration options.
+ *
+ * @return
+ * The options form array.
+ */
+ public function getFormOptions($database) {
+ $form['database'] = array(
+ '#type' => 'textfield',
+ '#title' => st('Database name'),
+ '#default_value' => empty($database['database']) ? '' : $database['database'],
+ '#size' => 45,
+ '#required' => TRUE,
+ '#description' => st('The name of the database your @drupal data will be stored in. It must exist on your server before @drupal can be installed.', array('@drupal' => drupal_install_profile_distribution_name())),
+ );
+
+ $form['username'] = array(
+ '#type' => 'textfield',
+ '#title' => st('Database username'),
+ '#default_value' => empty($database['username']) ? '' : $database['username'],
+ '#required' => TRUE,
+ '#size' => 45,
+ );
+
+ $form['password'] = array(
+ '#type' => 'password',
+ '#title' => st('Database password'),
+ '#default_value' => empty($database['password']) ? '' : $database['password'],
+ '#required' => FALSE,
+ '#size' => 45,
+ );
+
+ $form['advanced_options'] = array(
+ '#type' => 'fieldset',
+ '#title' => st('Advanced options'),
+ '#collapsible' => TRUE,
+ '#collapsed' => TRUE,
+ '#description' => st("These options are only necessary for some sites. If you're not sure what you should enter here, leave the default settings or check with your hosting provider."),
+ '#weight' => 10,
+ );
+
+ $profile = drupal_get_profile();
+ $db_prefix = ($profile == 'standard') ? 'drupal_' : $profile . '_';
+ $form['advanced_options']['db_prefix'] = array(
+ '#type' => 'textfield',
+ '#title' => st('Table prefix'),
+ '#default_value' => '',
+ '#size' => 45,
+ '#description' => st('If more than one application will be sharing this database, enter a table prefix such as %prefix for your @drupal site here.', array('@drupal' => drupal_install_profile_distribution_name(), '%prefix' => $db_prefix)),
+ '#weight' => 10,
+ );
+
+ $form['advanced_options']['host'] = array(
+ '#type' => 'textfield',
+ '#title' => st('Database host'),
+ '#default_value' => empty($database['host']) ? 'localhost' : $database['host'],
+ '#size' => 45,
+ // Hostnames can be 255 characters long.
+ '#maxlength' => 255,
+ '#required' => TRUE,
+ '#description' => st('If your database is located on a different server, change this.'),
+ );
+
+ $form['advanced_options']['port'] = array(
+ '#type' => 'textfield',
+ '#title' => st('Database port'),
+ '#default_value' => empty($database['port']) ? '' : $database['port'],
+ '#size' => 45,
+ // The maximum port number is 65536, 5 digits.
+ '#maxlength' => 5,
+ '#description' => st('If your database server is listening to a non-standard port, enter its number.'),
+ );
+
+ return $form;
+ }
+
+ /**
+ * Validates driver specific configuration settings.
+ *
+ * Checks to ensure correct basic database settings and that a proper
+ * connection to the database can be established.
+ *
+ * @param $database
+ * An array of driver specific configuration options.
+ *
+ * @return
+ * An array of driver configuration errors, keyed by form element name.
+ */
+ public function validateDatabaseSettings($database) {
+ $errors = array();
+
+ // Verify the table prefix.
+ if (!empty($database['prefix']) && is_string($database['prefix']) && !preg_match('/^[A-Za-z0-9_.]+$/', $database['prefix'])) {
+ $errors[$database['driver'] . '][advanced_options][db_prefix'] = st('The database table prefix you have entered, %prefix, is invalid. The table prefix can only contain alphanumeric characters, periods, or underscores.', array('%prefix' => $database['prefix']));
+ }
+
+ // Verify the database port.
+ if (!empty($database['port']) && !is_numeric($database['port'])) {
+ $errors[$database['driver'] . '][advanced_options][port'] = st('Database port must be a number.');
+ }
+
+ return $errors;
+ }
+
+}
+
+/**
+ * @class Exception class used to throw error if the DatabaseInstaller fails.
+ */
+class DatabaseTaskException extends Exception {
+}
+
+/**
+ * Replace values in settings.php with values in the submitted array.
+ *
+ * @param $settings
+ * An array of settings that need to be updated.
+ */
+function drupal_rewrite_settings($settings = array(), $prefix = '') {
+ $default_settings = 'sites/default/default.settings.php';
+ drupal_static_reset('conf_path');
+ $settings_file = conf_path(FALSE) . '/' . $prefix . 'settings.php';
+
+ // Build list of setting names and insert the values into the global namespace.
+ $keys = array();
+ foreach ($settings as $setting => $data) {
+ $GLOBALS[$setting] = $data['value'];
+ $keys[] = $setting;
+ }
+
+ $buffer = NULL;
+ $first = TRUE;
+ if ($fp = fopen(DRUPAL_ROOT . '/' . $default_settings, 'r')) {
+ // Step line by line through settings.php.
+ while (!feof($fp)) {
+ $line = fgets($fp);
+ if ($first && substr($line, 0, 5) != ' $data) {
+ if ($data['required']) {
+ $buffer .= "\$$setting = " . var_export($data['value'], TRUE) . ";\n";
+ }
+ }
+
+ $fp = fopen(DRUPAL_ROOT . '/' . $settings_file, 'w');
+ if ($fp && fwrite($fp, $buffer) === FALSE) {
+ throw new Exception(st('Failed to modify %settings. Verify the file permissions.', array('%settings' => $settings_file)));
+ }
+ }
+ else {
+ throw new Exception(st('Failed to open %settings. Verify the file permissions.', array('%settings' => $default_settings)));
+ }
+}
+
+/**
+ * Verify an install profile for installation.
+ *
+ * @param $install_state
+ * An array of information about the current installation state.
+ * @return
+ * The list of modules to install.
+ */
+function drupal_verify_profile($install_state) {
+ $profile = $install_state['parameters']['profile'];
+ $locale = $install_state['parameters']['locale'];
+
+ include_once DRUPAL_ROOT . '/includes/file.inc';
+ include_once DRUPAL_ROOT . '/includes/common.inc';
+
+ $profile_file = DRUPAL_ROOT . "/profiles/$profile/$profile.profile";
+
+ if (!isset($profile) || !file_exists($profile_file)) {
+ throw new Exception(install_no_profile_error());
+ }
+ $info = $install_state['profile_info'];
+
+ // Get a list of modules that exist in Drupal's assorted subdirectories.
+ $present_modules = array();
+ foreach (drupal_system_listing('/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.module$/', 'modules', 'name', 0) as $present_module) {
+ $present_modules[] = $present_module->name;
+ }
+
+ // The install profile is also a module, which needs to be installed after all the other dependencies
+ // have been installed.
+ $present_modules[] = drupal_get_profile();
+
+ // Verify that all of the profile's required modules are present.
+ $missing_modules = array_diff($info['dependencies'], $present_modules);
+
+ $requirements = array();
+
+ if (count($missing_modules)) {
+ $modules = array();
+ foreach ($missing_modules as $module) {
+ $modules[] = '' . drupal_ucfirst($module) . ' ';
+ }
+ $requirements['required_modules'] = array(
+ 'title' => st('Required modules'),
+ 'value' => st('Required modules not found.'),
+ 'severity' => REQUIREMENT_ERROR,
+ 'description' => st('The following modules are required but were not found. Move them into the appropriate modules subdirectory, such as sites/all/modules . Missing modules: !modules', array('!modules' => implode(', ', $modules))),
+ );
+ }
+ return $requirements;
+}
+
+/**
+ * Callback to install the system module.
+ *
+ * Separated from the installation of other modules so core system
+ * functions can be made available while other modules are installed.
+ */
+function drupal_install_system() {
+ $system_path = drupal_get_path('module', 'system');
+ require_once DRUPAL_ROOT . '/' . $system_path . '/system.install';
+ module_invoke('system', 'install');
+
+ $system_versions = drupal_get_schema_versions('system');
+ $system_version = $system_versions ? max($system_versions) : SCHEMA_INSTALLED;
+ db_insert('system')
+ ->fields(array('filename', 'name', 'type', 'owner', 'status', 'schema_version', 'bootstrap'))
+ ->values(array(
+ 'filename' => $system_path . '/system.module',
+ 'name' => 'system',
+ 'type' => 'module',
+ 'owner' => '',
+ 'status' => 1,
+ 'schema_version' => $system_version,
+ 'bootstrap' => 0,
+ ))
+ ->execute();
+ system_rebuild_module_data();
+}
+
+/**
+ * Uninstalls a given list of modules.
+ *
+ * @param $module_list
+ * The modules to uninstall.
+ * @param $uninstall_dependents
+ * If TRUE, the function will check that all modules which depend on the
+ * passed-in module list either are already uninstalled or contained in the
+ * list, and it will ensure that the modules are uninstalled in the correct
+ * order. This incurs a significant performance cost, so use FALSE if you
+ * know $module_list is already complete and in the correct order.
+ *
+ * @return
+ * FALSE if one or more dependent modules are missing from the list, TRUE
+ * otherwise.
+ */
+function drupal_uninstall_modules($module_list = array(), $uninstall_dependents = TRUE) {
+ if ($uninstall_dependents) {
+ // Get all module data so we can find dependents and sort.
+ $module_data = system_rebuild_module_data();
+ // Create an associative array with weights as values.
+ $module_list = array_flip(array_values($module_list));
+
+ $profile = drupal_get_profile();
+ while (list($module) = each($module_list)) {
+ if (!isset($module_data[$module]) || drupal_get_installed_schema_version($module) == SCHEMA_UNINSTALLED) {
+ // This module doesn't exist or is already uninstalled, skip it.
+ unset($module_list[$module]);
+ continue;
+ }
+ $module_list[$module] = $module_data[$module]->sort;
+
+ // If the module has any dependents which are not already uninstalled and
+ // not included in the passed-in list, abort. It is not safe to uninstall
+ // them automatically because uninstalling a module is a destructive
+ // operation.
+ foreach (array_keys($module_data[$module]->required_by) as $dependent) {
+ if (!isset($module_list[$dependent]) && drupal_get_installed_schema_version($dependent) != SCHEMA_UNINSTALLED && $dependent != $profile) {
+ return FALSE;
+ }
+ }
+ }
+
+ // Sort the module list by pre-calculated weights.
+ asort($module_list);
+ $module_list = array_keys($module_list);
+ }
+
+ foreach ($module_list as $module) {
+ // First, retrieve all the module's menu paths from db.
+ drupal_load('module', $module);
+ $paths = module_invoke($module, 'menu');
+
+ // Uninstall the module.
+ module_load_install($module);
+ module_invoke($module, 'uninstall');
+ drupal_uninstall_schema($module);
+
+ watchdog('system', '%module module uninstalled.', array('%module' => $module), WATCHDOG_INFO);
+
+ // Now remove the menu links for all paths declared by this module.
+ if (!empty($paths)) {
+ $paths = array_keys($paths);
+ // Clean out the names of load functions.
+ foreach ($paths as $index => $path) {
+ $parts = explode('/', $path, MENU_MAX_PARTS);
+ foreach ($parts as $k => $part) {
+ if (preg_match('/^%[a-z_]*$/', $part)) {
+ $parts[$k] = '%';
+ }
+ }
+ $paths[$index] = implode('/', $parts);
+ }
+
+ $result = db_select('menu_links')
+ ->fields('menu_links')
+ ->condition('router_path', $paths, 'IN')
+ ->condition('external', 0)
+ ->orderBy('depth')
+ ->execute();
+ // Remove all such items. Starting from those with the greatest depth will
+ // minimize the amount of re-parenting done by menu_link_delete().
+ foreach ($result as $item) {
+ _menu_delete_item($item, TRUE);
+ }
+ }
+
+ drupal_set_installed_schema_version($module, SCHEMA_UNINSTALLED);
+ }
+
+ if (!empty($module_list)) {
+ // Call hook_module_uninstall to let other modules act
+ module_invoke_all('modules_uninstalled', $module_list);
+ }
+
+ return TRUE;
+}
+
+/**
+ * Verify the state of the specified file.
+ *
+ * @param $file
+ * The file to check for.
+ * @param $mask
+ * An optional bitmask created from various FILE_* constants.
+ * @param $type
+ * The type of file. Can be file (default), dir, or link.
+ * @return
+ * TRUE on success or FALSE on failure. A message is set for the latter.
+ */
+function drupal_verify_install_file($file, $mask = NULL, $type = 'file') {
+ $return = TRUE;
+ // Check for files that shouldn't be there.
+ if (isset($mask) && ($mask & FILE_NOT_EXIST) && file_exists($file)) {
+ return FALSE;
+ }
+ // Verify that the file is the type of file it is supposed to be.
+ if (isset($type) && file_exists($file)) {
+ $check = 'is_' . $type;
+ if (!function_exists($check) || !$check($file)) {
+ $return = FALSE;
+ }
+ }
+
+ // Verify file permissions.
+ if (isset($mask)) {
+ $masks = array(FILE_EXIST, FILE_READABLE, FILE_WRITABLE, FILE_EXECUTABLE, FILE_NOT_READABLE, FILE_NOT_WRITABLE, FILE_NOT_EXECUTABLE);
+ foreach ($masks as $current_mask) {
+ if ($mask & $current_mask) {
+ switch ($current_mask) {
+ case FILE_EXIST:
+ if (!file_exists($file)) {
+ if ($type == 'dir') {
+ drupal_install_mkdir($file, $mask);
+ }
+ if (!file_exists($file)) {
+ $return = FALSE;
+ }
+ }
+ break;
+ case FILE_READABLE:
+ if (!is_readable($file) && !drupal_install_fix_file($file, $mask)) {
+ $return = FALSE;
+ }
+ break;
+ case FILE_WRITABLE:
+ if (!is_writable($file) && !drupal_install_fix_file($file, $mask)) {
+ $return = FALSE;
+ }
+ break;
+ case FILE_EXECUTABLE:
+ if (!is_executable($file) && !drupal_install_fix_file($file, $mask)) {
+ $return = FALSE;
+ }
+ break;
+ case FILE_NOT_READABLE:
+ if (is_readable($file) && !drupal_install_fix_file($file, $mask)) {
+ $return = FALSE;
+ }
+ break;
+ case FILE_NOT_WRITABLE:
+ if (is_writable($file) && !drupal_install_fix_file($file, $mask)) {
+ $return = FALSE;
+ }
+ break;
+ case FILE_NOT_EXECUTABLE:
+ if (is_executable($file) && !drupal_install_fix_file($file, $mask)) {
+ $return = FALSE;
+ }
+ break;
+ }
+ }
+ }
+ }
+ return $return;
+}
+
+/**
+ * Create a directory with specified permissions.
+ *
+ * @param $file
+ * The name of the directory to create;
+ * @param $mask
+ * The permissions of the directory to create.
+ * @param $message
+ * (optional) Whether to output messages. Defaults to TRUE.
+ * @return
+ * TRUE/FALSE whether or not the directory was successfully created.
+ */
+function drupal_install_mkdir($file, $mask, $message = TRUE) {
+ $mod = 0;
+ $masks = array(FILE_READABLE, FILE_WRITABLE, FILE_EXECUTABLE, FILE_NOT_READABLE, FILE_NOT_WRITABLE, FILE_NOT_EXECUTABLE);
+ foreach ($masks as $m) {
+ if ($mask & $m) {
+ switch ($m) {
+ case FILE_READABLE:
+ $mod |= 0444;
+ break;
+ case FILE_WRITABLE:
+ $mod |= 0222;
+ break;
+ case FILE_EXECUTABLE:
+ $mod |= 0111;
+ break;
+ }
+ }
+ }
+
+ if (@drupal_mkdir($file, $mod)) {
+ return TRUE;
+ }
+ else {
+ return FALSE;
+ }
+}
+
+/**
+ * Attempt to fix file permissions.
+ *
+ * The general approach here is that, because we do not know the security
+ * setup of the webserver, we apply our permission changes to all three
+ * digits of the file permission (i.e. user, group and all).
+ *
+ * To ensure that the values behave as expected (and numbers don't carry
+ * from one digit to the next) we do the calculation on the octal value
+ * using bitwise operations. This lets us remove, for example, 0222 from
+ * 0700 and get the correct value of 0500.
+ *
+ * @param $file
+ * The name of the file with permissions to fix.
+ * @param $mask
+ * The desired permissions for the file.
+ * @param $message
+ * (optional) Whether to output messages. Defaults to TRUE.
+ * @return
+ * TRUE/FALSE whether or not we were able to fix the file's permissions.
+ */
+function drupal_install_fix_file($file, $mask, $message = TRUE) {
+ // If $file does not exist, fileperms() issues a PHP warning.
+ if (!file_exists($file)) {
+ return FALSE;
+ }
+
+ $mod = fileperms($file) & 0777;
+ $masks = array(FILE_READABLE, FILE_WRITABLE, FILE_EXECUTABLE, FILE_NOT_READABLE, FILE_NOT_WRITABLE, FILE_NOT_EXECUTABLE);
+
+ // FILE_READABLE, FILE_WRITABLE, and FILE_EXECUTABLE permission strings
+ // can theoretically be 0400, 0200, and 0100 respectively, but to be safe
+ // we set all three access types in case the administrator intends to
+ // change the owner of settings.php after installation.
+ foreach ($masks as $m) {
+ if ($mask & $m) {
+ switch ($m) {
+ case FILE_READABLE:
+ if (!is_readable($file)) {
+ $mod |= 0444;
+ }
+ break;
+ case FILE_WRITABLE:
+ if (!is_writable($file)) {
+ $mod |= 0222;
+ }
+ break;
+ case FILE_EXECUTABLE:
+ if (!is_executable($file)) {
+ $mod |= 0111;
+ }
+ break;
+ case FILE_NOT_READABLE:
+ if (is_readable($file)) {
+ $mod &= ~0444;
+ }
+ break;
+ case FILE_NOT_WRITABLE:
+ if (is_writable($file)) {
+ $mod &= ~0222;
+ }
+ break;
+ case FILE_NOT_EXECUTABLE:
+ if (is_executable($file)) {
+ $mod &= ~0111;
+ }
+ break;
+ }
+ }
+ }
+
+ // chmod() will work if the web server is running as owner of the file.
+ // If PHP safe_mode is enabled the currently executing script must also
+ // have the same owner.
+ if (@chmod($file, $mod)) {
+ return TRUE;
+ }
+ else {
+ return FALSE;
+ }
+}
+
+
+/**
+ * Send the user to a different installer page.
+ *
+ * This issues an on-site HTTP redirect. Messages (and errors) are erased.
+ *
+ * @param $path
+ * An installer path.
+ */
+function install_goto($path) {
+ global $base_url;
+ include_once DRUPAL_ROOT . '/includes/common.inc';
+ header('Location: ' . $base_url . '/' . $path);
+ header('Cache-Control: no-cache'); // Not a permanent redirect.
+ drupal_exit();
+}
+
+/**
+ * Functional equivalent of t(), used when some systems are not available.
+ *
+ * Used during the install process, when database, theme, and localization
+ * system is possibly not yet available.
+ *
+ * @see t()
+ * @ingroup sanitization
+ */
+function st($string, array $args = array(), array $options = array()) {
+ static $locale_strings = NULL;
+ global $install_state;
+
+ if (empty($options['context'])) {
+ $options['context'] = '';
+ }
+
+ if (!isset($locale_strings)) {
+ $locale_strings = array();
+ if (isset($install_state['parameters']['profile']) && isset($install_state['parameters']['locale'])) {
+ // If the given locale was selected, there should be at least one .po file
+ // with its name ending in {$install_state['parameters']['locale']}.po
+ // This might or might not be the entire filename. It is also possible
+ // that multiple files end with the same extension, even if unlikely.
+ $po_files = file_scan_directory('./profiles/' . $install_state['parameters']['profile'] . '/translations', '/'. $install_state['parameters']['locale'] .'\.po$/', array('recurse' => FALSE));
+ if (count($po_files)) {
+ require_once DRUPAL_ROOT . '/includes/locale.inc';
+ foreach ($po_files as $po_file) {
+ _locale_import_read_po('mem-store', $po_file);
+ }
+ $locale_strings = _locale_import_one_string('mem-report');
+ }
+ }
+ }
+
+ require_once DRUPAL_ROOT . '/includes/theme.inc';
+ // Transform arguments before inserting them
+ foreach ($args as $key => $value) {
+ switch ($key[0]) {
+ // Escaped only
+ case '@':
+ $args[$key] = check_plain($value);
+ break;
+ // Escaped and placeholder
+ case '%':
+ default:
+ $args[$key] = '' . check_plain($value) . ' ';
+ break;
+ // Pass-through
+ case '!':
+ }
+ }
+ return strtr((!empty($locale_strings[$options['context']][$string]) ? $locale_strings[$options['context']][$string] : $string), $args);
+}
+
+/**
+ * Check an install profile's requirements.
+ *
+ * @param $profile
+ * Name of install profile to check.
+ * @return
+ * Array of the install profile's requirements.
+ */
+function drupal_check_profile($profile) {
+ include_once DRUPAL_ROOT . '/includes/file.inc';
+
+ $profile_file = DRUPAL_ROOT . "/profiles/$profile/$profile.profile";
+
+ if (!isset($profile) || !file_exists($profile_file)) {
+ throw new Exception(install_no_profile_error());
+ }
+
+ $info = install_profile_info($profile);
+
+ // Collect requirement testing results.
+ $requirements = array();
+ foreach ($info['dependencies'] as $module) {
+ module_load_install($module);
+ $function = $module . '_requirements';
+ if (function_exists($function)) {
+ $requirements = array_merge($requirements, $function('install'));
+ }
+ }
+ return $requirements;
+}
+
+/**
+ * Extract highest severity from requirements array.
+ *
+ * @param $requirements
+ * An array of requirements, in the same format as is returned by
+ * hook_requirements().
+ * @return
+ * The highest severity in the array.
+ */
+function drupal_requirements_severity(&$requirements) {
+ $severity = REQUIREMENT_OK;
+ foreach ($requirements as $requirement) {
+ if (isset($requirement['severity'])) {
+ $severity = max($severity, $requirement['severity']);
+ }
+ }
+ return $severity;
+}
+
+/**
+ * Check a module's requirements.
+ *
+ * @param $module
+ * Machine name of module to check.
+ * @return
+ * TRUE/FALSE depending on the requirements are in place.
+ */
+function drupal_check_module($module) {
+ module_load_install($module);
+ if (module_hook($module, 'requirements')) {
+ // Check requirements
+ $requirements = module_invoke($module, 'requirements', 'install');
+ if (is_array($requirements) && drupal_requirements_severity($requirements) == REQUIREMENT_ERROR) {
+ // Print any error messages
+ foreach ($requirements as $requirement) {
+ if (isset($requirement['severity']) && $requirement['severity'] == REQUIREMENT_ERROR) {
+ $message = $requirement['description'];
+ if (isset($requirement['value']) && $requirement['value']) {
+ $message .= ' (' . t('Currently using !item !version', array('!item' => $requirement['title'], '!version' => $requirement['value'])) . ')';
+ }
+ drupal_set_message($message, 'error');
+ }
+ }
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+/**
+ * Retrieve info about an install profile from its .info file.
+ *
+ * The information stored in a profile .info file is similar to that stored in
+ * a normal Drupal module .info file. For example:
+ * - name: The real name of the install profile for display purposes.
+ * - description: A brief description of the profile.
+ * - dependencies: An array of shortnames of other modules this install profile requires.
+ *
+ * Additional, less commonly-used information that can appear in a profile.info
+ * file but not in a normal Drupal module .info file includes:
+ * - distribution_name: The name of the Drupal distribution that is being
+ * installed, to be shown throughout the installation process. Defaults to
+ * 'Drupal'.
+ *
+ * Example of .info file:
+ * @code
+ * name = Minimal
+ * description = Start fresh, with only a few modules enabled.
+ * dependencies[] = block
+ * dependencies[] = dblog
+ * @endcode
+ *
+ * @param profile
+ * Name of profile.
+ * @param locale
+ * Name of locale used (if any).
+ * @return
+ * The info array.
+ */
+function install_profile_info($profile, $locale = 'en') {
+ $cache = &drupal_static(__FUNCTION__, array());
+
+ if (!isset($cache[$profile])) {
+ // Set defaults for module info.
+ $defaults = array(
+ 'dependencies' => array(),
+ 'description' => '',
+ 'distribution_name' => 'Drupal',
+ 'version' => NULL,
+ 'hidden' => FALSE,
+ 'php' => DRUPAL_MINIMUM_PHP,
+ );
+ $info = drupal_parse_info_file("profiles/$profile/$profile.info") + $defaults;
+ $info['dependencies'] = array_unique(array_merge(
+ drupal_required_modules(),
+ $info['dependencies'],
+ ($locale != 'en' && !empty($locale) ? array('locale') : array()))
+ );
+
+ // drupal_required_modules() includes the current profile as a dependency.
+ // Since a module can't depend on itself we remove that element of the array.
+ array_shift($info['dependencies']);
+
+ $cache[$profile] = $info;
+ }
+ return $cache[$profile];
+}
+
+/**
+ * Ensures the environment for a Drupal database on a predefined connection.
+ *
+ * This will run tasks that check that Drupal can perform all of the functions
+ * on a database, that Drupal needs. Tasks include simple checks like CREATE
+ * TABLE to database specific functions like stored procedures and client
+ * encoding.
+ */
+function db_run_tasks($driver) {
+ db_installer_object($driver)->runTasks();
+ return TRUE;
+}
+
+/**
+ * Returns a database installer object.
+ *
+ * @param $driver
+ * The name of the driver.
+ */
+function db_installer_object($driver) {
+ Database::loadDriverFile($driver, array('install.inc'));
+ $task_class = 'DatabaseTasks_' . $driver;
+ return new $task_class();
+}
diff --git a/backup/modules/20110602035425/drupal/includes/iso.inc b/backup/modules/20110602035425/drupal/includes/iso.inc
new file mode 100644
index 00000000..5d8a09c0
--- /dev/null
+++ b/backup/modules/20110602035425/drupal/includes/iso.inc
@@ -0,0 +1,483 @@
+ country name pairs.
+ *
+ * Get an array of all country code => country name pairs as laid out
+ * in ISO 3166-1 alpha-2.
+ * Grabbed from location project (http://drupal.org/project/location).
+ * @return
+ * An array of all country code => country name pairs.
+ */
+function _country_get_predefined_list() {
+ static $countries;
+
+ if (isset($countries)) {
+ return $countries;
+ }
+ $t = get_t();
+
+ $countries = array(
+ 'AD' => $t('Andorra'),
+ 'AE' => $t('United Arab Emirates'),
+ 'AF' => $t('Afghanistan'),
+ 'AG' => $t('Antigua and Barbuda'),
+ 'AI' => $t('Anguilla'),
+ 'AL' => $t('Albania'),
+ 'AM' => $t('Armenia'),
+ 'AN' => $t('Netherlands Antilles'),
+ 'AO' => $t('Angola'),
+ 'AQ' => $t('Antarctica'),
+ 'AR' => $t('Argentina'),
+ 'AS' => $t('American Samoa'),
+ 'AT' => $t('Austria'),
+ 'AU' => $t('Australia'),
+ 'AW' => $t('Aruba'),
+ 'AX' => $t('Aland Islands'),
+ 'AZ' => $t('Azerbaijan'),
+ 'BA' => $t('Bosnia and Herzegovina'),
+ 'BB' => $t('Barbados'),
+ 'BD' => $t('Bangladesh'),
+ 'BE' => $t('Belgium'),
+ 'BF' => $t('Burkina Faso'),
+ 'BG' => $t('Bulgaria'),
+ 'BH' => $t('Bahrain'),
+ 'BI' => $t('Burundi'),
+ 'BJ' => $t('Benin'),
+ 'BL' => $t('Saint Barthélemy'),
+ 'BM' => $t('Bermuda'),
+ 'BN' => $t('Brunei'),
+ 'BO' => $t('Bolivia'),
+ 'BR' => $t('Brazil'),
+ 'BS' => $t('Bahamas'),
+ 'BT' => $t('Bhutan'),
+ 'BV' => $t('Bouvet Island'),
+ 'BW' => $t('Botswana'),
+ 'BY' => $t('Belarus'),
+ 'BZ' => $t('Belize'),
+ 'CA' => $t('Canada'),
+ 'CC' => $t('Cocos (Keeling) Islands'),
+ 'CD' => $t('Congo (Kinshasa)'),
+ 'CF' => $t('Central African Republic'),
+ 'CG' => $t('Congo (Brazzaville)'),
+ 'CH' => $t('Switzerland'),
+ 'CI' => $t('Ivory Coast'),
+ 'CK' => $t('Cook Islands'),
+ 'CL' => $t('Chile'),
+ 'CM' => $t('Cameroon'),
+ 'CN' => $t('China'),
+ 'CO' => $t('Colombia'),
+ 'CR' => $t('Costa Rica'),
+ 'CU' => $t('Cuba'),
+ 'CV' => $t('Cape Verde'),
+ 'CX' => $t('Christmas Island'),
+ 'CY' => $t('Cyprus'),
+ 'CZ' => $t('Czech Republic'),
+ 'DE' => $t('Germany'),
+ 'DJ' => $t('Djibouti'),
+ 'DK' => $t('Denmark'),
+ 'DM' => $t('Dominica'),
+ 'DO' => $t('Dominican Republic'),
+ 'DZ' => $t('Algeria'),
+ 'EC' => $t('Ecuador'),
+ 'EE' => $t('Estonia'),
+ 'EG' => $t('Egypt'),
+ 'EH' => $t('Western Sahara'),
+ 'ER' => $t('Eritrea'),
+ 'ES' => $t('Spain'),
+ 'ET' => $t('Ethiopia'),
+ 'FI' => $t('Finland'),
+ 'FJ' => $t('Fiji'),
+ 'FK' => $t('Falkland Islands'),
+ 'FM' => $t('Micronesia'),
+ 'FO' => $t('Faroe Islands'),
+ 'FR' => $t('France'),
+ 'GA' => $t('Gabon'),
+ 'GB' => $t('United Kingdom'),
+ 'GD' => $t('Grenada'),
+ 'GE' => $t('Georgia'),
+ 'GF' => $t('French Guiana'),
+ 'GG' => $t('Guernsey'),
+ 'GH' => $t('Ghana'),
+ 'GI' => $t('Gibraltar'),
+ 'GL' => $t('Greenland'),
+ 'GM' => $t('Gambia'),
+ 'GN' => $t('Guinea'),
+ 'GP' => $t('Guadeloupe'),
+ 'GQ' => $t('Equatorial Guinea'),
+ 'GR' => $t('Greece'),
+ 'GS' => $t('South Georgia and the South Sandwich Islands'),
+ 'GT' => $t('Guatemala'),
+ 'GU' => $t('Guam'),
+ 'GW' => $t('Guinea-Bissau'),
+ 'GY' => $t('Guyana'),
+ 'HK' => $t('Hong Kong S.A.R., China'),
+ 'HM' => $t('Heard Island and McDonald Islands'),
+ 'HN' => $t('Honduras'),
+ 'HR' => $t('Croatia'),
+ 'HT' => $t('Haiti'),
+ 'HU' => $t('Hungary'),
+ 'ID' => $t('Indonesia'),
+ 'IE' => $t('Ireland'),
+ 'IL' => $t('Israel'),
+ 'IM' => $t('Isle of Man'),
+ 'IN' => $t('India'),
+ 'IO' => $t('British Indian Ocean Territory'),
+ 'IQ' => $t('Iraq'),
+ 'IR' => $t('Iran'),
+ 'IS' => $t('Iceland'),
+ 'IT' => $t('Italy'),
+ 'JE' => $t('Jersey'),
+ 'JM' => $t('Jamaica'),
+ 'JO' => $t('Jordan'),
+ 'JP' => $t('Japan'),
+ 'KE' => $t('Kenya'),
+ 'KG' => $t('Kyrgyzstan'),
+ 'KH' => $t('Cambodia'),
+ 'KI' => $t('Kiribati'),
+ 'KM' => $t('Comoros'),
+ 'KN' => $t('Saint Kitts and Nevis'),
+ 'KP' => $t('North Korea'),
+ 'KR' => $t('South Korea'),
+ 'KW' => $t('Kuwait'),
+ 'KY' => $t('Cayman Islands'),
+ 'KZ' => $t('Kazakhstan'),
+ 'LA' => $t('Laos'),
+ 'LB' => $t('Lebanon'),
+ 'LC' => $t('Saint Lucia'),
+ 'LI' => $t('Liechtenstein'),
+ 'LK' => $t('Sri Lanka'),
+ 'LR' => $t('Liberia'),
+ 'LS' => $t('Lesotho'),
+ 'LT' => $t('Lithuania'),
+ 'LU' => $t('Luxembourg'),
+ 'LV' => $t('Latvia'),
+ 'LY' => $t('Libya'),
+ 'MA' => $t('Morocco'),
+ 'MC' => $t('Monaco'),
+ 'MD' => $t('Moldova'),
+ 'ME' => $t('Montenegro'),
+ 'MF' => $t('Saint Martin (French part)'),
+ 'MG' => $t('Madagascar'),
+ 'MH' => $t('Marshall Islands'),
+ 'MK' => $t('Macedonia'),
+ 'ML' => $t('Mali'),
+ 'MM' => $t('Myanmar'),
+ 'MN' => $t('Mongolia'),
+ 'MO' => $t('Macao S.A.R., China'),
+ 'MP' => $t('Northern Mariana Islands'),
+ 'MQ' => $t('Martinique'),
+ 'MR' => $t('Mauritania'),
+ 'MS' => $t('Montserrat'),
+ 'MT' => $t('Malta'),
+ 'MU' => $t('Mauritius'),
+ 'MV' => $t('Maldives'),
+ 'MW' => $t('Malawi'),
+ 'MX' => $t('Mexico'),
+ 'MY' => $t('Malaysia'),
+ 'MZ' => $t('Mozambique'),
+ 'NA' => $t('Namibia'),
+ 'NC' => $t('New Caledonia'),
+ 'NE' => $t('Niger'),
+ 'NF' => $t('Norfolk Island'),
+ 'NG' => $t('Nigeria'),
+ 'NI' => $t('Nicaragua'),
+ 'NL' => $t('Netherlands'),
+ 'NO' => $t('Norway'),
+ 'NP' => $t('Nepal'),
+ 'NR' => $t('Nauru'),
+ 'NU' => $t('Niue'),
+ 'NZ' => $t('New Zealand'),
+ 'OM' => $t('Oman'),
+ 'PA' => $t('Panama'),
+ 'PE' => $t('Peru'),
+ 'PF' => $t('French Polynesia'),
+ 'PG' => $t('Papua New Guinea'),
+ 'PH' => $t('Philippines'),
+ 'PK' => $t('Pakistan'),
+ 'PL' => $t('Poland'),
+ 'PM' => $t('Saint Pierre and Miquelon'),
+ 'PN' => $t('Pitcairn'),
+ 'PR' => $t('Puerto Rico'),
+ 'PS' => $t('Palestinian Territory'),
+ 'PT' => $t('Portugal'),
+ 'PW' => $t('Palau'),
+ 'PY' => $t('Paraguay'),
+ 'QA' => $t('Qatar'),
+ 'RE' => $t('Reunion'),
+ 'RO' => $t('Romania'),
+ 'RS' => $t('Serbia'),
+ 'RU' => $t('Russia'),
+ 'RW' => $t('Rwanda'),
+ 'SA' => $t('Saudi Arabia'),
+ 'SB' => $t('Solomon Islands'),
+ 'SC' => $t('Seychelles'),
+ 'SD' => $t('Sudan'),
+ 'SE' => $t('Sweden'),
+ 'SG' => $t('Singapore'),
+ 'SH' => $t('Saint Helena'),
+ 'SI' => $t('Slovenia'),
+ 'SJ' => $t('Svalbard and Jan Mayen'),
+ 'SK' => $t('Slovakia'),
+ 'SL' => $t('Sierra Leone'),
+ 'SM' => $t('San Marino'),
+ 'SN' => $t('Senegal'),
+ 'SO' => $t('Somalia'),
+ 'SR' => $t('Suriname'),
+ 'ST' => $t('Sao Tome and Principe'),
+ 'SV' => $t('El Salvador'),
+ 'SY' => $t('Syria'),
+ 'SZ' => $t('Swaziland'),
+ 'TC' => $t('Turks and Caicos Islands'),
+ 'TD' => $t('Chad'),
+ 'TF' => $t('French Southern Territories'),
+ 'TG' => $t('Togo'),
+ 'TH' => $t('Thailand'),
+ 'TJ' => $t('Tajikistan'),
+ 'TK' => $t('Tokelau'),
+ 'TL' => $t('Timor-Leste'),
+ 'TM' => $t('Turkmenistan'),
+ 'TN' => $t('Tunisia'),
+ 'TO' => $t('Tonga'),
+ 'TR' => $t('Turkey'),
+ 'TT' => $t('Trinidad and Tobago'),
+ 'TV' => $t('Tuvalu'),
+ 'TW' => $t('Taiwan'),
+ 'TZ' => $t('Tanzania'),
+ 'UA' => $t('Ukraine'),
+ 'UG' => $t('Uganda'),
+ 'UM' => $t('United States Minor Outlying Islands'),
+ 'US' => $t('United States'),
+ 'UY' => $t('Uruguay'),
+ 'UZ' => $t('Uzbekistan'),
+ 'VA' => $t('Vatican'),
+ 'VC' => $t('Saint Vincent and the Grenadines'),
+ 'VE' => $t('Venezuela'),
+ 'VG' => $t('British Virgin Islands'),
+ 'VI' => $t('U.S. Virgin Islands'),
+ 'VN' => $t('Vietnam'),
+ 'VU' => $t('Vanuatu'),
+ 'WF' => $t('Wallis and Futuna'),
+ 'WS' => $t('Samoa'),
+ 'YE' => $t('Yemen'),
+ 'YT' => $t('Mayotte'),
+ 'ZA' => $t('South Africa'),
+ 'ZM' => $t('Zambia'),
+ 'ZW' => $t('Zimbabwe'),
+ );
+
+ // Sort the list.
+ natcasesort($countries);
+
+ return $countries;
+}
+
+/**
+ * @ingroup locale-api-predefined List of predefined languages
+ * @{
+ */
+
+/**
+ * Some of the common languages with their English and native names
+ *
+ * Based on ISO 639 and http://people.w3.org/rishida/names/languages.html
+ */
+function _locale_get_predefined_list() {
+ return array(
+ 'aa' => array('Afar'),
+ 'ab' => array('Abkhazian', 'аҧсуа бызшәа'),
+ 'ae' => array('Avestan'),
+ 'af' => array('Afrikaans'),
+ 'ak' => array('Akan'),
+ 'am' => array('Amharic', 'አማርኛ'),
+ 'ar' => array('Arabic', /* Left-to-right marker "" */ 'العربية', LANGUAGE_RTL),
+ 'as' => array('Assamese'),
+ 'ast' => array('Asturian'),
+ 'av' => array('Avar'),
+ 'ay' => array('Aymara'),
+ 'az' => array('Azerbaijani', 'azərbaycan'),
+ 'ba' => array('Bashkir'),
+ 'be' => array('Belarusian', 'Беларуская'),
+ 'bg' => array('Bulgarian', 'Български'),
+ 'bh' => array('Bihari'),
+ 'bi' => array('Bislama'),
+ 'bm' => array('Bambara', 'Bamanankan'),
+ 'bn' => array('Bengali'),
+ 'bo' => array('Tibetan'),
+ 'br' => array('Breton'),
+ 'bs' => array('Bosnian', 'Bosanski'),
+ 'ca' => array('Catalan', 'Català'),
+ 'ce' => array('Chechen'),
+ 'ch' => array('Chamorro'),
+ 'co' => array('Corsican'),
+ 'cr' => array('Cree'),
+ 'cs' => array('Czech', 'Čeština'),
+ 'cu' => array('Old Slavonic'),
+ 'cv' => array('Chuvash'),
+ 'cy' => array('Welsh', 'Cymraeg'),
+ 'da' => array('Danish', 'Dansk'),
+ 'de' => array('German', 'Deutsch'),
+ 'dv' => array('Maldivian'),
+ 'dz' => array('Bhutani'),
+ 'ee' => array('Ewe', 'Ɛʋɛ'),
+ 'el' => array('Greek', 'Ελληνικά'),
+ 'en' => array('English'),
+ 'en-gb' => array('English, British'),
+ 'eo' => array('Esperanto'),
+ 'es' => array('Spanish', 'Español'),
+ 'et' => array('Estonian', 'Eesti'),
+ 'eu' => array('Basque', 'Euskera'),
+ 'fa' => array('Persian', /* Left-to-right marker "" */ 'فارسی', LANGUAGE_RTL),
+ 'ff' => array('Fulah', 'Fulfulde'),
+ 'fi' => array('Finnish', 'Suomi'),
+ 'fil' => array('Filipino'),
+ 'fj' => array('Fiji'),
+ 'fo' => array('Faeroese'),
+ 'fr' => array('French', 'Français'),
+ 'fy' => array('Frisian', 'Frysk'),
+ 'ga' => array('Irish', 'Gaeilge'),
+ 'gd' => array('Scots Gaelic'),
+ 'gl' => array('Galician', 'Galego'),
+ 'gn' => array('Guarani'),
+ 'gsw-berne' => array('Swiss German'),
+ 'gu' => array('Gujarati'),
+ 'gv' => array('Manx'),
+ 'ha' => array('Hausa'),
+ 'he' => array('Hebrew', /* Left-to-right marker "" */ 'עברית', LANGUAGE_RTL),
+ 'hi' => array('Hindi', 'हिन्दी'),
+ 'ho' => array('Hiri Motu'),
+ 'hr' => array('Croatian', 'Hrvatski'),
+ 'ht' => array('Haitian Creole'),
+ 'hu' => array('Hungarian', 'Magyar'),
+ 'hy' => array('Armenian', 'Հայերեն'),
+ 'hz' => array('Herero'),
+ 'ia' => array('Interlingua'),
+ 'id' => array('Indonesian', 'Bahasa Indonesia'),
+ 'ie' => array('Interlingue'),
+ 'ig' => array('Igbo'),
+ 'ik' => array('Inupiak'),
+ 'is' => array('Icelandic', 'Íslenska'),
+ 'it' => array('Italian', 'Italiano'),
+ 'iu' => array('Inuktitut'),
+ 'ja' => array('Japanese', '日本語'),
+ 'jv' => array('Javanese'),
+ 'ka' => array('Georgian'),
+ 'kg' => array('Kongo'),
+ 'ki' => array('Kikuyu'),
+ 'kj' => array('Kwanyama'),
+ 'kk' => array('Kazakh', 'Қазақ'),
+ 'kl' => array('Greenlandic'),
+ 'km' => array('Cambodian'),
+ 'kn' => array('Kannada', 'ಕನ್ನಡ'),
+ 'ko' => array('Korean', '한국어'),
+ 'kr' => array('Kanuri'),
+ 'ks' => array('Kashmiri'),
+ 'ku' => array('Kurdish', 'Kurdî'),
+ 'kv' => array('Komi'),
+ 'kw' => array('Cornish'),
+ 'ky' => array('Kyrgyz', 'Кыргызча'),
+ 'la' => array('Latin', 'Latina'),
+ 'lb' => array('Luxembourgish'),
+ 'lg' => array('Luganda'),
+ 'ln' => array('Lingala'),
+ 'lo' => array('Laothian'),
+ 'lt' => array('Lithuanian', 'Lietuvių'),
+ 'lv' => array('Latvian', 'Latviešu'),
+ 'mg' => array('Malagasy'),
+ 'mh' => array('Marshallese'),
+ 'mi' => array('Māori'),
+ 'mk' => array('Macedonian', 'Македонски'),
+ 'ml' => array('Malayalam', 'മലയാളം'),
+ 'mn' => array('Mongolian'),
+ 'mo' => array('Moldavian'),
+ 'mr' => array('Marathi'),
+ 'ms' => array('Malay', 'Bahasa Melayu'),
+ 'mt' => array('Maltese', 'Malti'),
+ 'my' => array('Burmese'),
+ 'na' => array('Nauru'),
+ 'nd' => array('North Ndebele'),
+ 'ne' => array('Nepali'),
+ 'ng' => array('Ndonga'),
+ 'nl' => array('Dutch', 'Nederlands'),
+ 'nb' => array('Norwegian Bokmål', 'Bokmål'),
+ 'nn' => array('Norwegian Nynorsk', 'Nynorsk'),
+ 'nr' => array('South Ndebele'),
+ 'nv' => array('Navajo'),
+ 'ny' => array('Chichewa'),
+ 'oc' => array('Occitan'),
+ 'om' => array('Oromo'),
+ 'or' => array('Oriya'),
+ 'os' => array('Ossetian'),
+ 'pa' => array('Punjabi'),
+ 'pi' => array('Pali'),
+ 'pl' => array('Polish', 'Polski'),
+ 'ps' => array('Pashto', /* Left-to-right marker "" */ 'پښتو', LANGUAGE_RTL),
+ 'pt' => array('Portuguese, International'),
+ 'pt-pt' => array('Portuguese, Portugal', 'Português'),
+ 'pt-br' => array('Portuguese, Brazil', 'Português'),
+ 'qu' => array('Quechua'),
+ 'rm' => array('Rhaeto-Romance'),
+ 'rn' => array('Kirundi'),
+ 'ro' => array('Romanian', 'Română'),
+ 'ru' => array('Russian', 'Русский'),
+ 'rw' => array('Kinyarwanda'),
+ 'sa' => array('Sanskrit'),
+ 'sc' => array('Sardinian'),
+ 'sco' => array('Scots'),
+ 'sd' => array('Sindhi'),
+ 'se' => array('Northern Sami'),
+ 'sg' => array('Sango'),
+ 'sh' => array('Serbo-Croatian'),
+ 'si' => array('Sinhala', 'සිංහල'),
+ 'sk' => array('Slovak', 'Slovenčina'),
+ 'sl' => array('Slovenian', 'Slovenščina'),
+ 'sm' => array('Samoan'),
+ 'sn' => array('Shona'),
+ 'so' => array('Somali'),
+ 'sq' => array('Albanian', 'Shqip'),
+ 'sr' => array('Serbian', 'Српски'),
+ 'ss' => array('Siswati'),
+ 'st' => array('Sesotho'),
+ 'su' => array('Sudanese'),
+ 'sv' => array('Swedish', 'Svenska'),
+ 'sw' => array('Swahili', 'Kiswahili'),
+ 'ta' => array('Tamil', 'தமிழ்'),
+ 'te' => array('Telugu', 'తెలుగు'),
+ 'tg' => array('Tajik'),
+ 'th' => array('Thai', 'ภาษาไทย'),
+ 'ti' => array('Tigrinya'),
+ 'tk' => array('Turkmen'),
+ 'tl' => array('Tagalog'),
+ 'tn' => array('Setswana'),
+ 'to' => array('Tonga'),
+ 'tr' => array('Turkish', 'Türkçe'),
+ 'ts' => array('Tsonga'),
+ 'tt' => array('Tatar', 'Tatarça'),
+ 'tw' => array('Twi'),
+ 'ty' => array('Tahitian'),
+ 'ug' => array('Uighur'),
+ 'uk' => array('Ukrainian', 'Українська'),
+ 'ur' => array('Urdu', /* Left-to-right marker "" */ 'اردو', LANGUAGE_RTL),
+ 'uz' => array('Uzbek', "o'zbek"),
+ 've' => array('Venda'),
+ 'vi' => array('Vietnamese', 'Tiếng Việt'),
+ 'wo' => array('Wolof'),
+ 'xh' => array('Xhosa', 'isiXhosa'),
+ 'xx-lolspeak' => array('Lolspeak'),
+ 'yi' => array('Yiddish'),
+ 'yo' => array('Yoruba', 'Yorùbá'),
+ 'za' => array('Zhuang'),
+ 'zh-hans' => array('Chinese, Simplified', '简体中文'),
+ 'zh-hant' => array('Chinese, Traditional', '繁體中文'),
+ 'zu' => array('Zulu', 'isiZulu'),
+ );
+}
+/**
+ * @} End of "locale-api-languages-predefined"
+ */
diff --git a/backup/modules/20110602035425/drupal/includes/language.inc b/backup/modules/20110602035425/drupal/includes/language.inc
new file mode 100644
index 00000000..61bbbd76
--- /dev/null
+++ b/backup/modules/20110602035425/drupal/includes/language.inc
@@ -0,0 +1,407 @@
+ $info) {
+ if (!isset($info['fixed'])) {
+ $result[] = $type;
+ }
+ }
+ return $result;
+ }
+
+ return $configurable;
+}
+
+/**
+ * Disable the given language types.
+ *
+ * @param $types
+ * An array of language types.
+ */
+function language_types_disable($types) {
+ $enabled_types = variable_get('language_types', drupal_language_types());
+
+ foreach ($types as $type) {
+ unset($enabled_types[$type]);
+ }
+
+ variable_set('language_types', $enabled_types);
+}
+
+/**
+ * Check if a language provider is enabled.
+ *
+ * This has two possible behaviors:
+ * - If $provider_id is given return its ID if enabled, FALSE otherwise.
+ * - If no ID is passed the first enabled language provider is returned.
+ *
+ * @param $type
+ * The language negotiation type.
+ * @param $provider_id
+ * The language provider ID.
+ *
+ * @return
+ * The provider ID if it is enabled, FALSE otherwise.
+ */
+function language_negotiation_get($type, $provider_id = NULL) {
+ $negotiation = variable_get("language_negotiation_$type", array());
+
+ if (empty($negotiation)) {
+ return empty($provider_id) ? LANGUAGE_NEGOTIATION_DEFAULT : FALSE;
+ }
+
+ if (empty($provider_id)) {
+ return key($negotiation);
+ }
+
+ if (isset($negotiation[$provider_id])) {
+ return $provider_id;
+ }
+
+ return FALSE;
+}
+
+/**
+ * Check if the given language provider is enabled for any configurable language
+ * type.
+ *
+ * @param $provider_id
+ * The language provider ID.
+ *
+ * @return
+ * TRUE if there is at least one language type for which the give language
+ * provider is enabled, FALSE otherwise.
+ */
+function language_negotiation_get_any($provider_id) {
+ foreach (language_types_configurable() as $type) {
+ if (language_negotiation_get($type, $provider_id)) {
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+/**
+ * Return the language switch links for the given language.
+ *
+ * @param $type
+ * The language negotiation type.
+ * @param $path
+ * The internal path the switch links will be relative to.
+ *
+ * @return
+ * A keyed array of links ready to be themed.
+ */
+function language_negotiation_get_switch_links($type, $path) {
+ $links = FALSE;
+ $negotiation = variable_get("language_negotiation_$type", array());
+
+ foreach ($negotiation as $id => $provider) {
+ if (isset($provider['callbacks']['switcher'])) {
+ if (isset($provider['file'])) {
+ require_once DRUPAL_ROOT . '/' . $provider['file'];
+ }
+
+ $callback = $provider['callbacks']['switcher'];
+ $result = $callback($type, $path);
+
+ if (!empty($result)) {
+ // Allow modules to provide translations for specific links.
+ drupal_alter('language_switch_links', $result, $type, $path);
+ $links = (object) array('links' => $result, 'provider' => $id);
+ break;
+ }
+ }
+ }
+
+ return $links;
+}
+
+
+/**
+ * Save a list of language providers.
+ *
+ * @param $type
+ * The language negotiation type.
+ * @param $language_providers
+ * An array of language provider ids.
+ */
+function language_negotiation_set($type, $language_providers) {
+ // Save only the necessary fields.
+ $provider_fields = array('callbacks', 'file', 'cache');
+
+ $negotiation = array();
+ $providers_weight = array();
+ $defined_providers = language_negotiation_info();
+ $default_types = language_types_configurable();
+
+ // Initialize the providers weight list.
+ foreach ($language_providers as $id => $provider) {
+ $providers_weight[$id] = language_provider_weight($provider);
+ }
+
+ // Order providers list by weight.
+ asort($providers_weight);
+
+ foreach ($providers_weight as $id => $weight) {
+ if (isset($defined_providers[$id])) {
+ $provider = $defined_providers[$id];
+ // If the provider does not express any preference about types, make it
+ // available for any configurable type.
+ $types = array_flip(isset($provider['types']) ? $provider['types'] : $default_types);
+ // Check if the provider is defined and has the right type.
+ if (isset($types[$type])) {
+ $provider_data = array();
+ foreach ($provider_fields as $field) {
+ if (isset($provider[$field])) {
+ $provider_data[$field] = $provider[$field];
+ }
+ }
+ $negotiation[$id] = $provider_data;
+ }
+ }
+ }
+
+ variable_set("language_negotiation_$type", $negotiation);
+}
+
+/**
+ * Return all the defined language providers.
+ *
+ * @return
+ * An array of language providers.
+ */
+function language_negotiation_info() {
+ $language_providers = &drupal_static(__FUNCTION__);
+
+ if (!isset($language_providers)) {
+ // Collect all the module-defined language negotiation providers.
+ $language_providers = module_invoke_all('language_negotiation_info');
+
+ // Add the default language provider.
+ $language_providers[LANGUAGE_NEGOTIATION_DEFAULT] = array(
+ 'callbacks' => array('language' => 'language_from_default'),
+ 'weight' => 10,
+ 'name' => t('Default'),
+ 'description' => t('Use the default site language (@language_name).', array('@language_name' => language_default()->native)),
+ );
+
+ // Let other modules alter the list of language providers.
+ drupal_alter('language_negotiation_info', $language_providers);
+ }
+
+ return $language_providers;
+}
+
+/**
+ * Helper function used to cache the language providers results.
+ *
+ * @param $provider_id
+ * The language provider ID.
+ * @param $provider
+ * The language provider to be invoked. If not passed it will be explicitly
+ * loaded through language_negotiation_info().
+ *
+ * @return
+ * The language provider's return value.
+ */
+function language_provider_invoke($provider_id, $provider = NULL) {
+ $results = &drupal_static(__FUNCTION__);
+
+ if (!isset($results[$provider_id])) {
+ global $user;
+
+ // Get languages grouped by status and select only the enabled ones.
+ $languages = language_list('enabled');
+ $languages = $languages[1];
+
+ if (!isset($provider)) {
+ $providers = language_negotiation_info();
+ $provider = $providers[$provider_id];
+ }
+
+ if (isset($provider['file'])) {
+ require_once DRUPAL_ROOT . '/' . $provider['file'];
+ }
+
+ // If the language provider has no cache preference or this is satisfied
+ // we can execute the callback.
+ $cache = !isset($provider['cache']) || $user->uid || $provider['cache'] == variable_get('cache', 0);
+ $callback = isset($provider['callbacks']['language']) ? $provider['callbacks']['language'] : FALSE;
+ $langcode = $cache && function_exists($callback) ? $callback($languages) : FALSE;
+ $results[$provider_id] = isset($languages[$langcode]) ? $languages[$langcode] : FALSE;
+ }
+
+ return $results[$provider_id];
+}
+
+/**
+ * Return the passed language provider weight or a default value.
+ *
+ * @param $provider
+ * A language provider data structure.
+ *
+ * @return
+ * A numeric weight.
+ */
+function language_provider_weight($provider) {
+ $default = is_numeric($provider) ? $provider : 0;
+ return isset($provider['weight']) && is_numeric($provider['weight']) ? $provider['weight'] : $default;
+}
+
+/**
+ * Choose a language for the given type based on language negotiation settings.
+ *
+ * @param $type
+ * The language type.
+ *
+ * @return
+ * The negotiated language object.
+ */
+function language_initialize($type) {
+ // Execute the language providers in the order they were set up and return the
+ // first valid language found.
+ $negotiation = variable_get("language_negotiation_$type", array());
+
+ foreach ($negotiation as $id => $provider) {
+ $language = language_provider_invoke($id, $provider);
+ if ($language) {
+ return $language;
+ }
+ }
+
+ // If no other language was found use the default one.
+ return language_default();
+}
+
+/**
+ * Default language provider.
+ *
+ * @return
+ * The default language code.
+ */
+function language_from_default() {
+ return language_default()->language;
+}
+
+/**
+ * Split the given path into prefix and actual path.
+ *
+ * Parse the given path and return the language object identified by the
+ * prefix and the actual path.
+ *
+ * @param $path
+ * The path to split.
+ * @param $languages
+ * An array of valid languages.
+ *
+ * @return
+ * An array composed of:
+ * - A language object corresponding to the identified prefix on success,
+ * FALSE otherwise.
+ * - The path without the prefix on success, the given path otherwise.
+ */
+function language_url_split_prefix($path, $languages) {
+ $args = empty($path) ? array() : explode('/', $path);
+ $prefix = array_shift($args);
+
+ // Search prefix within enabled languages.
+ foreach ($languages as $language) {
+ if (!empty($language->prefix) && $language->prefix == $prefix) {
+ // Rebuild $path with the language removed.
+ return array($language, implode('/', $args));
+ }
+ }
+
+ return array(FALSE, $path);
+}
+
+/**
+ * Return the possible fallback languages ordered by language weight.
+ *
+ * @param
+ * The language type.
+ *
+ * @return
+ * An array of language codes.
+ */
+function language_fallback_get_candidates($type = LANGUAGE_TYPE_CONTENT) {
+ $fallback_candidates = &drupal_static(__FUNCTION__);
+
+ if (!isset($fallback_candidates)) {
+ $fallback_candidates = array();
+
+ // Get languages ordered by weight.
+ // Use array keys to avoid duplicated entries.
+ foreach (language_list('weight') as $languages) {
+ foreach ($languages as $language) {
+ $fallback_candidates[$language->language] = NULL;
+ }
+ }
+
+ $fallback_candidates = array_keys($fallback_candidates);
+ $fallback_candidates[] = LANGUAGE_NONE;
+
+ // Let other modules hook in and add/change candidates.
+ drupal_alter('language_fallback_candidates', $fallback_candidates);
+ }
+
+ return $fallback_candidates;
+}
diff --git a/backup/modules/20110602035425/drupal/includes/locale.inc b/backup/modules/20110602035425/drupal/includes/locale.inc
new file mode 100644
index 00000000..e59e28e2
--- /dev/null
+++ b/backup/modules/20110602035425/drupal/includes/locale.inc
@@ -0,0 +1,2203 @@
+language) ? $language->language : FALSE;
+}
+
+/**
+ * Identify language from the Accept-language HTTP header we got.
+ *
+ * We perform browser accept-language parsing only if page cache is disabled,
+ * otherwise we would cache a user-specific preference.
+ *
+ * @param $languages
+ * An array of valid language objects.
+ *
+ * @return
+ * A valid language code on success, FALSE otherwise.
+ */
+function locale_language_from_browser($languages) {
+ // Specified by the user via the browser's Accept Language setting
+ // Samples: "hu, en-us;q=0.66, en;q=0.33", "hu,en-us;q=0.5"
+ $browser_langs = array();
+
+ if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
+ $browser_accept = explode(",", $_SERVER['HTTP_ACCEPT_LANGUAGE']);
+ foreach ($browser_accept as $langpart) {
+ // The language part is either a code or a code with a quality.
+ // We cannot do anything with a * code, so it is skipped.
+ // If the quality is missing, it is assumed to be 1 according to the RFC.
+ if (preg_match("!([a-z-]+)(;q=([0-9\\.]+))?!", trim($langpart), $found)) {
+ $browser_langs[$found[1]] = (isset($found[3]) ? (float) $found[3] : 1.0);
+ }
+ }
+ }
+
+ // Order the codes by quality
+ arsort($browser_langs);
+
+ // Try to find the first preferred language we have
+ foreach ($browser_langs as $langcode => $q) {
+ if (isset($languages[$langcode])) {
+ return $langcode;
+ }
+ }
+
+ return FALSE;
+}
+
+/**
+ * Identify language from the user preferences.
+ *
+ * @param $languages
+ * An array of valid language objects.
+ *
+ * @return
+ * A valid language code on success, FALSE otherwise.
+ */
+function locale_language_from_user($languages) {
+ // User preference (only for logged users).
+ global $user;
+
+ if ($user->uid) {
+ return $user->language;
+ }
+
+ // No language preference from the user.
+ return FALSE;
+}
+
+/**
+ * Identify language from a request/session parameter.
+ *
+ * @param $languages
+ * An array of valid language objects.
+ *
+ * @return
+ * A valid language code on success, FALSE otherwise.
+ */
+function locale_language_from_session($languages) {
+ $param = variable_get('locale_language_negotiation_session_param', 'language');
+
+ // Request parameter: we need to update the session parameter only if we have
+ // an authenticated user.
+ if (isset($_GET[$param]) && isset($languages[$langcode = $_GET[$param]])) {
+ global $user;
+ if ($user->uid) {
+ $_SESSION[$param] = $langcode;
+ }
+ return $langcode;
+ }
+
+ // Session parameter.
+ if (isset($_SESSION[$param])) {
+ return $_SESSION[$param];
+ }
+
+ return FALSE;
+}
+
+/**
+ * Identify language via URL prefix or domain.
+ *
+ * @param $languages
+ * An array of valid language objects.
+ *
+ * @return
+ * A valid language code on success, FALSE otherwise.
+ */
+function locale_language_from_url($languages) {
+ $language_url = FALSE;
+
+ if (!language_negotiation_get_any(LOCALE_LANGUAGE_NEGOTIATION_URL)) {
+ return $language_url;
+ }
+
+ switch (variable_get('locale_language_negotiation_url_part', LOCALE_LANGUAGE_NEGOTIATION_URL_PREFIX)) {
+ case LOCALE_LANGUAGE_NEGOTIATION_URL_PREFIX:
+ // $_GET['q'] might not be available at this time, because
+ // path initialization runs after the language bootstrap phase.
+ list($language, $_GET['q']) = language_url_split_prefix(isset($_GET['q']) ? $_GET['q'] : NULL, $languages);
+ if ($language !== FALSE) {
+ $language_url = $language->language;
+ }
+ break;
+
+ case LOCALE_LANGUAGE_NEGOTIATION_URL_DOMAIN:
+ foreach ($languages as $language) {
+ $host = parse_url($language->domain, PHP_URL_HOST);
+ if ($host && ($_SERVER['HTTP_HOST'] == $host)) {
+ $language_url = $language->language;
+ break;
+ }
+ }
+ break;
+ }
+
+ return $language_url;
+}
+
+/**
+ * Determines the language to be assigned to URLs when none is detected.
+ *
+ * The language negotiation process has a fallback chain that ends with the
+ * default language provider. Each built-in language type has a separate
+ * initialization:
+ * - Interface language, which is the only configurable one, always gets a valid
+ * value. If no request-specific language is detected, the default language
+ * will be used.
+ * - Content language merely inherits the interface language by default.
+ * - URL language is detected from the requested URL and will be used to rewrite
+ * URLs appearing in the page being rendered. If no language can be detected,
+ * there are two possibilities:
+ * - If the default language has no configured path prefix or domain, then the
+ * default language is used. This guarantees that (missing) URL prefixes are
+ * preserved when navigating through the site.
+ * - If the default language has a configured path prefix or domain, a
+ * requested URL having an empty prefix or domain is an anomaly that must be
+ * fixed. This is done by introducing a prefix or domain in the rendered
+ * page matching the detected interface language.
+ *
+ * @param $languages
+ * (optional) An array of valid language objects. This is passed by
+ * language_provider_invoke() to every language provider callback, but it is
+ * not actually needed here. Defaults to NULL.
+ * @param $language_type
+ * (optional) The language type to fall back to. Defaults to the interface
+ * language.
+ *
+ * @return
+ * A valid language code.
+ */
+function locale_language_url_fallback($language = NULL, $language_type = LANGUAGE_TYPE_INTERFACE) {
+ $default = language_default();
+ $prefix = (variable_get('locale_language_negotiation_url_part', LOCALE_LANGUAGE_NEGOTIATION_URL_PREFIX) == LOCALE_LANGUAGE_NEGOTIATION_URL_PREFIX);
+
+ // If the default language is not configured to convey language information,
+ // a missing URL language information indicates that URL language should be
+ // the default one, otherwise we fall back to an already detected language.
+ if (($prefix && empty($default->prefix)) || (!$prefix && empty($default->domain))) {
+ return $default->language;
+ }
+ else {
+ return $GLOBALS[$language_type]->language;
+ }
+}
+
+/**
+ * Return the URL language switcher block. Translation links may be provided by
+ * other modules.
+ */
+function locale_language_switcher_url($type, $path) {
+ $languages = language_list('enabled');
+ $links = array();
+
+ foreach ($languages[1] as $language) {
+ $links[$language->language] = array(
+ 'href' => $path,
+ 'title' => $language->native,
+ 'language' => $language,
+ 'attributes' => array('class' => array('language-link')),
+ );
+ }
+
+ return $links;
+}
+
+/**
+ * Return the session language switcher block.
+ */
+function locale_language_switcher_session($type, $path) {
+ drupal_add_css(drupal_get_path('module', 'locale') . '/locale.css');
+
+ $param = variable_get('locale_language_negotiation_session_param', 'language');
+ $language_query = isset($_SESSION[$param]) ? $_SESSION[$param] : $GLOBALS[$type]->language;
+
+ $languages = language_list('enabled');
+ $links = array();
+
+ $query = $_GET;
+ unset($query['q']);
+
+ foreach ($languages[1] as $language) {
+ $langcode = $language->language;
+ $links[$langcode] = array(
+ 'href' => $path,
+ 'title' => $language->native,
+ 'attributes' => array('class' => array('language-link')),
+ 'query' => $query,
+ );
+ if ($language_query != $langcode) {
+ $links[$langcode]['query'][$param] = $langcode;
+ }
+ else {
+ $links[$langcode]['attributes']['class'][] = ' session-active';
+ }
+ }
+
+ return $links;
+}
+
+/**
+ * Rewrite URLs for the URL language provider.
+ */
+function locale_language_url_rewrite_url(&$path, &$options) {
+ static $drupal_static_fast;
+ if (!isset($drupal_static_fast)) {
+ $drupal_static_fast['languages'] = &drupal_static(__FUNCTION__);
+ }
+ $languages = &$drupal_static_fast['languages'];
+
+ if (!isset($languages)) {
+ $languages = language_list('enabled');
+ $languages = array_flip(array_keys($languages[1]));
+ }
+
+ // Language can be passed as an option, or we go for current URL language.
+ if (!isset($options['language'])) {
+ global $language_url;
+ $options['language'] = $language_url;
+ }
+ // We allow only enabled languages here.
+ elseif (!isset($languages[$options['language']->language])) {
+ unset($options['language']);
+ return;
+ }
+
+ if (isset($options['language'])) {
+ switch (variable_get('locale_language_negotiation_url_part', LOCALE_LANGUAGE_NEGOTIATION_URL_PREFIX)) {
+ case LOCALE_LANGUAGE_NEGOTIATION_URL_DOMAIN:
+ if ($options['language']->domain) {
+ // Ask for an absolute URL with our modified base_url.
+ $options['absolute'] = TRUE;
+ $options['base_url'] = $options['language']->domain;
+ }
+ break;
+
+ case LOCALE_LANGUAGE_NEGOTIATION_URL_PREFIX:
+ if (!empty($options['language']->prefix)) {
+ $options['prefix'] = $options['language']->prefix . '/';
+ }
+ break;
+ }
+ }
+}
+
+/**
+ * Rewrite URLs for the Session language provider.
+ */
+function locale_language_url_rewrite_session(&$path, &$options) {
+ static $query_rewrite, $query_param, $query_value;
+
+ // The following values are not supposed to change during a single page
+ // request processing.
+ if (!isset($query_rewrite)) {
+ global $user;
+ if (!$user->uid) {
+ $languages = language_list('enabled');
+ $languages = $languages[1];
+ $query_param = check_plain(variable_get('locale_language_negotiation_session_param', 'language'));
+ $query_value = isset($_GET[$query_param]) ? check_plain($_GET[$query_param]) : NULL;
+ $query_rewrite = isset($languages[$query_value]) && language_negotiation_get_any(LOCALE_LANGUAGE_NEGOTIATION_SESSION);
+ }
+ else {
+ $query_rewrite = FALSE;
+ }
+ }
+
+ // If the user is anonymous, the user language provider is enabled, and the
+ // corresponding option has been set, we must preserve any explicit user
+ // language preference even with cookies disabled.
+ if ($query_rewrite) {
+ if (is_string($options['query'])) {
+ $options['query'] = drupal_get_query_array($options['query']);
+ }
+ if (!isset($options['query'][$query_param])) {
+ $options['query'][$query_param] = $query_value;
+ }
+ }
+}
+
+/**
+ * @} End of "locale-languages-negotiation"
+ */
+
+/**
+ * Check that a string is safe to be added or imported as a translation.
+ *
+ * This test can be used to detect possibly bad translation strings. It should
+ * not have any false positives. But it is only a test, not a transformation,
+ * as it destroys valid HTML. We cannot reliably filter translation strings
+ * on import because some strings are irreversibly corrupted. For example,
+ * a & in the translation would get encoded to & by filter_xss()
+ * before being put in the database, and thus would be displayed incorrectly.
+ *
+ * The allowed tag list is like filter_xss_admin(), but omitting div and img as
+ * not needed for translation and likely to cause layout issues (div) or a
+ * possible attack vector (img).
+ */
+function locale_string_is_safe($string) {
+ return decode_entities($string) == decode_entities(filter_xss($string, array('a', 'abbr', 'acronym', 'address', 'b', 'bdo', 'big', 'blockquote', 'br', 'caption', 'cite', 'code', 'col', 'colgroup', 'dd', 'del', 'dfn', 'dl', 'dt', 'em', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'i', 'ins', 'kbd', 'li', 'ol', 'p', 'pre', 'q', 'samp', 'small', 'span', 'strong', 'sub', 'sup', 'table', 'tbody', 'td', 'tfoot', 'th', 'thead', 'tr', 'tt', 'ul', 'var')));
+}
+
+/**
+ * @defgroup locale-api-add Language addition API
+ * @{
+ * Add a language.
+ *
+ * The language addition API is used to create languages and store them.
+ */
+
+/**
+ * API function to add a language.
+ *
+ * @param $langcode
+ * Language code.
+ * @param $name
+ * English name of the language
+ * @param $native
+ * Native name of the language
+ * @param $direction
+ * LANGUAGE_LTR or LANGUAGE_RTL
+ * @param $domain
+ * Optional custom domain name with protocol, without
+ * trailing slash (eg. http://de.example.com).
+ * @param $prefix
+ * Optional path prefix for the language. Defaults to the
+ * language code if omitted.
+ * @param $enabled
+ * Optionally TRUE to enable the language when created or FALSE to disable.
+ * @param $default
+ * Optionally set this language to be the default.
+ */
+function locale_add_language($langcode, $name = NULL, $native = NULL, $direction = LANGUAGE_LTR, $domain = '', $prefix = '', $enabled = TRUE, $default = FALSE) {
+ // Default prefix on language code.
+ if (empty($prefix)) {
+ $prefix = $langcode;
+ }
+
+ // If name was not set, we add a predefined language.
+ if (!isset($name)) {
+ include_once DRUPAL_ROOT . '/includes/iso.inc';
+ $predefined = _locale_get_predefined_list();
+ $name = $predefined[$langcode][0];
+ $native = isset($predefined[$langcode][1]) ? $predefined[$langcode][1] : $predefined[$langcode][0];
+ $direction = isset($predefined[$langcode][2]) ? $predefined[$langcode][2] : LANGUAGE_LTR;
+ }
+
+ db_insert('languages')
+ ->fields(array(
+ 'language' => $langcode,
+ 'name' => $name,
+ 'native' => $native,
+ 'direction' => $direction,
+ 'domain' => $domain,
+ 'prefix' => $prefix,
+ 'enabled' => $enabled,
+ ))
+ ->execute();
+
+ // Only set it as default if enabled.
+ if ($enabled && $default) {
+ variable_set('language_default', (object) array('language' => $langcode, 'name' => $name, 'native' => $native, 'direction' => $direction, 'enabled' => (int) $enabled, 'plurals' => 0, 'formula' => '', 'domain' => '', 'prefix' => $prefix, 'weight' => 0, 'javascript' => ''));
+ }
+
+ if ($enabled) {
+ // Increment enabled language count if we are adding an enabled language.
+ variable_set('language_count', variable_get('language_count', 1) + 1);
+ }
+
+ // Kill the static cache in language_list().
+ drupal_static_reset('language_list');
+
+ // Force JavaScript translation file creation for the newly added language.
+ _locale_invalidate_js($langcode);
+
+ watchdog('locale', 'The %language language (%code) has been created.', array('%language' => $name, '%code' => $langcode));
+
+ module_invoke_all('multilingual_settings_changed');
+}
+/**
+ * @} End of "locale-api-add"
+ */
+
+/**
+ * @defgroup locale-api-import-export Translation import/export API.
+ * @{
+ * Functions to import and export translations.
+ *
+ * These functions provide the ability to import translations from
+ * external files and to export translations and translation templates.
+ */
+
+/**
+ * Parses Gettext Portable Object file information and inserts into database
+ *
+ * @param $file
+ * Drupal file object corresponding to the PO file to import.
+ * @param $langcode
+ * Language code.
+ * @param $mode
+ * Should existing translations be replaced LOCALE_IMPORT_KEEP or
+ * LOCALE_IMPORT_OVERWRITE.
+ * @param $group
+ * Text group to import PO file into (eg. 'default' for interface
+ * translations).
+ */
+function _locale_import_po($file, $langcode, $mode, $group = NULL) {
+ // Try to allocate enough time to parse and import the data.
+ drupal_set_time_limit(240);
+
+ // Check if we have the language already in the database.
+ if (!db_query("SELECT COUNT(language) FROM {languages} WHERE language = :language", array(':language' => $langcode))->fetchField()) {
+ drupal_set_message(t('The language selected for import is not supported.'), 'error');
+ return FALSE;
+ }
+
+ // Get strings from file (returns on failure after a partial import, or on success)
+ $status = _locale_import_read_po('db-store', $file, $mode, $langcode, $group);
+ if ($status === FALSE) {
+ // Error messages are set in _locale_import_read_po().
+ return FALSE;
+ }
+
+ // Get status information on import process.
+ list($header_done, $additions, $updates, $deletes, $skips) = _locale_import_one_string('db-report');
+
+ if (!$header_done) {
+ drupal_set_message(t('The translation file %filename appears to have a missing or malformed header.', array('%filename' => $file->filename)), 'error');
+ }
+
+ // Clear cache and force refresh of JavaScript translations.
+ _locale_invalidate_js($langcode);
+ cache_clear_all('locale:', 'cache', TRUE);
+
+ // Rebuild the menu, strings may have changed.
+ menu_rebuild();
+
+ drupal_set_message(t('The translation was successfully imported. There are %number newly created translated strings, %update strings were updated and %delete strings were removed.', array('%number' => $additions, '%update' => $updates, '%delete' => $deletes)));
+ watchdog('locale', 'Imported %file into %locale: %number new strings added, %update updated and %delete removed.', array('%file' => $file->filename, '%locale' => $langcode, '%number' => $additions, '%update' => $updates, '%delete' => $deletes));
+ if ($skips) {
+ $skip_message = format_plural($skips, 'One translation string was skipped because it contains disallowed HTML.', '@count translation strings were skipped because they contain disallowed HTML.');
+ drupal_set_message($skip_message);
+ watchdog('locale', '@count disallowed HTML string(s) in %file', array('@count' => $skips, '%file' => $file->uri), WATCHDOG_WARNING);
+ }
+ return TRUE;
+}
+
+/**
+ * Parses Gettext Portable Object file into an array
+ *
+ * @param $op
+ * Storage operation type: db-store or mem-store.
+ * @param $file
+ * Drupal file object corresponding to the PO file to import.
+ * @param $mode
+ * Should existing translations be replaced LOCALE_IMPORT_KEEP or
+ * LOCALE_IMPORT_OVERWRITE.
+ * @param $lang
+ * Language code.
+ * @param $group
+ * Text group to import PO file into (eg. 'default' for interface
+ * translations).
+ */
+function _locale_import_read_po($op, $file, $mode = NULL, $lang = NULL, $group = 'default') {
+
+ $fd = fopen($file->uri, "rb"); // File will get closed by PHP on return
+ if (!$fd) {
+ _locale_import_message('The translation import failed, because the file %filename could not be read.', $file);
+ return FALSE;
+ }
+
+ $context = "COMMENT"; // Parser context: COMMENT, MSGID, MSGID_PLURAL, MSGSTR and MSGSTR_ARR
+ $current = array(); // Current entry being read
+ $plural = 0; // Current plural form
+ $lineno = 0; // Current line
+
+ while (!feof($fd)) {
+ $line = fgets($fd, 10*1024); // A line should not be this long
+ if ($lineno == 0) {
+ // The first line might come with a UTF-8 BOM, which should be removed.
+ $line = str_replace("\xEF\xBB\xBF", '', $line);
+ }
+ $lineno++;
+ $line = trim(strtr($line, array("\\\n" => "")));
+
+ if (!strncmp("#", $line, 1)) { // A comment
+ if ($context == "COMMENT") { // Already in comment context: add
+ $current["#"][] = substr($line, 1);
+ }
+ elseif (($context == "MSGSTR") || ($context == "MSGSTR_ARR")) { // End current entry, start a new one
+ _locale_import_one_string($op, $current, $mode, $lang, $file, $group);
+ $current = array();
+ $current["#"][] = substr($line, 1);
+ $context = "COMMENT";
+ }
+ else { // Parse error
+ _locale_import_message('The translation file %filename contains an error: "msgstr" was expected but not found on line %line.', $file, $lineno);
+ return FALSE;
+ }
+ }
+ elseif (!strncmp("msgid_plural", $line, 12)) {
+ if ($context != "MSGID") { // Must be plural form for current entry
+ _locale_import_message('The translation file %filename contains an error: "msgid_plural" was expected but not found on line %line.', $file, $lineno);
+ return FALSE;
+ }
+ $line = trim(substr($line, 12));
+ $quoted = _locale_import_parse_quoted($line);
+ if ($quoted === FALSE) {
+ _locale_import_message('The translation file %filename contains a syntax error on line %line.', $file, $lineno);
+ return FALSE;
+ }
+ $current["msgid"] = $current["msgid"] . "\0" . $quoted;
+ $context = "MSGID_PLURAL";
+ }
+ elseif (!strncmp("msgid", $line, 5)) {
+ if (($context == "MSGSTR") || ($context == "MSGSTR_ARR")) { // End current entry, start a new one
+ _locale_import_one_string($op, $current, $mode, $lang, $file, $group);
+ $current = array();
+ }
+ elseif ($context == "MSGID") { // Already in this context? Parse error
+ _locale_import_message('The translation file %filename contains an error: "msgid" is unexpected on line %line.', $file, $lineno);
+ return FALSE;
+ }
+ $line = trim(substr($line, 5));
+ $quoted = _locale_import_parse_quoted($line);
+ if ($quoted === FALSE) {
+ _locale_import_message('The translation file %filename contains a syntax error on line %line.', $file, $lineno);
+ return FALSE;
+ }
+ $current["msgid"] = $quoted;
+ $context = "MSGID";
+ }
+ elseif (!strncmp("msgctxt", $line, 7)) {
+ if (($context == "MSGSTR") || ($context == "MSGSTR_ARR")) { // End current entry, start a new one
+ _locale_import_one_string($op, $current, $mode, $lang, $file, $group);
+ $current = array();
+ }
+ elseif (!empty($current["msgctxt"])) { // Already in this context? Parse error
+ _locale_import_message('The translation file %filename contains an error: "msgctxt" is unexpected on line %line.', $file, $lineno);
+ return FALSE;
+ }
+ $line = trim(substr($line, 7));
+ $quoted = _locale_import_parse_quoted($line);
+ if ($quoted === FALSE) {
+ _locale_import_message('The translation file %filename contains a syntax error on line %line.', $file, $lineno);
+ return FALSE;
+ }
+ $current["msgctxt"] = $quoted;
+ $context = "MSGCTXT";
+ }
+ elseif (!strncmp("msgstr[", $line, 7)) {
+ if (($context != "MSGID") && ($context != "MSGCTXT") && ($context != "MSGID_PLURAL") && ($context != "MSGSTR_ARR")) { // Must come after msgid, msgxtxt, msgid_plural, or msgstr[]
+ _locale_import_message('The translation file %filename contains an error: "msgstr[]" is unexpected on line %line.', $file, $lineno);
+ return FALSE;
+ }
+ if (strpos($line, "]") === FALSE) {
+ _locale_import_message('The translation file %filename contains a syntax error on line %line.', $file, $lineno);
+ return FALSE;
+ }
+ $frombracket = strstr($line, "[");
+ $plural = substr($frombracket, 1, strpos($frombracket, "]") - 1);
+ $line = trim(strstr($line, " "));
+ $quoted = _locale_import_parse_quoted($line);
+ if ($quoted === FALSE) {
+ _locale_import_message('The translation file %filename contains a syntax error on line %line.', $file, $lineno);
+ return FALSE;
+ }
+ $current["msgstr"][$plural] = $quoted;
+ $context = "MSGSTR_ARR";
+ }
+ elseif (!strncmp("msgstr", $line, 6)) {
+ if (($context != "MSGID") && ($context != "MSGCTXT")) { // Should come just after a msgid or msgctxt block
+ _locale_import_message('The translation file %filename contains an error: "msgstr" is unexpected on line %line.', $file, $lineno);
+ return FALSE;
+ }
+ $line = trim(substr($line, 6));
+ $quoted = _locale_import_parse_quoted($line);
+ if ($quoted === FALSE) {
+ _locale_import_message('The translation file %filename contains a syntax error on line %line.', $file, $lineno);
+ return FALSE;
+ }
+ $current["msgstr"] = $quoted;
+ $context = "MSGSTR";
+ }
+ elseif ($line != "") {
+ $quoted = _locale_import_parse_quoted($line);
+ if ($quoted === FALSE) {
+ _locale_import_message('The translation file %filename contains a syntax error on line %line.', $file, $lineno);
+ return FALSE;
+ }
+ if (($context == "MSGID") || ($context == "MSGID_PLURAL")) {
+ $current["msgid"] .= $quoted;
+ }
+ elseif ($context == "MSGCTXT") {
+ $current["msgctxt"] .= $quoted;
+ }
+ elseif ($context == "MSGSTR") {
+ $current["msgstr"] .= $quoted;
+ }
+ elseif ($context == "MSGSTR_ARR") {
+ $current["msgstr"][$plural] .= $quoted;
+ }
+ else {
+ _locale_import_message('The translation file %filename contains an error: there is an unexpected string on line %line.', $file, $lineno);
+ return FALSE;
+ }
+ }
+ }
+
+ // End of PO file, flush last entry.
+ if (($context == "MSGSTR") || ($context == "MSGSTR_ARR")) {
+ _locale_import_one_string($op, $current, $mode, $lang, $file, $group);
+ }
+ elseif ($context != "COMMENT") {
+ _locale_import_message('The translation file %filename ended unexpectedly at line %line.', $file, $lineno);
+ return FALSE;
+ }
+
+}
+
+/**
+ * Sets an error message occurred during locale file parsing.
+ *
+ * @param $message
+ * The message to be translated.
+ * @param $file
+ * Drupal file object corresponding to the PO file to import.
+ * @param $lineno
+ * An optional line number argument.
+ */
+function _locale_import_message($message, $file, $lineno = NULL) {
+ $vars = array('%filename' => $file->filename);
+ if (isset($lineno)) {
+ $vars['%line'] = $lineno;
+ }
+ $t = get_t();
+ drupal_set_message($t($message, $vars), 'error');
+}
+
+/**
+ * Imports a string into the database
+ *
+ * @param $op
+ * Operation to perform: 'db-store', 'db-report', 'mem-store' or 'mem-report'.
+ * @param $value
+ * Details of the string stored.
+ * @param $mode
+ * Should existing translations be replaced LOCALE_IMPORT_KEEP or
+ * LOCALE_IMPORT_OVERWRITE.
+ * @param $lang
+ * Language to store the string in.
+ * @param $file
+ * Object representation of file being imported, only required when op is
+ * 'db-store'.
+ * @param $group
+ * Text group to import PO file into (eg. 'default' for interface
+ * translations).
+ */
+function _locale_import_one_string($op, $value = NULL, $mode = NULL, $lang = NULL, $file = NULL, $group = 'default') {
+ $report = &drupal_static(__FUNCTION__, array('additions' => 0, 'updates' => 0, 'deletes' => 0, 'skips' => 0));
+ $header_done = &drupal_static(__FUNCTION__ . ':header_done', FALSE);
+ $strings = &drupal_static(__FUNCTION__ . ':strings', array());
+
+ switch ($op) {
+ // Return stored strings
+ case 'mem-report':
+ return $strings;
+
+ // Store string in memory (only supports single strings)
+ case 'mem-store':
+ $strings[isset($value['msgctxt']) ? $value['msgctxt'] : ''][$value['msgid']] = $value['msgstr'];
+ return;
+
+ // Called at end of import to inform the user
+ case 'db-report':
+ return array($header_done, $report['additions'], $report['updates'], $report['deletes'], $report['skips']);
+
+ // Store the string we got in the database.
+ case 'db-store':
+ // We got header information.
+ if ($value['msgid'] == '') {
+ $languages = language_list();
+ if (($mode != LOCALE_IMPORT_KEEP) || empty($languages[$lang]->plurals)) {
+ // Since we only need to parse the header if we ought to update the
+ // plural formula, only run this if we don't need to keep existing
+ // data untouched or if we don't have an existing plural formula.
+ $header = _locale_import_parse_header($value['msgstr']);
+
+ // Get the plural formula and update in database.
+ if (isset($header["Plural-Forms"]) && $p = _locale_import_parse_plural_forms($header["Plural-Forms"], $file->uri)) {
+ list($nplurals, $plural) = $p;
+ db_update('languages')
+ ->fields(array(
+ 'plurals' => $nplurals,
+ 'formula' => $plural,
+ ))
+ ->condition('language', $lang)
+ ->execute();
+ }
+ else {
+ db_update('languages')
+ ->fields(array(
+ 'plurals' => 0,
+ 'formula' => '',
+ ))
+ ->condition('language', $lang)
+ ->execute();
+ }
+ }
+ $header_done = TRUE;
+ }
+
+ else {
+ // Some real string to import.
+ $comments = _locale_import_shorten_comments(empty($value['#']) ? array() : $value['#']);
+
+ if (strpos($value['msgid'], "\0")) {
+ // This string has plural versions.
+ $english = explode("\0", $value['msgid'], 2);
+ $entries = array_keys($value['msgstr']);
+ for ($i = 3; $i <= count($entries); $i++) {
+ $english[] = $english[1];
+ }
+ $translation = array_map('_locale_import_append_plural', $value['msgstr'], $entries);
+ $english = array_map('_locale_import_append_plural', $english, $entries);
+ foreach ($translation as $key => $trans) {
+ if ($key == 0) {
+ $plid = 0;
+ }
+ $plid = _locale_import_one_string_db($report, $lang, isset($value['msgctxt']) ? $value['msgctxt'] : '', $english[$key], $trans, $group, $comments, $mode, $plid, $key);
+ }
+ }
+
+ else {
+ // A simple string to import.
+ $english = $value['msgid'];
+ $translation = $value['msgstr'];
+ _locale_import_one_string_db($report, $lang, isset($value['msgctxt']) ? $value['msgctxt'] : '', $english, $translation, $group, $comments, $mode);
+ }
+ }
+ } // end of db-store operation
+}
+
+/**
+ * Import one string into the database.
+ *
+ * @param $report
+ * Report array summarizing the number of changes done in the form:
+ * array(inserts, updates, deletes).
+ * @param $langcode
+ * Language code to import string into.
+ * @param $context
+ * The context of this string.
+ * @param $source
+ * Source string.
+ * @param $translation
+ * Translation to language specified in $langcode.
+ * @param $textgroup
+ * Name of textgroup to store translation in.
+ * @param $location
+ * Location value to save with source string.
+ * @param $mode
+ * Import mode to use, LOCALE_IMPORT_KEEP or LOCALE_IMPORT_OVERWRITE.
+ * @param $plid
+ * Optional plural ID to use.
+ * @param $plural
+ * Optional plural value to use.
+ *
+ * @return
+ * The string ID of the existing string modified or the new string added.
+ */
+function _locale_import_one_string_db(&$report, $langcode, $context, $source, $translation, $textgroup, $location, $mode, $plid = 0, $plural = 0) {
+ $lid = db_query("SELECT lid FROM {locales_source} WHERE source = :source AND context = :context AND textgroup = :textgroup", array(':source' => $source, ':context' => $context, ':textgroup' => $textgroup))->fetchField();
+
+ if (!empty($translation)) {
+ // Skip this string unless it passes a check for dangerous code.
+ // Text groups other than default still can contain HTML tags
+ // (i.e. translatable blocks).
+ if ($textgroup == "default" && !locale_string_is_safe($translation)) {
+ $report['skips']++;
+ $lid = 0;
+ }
+ elseif ($lid) {
+ // We have this source string saved already.
+ db_update('locales_source')
+ ->fields(array(
+ 'location' => $location,
+ ))
+ ->condition('lid', $lid)
+ ->execute();
+
+ $exists = db_query("SELECT COUNT(lid) FROM {locales_target} WHERE lid = :lid AND language = :language", array(':lid' => $lid, ':language' => $langcode))->fetchField();
+
+ if (!$exists) {
+ // No translation in this language.
+ db_insert('locales_target')
+ ->fields(array(
+ 'lid' => $lid,
+ 'language' => $langcode,
+ 'translation' => $translation,
+ 'plid' => $plid,
+ 'plural' => $plural,
+ ))
+ ->execute();
+
+ $report['additions']++;
+ }
+ elseif ($mode == LOCALE_IMPORT_OVERWRITE) {
+ // Translation exists, only overwrite if instructed.
+ db_update('locales_target')
+ ->fields(array(
+ 'translation' => $translation,
+ 'plid' => $plid,
+ 'plural' => $plural,
+ ))
+ ->condition('language', $langcode)
+ ->condition('lid', $lid)
+ ->execute();
+
+ $report['updates']++;
+ }
+ }
+ else {
+ // No such source string in the database yet.
+ $lid = db_insert('locales_source')
+ ->fields(array(
+ 'location' => $location,
+ 'source' => $source,
+ 'context' => (string) $context,
+ 'textgroup' => $textgroup,
+ ))
+ ->execute();
+
+ db_insert('locales_target')
+ ->fields(array(
+ 'lid' => $lid,
+ 'language' => $langcode,
+ 'translation' => $translation,
+ 'plid' => $plid,
+ 'plural' => $plural
+ ))
+ ->execute();
+
+ $report['additions']++;
+ }
+ }
+ elseif ($mode == LOCALE_IMPORT_OVERWRITE) {
+ // Empty translation, remove existing if instructed.
+ db_delete('locales_target')
+ ->condition('language', $langcode)
+ ->condition('lid', $lid)
+ ->condition('plid', $plid)
+ ->condition('plural', $plural)
+ ->execute();
+
+ $report['deletes']++;
+ }
+
+ return $lid;
+}
+
+/**
+ * Parses a Gettext Portable Object file header
+ *
+ * @param $header
+ * A string containing the complete header.
+ *
+ * @return
+ * An associative array of key-value pairs.
+ */
+function _locale_import_parse_header($header) {
+ $header_parsed = array();
+ $lines = array_map('trim', explode("\n", $header));
+ foreach ($lines as $line) {
+ if ($line) {
+ list($tag, $contents) = explode(":", $line, 2);
+ $header_parsed[trim($tag)] = trim($contents);
+ }
+ }
+ return $header_parsed;
+}
+
+/**
+ * Parses a Plural-Forms entry from a Gettext Portable Object file header
+ *
+ * @param $pluralforms
+ * A string containing the Plural-Forms entry.
+ * @param $filepath
+ * A string containing the filepath.
+ *
+ * @return
+ * An array containing the number of plurals and a
+ * formula in PHP for computing the plural form.
+ */
+function _locale_import_parse_plural_forms($pluralforms, $filepath) {
+ // First, delete all whitespace
+ $pluralforms = strtr($pluralforms, array(" " => "", "\t" => ""));
+
+ // Select the parts that define nplurals and plural
+ $nplurals = strstr($pluralforms, "nplurals=");
+ if (strpos($nplurals, ";")) {
+ $nplurals = substr($nplurals, 9, strpos($nplurals, ";") - 9);
+ }
+ else {
+ return FALSE;
+ }
+ $plural = strstr($pluralforms, "plural=");
+ if (strpos($plural, ";")) {
+ $plural = substr($plural, 7, strpos($plural, ";") - 7);
+ }
+ else {
+ return FALSE;
+ }
+
+ // Get PHP version of the plural formula
+ $plural = _locale_import_parse_arithmetic($plural);
+
+ if ($plural !== FALSE) {
+ return array($nplurals, $plural);
+ }
+ else {
+ drupal_set_message(t('The translation file %filepath contains an error: the plural formula could not be parsed.', array('%filepath' => $filepath)), 'error');
+ return FALSE;
+ }
+}
+
+/**
+ * Parses and sanitizes an arithmetic formula into a PHP expression
+ *
+ * While parsing, we ensure, that the operators have the right
+ * precedence and associativity.
+ *
+ * @param $string
+ * A string containing the arithmetic formula.
+ *
+ * @return
+ * The PHP version of the formula.
+ */
+function _locale_import_parse_arithmetic($string) {
+ // Operator precedence table
+ $precedence = array("(" => -1, ")" => -1, "?" => 1, ":" => 1, "||" => 3, "&&" => 4, "==" => 5, "!=" => 5, "<" => 6, ">" => 6, "<=" => 6, ">=" => 6, "+" => 7, "-" => 7, "*" => 8, "/" => 8, "%" => 8);
+ // Right associativity
+ $right_associativity = array("?" => 1, ":" => 1);
+
+ $tokens = _locale_import_tokenize_formula($string);
+
+ // Parse by converting into infix notation then back into postfix
+ // Operator stack - holds math operators and symbols
+ $operator_stack = array();
+ // Element Stack - holds data to be operated on
+ $element_stack = array();
+
+ foreach ($tokens as $token) {
+ $current_token = $token;
+
+ // Numbers and the $n variable are simply pushed into $element_stack
+ if (is_numeric($token)) {
+ $element_stack[] = $current_token;
+ }
+ elseif ($current_token == "n") {
+ $element_stack[] = '$n';
+ }
+ elseif ($current_token == "(") {
+ $operator_stack[] = $current_token;
+ }
+ elseif ($current_token == ")") {
+ $topop = array_pop($operator_stack);
+ while (isset($topop) && ($topop != "(")) {
+ $element_stack[] = $topop;
+ $topop = array_pop($operator_stack);
+ }
+ }
+ elseif (!empty($precedence[$current_token])) {
+ // If it's an operator, then pop from $operator_stack into $element_stack until the
+ // precedence in $operator_stack is less than current, then push into $operator_stack
+ $topop = array_pop($operator_stack);
+ while (isset($topop) && ($precedence[$topop] >= $precedence[$current_token]) && !(($precedence[$topop] == $precedence[$current_token]) && !empty($right_associativity[$topop]) && !empty($right_associativity[$current_token]))) {
+ $element_stack[] = $topop;
+ $topop = array_pop($operator_stack);
+ }
+ if ($topop) {
+ $operator_stack[] = $topop; // Return element to top
+ }
+ $operator_stack[] = $current_token; // Parentheses are not needed
+ }
+ else {
+ return FALSE;
+ }
+ }
+
+ // Flush operator stack
+ $topop = array_pop($operator_stack);
+ while ($topop != NULL) {
+ $element_stack[] = $topop;
+ $topop = array_pop($operator_stack);
+ }
+
+ // Now extract formula from stack
+ $previous_size = count($element_stack) + 1;
+ while (count($element_stack) < $previous_size) {
+ $previous_size = count($element_stack);
+ for ($i = 2; $i < count($element_stack); $i++) {
+ $op = $element_stack[$i];
+ if (!empty($precedence[$op])) {
+ $f = "";
+ if ($op == ":") {
+ $f = $element_stack[$i - 2] . "):" . $element_stack[$i - 1] . ")";
+ }
+ elseif ($op == "?") {
+ $f = "(" . $element_stack[$i - 2] . "?(" . $element_stack[$i - 1];
+ }
+ else {
+ $f = "(" . $element_stack[$i - 2] . $op . $element_stack[$i - 1] . ")";
+ }
+ array_splice($element_stack, $i - 2, 3, $f);
+ break;
+ }
+ }
+ }
+
+ // If only one element is left, the number of operators is appropriate
+ if (count($element_stack) == 1) {
+ return $element_stack[0];
+ }
+ else {
+ return FALSE;
+ }
+}
+
+/**
+ * Backward compatible implementation of token_get_all() for formula parsing
+ *
+ * @param $string
+ * A string containing the arithmetic formula.
+ *
+ * @return
+ * The PHP version of the formula.
+ */
+function _locale_import_tokenize_formula($formula) {
+ $formula = str_replace(" ", "", $formula);
+ $tokens = array();
+ for ($i = 0; $i < strlen($formula); $i++) {
+ if (is_numeric($formula[$i])) {
+ $num = $formula[$i];
+ $j = $i + 1;
+ while ($j < strlen($formula) && is_numeric($formula[$j])) {
+ $num .= $formula[$j];
+ $j++;
+ }
+ $i = $j - 1;
+ $tokens[] = $num;
+ }
+ elseif ($pos = strpos(" =<>!&|", $formula[$i])) { // We won't have a space
+ $next = $formula[$i + 1];
+ switch ($pos) {
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ if ($next == '=') {
+ $tokens[] = $formula[$i] . '=';
+ $i++;
+ }
+ else {
+ $tokens[] = $formula[$i];
+ }
+ break;
+ case 5:
+ if ($next == '&') {
+ $tokens[] = '&&';
+ $i++;
+ }
+ else {
+ $tokens[] = $formula[$i];
+ }
+ break;
+ case 6:
+ if ($next == '|') {
+ $tokens[] = '||';
+ $i++;
+ }
+ else {
+ $tokens[] = $formula[$i];
+ }
+ break;
+ }
+ }
+ else {
+ $tokens[] = $formula[$i];
+ }
+ }
+ return $tokens;
+}
+
+/**
+ * Modify a string to contain proper count indices
+ *
+ * This is a callback function used via array_map()
+ *
+ * @param $entry
+ * An array element.
+ * @param $key
+ * Index of the array element.
+ */
+function _locale_import_append_plural($entry, $key) {
+ // No modifications for 0, 1
+ if ($key == 0 || $key == 1) {
+ return $entry;
+ }
+
+ // First remove any possibly false indices, then add new ones
+ $entry = preg_replace('/(@count)\[[0-9]\]/', '\\1', $entry);
+ return preg_replace('/(@count)/', "\\1[$key]", $entry);
+}
+
+/**
+ * Generate a short, one string version of the passed comment array
+ *
+ * @param $comment
+ * An array of strings containing a comment.
+ *
+ * @return
+ * Short one string version of the comment.
+ */
+function _locale_import_shorten_comments($comment) {
+ $comm = '';
+ while (count($comment)) {
+ $test = $comm . substr(array_shift($comment), 1) . ', ';
+ if (strlen($comm) < 130) {
+ $comm = $test;
+ }
+ else {
+ break;
+ }
+ }
+ return trim(substr($comm, 0, -2));
+}
+
+/**
+ * Parses a string in quotes
+ *
+ * @param $string
+ * A string specified with enclosing quotes.
+ *
+ * @return
+ * The string parsed from inside the quotes.
+ */
+function _locale_import_parse_quoted($string) {
+ if (substr($string, 0, 1) != substr($string, -1, 1)) {
+ return FALSE; // Start and end quotes must be the same
+ }
+ $quote = substr($string, 0, 1);
+ $string = substr($string, 1, -1);
+ if ($quote == '"') { // Double quotes: strip slashes
+ return stripcslashes($string);
+ }
+ elseif ($quote == "'") { // Simple quote: return as-is
+ return $string;
+ }
+ else {
+ return FALSE; // Unrecognized quote
+ }
+}
+/**
+ * @} End of "locale-api-import-export"
+ */
+
+/**
+ * Parses a JavaScript file, extracts strings wrapped in Drupal.t() and
+ * Drupal.formatPlural() and inserts them into the database.
+ */
+function _locale_parse_js_file($filepath) {
+ global $language;
+
+ // The file path might contain a query string, so make sure we only use the
+ // actual file.
+ $parsed_url = drupal_parse_url($filepath);
+ $filepath = $parsed_url['path'];
+ // Load the JavaScript file.
+ $file = file_get_contents($filepath);
+
+ // Match all calls to Drupal.t() in an array.
+ // Note: \s also matches newlines with the 's' modifier.
+ preg_match_all('~[^\w]Drupal\s*\.\s*t\s*\(\s*(' . LOCALE_JS_STRING . ')\s*[,\)]~s', $file, $t_matches);
+
+ // Match all Drupal.formatPlural() calls in another array.
+ preg_match_all('~[^\w]Drupal\s*\.\s*formatPlural\s*\(\s*.+?\s*,\s*(' . LOCALE_JS_STRING . ')\s*,\s*((?:(?:\'(?:\\\\\'|[^\'])*@count(?:\\\\\'|[^\'])*\'|"(?:\\\\"|[^"])*@count(?:\\\\"|[^"])*")(?:\s*\+\s*)?)+)\s*[,\)]~s', $file, $plural_matches);
+
+ // Loop through all matches and process them.
+ $all_matches = array_merge($plural_matches[1], $t_matches[1]);
+ foreach ($all_matches as $key => $string) {
+ $strings = array($string);
+
+ // If there is also a plural version of this string, add it to the strings array.
+ if (isset($plural_matches[2][$key])) {
+ $strings[] = $plural_matches[2][$key];
+ }
+
+ foreach ($strings as $key => $string) {
+ // Remove the quotes and string concatenations from the string.
+ $string = implode('', preg_split('~(? $string))->fetchObject();
+ if ($source) {
+ // We already have this source string and now have to add the location
+ // to the location column, if this file is not yet present in there.
+ $locations = preg_split('~\s*;\s*~', $source->location);
+
+ if (!in_array($filepath, $locations)) {
+ $locations[] = $filepath;
+ $locations = implode('; ', $locations);
+
+ // Save the new locations string to the database.
+ db_update('locales_source')
+ ->fields(array(
+ 'location' => $locations,
+ ))
+ ->condition('lid', $source->lid)
+ ->execute();
+ }
+ }
+ else {
+ // We don't have the source string yet, thus we insert it into the database.
+ db_insert('locales_source')
+ ->fields(array(
+ 'location' => $filepath,
+ 'source' => $string,
+ 'context' => '',
+ 'textgroup' => 'default',
+ ))
+ ->execute();
+ }
+ }
+ }
+}
+
+/**
+ * @addtogroup locale-api-import-export
+ * @{
+ */
+
+/**
+ * Generates a structured array of all strings with translations in
+ * $language, if given. This array can be used to generate an export
+ * of the string in the database.
+ *
+ * @param $language
+ * Language object to generate the output for, or NULL if generating
+ * translation template.
+ * @param $group
+ * Text group to export PO file from (eg. 'default' for interface
+ * translations).
+ */
+function _locale_export_get_strings($language = NULL, $group = 'default') {
+ if (isset($language)) {
+ $result = db_query("SELECT s.lid, s.source, s.context, s.location, t.translation, t.plid, t.plural FROM {locales_source} s LEFT JOIN {locales_target} t ON s.lid = t.lid AND t.language = :language WHERE s.textgroup = :textgroup ORDER BY t.plid, t.plural", array(':language' => $language->language, ':textgroup' => $group));
+ }
+ else {
+ $result = db_query("SELECT s.lid, s.source, s.context, s.location, t.plid, t.plural FROM {locales_source} s LEFT JOIN {locales_target} t ON s.lid = t.lid WHERE s.textgroup = :textgroup ORDER BY t.plid, t.plural", array(':textgroup' => $group));
+ }
+ $strings = array();
+ foreach ($result as $child) {
+ $string = array(
+ 'comment' => $child->location,
+ 'source' => $child->source,
+ 'context' => $child->context,
+ 'translation' => isset($child->translation) ? $child->translation : '',
+ );
+ if ($child->plid) {
+ // Has a parent lid. Since we process in the order of plids,
+ // we already have the parent in the array, so we can add the
+ // lid to the next plural version to it. This builds a linked
+ // list of plurals.
+ $string['child'] = TRUE;
+ $strings[$child->plid]['plural'] = $child->lid;
+ }
+ $strings[$child->lid] = $string;
+ }
+ return $strings;
+}
+
+/**
+ * Generates the PO(T) file contents for given strings.
+ *
+ * @param $language
+ * Language object to generate the output for, or NULL if generating
+ * translation template.
+ * @param $strings
+ * Array of strings to export. See _locale_export_get_strings()
+ * on how it should be formatted.
+ * @param $header
+ * The header portion to use for the output file. Defaults
+ * are provided for PO and POT files.
+ */
+function _locale_export_po_generate($language = NULL, $strings = array(), $header = NULL) {
+ global $user;
+
+ if (!isset($header)) {
+ if (isset($language)) {
+ $header = '# ' . $language->name . ' translation of ' . variable_get('site_name', 'Drupal') . "\n";
+ $header .= '# Generated by ' . $user->name . ' <' . $user->mail . ">\n";
+ $header .= "#\n";
+ $header .= "msgid \"\"\n";
+ $header .= "msgstr \"\"\n";
+ $header .= "\"Project-Id-Version: PROJECT VERSION\\n\"\n";
+ $header .= "\"POT-Creation-Date: " . date("Y-m-d H:iO") . "\\n\"\n";
+ $header .= "\"PO-Revision-Date: " . date("Y-m-d H:iO") . "\\n\"\n";
+ $header .= "\"Last-Translator: NAME \\n\"\n";
+ $header .= "\"Language-Team: LANGUAGE \\n\"\n";
+ $header .= "\"MIME-Version: 1.0\\n\"\n";
+ $header .= "\"Content-Type: text/plain; charset=utf-8\\n\"\n";
+ $header .= "\"Content-Transfer-Encoding: 8bit\\n\"\n";
+ if ($language->formula && $language->plurals) {
+ $header .= "\"Plural-Forms: nplurals=" . $language->plurals . "; plural=" . strtr($language->formula, array('$' => '')) . ";\\n\"\n";
+ }
+ }
+ else {
+ $header = "# LANGUAGE translation of PROJECT\n";
+ $header .= "# Copyright (c) YEAR NAME \n";
+ $header .= "#\n";
+ $header .= "msgid \"\"\n";
+ $header .= "msgstr \"\"\n";
+ $header .= "\"Project-Id-Version: PROJECT VERSION\\n\"\n";
+ $header .= "\"POT-Creation-Date: " . date("Y-m-d H:iO") . "\\n\"\n";
+ $header .= "\"PO-Revision-Date: YYYY-mm-DD HH:MM+ZZZZ\\n\"\n";
+ $header .= "\"Last-Translator: NAME \\n\"\n";
+ $header .= "\"Language-Team: LANGUAGE \\n\"\n";
+ $header .= "\"MIME-Version: 1.0\\n\"\n";
+ $header .= "\"Content-Type: text/plain; charset=utf-8\\n\"\n";
+ $header .= "\"Content-Transfer-Encoding: 8bit\\n\"\n";
+ $header .= "\"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\\n\"\n";
+ }
+ }
+
+ $output = $header . "\n";
+
+ foreach ($strings as $lid => $string) {
+ // Only process non-children, children are output below their parent.
+ if (!isset($string['child'])) {
+ if ($string['comment']) {
+ $output .= '#: ' . $string['comment'] . "\n";
+ }
+ if (!empty($string['context'])) {
+ $output .= 'msgctxt ' . _locale_export_string($string['context']);
+ }
+ $output .= 'msgid ' . _locale_export_string($string['source']);
+ if (!empty($string['plural'])) {
+ $plural = $string['plural'];
+ $output .= 'msgid_plural ' . _locale_export_string($strings[$plural]['source']);
+ if (isset($language)) {
+ $translation = $string['translation'];
+ for ($i = 0; $i < $language->plurals; $i++) {
+ $output .= 'msgstr[' . $i . '] ' . _locale_export_string($translation);
+ if ($plural) {
+ $translation = _locale_export_remove_plural($strings[$plural]['translation']);
+ $plural = isset($strings[$plural]['plural']) ? $strings[$plural]['plural'] : 0;
+ }
+ else {
+ $translation = '';
+ }
+ }
+ }
+ else {
+ $output .= 'msgstr[0] ""' . "\n";
+ $output .= 'msgstr[1] ""' . "\n";
+ }
+ }
+ else {
+ $output .= 'msgstr ' . _locale_export_string($string['translation']);
+ }
+ $output .= "\n";
+ }
+ }
+ return $output;
+}
+
+/**
+ * Write a generated PO or POT file to the output.
+ *
+ * @param $language
+ * Language object to generate the output for, or NULL if generating
+ * translation template.
+ * @param $output
+ * The PO(T) file to output as a string. See _locale_export_generate_po()
+ * on how it can be generated.
+ */
+function _locale_export_po($language = NULL, $output = NULL) {
+ // Log the export event.
+ if (isset($language)) {
+ $filename = $language->language . '.po';
+ watchdog('locale', 'Exported %locale translation file: %filename.', array('%locale' => $language->name, '%filename' => $filename));
+ }
+ else {
+ $filename = 'drupal.pot';
+ watchdog('locale', 'Exported translation file: %filename.', array('%filename' => $filename));
+ }
+ // Download the file for the client.
+ header("Content-Disposition: attachment; filename=$filename");
+ header("Content-Type: text/plain; charset=utf-8");
+ print $output;
+ drupal_exit();
+}
+
+/**
+ * Print out a string on multiple lines
+ */
+function _locale_export_string($str) {
+ $stri = addcslashes($str, "\0..\37\\\"");
+ $parts = array();
+
+ // Cut text into several lines
+ while ($stri != "") {
+ $i = strpos($stri, "\\n");
+ if ($i === FALSE) {
+ $curstr = $stri;
+ $stri = "";
+ }
+ else {
+ $curstr = substr($stri, 0, $i + 2);
+ $stri = substr($stri, $i + 2);
+ }
+ $curparts = explode("\n", _locale_export_wrap($curstr, 70));
+ $parts = array_merge($parts, $curparts);
+ }
+
+ // Multiline string
+ if (count($parts) > 1) {
+ return "\"\"\n\"" . implode("\"\n\"", $parts) . "\"\n";
+ }
+ // Single line string
+ elseif (count($parts) == 1) {
+ return "\"$parts[0]\"\n";
+ }
+ // No translation
+ else {
+ return "\"\"\n";
+ }
+}
+
+/**
+ * Custom word wrapping for Portable Object (Template) files.
+ */
+function _locale_export_wrap($str, $len) {
+ $words = explode(' ', $str);
+ $return = array();
+
+ $cur = "";
+ $nstr = 1;
+ while (count($words)) {
+ $word = array_shift($words);
+ if ($nstr) {
+ $cur = $word;
+ $nstr = 0;
+ }
+ elseif (strlen("$cur $word") > $len) {
+ $return[] = $cur . " ";
+ $cur = $word;
+ }
+ else {
+ $cur = "$cur $word";
+ }
+ }
+ $return[] = $cur;
+
+ return implode("\n", $return);
+}
+
+/**
+ * Removes plural index information from a string
+ */
+function _locale_export_remove_plural($entry) {
+ return preg_replace('/(@count)\[[0-9]\]/', '\\1', $entry);
+}
+/**
+ * @} End of "locale-api-import-export"
+ */
+
+/**
+ * @defgroup locale-api-seek Translation search API
+ * @{
+ * Functions to search in translation files.
+ *
+ * These functions provide the functionality to search for specific
+ * translations.
+ */
+
+/**
+ * Perform a string search and display results in a table
+ */
+function _locale_translate_seek() {
+ $output = '';
+
+ // We have at least one criterion to match
+ if (!($query = _locale_translate_seek_query())) {
+ $query = array(
+ 'translation' => 'all',
+ 'group' => 'all',
+ 'language' => 'all',
+ 'string' => '',
+ );
+ }
+
+ $sql_query = db_select('locales_source', 's');
+ $sql_query->leftJoin('locales_target', 't', 't.lid = s.lid');
+ $sql_query->fields('s', array('source', 'location', 'context', 'lid', 'textgroup'));
+ $sql_query->fields('t', array('translation', 'language'));
+
+ // Compute LIKE section.
+ switch ($query['translation']) {
+ case 'translated':
+ $sql_query->condition('t.translation', '%' . db_like($query['string']) . '%', 'LIKE');
+ $sql_query->orderBy('t.translation', 'DESC');
+ break;
+ case 'untranslated':
+ $sql_query->condition(db_and()
+ ->condition('s.source', '%' . db_like($query['string']) . '%', 'LIKE')
+ ->isNull('t.translation')
+ );
+ $sql_query->orderBy('s.source');
+ break;
+ case 'all' :
+ default:
+ $condition = db_or()
+ ->condition('s.source', '%' . db_like($query['string']) . '%', 'LIKE');
+ if ($query['language'] != 'en') {
+ // Only search in translations if the language is not forced to English.
+ $condition->condition('t.translation', '%' . db_like($query['string']) . '%', 'LIKE');
+ }
+ $sql_query->condition($condition);
+ break;
+ }
+
+ $limit_language = NULL;
+ if ($query['language'] != 'en' && $query['language'] != 'all') {
+ $sql_query->condition('language', $query['language']);
+ $limit_language = $query['language'];
+ }
+
+ // Add a condition on the text group.
+ if (!empty($query['group']) && $query['group'] != 'all') {
+ $sql_query->condition('s.textgroup', $query['group']);
+ }
+
+ $sql_query = $sql_query->extend('PagerDefault')->limit(50);
+ $locales = $sql_query->execute();
+
+ $groups = module_invoke_all('locale', 'groups');
+ $header = array(t('Text group'), t('String'), t('Context'), ($limit_language) ? t('Language') : t('Languages'), array('data' => t('Operations'), 'colspan' => '2'));
+
+ $strings = array();
+ foreach ($locales as $locale) {
+ if (!isset($strings[$locale->lid])) {
+ $strings[$locale->lid] = array(
+ 'group' => $locale->textgroup,
+ 'languages' => array(),
+ 'location' => $locale->location,
+ 'source' => $locale->source,
+ 'context' => $locale->context,
+ );
+ }
+ if (isset($locale->language)) {
+ $strings[$locale->lid]['languages'][$locale->language] = $locale->translation;
+ }
+ }
+
+ $rows = array();
+ foreach ($strings as $lid => $string) {
+ $rows[] = array(
+ $groups[$string['group']],
+ array('data' => check_plain(truncate_utf8($string['source'], 150, FALSE, TRUE)) . '' . $string['location'] . ' '),
+ $string['context'],
+ array('data' => _locale_translate_language_list($string['languages'], $limit_language), 'align' => 'center'),
+ array('data' => l(t('edit'), "admin/config/regional/translate/edit/$lid", array('query' => drupal_get_destination())), 'class' => array('nowrap')),
+ array('data' => l(t('delete'), "admin/config/regional/translate/delete/$lid", array('query' => drupal_get_destination())), 'class' => array('nowrap')),
+ );
+ }
+
+ $output .= theme('table', array('header' => $header, 'rows' => $rows, 'empty' => t('No strings available.')));
+ $output .= theme('pager');
+
+ return $output;
+}
+
+/**
+ * Build array out of search criteria specified in request variables
+ */
+function _locale_translate_seek_query() {
+ $query = &drupal_static(__FUNCTION__);
+ if (!isset($query)) {
+ $query = array();
+ $fields = array('string', 'language', 'translation', 'group');
+ foreach ($fields as $field) {
+ if (isset($_SESSION['locale_translation_filter'][$field])) {
+ $query[$field] = $_SESSION['locale_translation_filter'][$field];
+ }
+ }
+ }
+ return $query;
+}
+
+/**
+ * Force the JavaScript translation file(s) to be refreshed.
+ *
+ * This function sets a refresh flag for a specified language, or all
+ * languages except English, if none specified. JavaScript translation
+ * files are rebuilt (with locale_update_js_files()) the next time a
+ * request is served in that language.
+ *
+ * @param $langcode
+ * The language code for which the file needs to be refreshed.
+ *
+ * @return
+ * New content of the 'javascript_parsed' variable.
+ */
+function _locale_invalidate_js($langcode = NULL) {
+ $parsed = variable_get('javascript_parsed', array());
+
+ if (empty($langcode)) {
+ // Invalidate all languages.
+ $languages = language_list();
+ unset($languages['en']);
+ foreach ($languages as $lcode => $data) {
+ $parsed['refresh:' . $lcode] = 'waiting';
+ }
+ }
+ else {
+ // Invalidate single language.
+ $parsed['refresh:' . $langcode] = 'waiting';
+ }
+
+ variable_set('javascript_parsed', $parsed);
+ return $parsed;
+}
+
+/**
+ * (Re-)Creates the JavaScript translation file for a language.
+ *
+ * @param $language
+ * The language, the translation file should be (re)created for.
+ */
+function _locale_rebuild_js($langcode = NULL) {
+ if (!isset($langcode)) {
+ global $language;
+ }
+ else {
+ // Get information about the locale.
+ $languages = language_list();
+ $language = $languages[$langcode];
+ }
+
+ // Construct the array for JavaScript translations.
+ // Only add strings with a translation to the translations array.
+ $result = db_query("SELECT s.lid, s.source, t.translation FROM {locales_source} s INNER JOIN {locales_target} t ON s.lid = t.lid AND t.language = :language WHERE s.location LIKE '%.js%' AND s.textgroup = :textgroup AND t.translation IS NOT NULL", array(':language' => $language->language, ':textgroup' => 'default'));
+
+ $translations = array();
+ foreach ($result as $data) {
+ $translations[$data->source] = $data->translation;
+ }
+
+ // Construct the JavaScript file, if there are translations.
+ $data_hash = NULL;
+ $data = $status = '';
+ if (!empty($translations)) {
+
+ $data = "Drupal.locale = { ";
+
+ if (!empty($language->formula)) {
+ $data .= "'pluralFormula': function (\$n) { return Number({$language->formula}); }, ";
+ }
+
+ $data .= "'strings': " . drupal_json_encode($translations) . " };";
+ $data_hash = drupal_hash_base64($data);
+ }
+
+ // Construct the filepath where JS translation files are stored.
+ // There is (on purpose) no front end to edit that variable.
+ $dir = 'public://' . variable_get('locale_js_directory', 'languages');
+
+ // Delete old file, if we have no translations anymore, or a different file to be saved.
+ $changed_hash = $language->javascript != $data_hash;
+ if (!empty($language->javascript) && (!$data || $changed_hash)) {
+ file_unmanaged_delete($dir . '/' . $language->language . '_' . $language->javascript . '.js');
+ $language->javascript = '';
+ $status = 'deleted';
+ }
+
+ // Only create a new file if the content has changed or the original file got
+ // lost.
+ $dest = $dir . '/' . $language->language . '_' . $data_hash . '.js';
+ if ($data && ($changed_hash || !file_exists($dest))) {
+ // Ensure that the directory exists and is writable, if possible.
+ file_prepare_directory($dir, FILE_CREATE_DIRECTORY);
+
+ // Save the file.
+ if (file_unmanaged_save_data($data, $dest)) {
+ $language->javascript = $data_hash;
+ // If we deleted a previous version of the file and we replace it with a
+ // new one we have an update.
+ if ($status == 'deleted') {
+ $status = 'updated';
+ }
+ // If the file did not exist previously and the data has changed we have
+ // a fresh creation.
+ elseif ($changed_hash) {
+ $status = 'created';
+ }
+ // If the data hash is unchanged the translation was lost and has to be
+ // rebuilt.
+ else {
+ $status = 'rebuilt';
+ }
+ }
+ else {
+ $language->javascript = '';
+ $status = 'error';
+ }
+ }
+
+ // Save the new JavaScript hash (or an empty value if the file just got
+ // deleted). Act only if some operation was executed that changed the hash
+ // code.
+ if ($status && $changed_hash) {
+ db_update('languages')
+ ->fields(array(
+ 'javascript' => $language->javascript,
+ ))
+ ->condition('language', $language->language)
+ ->execute();
+
+ // Update the default language variable if the default language has been altered.
+ // This is necessary to keep the variable consistent with the database
+ // version of the language and to prevent checking against an outdated hash.
+ $default_langcode = language_default('language');
+ if ($default_langcode == $language->language) {
+ $default = db_query("SELECT * FROM {languages} WHERE language = :language", array(':language' => $default_langcode))->fetchObject();
+ variable_set('language_default', $default);
+ }
+ }
+
+ // Log the operation and return success flag.
+ switch ($status) {
+ case 'updated':
+ watchdog('locale', 'Updated JavaScript translation file for the language %language.', array('%language' => t($language->name)));
+ return TRUE;
+ case 'rebuilt':
+ watchdog('locale', 'JavaScript translation file %file.js was lost.', array('%file' => $language->javascript), WATCHDOG_WARNING);
+ // Proceed to the 'created' case as the JavaScript translation file has
+ // been created again.
+ case 'created':
+ watchdog('locale', 'Created JavaScript translation file for the language %language.', array('%language' => t($language->name)));
+ return TRUE;
+ case 'deleted':
+ watchdog('locale', 'Removed JavaScript translation file for the language %language, because no translations currently exist for that language.', array('%language' => t($language->name)));
+ return TRUE;
+ case 'error':
+ watchdog('locale', 'An error occurred during creation of the JavaScript translation file for the language %language.', array('%language' => t($language->name)), WATCHDOG_ERROR);
+ return FALSE;
+ default:
+ // No operation needed.
+ return TRUE;
+ }
+}
+
+/**
+ * List languages in search result table
+ */
+function _locale_translate_language_list($translation, $limit_language) {
+ // Add CSS.
+ drupal_add_css(drupal_get_path('module', 'locale') . '/locale.css');
+
+ $languages = language_list();
+ unset($languages['en']);
+ $output = '';
+ foreach ($languages as $langcode => $language) {
+ if (!$limit_language || $limit_language == $langcode) {
+ $output .= (!empty($translation[$langcode])) ? $langcode . ' ' : "$langcode ";
+ }
+ }
+
+ return $output;
+}
+/**
+ * @} End of "locale-api-seek"
+ */
+
+/**
+ * @defgroup locale-api-predefined List of predefined languages
+ * @{
+ * API to provide a list of predefined languages.
+ */
+
+/**
+ * Prepares the language code list for a select form item with only the unsupported ones
+ */
+function _locale_prepare_predefined_list() {
+ include_once DRUPAL_ROOT . '/includes/iso.inc';
+ $languages = language_list();
+ $predefined = _locale_get_predefined_list();
+ foreach ($predefined as $key => $value) {
+ if (isset($languages[$key])) {
+ unset($predefined[$key]);
+ continue;
+ }
+ // Include native name in output, if possible
+ if (count($value) > 1) {
+ $tname = t($value[0]);
+ $predefined[$key] = ($tname == $value[1]) ? $tname : "$tname ($value[1])";
+ }
+ else {
+ $predefined[$key] = t($value[0]);
+ }
+ }
+ asort($predefined);
+ return $predefined;
+}
+
+/**
+ * @} End of "locale-api-languages-predefined"
+ */
+
+/**
+ * @defgroup locale-autoimport Automatic interface translation import
+ * @{
+ * Functions to create batches for importing translations.
+ *
+ * These functions can be used to import translations for installed
+ * modules.
+ */
+
+/**
+ * Prepare a batch to import translations for all enabled
+ * modules in a given language.
+ *
+ * @param $langcode
+ * Language code to import translations for.
+ * @param $finished
+ * Optional finished callback for the batch.
+ * @param $skip
+ * Array of component names to skip. Used in the installer for the
+ * second pass import, when most components are already imported.
+ *
+ * @return
+ * A batch structure or FALSE if no files found.
+ */
+function locale_batch_by_language($langcode, $finished = NULL, $skip = array()) {
+ // Collect all files to import for all enabled modules and themes.
+ $files = array();
+ $components = array();
+ $query = db_select('system', 's');
+ $query->fields('s', array('name', 'filename'));
+ $query->condition('s.status', 1);
+ if (count($skip)) {
+ $query->condition('name', $skip, 'NOT IN');
+ }
+ $result = $query->execute();
+ foreach ($result as $component) {
+ // Collect all files for all components, names as $langcode.po or
+ // with names ending with $langcode.po. This allows for filenames
+ // like node-module.de.po to let translators use small files and
+ // be able to import in smaller chunks.
+ $files = array_merge($files, file_scan_directory(dirname($component->filename) . '/translations', '/(^|\.)' . $langcode . '\.po$/', array('recurse' => FALSE)));
+ $components[] = $component->name;
+ }
+
+ return _locale_batch_build($files, $finished, $components);
+}
+
+/**
+ * Prepare a batch to run when installing modules or enabling themes.
+ *
+ * This batch will import translations for the newly added components
+ * in all the languages already set up on the site.
+ *
+ * @param $components
+ * An array of component (theme and/or module) names to import
+ * translations for.
+ * @param $finished
+ * Optional finished callback for the batch.
+ */
+function locale_batch_by_component($components, $finished = '_locale_batch_system_finished') {
+ $files = array();
+ $languages = language_list('enabled');
+ unset($languages[1]['en']);
+ if (count($languages[1])) {
+ $language_list = join('|', array_keys($languages[1]));
+ // Collect all files to import for all $components.
+ $result = db_query("SELECT name, filename FROM {system} WHERE status = 1");
+ foreach ($result as $component) {
+ if (in_array($component->name, $components)) {
+ // Collect all files for this component in all enabled languages, named
+ // as $langcode.po or with names ending with $langcode.po. This allows
+ // for filenames like node-module.de.po to let translators use small
+ // files and be able to import in smaller chunks.
+ $files = array_merge($files, file_scan_directory(dirname($component->filename) . '/translations', '/(^|\.)(' . $language_list . ')\.po$/', array('recurse' => FALSE)));
+ }
+ }
+ return _locale_batch_build($files, $finished);
+ }
+ return FALSE;
+}
+
+/**
+ * Build a locale batch from an array of files.
+ *
+ * @param $files
+ * Array of files to import.
+ * @param $finished
+ * Optional finished callback for the batch.
+ * @param $components
+ * Optional list of component names the batch covers. Used in the installer.
+ *
+ * @return
+ * A batch structure.
+ */
+function _locale_batch_build($files, $finished = NULL, $components = array()) {
+ $t = get_t();
+ if (count($files)) {
+ $operations = array();
+ foreach ($files as $file) {
+ // We call _locale_batch_import for every batch operation.
+ $operations[] = array('_locale_batch_import', array($file->uri));
+ }
+ $batch = array(
+ 'operations' => $operations,
+ 'title' => $t('Importing interface translations'),
+ 'init_message' => $t('Starting import'),
+ 'error_message' => $t('Error importing interface translations'),
+ 'file' => 'includes/locale.inc',
+ // This is not a batch API construct, but data passed along to the
+ // installer, so we know what did we import already.
+ '#components' => $components,
+ );
+ if (isset($finished)) {
+ $batch['finished'] = $finished;
+ }
+ return $batch;
+ }
+ return FALSE;
+}
+
+/**
+ * Perform interface translation import as a batch step.
+ *
+ * @param $filepath
+ * Path to a file to import.
+ * @param $results
+ * Contains a list of files imported.
+ */
+function _locale_batch_import($filepath, &$context) {
+ // The filename is either {langcode}.po or {prefix}.{langcode}.po, so
+ // we can extract the language code to use for the import from the end.
+ if (preg_match('!(/|\.)([^\./]+)\.po$!', $filepath, $langcode)) {
+ $file = (object) array('filename' => basename($filepath), 'uri' => $filepath);
+ _locale_import_read_po('db-store', $file, LOCALE_IMPORT_KEEP, $langcode[2]);
+ $context['results'][] = $filepath;
+ }
+}
+
+/**
+ * Finished callback of system page locale import batch.
+ * Inform the user of translation files imported.
+ */
+function _locale_batch_system_finished($success, $results) {
+ if ($success) {
+ drupal_set_message(format_plural(count($results), 'One translation file imported for the newly installed modules.', '@count translation files imported for the newly installed modules.'));
+ }
+}
+
+/**
+ * Finished callback of language addition locale import batch.
+ * Inform the user of translation files imported.
+ */
+function _locale_batch_language_finished($success, $results) {
+ if ($success) {
+ drupal_set_message(format_plural(count($results), 'One translation file imported for the enabled modules.', '@count translation files imported for the enabled modules.'));
+ }
+}
+
+/**
+ * @} End of "locale-autoimport"
+ */
+
+/**
+ * Get list of all predefined and custom countries.
+ *
+ * @return
+ * An array of all country code => country name pairs.
+ */
+function country_get_list() {
+ include_once DRUPAL_ROOT . '/includes/iso.inc';
+ $countries = _country_get_predefined_list();
+ // Allow other modules to modify the country list.
+ drupal_alter('countries', $countries);
+ return $countries;
+}
+
+/**
+ * Save locale specific date formats to the database.
+ *
+ * @param $langcode
+ * Language code, can be 2 characters, e.g. 'en' or 5 characters, e.g.
+ * 'en-CA'.
+ * @param $type
+ * Date format type, e.g. 'short', 'medium'.
+ * @param $format
+ * The date format string.
+ */
+function locale_date_format_save($langcode, $type, $format) {
+ $locale_format = array();
+ $locale_format['language'] = $langcode;
+ $locale_format['type'] = $type;
+ $locale_format['format'] = $format;
+
+ $is_existing = (bool) db_query_range('SELECT 1 FROM {date_format_locale} WHERE language = :langcode AND type = :type', 0, 1, array(':langcode' => $langcode, ':type' => $type))->fetchField();
+ if ($is_existing) {
+ $keys = array('type', 'language');
+ drupal_write_record('date_format_locale', $locale_format, $keys);
+ }
+ else {
+ drupal_write_record('date_format_locale', $locale_format);
+ }
+}
+
+/**
+ * Select locale date format details from database.
+ *
+ * @param $languages
+ * An array of language codes.
+ *
+ * @return
+ * An array of date formats.
+ */
+function locale_get_localized_date_format($languages) {
+ $formats = array();
+
+ // Get list of different format types.
+ $format_types = system_get_date_types();
+ $short_default = variable_get('date_format_short', 'm/d/Y - H:i');
+
+ // Loop through each language until we find one with some date formats
+ // configured.
+ foreach ($languages as $language) {
+ $date_formats = system_date_format_locale($language);
+ if (!empty($date_formats)) {
+ // We have locale-specific date formats, so check for their types. If
+ // we're missing a type, use the default setting instead.
+ foreach ($format_types as $type => $type_info) {
+ // If format exists for this language, use it.
+ if (!empty($date_formats[$type])) {
+ $formats['date_format_' . $type] = $date_formats[$type];
+ }
+ // Otherwise get default variable setting. If this is not set, default
+ // to the short format.
+ else {
+ $formats['date_format_' . $type] = variable_get('date_format_' . $type, $short_default);
+ }
+ }
+
+ // Return on the first match.
+ return $formats;
+ }
+ }
+
+ // No locale specific formats found, so use defaults.
+ $system_types = array('short', 'medium', 'long');
+ // Handle system types separately as they have defaults if no variable exists.
+ $formats['date_format_short'] = $short_default;
+ $formats['date_format_medium'] = variable_get('date_format_medium', 'D, m/d/Y - H:i');
+ $formats['date_format_long'] = variable_get('date_format_long', 'l, F j, Y - H:i');
+
+ // For non-system types, get the default setting, otherwise use the short
+ // format.
+ foreach ($format_types as $type => $type_info) {
+ if (!in_array($type, $system_types)) {
+ $formats['date_format_' . $type] = variable_get('date_format_' . $type, $short_default);
+ }
+ }
+
+ return $formats;
+}
diff --git a/backup/modules/20110602035425/drupal/includes/lock.inc b/backup/modules/20110602035425/drupal/includes/lock.inc
new file mode 100644
index 00000000..ddf4e754
--- /dev/null
+++ b/backup/modules/20110602035425/drupal/includes/lock.inc
@@ -0,0 +1,252 @@
+fields(array('expire' => $expire))
+ ->condition('name', $name)
+ ->condition('value', _lock_id())
+ ->execute();
+ if (!$success) {
+ // The lock was broken.
+ unset($locks[$name]);
+ }
+ return $success;
+ }
+ else {
+ // Optimistically try to acquire the lock, then retry once if it fails.
+ // The first time through the loop cannot be a retry.
+ $retry = FALSE;
+ // We always want to do this code at least once.
+ do {
+ try {
+ db_insert('semaphore')
+ ->fields(array(
+ 'name' => $name,
+ 'value' => _lock_id(),
+ 'expire' => $expire,
+ ))
+ ->execute();
+ // We track all acquired locks in the global variable.
+ $locks[$name] = TRUE;
+ // We never need to try again.
+ $retry = FALSE;
+ }
+ catch (PDOException $e) {
+ // Suppress the error. If this is our first pass through the loop,
+ // then $retry is FALSE. In this case, the insert must have failed
+ // meaning some other request acquired the lock but did not release it.
+ // We decide whether to retry by checking lock_may_be_available()
+ // Since this will break the lock in case it is expired.
+ $retry = $retry ? FALSE : lock_may_be_available($name);
+ }
+ // We only retry in case the first attempt failed, but we then broke
+ // an expired lock.
+ } while ($retry);
+ }
+ return isset($locks[$name]);
+}
+
+/**
+ * Check if lock acquired by a different process may be available.
+ *
+ * If an existing lock has expired, it is removed.
+ *
+ * @param $name
+ * The name of the lock.
+ *
+ * @return
+ * TRUE if there is no lock or it was removed, FALSE otherwise.
+ */
+function lock_may_be_available($name) {
+ $lock = db_query('SELECT expire, value FROM {semaphore} WHERE name = :name', array(':name' => $name))->fetchAssoc();
+ if (!$lock) {
+ return TRUE;
+ }
+ $expire = (float) $lock['expire'];
+ $now = microtime(TRUE);
+ if ($now > $expire) {
+ // We check two conditions to prevent a race condition where another
+ // request acquired the lock and set a new expire time. We add a small
+ // number to $expire to avoid errors with float to string conversion.
+ return (bool) db_delete('semaphore')
+ ->condition('name', $name)
+ ->condition('value', $lock['value'])
+ ->condition('expire', 0.0001 + $expire, '<=')
+ ->execute();
+ }
+ return FALSE;
+}
+
+/**
+ * Wait for a lock to be available.
+ *
+ * This function may be called in a request that fails to acquire a desired
+ * lock. This will block further execution until the lock is available or the
+ * specified delay in seconds is reached. This should not be used with locks
+ * that are acquired very frequently, since the lock is likely to be acquired
+ * again by a different request during the sleep().
+ *
+ * @param $name
+ * The name of the lock.
+ * @param $delay
+ * The maximum number of seconds to wait, as an integer.
+ *
+ * @return
+ * TRUE if the lock holds, FALSE if it is available.
+ */
+function lock_wait($name, $delay = 30) {
+ $delay = (int) $delay;
+ while ($delay--) {
+ // This function should only be called by a request that failed to get a
+ // lock, so we sleep first to give the parallel request a chance to finish
+ // and release the lock.
+ sleep(1);
+ if (lock_may_be_available($name)) {
+ // No longer need to wait.
+ return FALSE;
+ }
+ }
+ // The caller must still wait longer to get the lock.
+ return TRUE;
+}
+
+/**
+ * Release a lock previously acquired by lock_acquire().
+ *
+ * This will release the named lock if it is still held by the current request.
+ *
+ * @param $name
+ * The name of the lock.
+ */
+function lock_release($name) {
+ global $locks;
+
+ unset($locks[$name]);
+ db_delete('semaphore')
+ ->condition('name', $name)
+ ->condition('value', _lock_id())
+ ->execute();
+}
+
+/**
+ * Release all previously acquired locks.
+ */
+function lock_release_all($lock_id = NULL) {
+ global $locks;
+
+ $locks = array();
+ if (empty($lock_id)) {
+ $lock_id = _lock_id();
+ }
+ db_delete('semaphore')
+ ->condition('value', $lock_id)
+ ->execute();
+}
+
+/**
+ * @} End of "defgroup lock".
+ */
diff --git a/backup/modules/20110602035425/drupal/includes/mail.inc b/backup/modules/20110602035425/drupal/includes/mail.inc
new file mode 100644
index 00000000..6481bac9
--- /dev/null
+++ b/backup/modules/20110602035425/drupal/includes/mail.inc
@@ -0,0 +1,587 @@
+mail() sends the e-mail, which can
+ * be reused if the exact same composed e-mail is to be sent to multiple
+ * recipients.
+ *
+ * Finding out what language to send the e-mail with needs some consideration.
+ * If you send e-mail to a user, her preferred language should be fine, so
+ * use user_preferred_language(). If you send email based on form values
+ * filled on the page, there are two additional choices if you are not
+ * sending the e-mail to a user on the site. You can either use the language
+ * used to generate the page ($language global variable) or the site default
+ * language. See language_default(). The former is good if sending e-mail to
+ * the person filling the form, the later is good if you send e-mail to an
+ * address previously set up (like contact addresses in a contact form).
+ *
+ * Taking care of always using the proper language is even more important
+ * when sending e-mails in a row to multiple users. Hook_mail() abstracts
+ * whether the mail text comes from an administrator setting or is
+ * static in the source code. It should also deal with common mail tokens,
+ * only receiving $params which are unique to the actual e-mail at hand.
+ *
+ * An example:
+ *
+ * @code
+ * function example_notify($accounts) {
+ * foreach ($accounts as $account) {
+ * $params['account'] = $account;
+ * // example_mail() will be called based on the first drupal_mail() parameter.
+ * drupal_mail('example', 'notice', $account->mail, user_preferred_language($account), $params);
+ * }
+ * }
+ *
+ * function example_mail($key, &$message, $params) {
+ * $data['user'] = $params['account'];
+ * $options['language'] = $message['language'];
+ * user_mail_tokens($variables, $data, $options);
+ * switch($key) {
+ * case 'notice':
+ * $langcode = $message['language']->language;
+ * $message['subject'] = t('Notification from !site', $variables, array('langcode' => $langcode));
+ * $message['body'][] = t("Dear !username\n\nThere is new content available on the site.", $variables, array('langcode' => $langcode));
+ * break;
+ * }
+ * }
+ * @endcode
+ *
+ * @param $module
+ * A module name to invoke hook_mail() on. The {$module}_mail() hook will be
+ * called to complete the $message structure which will already contain common
+ * defaults.
+ * @param $key
+ * A key to identify the e-mail sent. The final e-mail id for e-mail altering
+ * will be {$module}_{$key}.
+ * @param $to
+ * The e-mail address or addresses where the message will be sent to. The
+ * formatting of this string must comply with RFC 2822. Some examples are:
+ * - user@example.com
+ * - user@example.com, anotheruser@example.com
+ * - User
+ * - User , Another User
+ * @param $language
+ * Language object to use to compose the e-mail.
+ * @param $params
+ * Optional parameters to build the e-mail.
+ * @param $from
+ * Sets From to this value, if given.
+ * @param $send
+ * Send the message directly, without calling drupal_mail_system()->mail()
+ * manually.
+ *
+ * @return
+ * The $message array structure containing all details of the
+ * message. If already sent ($send = TRUE), then the 'result' element
+ * will contain the success indicator of the e-mail, failure being already
+ * written to the watchdog. (Success means nothing more than the message being
+ * accepted at php-level, which still doesn't guarantee it to be delivered.)
+ */
+function drupal_mail($module, $key, $to, $language, $params = array(), $from = NULL, $send = TRUE) {
+ $default_from = variable_get('site_mail', ini_get('sendmail_from'));
+
+ // Bundle up the variables into a structured array for altering.
+ $message = array(
+ 'id' => $module . '_' . $key,
+ 'module' => $module,
+ 'key' => $key,
+ 'to' => $to,
+ 'from' => isset($from) ? $from : $default_from,
+ 'language' => $language,
+ 'params' => $params,
+ 'subject' => '',
+ 'body' => array()
+ );
+
+ // Build the default headers
+ $headers = array(
+ 'MIME-Version' => '1.0',
+ 'Content-Type' => 'text/plain; charset=UTF-8; format=flowed; delsp=yes',
+ 'Content-Transfer-Encoding' => '8Bit',
+ 'X-Mailer' => 'Drupal'
+ );
+ if ($default_from) {
+ // To prevent e-mail from looking like spam, the addresses in the Sender and
+ // Return-Path headers should have a domain authorized to use the originating
+ // SMTP server. Errors-To is redundant, but shouldn't hurt.
+ $headers['From'] = $headers['Sender'] = $headers['Return-Path'] = $headers['Errors-To'] = $default_from;
+ }
+ if ($from) {
+ $headers['From'] = $from;
+ }
+ $message['headers'] = $headers;
+
+ // Build the e-mail (get subject and body, allow additional headers) by
+ // invoking hook_mail() on this module. We cannot use module_invoke() as
+ // we need to have $message by reference in hook_mail().
+ if (function_exists($function = $module . '_mail')) {
+ $function($key, $message, $params);
+ }
+
+ // Invoke hook_mail_alter() to allow all modules to alter the resulting e-mail.
+ drupal_alter('mail', $message);
+
+ // Retrieve the responsible implementation for this message.
+ $system = drupal_mail_system($module, $key);
+
+ // Format the message body.
+ $message = $system->format($message);
+
+ // Optionally send e-mail.
+ if ($send) {
+ $message['result'] = $system->mail($message);
+
+ // Log errors
+ if (!$message['result']) {
+ watchdog('mail', 'Error sending e-mail (from %from to %to).', array('%from' => $message['from'], '%to' => $message['to']), WATCHDOG_ERROR);
+ drupal_set_message(t('Unable to send e-mail. Contact the site administrator if the problem persists.'), 'error');
+ }
+ }
+
+ return $message;
+}
+
+/**
+ * Returns an object that implements the MailSystemInterface.
+ *
+ * Allows for one or more custom mail backends to format and send mail messages
+ * composed using drupal_mail().
+ *
+ * An implementation needs to implement the following methods:
+ * - format: Allows to preprocess, format, and postprocess a mail
+ * message before it is passed to the sending system. By default, all messages
+ * may contain HTML and are converted to plain-text by the DefaultMailSystem
+ * implementation. For example, an alternative implementation could override
+ * the default implementation and additionally sanitize the HTML for usage in
+ * a MIME-encoded e-mail, but still invoking the DefaultMailSystem
+ * implementation to generate an alternate plain-text version for sending.
+ * - mail: Sends a message through a custom mail sending engine.
+ * By default, all messages are sent via PHP's mail() function by the
+ * DefaultMailSystem implementation.
+ *
+ * The selection of a particular implementation is controlled via the variable
+ * 'mail_system', which is a keyed array. The default implementation
+ * is the class whose name is the value of 'default-system' key. A more specific
+ * match first to key and then to module will be used in preference to the
+ * default. To specificy a different class for all mail sent by one module, set
+ * the class name as the value for the key corresponding to the module name. To
+ * specificy a class for a particular message sent by one module, set the class
+ * name as the value for the array key that is the message id, which is
+ * "${module}_${key}".
+ *
+ * For example to debug all mail sent by the user module by logging it to a
+ * file, you might set the variable as something like:
+ *
+ * @code
+ * array(
+ * 'default-system' => 'DefaultMailSystem',
+ * 'user' => 'DevelMailLog',
+ * );
+ * @endcode
+ *
+ * Finally, a different system can be specified for a specific e-mail ID (see
+ * the $key param), such as one of the keys used by the contact module:
+ *
+ * @code
+ * array(
+ * 'default-system' => 'DefaultMailSystem',
+ * 'user' => 'DevelMailLog',
+ * 'contact_page_autoreply' => 'DrupalDevNullMailSend',
+ * );
+ * @endcode
+ *
+ * Other possible uses for system include a mail-sending class that actually
+ * sends (or duplicates) each message to SMS, Twitter, instant message, etc, or
+ * a class that queues up a large number of messages for more efficient bulk
+ * sending or for sending via a remote gateway so as to reduce the load
+ * on the local server.
+ *
+ * @param $module
+ * The module name which was used by drupal_mail() to invoke hook_mail().
+ * @param $key
+ * A key to identify the e-mail sent. The final e-mail ID for the e-mail
+ * alter hook in drupal_mail() would have been {$module}_{$key}.
+ *
+ * @return MailSystemInterface
+ */
+function drupal_mail_system($module, $key) {
+ $instances = &drupal_static(__FUNCTION__, array());
+
+ $id = $module . '_' . $key;
+
+ $configuration = variable_get('mail_system', array('default-system' => 'DefaultMailSystem'));
+
+ // Look for overrides for the default class, starting from the most specific
+ // id, and falling back to the module name.
+ if (isset($configuration[$id])) {
+ $class = $configuration[$id];
+ }
+ elseif (isset($configuration[$module])) {
+ $class = $configuration[$module];
+ }
+ else {
+ $class = $configuration['default-system'];
+ }
+
+ if (empty($instances[$class])) {
+ $interfaces = class_implements($class);
+ if (isset($interfaces['MailSystemInterface'])) {
+ $instances[$class] = new $class();
+ }
+ else {
+ throw new Exception(t('Class %class does not implement interface %interface', array('%class' => $class, '%interface' => 'MailSystemInterface')));
+ }
+ }
+ return $instances[$class];
+}
+
+/**
+ * An interface for pluggable mail back-ends.
+ */
+interface MailSystemInterface {
+ /**
+ * Format a message composed by drupal_mail() prior sending.
+ *
+ * @param $message
+ * A message array, as described in hook_mail_alter().
+ *
+ * @return
+ * The formatted $message.
+ */
+ public function format(array $message);
+
+ /**
+ * Send a message composed by drupal_mail().
+ *
+ * @param $message
+ * Message array with at least the following elements:
+ * - id: A unique identifier of the e-mail type. Examples: 'contact_user_copy',
+ * 'user_password_reset'.
+ * - to: The mail address or addresses where the message will be sent to.
+ * The formatting of this string must comply with RFC 2822. Some examples:
+ * - user@example.com
+ * - user@example.com, anotheruser@example.com
+ * - User
+ * - User , Another User
+ * - subject: Subject of the e-mail to be sent. This must not contain any
+ * newline characters, or the mail may not be sent properly.
+ * - body: Message to be sent. Accepts both CRLF and LF line-endings.
+ * E-mail bodies must be wrapped. You can use drupal_wrap_mail() for
+ * smart plain text wrapping.
+ * - headers: Associative array containing all additional mail headers not
+ * defined by one of the other parameters. PHP's mail() looks for Cc
+ * and Bcc headers and sends the mail to addresses in these headers too.
+ *
+ * @return
+ * TRUE if the mail was successfully accepted for delivery, otherwise FALSE.
+ */
+ public function mail(array $message);
+}
+
+/**
+ * Perform format=flowed soft wrapping for mail (RFC 3676).
+ *
+ * We use delsp=yes wrapping, but only break non-spaced languages when
+ * absolutely necessary to avoid compatibility issues.
+ *
+ * We deliberately use LF rather than CRLF, see drupal_mail().
+ *
+ * @param $text
+ * The plain text to process.
+ * @param $indent (optional)
+ * A string to indent the text with. Only '>' characters are repeated on
+ * subsequent wrapped lines. Others are replaced by spaces.
+ */
+function drupal_wrap_mail($text, $indent = '') {
+ // Convert CRLF into LF.
+ $text = str_replace("\r", '', $text);
+ // See if soft-wrapping is allowed.
+ $clean_indent = _drupal_html_to_text_clean($indent);
+ $soft = strpos($clean_indent, ' ') === FALSE;
+ // Check if the string has line breaks.
+ if (strpos($text, "\n") !== FALSE) {
+ // Remove trailing spaces to make existing breaks hard.
+ $text = preg_replace('/ +\n/m', "\n", $text);
+ // Wrap each line at the needed width.
+ $lines = explode("\n", $text);
+ array_walk($lines, '_drupal_wrap_mail_line', array('soft' => $soft, 'length' => strlen($indent)));
+ $text = implode("\n", $lines);
+ }
+ else {
+ // Wrap this line.
+ _drupal_wrap_mail_line($text, 0, array('soft' => $soft, 'length' => strlen($indent)));
+ }
+ // Empty lines with nothing but spaces.
+ $text = preg_replace('/^ +\n/m', "\n", $text);
+ // Space-stuff special lines.
+ $text = preg_replace('/^(>| |From)/m', ' $1', $text);
+ // Apply indentation. We only include non-'>' indentation on the first line.
+ $text = $indent . substr(preg_replace('/^/m', $clean_indent, $text), strlen($indent));
+
+ return $text;
+}
+
+/**
+ * Transform an HTML string into plain text, preserving the structure of the
+ * markup. Useful for preparing the body of a node to be sent by e-mail.
+ *
+ * The output will be suitable for use as 'format=flowed; delsp=yes' text
+ * (RFC 3676) and can be passed directly to drupal_mail() for sending.
+ *
+ * We deliberately use LF rather than CRLF, see drupal_mail().
+ *
+ * This function provides suitable alternatives for the following tags:
+ *