Commit 7de41539 authored by Dries's avatar Dries
Browse files

- Patch #77919 by chx, eaton, moshe, et al: enable programmaticaly submitted...

- Patch #77919 by chx, eaton, moshe, et al: enable programmaticaly submitted forms via the pull model.
parent e2f42cf3
......@@ -44,6 +44,8 @@ Drupal x.x.x, xxxx-xx-xx (development version)
- removed the archive module.
- upgrade system:
* created space for update branches.
- forms API:
* made it possible to programmatically submit forms.
- split up and removed drupal.css.
- added nested lists generation.
......
......@@ -4,59 +4,138 @@
/**
* @defgroup form Form generation
* @{
* Functions to enable output of HTML forms and form elements.
* Functions to enable the processing and display of HTML forms.
*
* Drupal uses these functions to achieve consistency in its form presentation,
* while at the same time simplifying code and reducing the amount of HTML that
* must be explicitly generated by modules. See the reference at
* Drupal uses these functions to achieve consistency in its form processing and
* presentation, while simplifying code and reducing the amount of HTML that
* must be explicitly generated by modules.
*
* The drupal_get_form() function handles retrieving, processing, and
* displaying a rendered HTML form for modules automatically. For example:
*
* // display the user registration form
* $output = drupal_get_form('user_register');
*
* Forms can also be built and submitted programmatically without any user input
* by populating $form['#post']['edit'] with values to be submitted. For example:
*
* // register a new user
* $form = drupal_retrieve_form('user_register');
* $form['#post']['edit']['name'] = 'robo-user';
* $form['#post']['edit']['mail'] = 'robouser@example.com';
* $form['#post']['edit']['pass'] = 'password';
* drupal_process_form('user_register', $form);
*
* Calling form_get_errors() will list any validation errors that prevented the
* form from being submitted.
*
* For information on the format of the structured arrays used to define forms,
* and more detailed explanations of the Form API workflow, see the reference at
* http://api.drupal.org/api/HEAD/file/developer/topics/forms_api_reference.html
* and the quickstart guide at
* http://api.drupal.org/api/HEAD/file/developer/topics/forms_api.html
*/
/**
* Processes a form array and produces the HTML output of a form.
* If there is input in the $_POST['edit'] variable, this function
* will attempt to validate it, using drupal_validate_form(),
* and then submit the form using drupal_submit_form().
* Retrieves a form from a builder function, passes it on for
* processing, and renders the form or redirects to its destination
* as appropriate.
*
* @param $form_id
* The unique string identifying the desired form. If a function
* with that name exists, it is called to build the form array.
* Modules that need to generate the same form (or very similar forms)
* using different $form_ids can implement hook_forms(), which maps
* different $form_id values to the proper form building function. Examples
* may be found in node_forms(), search_forms(), and user_forms().
* @param ...
* Any additional arguments needed by the form building function.
* @return
* The rendered form.
*/
function drupal_get_form($form_id) {
$args = func_get_args();
$form = call_user_func_array('drupal_retrieve_form', $args);
$redirect = drupal_process_form($form_id, $form);
if (isset($redirect)) {
drupal_redirect_form($form, $redirect);
}
return drupal_render_form($form_id, $form);
}
/**
* Retrieves the structured array that defines a given form.
*
* @param $form_id
* The unique string identifying the desired form. If a function
* with that name exists, it is called to build the form array.
* Modules that need to generate the same form (or very similar forms)
* using different $form_ids can implement hook_forms(), which maps
* different $form_id values to the proper form building function.
* @param ...
* Any additional arguments needed by the form building function.
*/
function drupal_retrieve_form($form_id) {
static $forms;
$args = func_get_args();
array_shift($args);
if (!function_exists($form_id)) {
if (!isset($forms)) {
$forms = module_invoke_all('forms');
}
$form_definition = $forms[$form_id];
if (isset($form_definition['callback arguments'])) {
$args = array_merge($form_definition['callback arguments'], $args);
}
if (isset($form_definition['callback'])) {
$callback = $form_definition['callback'];
}
}
// $callback comes from a hook_forms() implementation
return call_user_func_array(isset($callback) ? $callback : $form_id, $args);
}
/**
* This function is the heart of form API. The form gets built, validated and in
* appropriate cases, submitted.
*
* @param $form_id
* A unique string identifying the form. Allows each form to be
* themed. Pass NULL to suppress the form_id parameter (produces
* a shorter URL with method=get)
* The unique string identifying the current form.
* @param $form
* An associative array containing the structure of the form.
* @param $callback
* An optional callback that will be used in addition to the form_id.
*
* @return
* The path to redirect the user to upon completion.
*/
function drupal_get_form($form_id, &$form, $callback = NULL) {
function drupal_process_form($form_id, &$form) {
global $form_values, $form_submitted, $user, $form_button_counter;
static $saved_globals = array();
// Save globals in case of indirect recursive call
// In some scenerios, this function can be called recursively. Pushing any pre-existing
// $form_values and form submission data lets us start fresh without clobbering work done
// in earlier recursive calls.
array_push($saved_globals, array($form_values, $form_submitted, $form_button_counter));
$form_values = array();
$form_submitted = FALSE;
$form_button_counter = array(0, 0);
$form = drupal_build_form($form_id, $form, $callback);
if (!empty($_POST['edit']) && (($_POST['edit']['form_id'] == $form_id) || ($_POST['edit']['form_id'] == $callback))) {
drupal_validate_form($form_id, $form, $callback);
drupal_prepare_form($form_id, $form);
if (($form['#programmed']) || (!empty($_POST['edit']) && (($_POST['edit']['form_id'] == $form_id) || ($_POST['edit']['form_id'] == $form['#base'])))) {
drupal_validate_form($form_id, $form);
// IE does not send a button value when there is only one submit button (and no non-submit buttons)
// and you submit by pressing enter.
// In that case we accept a submission without button values.
if (($form_submitted || (!$form_button_counter[0] && $form_button_counter[1])) && !form_get_errors()) {
$redirect = drupal_submit_form($form_id, $form, $callback);
drupal_redirect_form($form, $redirect);
if ((($form['#programmed']) || $form_submitted || (!$form_button_counter[0] && $form_button_counter[1])) && !form_get_errors()) {
$redirect = drupal_submit_form($form_id, $form);
}
}
$output = drupal_render_form($form_id, $form, $callback);
// We've finished calling functions that alter the global values, so we can
// restore the ones that were there before this function was called.
list($form_values, $form_submitted, $form_button_counter) = array_pop($saved_globals);
return $output;
return $redirect;
}
/**
......@@ -69,12 +148,25 @@ function drupal_get_form($form_id, &$form, $callback = NULL) {
* theming, and hook_form_alter functions.
* @param $form
* An associative array containing the structure of the form.
* @param $callback
* An optional callback that will be used in addition to the form_id.
*
*/
function drupal_build_form($form_id, &$form, $callback = NULL) {
function drupal_prepare_form($form_id, &$form) {
$form['#type'] = 'form';
if (!isset($form['#post'])) {
$form['#post'] = $_POST;
$form['#programmed'] = FALSE;
}
else {
$form['#programmed'] = TRUE;
}
// If $base is set, it is used in place of $form_id when constructing validation,
// submission, and theming functions. Useful for mapping many similar or duplicate
// forms with different $form_ids to the same processing functions.
if (isset($form['#base'])) {
$base = $form['#base'];
}
if (isset($form['#token'])) {
// If the page cache is on and an anonymous user issues a GET request,
// unset the token because the token in the cached page would not match,
......@@ -105,8 +197,8 @@ function drupal_build_form($form_id, &$form, $callback = NULL) {
if (function_exists($form_id .'_validate')) {
$form['#validate'] = array($form_id .'_validate' => array());
}
elseif (function_exists($callback .'_validate')) {
$form['#validate'] = array($callback .'_validate' => array());
elseif (function_exists($base .'_validate')) {
$form['#validate'] = array($base .'_validate' => array());
}
}
......@@ -116,8 +208,8 @@ function drupal_build_form($form_id, &$form, $callback = NULL) {
// $form_values because it will change later
$form['#submit'] = array($form_id .'_submit' => array());
}
elseif (function_exists($callback .'_submit')) {
$form['#submit'] = array($callback .'_submit' => array());
elseif (function_exists($base .'_submit')) {
$form['#submit'] = array($base .'_submit' => array());
}
}
......@@ -127,8 +219,6 @@ function drupal_build_form($form_id, &$form, $callback = NULL) {
}
$form = form_builder($form_id, $form);
return $form;
}
......@@ -141,11 +231,9 @@ function drupal_build_form($form_id, &$form, $callback = NULL) {
* theming, and hook_form_alter functions.
* @param $form
* An associative array containing the structure of the form.
* @param $callback
* An optional callback that will be used in addition to the form_id.
*
*/
function drupal_validate_form($form_id, $form, $callback = NULL) {
function drupal_validate_form($form_id, $form) {
global $form_values;
static $validated_forms = array();
......@@ -153,7 +241,7 @@ function drupal_validate_form($form_id, $form, $callback = NULL) {
return;
}
// If the session token was set by drupal_build_form(), ensure that it
// If the session token was set by drupal_prepare_form(), ensure that it
// matches the current user's session
if (isset($form['#token'])) {
if ($form_values['form_token'] != md5(session_id() . $form['#token'] . variable_get('drupal_private_key', ''))) {
......@@ -175,14 +263,12 @@ function drupal_validate_form($form_id, $form, $callback = NULL) {
* theming, and hook_form_alter functions.
* @param $form
* An associative array containing the structure of the form.
* @param $callback
* An optional callback that will be used in addition to the form_id.
* @return
* A string containing the path of the page to display when processing
* is complete.
*
*/
function drupal_submit_form($form_id, $form, $callback = NULL) {
function drupal_submit_form($form_id, $form) {
global $form_values;
$default_args = array($form_id, &$form_values);
......@@ -209,21 +295,23 @@ function drupal_submit_form($form_id, $form, $callback = NULL) {
* theming, and hook_form_alter functions.
* @param $form
* An associative array containing the structure of the form.
* @param $callback
* An optional callback that will be used in addition to the form_id.
* @return
* A string containing the path of the page to display when processing
* is complete.
*
*/
function drupal_render_form($form_id, &$form, $callback = NULL) {
function drupal_render_form($form_id, &$form) {
// Don't override #theme if someone already set it.
if (isset($form['#base'])) {
$base = $form['#base'];
}
if (!isset($form['#theme'])) {
if (theme_get_function($form_id)) {
$form['#theme'] = $form_id;
}
elseif (theme_get_function($callback)) {
$form['#theme'] = $callback;
elseif (theme_get_function($base)) {
$form['#theme'] = $base;
}
}
......@@ -408,8 +496,8 @@ function form_builder($form_id, $form) {
$form['#id'] = 'edit-' . implode('-', $form['#parents']);
}
$posted = (isset($_POST['edit']) && ($_POST['edit']['form_id'] == $form_id));
$edit = $posted ? $_POST['edit'] : array();
$posted = (($form['#programmed']) || (isset($_POST['edit']) && ($_POST['edit']['form_id'] == $form_id)));
$edit = $posted ? $form['#post']['edit'] : array();
foreach ($form['#parents'] as $parent) {
$edit = isset($edit[$parent]) ? $edit[$parent] : NULL;
}
......@@ -490,6 +578,8 @@ function form_builder($form_id, $form) {
// Recurse through all child elements.
$count = 0;
foreach (element_children($form) as $key) {
$form[$key]['#post'] = $form['#post'];
$form[$key]['#programmed'] = $form['#programmed'];
// don't squash an existing tree value
if (!isset($form[$key]['#tree'])) {
$form[$key]['#tree'] = $form['#tree'];
......@@ -769,7 +859,7 @@ function password_confirm_validate($form) {
form_error($form, t('The specified passwords do not match.'));
}
}
elseif ($form['#required'] && !empty($_POST['edit'])) {
elseif ($form['#required'] && !empty($form['#post']['edit'])) {
form_error($form, t('Password field is required.'));
}
......
......@@ -558,7 +558,7 @@ function st($string, $args = array()) {
/**
* Helper function: apply the appropriate transformation to st() and t() placeholders.
*/
*/
function _st(&$value, $key) {
switch ($key[0]) {
// Escaped only
......
......@@ -75,8 +75,9 @@ function _locale_admin_manage_screen() {
'#default_value' => $isdefault,
);
$form['submit'] = array('#type' => 'submit', '#value' => t('Save configuration'));
$form['#base'] = 'locale_admin_manage_screen';
return drupal_get_form('_locale_admin_manage_screen', $form, 'locale_admin_manage_screen');
return $form;
}
/**
......@@ -123,12 +124,8 @@ function _locale_admin_manage_screen_submit($form_id, $form_values) {
return 'admin/settings/locale/language/overview';
}
/**
* User interface for the language addition screen.
*/
function _locale_admin_manage_add_screen() {
function locale_add_language_form() {
$isocodes = _locale_prepare_iso_list();
$form = array();
$form['language list'] = array('#type' => 'fieldset',
'#title' => t('Language list'),
......@@ -141,9 +138,10 @@ function _locale_admin_manage_add_screen() {
'#description' => t('Select your language here, or add it below, if you are unable to find it.'),
);
$form['language list']['submit'] = array('#type' => 'submit', '#value' => t('Add language'));
return $form;
}
$output = drupal_get_form('locale_add_language_form', $form);
function locale_custom_language_form() {
$form = array();
$form['custom language'] = array('#type' => 'fieldset',
'#title' => t('Custom language'),
......@@ -163,10 +161,17 @@ function _locale_admin_manage_add_screen() {
'#description' => t('Name of the language. Will be available for translation in all languages.'),
);
$form['custom language']['submit'] = array('#type' => 'submit', '#value' => t('Add custom language'));
// Use the validation and submit functions of the add language form.
$output .= drupal_get_form('locale_custom_language_form', $form, 'locale_add_language_form');
$form['#base'] = 'locale_add_language_form';
return $form;
}
/**
* User interface for the language addition screen.
*/
function _locale_admin_manage_add_screen() {
$output = drupal_get_form('locale_add_language_form');
$output .= drupal_get_form('locale_custom_language_form');
return $output;
}
......@@ -205,7 +210,7 @@ function locale_add_language_form_submit($form_id, $form_values) {
/**
* User interface for the translation import screen.
*/
function _locale_admin_import_screen() {
function _locale_admin_import() {
$languages = locale_supported_languages(FALSE, TRUE);
$languages = array_map('t', $languages['name']);
unset($languages['en']);
......@@ -242,7 +247,7 @@ function _locale_admin_import_screen() {
$form['import']['submit'] = array('#type' => 'submit', '#value' => t('Import'));
$form['#attributes']['enctype'] = 'multipart/form-data';
return drupal_get_form('_locale_admin_import', $form);
return $form;
}
/**
......@@ -267,6 +272,32 @@ function _locale_admin_import_submit($form_id, $form_values) {
return 'admin/settings/locale';
}
function _locale_export_po_form() {
$form['export'] = array('#type' => 'fieldset',
'#title' => t('Export translation'),
'#collapsible' => TRUE,
);
$form['export']['langcode'] = array('#type' => 'select',
'#title' => t('Language name'),
'#options' => $languages,
'#description' => t('Select the language you would like to export in gettext Portable Object (.po) format.'),
);
$form['export']['submit'] = array('#type' => 'submit', '#value' => t('Export'));
return $form;
}
function _locale_export_pot_form() {
// Complete template export of the strings
$form['export'] = array('#type' => 'fieldset',
'#title' => t('Export template'),
'#collapsible' => TRUE,
'#description' => t('Generate a gettext Portable Object Template (.pot) file with all the interface strings from the Drupal locale database.'),
);
$form['export']['submit'] = array('#type' => 'submit', '#value' => t('Export'));
$form['#base'] = '_locale_export_po_form';
return $form;
}
/**
* User interface for the translation export screen
*/
......@@ -275,31 +306,13 @@ function _locale_admin_export_screen() {
$languages = array_map('t', $languages['name']);
unset($languages['en']);
$output = '';
// Offer language specific export if any language is set up
if (count($languages)) {
$form = array();
$form['export'] = array('#type' => 'fieldset',
'#title' => t('Export translation'),
'#collapsible' => TRUE,
);
$form['export']['langcode'] = array('#type' => 'select',
'#title' => t('Language name'),
'#options' => $languages,
'#description' => t('Select the language you would like to export in gettext Portable Object (.po) format.'),
);
$form['export']['submit'] = array('#type' => 'submit', '#value' => t('Export'));
$output = drupal_get_form('_locale_export_po', $form);
$output = drupal_get_form('_locale_export_po_form');
}
// Complete template export of the strings
$form = array();
$form['export'] = array('#type' => 'fieldset',
'#title' => t('Export template'),
'#collapsible' => TRUE,
'#description' => t('Generate a gettext Portable Object Template (.pot) file with all the interface strings from the Drupal locale database.'),
);
$form['export']['submit'] = array('#type' => 'submit', '#value' => t('Export'));
$output .= drupal_get_form('_locale_export_pot', $form, '_locale_export_po');
$output .= drupal_get_form('_locale_export_pot_form');
return $output;
}
......@@ -307,7 +320,7 @@ function _locale_admin_export_screen() {
/**
* Process a locale export form submissions.
*/
function _locale_export_po_submit($form_id, $form_values) {
function _locale_export_po_form_submit($form_id, $form_values) {
_locale_export_po($form_values['langcode']);
}
......@@ -346,7 +359,7 @@ function _locale_string_seek_form() {
$form['search']['submit'] = array('#type' => 'submit', '#value' => t('Search'));
$form['#redirect'] = FALSE;
return drupal_get_form('_locale_string_seek', $form);
return $form;
}
/**
......@@ -398,7 +411,7 @@ function _locale_string_edit($lid) {
$form['lid'] = array('#type' => 'value', '#value' => $lid);
$form['submit'] = array('#type' => 'submit', '#value' => t('Save translations'));
return drupal_get_form('_locale_string_edit', $form);
return $form;
}
/**
......@@ -1235,9 +1248,9 @@ function _locale_string_language_list($translation) {
* Build object out of search criteria specified in request variables
*/
function _locale_string_seek_query() {
static $query = NULL;
static $query;
if (is_null($query)) {
if (!isset($query)) {
$fields = array('string', 'language', 'searchin');
$query = new StdClass;
if (is_array($_REQUEST['edit'])) {
......
......@@ -101,7 +101,7 @@ function install_verify_settings() {
$db_path = ltrim(urldecode($url['path']), '/');
$settings_file = './'. conf_path() .'/settings.php';
_install_settings_validate($db_prefix, $db_type, $db_user, $db_pass, $db_host, $db_path, $settings_file);
_install_settings_form_validate($db_prefix, $db_type, $db_user, $db_pass, $db_host, $db_path, $settings_file);
if (!form_get_errors()) {
return TRUE;
}
......@@ -124,11 +124,11 @@ function install_change_settings() {
// We always need this because we want to run form_get_errors.
include_once './includes/form.inc';
drupal_maintenance_theme();
// The existing database settings are not working, so we need write access
// to settings.php to change them.
if (!drupal_verify_install_file($settings_file, FILE_EXIST|FILE_READABLE|FILE_WRITABLE)) {
drupal_maintenance_theme();
drupal_set_message(st('The @drupal installer requires write permissions to %file during the installation process.', array('@drupal' => drupal_install_profile_name(), '%file' => $settings_file)), 'error');
drupal_set_title('Drupal database setup');
......@@ -140,8 +140,17 @@ function install_change_settings() {
if ($db_url == 'mysql://username:password@localhost/databasename') {
$db_user = $db_pass = $db_path = '';
}
$output = drupal_get_form('install_settings_form', $profile, $settings_file, $db_url, $db_type, $db_prefix, $db_user, $db_pass, $db_host);
drupal_set_title('Database configuration');
print theme('install_page', $output);
exit;
}
/**
* Form API array definition for install_settings.
*/
function install_settings_form($profile, $settings_file, $db_url, $db_type, $db_prefix, $db_user, $db_pass, $db_host) {
$db_types = drupal_detect_database_types();
if (count($db_types) == 0) {
$form['no_db_types'] = array(
......@@ -170,7 +179,7 @@ function install_change_settings() {
}
else {
if (count($db_types) == 1) {
$db_types = array_values($db_types);
$db_types = array_values($db_types);
$form['basic_options']['db_type'] = array(
'#type' => 'hidden',
'#value' => $db_types[0],
......@@ -250,26 +259,21 @@ function install_change_settings() {
$form['_db_url'] = array('#type' => 'value');
$form['#action'] = "install.php?profile=$profile";
$form['#redirect'] = NULL;
drupal_maintenance_theme();
}
$output = drupal_get_form('install_settings', $form);
drupal_set_title('Database configuration');
print theme('install_page', $output);
exit;
return $form;
}
/**
* Form API validate for install_settings form.
*/
function install_settings_validate($form_id, $form_values, $form) {
function install_settings_form_validate($form_id, $form_values, $form) {
global $db_url;
_install_settings_validate($form_values['db_prefix'], $form_values['db_type'], $form_values['db_user'], $form_values['db_pass'], $form_values['db_host'], $form_values['db_path'], $form_values['settings_file'], $form);
_install_settings_form_validate($form_values['db_prefix'], $form_values['db_type'], $form_values['db_user'], $form_values['db_pass'], $form_values['db_host'], $form_values['db_path'], $form_values['settings_file'], $form);
}
/**
* Helper function for install_settings_validate.
*/
function _install_settings_validate($db_prefix, $db_type, $db_user, $db_pass, $db_host, $db_path, $settings_file, $form = NULL) {
function _install_settings_form_validate($db_prefix, $db_type, $db_user, $db_pass, $db_host, $db_path, $settings_file, $form = NULL) {
global $db_url;
// Check for default username/password
......@@ -313,7 +317,7 @@ function _install_settings_validate($db_prefix, $db_type, $db_user, $db_pass, $d
/**
* Form API submit for install_settings form.
*/
function install_settings_submit($form_id, $form_values) {
function install_settings_form_submit($form_id, $form_values) {
global $profile;
// Update global settings array and save
......@@ -349,42 +353,46 @@ function install_select_profile() {
return $profile->name;
}
elseif (sizeof($profiles) > 1) {
drupal_maintenance_theme();
$form = '';
foreach ($profiles as $profile) {
include_once($profile->filename);
if ($_POST['edit']['profile'] == $profile->name) {
return $profile->name;
}
// Load profile details.
$function = $profile->name .'_profile_details';
if (function_exists($function)) {
$details = $function();
}
// If set, used defined name. Otherwise use file name.
$name = isset($details['name']) ? $details['name'] : $profile->name;
$form['profile'][$name] = array(
'#type' => 'radio',
'#value' => 'default',
'#return_value' => $profile->name,
'#title' => $name,
'#description' => isset($details['description']) ? $details['description'] : '',
'#parents' => array('profile'),
);
}