asaf.module in Asaf (ajax submit for any form) 8
Same filename and directory in other branches
Main module file.
File
asaf.moduleView source
<?php
/**
* @file
* Main module file.
*/
/**
* @todo
* - splpit administration form to route and controller
*/
define('ASAF_SETTINGS_STATUS_MESSAGES', 'status messages');
define('ASAF_SETTINGS_PAGE_CACHE', 'page cache');
define('ASAF_SETTINGS_CALL_COMMANDS_ALTER', 'call commands alter');
define('ASAF_SETTINGS_CALL_COMMANDS_ALTER_ALWAYS', 'always');
define('ASAF_SETTINGS_CALL_COMMANDS_ALTER_IF_NO_ERRORS', 'no errors');
function asaf_menu() {
$items = array();
$items['system/ajax/asaf/pagecache'] = array(
'title' => 'ASAF callback',
'page callback' => 'asaf_pagecache_form_callback',
'delivery callback' => 'ajax_deliver',
'access callback' => TRUE,
'theme callback' => 'ajax_base_page_theme',
'type' => MENU_CALLBACK,
'file' => 'asaf.pages.inc',
);
return $items;
}
function asaf_form_alter(&$form, &$form_state, $form_id) {
$buttons = asaf_is_handled_form($form_id);
if (\Drupal::config('asaf_settings.config')
->get('asaf_show_form_ids')) {
drupal_set_message(t('Form id: %form_id', array(
'%form_id' => $form_id,
)));
}
if (isset($buttons) && is_array($buttons)) {
$options = array();
if (\Drupal::config('asaf_settings.config')
->get('asaf_autoload_form_stuff')) {
$options['needed_files'] = asaf_get_form_stuff($form, $form_state, $form_id);
}
asaf_prepare_form($form, $form_state, $buttons, $options);
}
}
function asaf_batch_alter(&$batch) {
if (asaf_is_asaf()) {
list($form, $form_state) = _asaf_get_form_details_from_stacktrace();
if (isset($form_state['asaf']['form_path']) && $form_state['asaf']['form_path']) {
$batch['source_url'] = $form_state['asaf']['form_path'];
}
if (isset($batch['form_state']['no_redirect'])) {
$batch['form_state']['no_redirect'] = FALSE;
}
}
}
/*
* For the asaf processed forms we should prevent sending Location header. Instead this we send json with standart ajax command "redirect"
*/
function asaf_drupal_goto_alter(&$path, &$options, &$http_response_code) {
// Checking is asaf enabled
if (asaf_is_asaf()) {
// Trying to find actual $form and $form_state from stacktrace
// TODO: This code a little bit smells, and should be refactored (if it's possible)
list($form, $form_state) = _asaf_get_form_details_from_stacktrace();
if (is_array($form) && is_array($form_state) && isset($form_state['triggering_element']['#asaf_control'])) {
$form_state['redirect'] = array(
$path,
$options,
);
$page_callback_result = asaf_ajax_callback($form, $form_state);
// Emulate correct response
$router_item = menu_get_item(request_path());
$default_delivery_callback = isset($router_item) && $router_item ? $router_item['delivery_callback'] : NULL;
drupal_deliver_page($page_callback_result, $default_delivery_callback);
exit;
}
}
}
function asaf_hook_info() {
$hooks = array(
'asaf_form_ajax_commands_alter' => array(
'group' => 'forms',
),
);
return $hooks;
}
/**
* Attach ajax handlers to the specified buttons.
*
* @param $form
* An associative array containing the structure of the form.
* @param $form_state
* A keyed array containing the current state of the form. Asaf processed flag
* asaf options will save in $form_state.
* @param $buttons
* (optional) Array containing information about buttons, which should be handled. Empty array
* means all buttons should be handled. Not empty associative array can have the following keys:
* - included: list of buttons which should be handled;
* @code
* $buttons = array(
* 'included' => array(
* 'actions][submit' => array(...),
* 'actions][preview' => '',
* );
* );
* @endcode
* If value of buttons item is array, it will be added to the button form element.
* - excluded: list of buttons which should not be handled.
* Inclided section have prioriry over excluded section.
* @param $options
* (optional) An associative array with asaf options. Can have the folowing keys:
* - needed_files: Array of files which should be loaded for the correct form handling. File item
* can be module name instead of correct filename. In this case all *.inc files from this module
* folder will be loaded automatically.
*
* @code
* $options = array(
* 'needed_files' => array(
* 'modules/user/user.pages.inc', // this file will be loaded
* 'webform' // all inc files from webform module folder will be loaded
* );
* );
* @endcode
*
*/
function asaf_prepare_form(&$form, &$form_state, array $buttons = array(), array $options = array()) {
$options += asaf_default_options();
$form_state += array(
'asaf' => array(),
);
$form_state['asaf']['form_path'] = isset($form_state['values']['asaf_form_path']) ? $form_state['values']['asaf_form_path'] : request_path();
$form_state['asaf']['buttons'] = $buttons;
$form_state['asaf']['options'] = $options;
// Default renderable area
$form['#asaf_area'] = 'form';
asaf_mark_buttons($form, '', $form_state);
asaf_handle_buttons($form, '', $form_state);
asaf_handle_areas($form, '', $form_state);
// Need for fast way decoding asaf forms instead of a slow call _asaf_get_form_details_from_stacktrace()
$form['asaf_form'] = array(
'#type' => 'hidden',
);
if (isset($options['needed_files']) && !empty($options['needed_files'])) {
asaf_register_needed_files($form_state, $options['needed_files']);
}
if ($options[ASAF_SETTINGS_PAGE_CACHE]) {
$form['asaf_form_path'] = array(
'#type' => 'hidden',
'#value' => $form_state['asaf']['form_path'],
);
$form['asaf_id_prefix'] = array(
'#type' => 'hidden',
'#value' => $form_state['asaf']['id_prefix'],
);
$form['#process'][] = 'asaf_form_pagecache_process';
}
$form['#attached']['js'][] = array(
'type' => 'setting',
'data' => array(
'asaf' => array(
'submitByEnter' => \Drupal::config('asaf_settings.config')
->get('asaf_form_submit_by_enter'),
),
),
);
}
function asaf_form_pagecache_process($form, &$form_state) {
$form['asaf_form']['#value'] = asaf_get_security_token($form['#form_id'], $form['#build_id']);
$form_options = array(
'needed_files' => !empty($form_state['build_info']['files']) ? $form_state['build_info']['files'] : array(),
);
// Saving form options (first of all needed_files) for the half of the year
cache_set('asaf_form_' . $form['#form_id'] . '_options', $form_options, 'cache_form', REQUEST_TIME + 15768000);
return $form;
}
/*
function asaf_default_options() {
return array(
ASAF_SETTINGS_STATUS_MESSAGES => 'default',
ASAF_SETTINGS_PAGE_CACHE => variable_get('cache', FALSE),
ASAF_SETTINGS_CALL_COMMANDS_ALTER => ASAF_SETTINGS_CALL_COMMANDS_ALTER_IF_NO_ERRORS,
);
}
*/
function asaf_mark_buttons(&$element, $key, &$form_state) {
$buttons = $form_state['asaf']['buttons'];
if (!isset($element['#asaf_control']) && isset($element['#type']) && $element['#type'] == 'submit' && asaf_is_handled_button($key, $buttons)) {
$element['#asaf_control'] = 'asaf_submit';
$element += asaf_get_handled_button_options($key, $buttons);
}
foreach (element_children($element) as $child_key) {
asaf_mark_buttons($element[$child_key], $key ? $key . '][' . $child_key : $child_key, $form_state);
}
}
function asaf_handle_buttons(&$element, $key, &$form_state) {
if (!empty($element['#asaf_control'])) {
$element += array(
'#type' => 'submit',
'#attributes' => array(),
'#id' => drupal_html_id(asaf_get_id($key, $form_state)),
'#ajax' => array(
'callback' => 'asaf_ajax_callback',
'path' => $form_state['asaf']['options'][ASAF_SETTINGS_PAGE_CACHE] ? 'system/ajax/asaf/pagecache' : 'system/ajax',
'wrapper' => asaf_get_area_wrapper_id(!empty($element['#asaf_target_area']) ? is_array($element['#asaf_target_area']) ? reset($element['#asaf_target_area']) : $element['#asaf_target_area'] : 'form', $form_state),
'progress' => array(
'type' => 'throbber',
'message' => '',
),
),
);
$element['#attributes'] += array(
'class' => array(),
);
$element['#attributes']['class'][] = 'asaf-control-' . $element['#asaf_control'];
$element['#attached']['js'][] = drupal_get_path('module', 'asaf') . '/js/asaf.js';
}
foreach (element_children($element) as $child_key) {
asaf_handle_buttons($element[$child_key], $key ? $key . '][' . $child_key : $child_key, $form_state);
}
}
function asaf_handle_areas(&$element, $key, &$form_state) {
if (!empty($element['#asaf_area'])) {
$element['#asaf_area_id'] = asaf_get_area_wrapper_id($element['#asaf_area'], $form_state);
/* When the form inserted directly in block, like user_login_block, or any other form which implemented in the
* following way:
*
* function HOOK_block_view($delta = '') {
* return array(
* 'subject' => t('Form title'),
* 'content' => drupal_get_form('form_constructor'),
* );
* }
*
* form area wrapper doesn't work correctly because block module use block.tpl.php as theme wrapper for block
* content. In this case block wrapper HTML (block wrapper and block title) will be rendered inside form area
* wrapper, and it will be loosed after first form update with asaf. To resolve this bug we have to wrap form
* manually on the client-side using data-asaf-area-wrapper-id attribute */
if ($element['#asaf_area'] == 'form') {
$element['#attributes']['data-asaf-area-name'] = $element['#asaf_area'];
$element['#attributes']['data-asaf-area-wrapper-id'] = $element['#asaf_area_id'];
}
else {
$element['#prefix'] = '<div id="' . $element['#asaf_area_id'] . '" class="asaf-area-wrapper asaf-' . $element['#asaf_area'] . '-area-wrapper">' . (isset($element['#prefix']) ? $element['#prefix'] : '');
$element['#suffix'] = (isset($element['#suffix']) ? $element['#suffix'] : '') . '</div>';
$element['#attributes']['data-asaf-area-name'] = $element['#asaf_area'];
}
}
foreach (element_children($element) as $child_key) {
asaf_handle_areas($element[$child_key], $key ? $key . '][' . $child_key : $child_key, $form_state);
}
}
function asaf_find_area($areaName, $element) {
if (!isset($element['#asaf_area']) || $element['#asaf_area'] != $areaName) {
foreach (element_children($element) as $key) {
$child = asaf_find_area($areaName, $element[$key]);
if (isset($child['#asaf_area']) && $child['#asaf_area'] == $areaName) {
$element = $child;
break;
}
}
}
return $element;
}
function asaf_ajax_callback(&$form, &$form_state) {
$commands = array();
if ($form_state['asaf']['options'][ASAF_SETTINGS_STATUS_MESSAGES] == 'hide') {
theme('status_messages');
}
if (isset($form_state['redirect'])) {
if (!is_array($form_state['redirect'])) {
$form_state['redirect'] = array(
$form_state['redirect'],
array(
'query' => array(),
),
);
}
$commands[] = asaf_ajax_command_redirect($form_state['redirect']);
}
else {
$processed_areas = array();
$targets = !empty($form_state['triggering_element']['#asaf_target_area']) ? $form_state['triggering_element']['#asaf_target_area'] : 'form';
$targets = !is_array($targets) ? array(
$targets,
) : $targets;
$area_render_command_callback = isset($form_state['asaf']['options']['form_render_command_callback']) && $form_state['asaf']['options']['form_render_command_callback'] && function_exists($form_state['asaf']['options']['form_render_command_callback']) ? $form_state['asaf']['options']['form_render_command_callback'] : 'asaf_default_area_render_command';
foreach ($targets as $areaName) {
$area = asaf_find_area($areaName, $form);
if ($area['#asaf_area'] == $areaName && !isset($processed_areas[$areaName])) {
$area_command = $area_render_command_callback($area, $form_state);
if (!empty($area_command)) {
$commands[] = $area_command;
}
$processed_areas[$areaName] = TRUE;
}
}
// Send to the client new form_buid_id when pagecache mode enabled and form rebuild partially, without whole form
if ($form_state['asaf']['options'][ASAF_SETTINGS_PAGE_CACHE] && !isset($processed_areas['form'])) {
$commands[] = ajax_command_invoke('#' . asaf_get_area_wrapper_id('form', $form_state) . ' input[name="form_build_id"]', 'val', array(
$form['#build_id'],
));
}
}
// Flag allow some command alter hooks returns different list of command for validation errors case.
$form_state['asaf_form_status'] = form_get_errors() ? 'errors' : '';
if ($form_state['asaf_form_status'] == 'errors' && $form_state['asaf']['options'][ASAF_SETTINGS_STATUS_MESSAGES] == 'none') {
$form_state['asaf']['options'][ASAF_SETTINGS_STATUS_MESSAGES] = 'default';
}
if (isset($form_state['asaf_ajax_commands']) && is_array($form_state['asaf_ajax_commands'])) {
$commands = array_merge($commands, $form_state['asaf_ajax_commands']);
}
if ($form_state['asaf']['options'][ASAF_SETTINGS_STATUS_MESSAGES] == 'default') {
$commands[] = ajax_command_prepend(NULL, theme('status_messages'));
}
if ($form_state['asaf_form_status'] != 'errors' || $form_state['asaf']['options'][ASAF_SETTINGS_CALL_COMMANDS_ALTER] == ASAF_SETTINGS_CALL_COMMANDS_ALTER_ALWAYS) {
drupal_alter('asaf_form_ajax_commands', $commands, $form, $form_state, $form['#form_id']);
drupal_alter('asaf_form_' . $form['#form_id'] . '_ajax_commands', $commands, $form, $form_state);
// We don't use $form_id because we need real form id, not $form['#id'] or autogenerated value
}
unset($form_state['asaf_ajax_commands']);
return array(
'#type' => 'ajax',
'#commands' => $commands,
);
}
function asaf_default_area_render_command($area, &$form_state) {
if ($form_state['asaf']['options'][ASAF_SETTINGS_STATUS_MESSAGES] == 'area') {
$messages = theme('status_messages');
if ($messages) {
$area = array(
'asaf_messages' => array(
'#markup' => $messages,
'#weight' => -99999999,
),
) + $area;
}
}
return ajax_command_replace('#' . asaf_get_area_wrapper_id($area['#asaf_area'], $form_state), drupal_render($area));
}
function asaf_register_needed_files(&$form_state, array $files) {
if (!isset($form_state['build_info']['files'])) {
$form_state['build_info']['files'] = array();
}
foreach ($files as $file) {
if (is_array($file) && isset($file['module']) || is_string($file) && file_exists($file)) {
$form_state['build_info']['files'][] = $file;
}
elseif (is_string($file) && module_exists($file)) {
// Loading all includes of the module
$path = drupal_get_path('module', $file);
$destination = DRUPAL_ROOT . '/' . $path;
$pattern = '/.inc$/';
$matches = array_keys(file_scan_directory($destination, $pattern));
if (is_array($matches)) {
foreach ($matches as $inc) {
$parts = explode(DRUPAL_ROOT . '/', $inc);
if (isset($parts[1]) && $parts[1]) {
$form_state['build_info']['files'][] = $parts[1];
}
}
}
}
}
}
function asaf_get_needed_files_list(array $files) {
$list = array();
foreach ($files as $file) {
if (is_array($file) && isset($file['module']) || is_string($file) && file_exists($file)) {
$list[] = $file;
}
elseif (is_string($file) && module_exists($file)) {
// Loading all includes of the module
$path = drupal_get_path('module', $file);
$destination = DRUPAL_ROOT . '/' . $path;
$list[] = $path . '/' . $file . '.module';
$pattern = '/.inc$/';
$matches = array_keys(file_scan_directory($destination, $pattern));
if (is_array($matches)) {
foreach ($matches as $inc) {
$parts = explode(DRUPAL_ROOT . '/', $inc);
if (isset($parts[1]) && $parts[1]) {
$list[] = $parts[1];
}
}
}
}
}
return $list;
}
function asaf_load_needed_files(array $files) {
foreach ($files as $file) {
if (is_array($file) && isset($file['module'])) {
$file += array(
'type' => 'inc',
'name' => $file['module'],
);
module_load_include($file['type'], $file['module'], $file['name']);
}
elseif (file_exists($file)) {
require_once DRUPAL_ROOT . '/' . $file;
}
}
}
function asaf_is_handled_form($form_id) {
$forms =& drupal_static(__FUNCTION__, NULL);
if (!isset($forms)) {
$forms = asaf_get_handled_forms_list();
}
return isset($form_id) && isset($forms[$form_id]) ? $forms[$form_id] : FALSE;
}
function asaf_is_handled_button($key, $buttons) {
$handled = TRUE;
if (isset($buttons['included'])) {
$handled = isset($buttons['included'][$key]);
}
elseif (isset($buttons['excluded'])) {
$handled = !isset($buttons['excluded'][$key]);
}
return $handled;
}
function asaf_get_handled_button_options($key, $buttons) {
return isset($buttons['included'][$key]) && is_array($buttons['included'][$key]) ? $buttons['included'][$key] : array();
}
function asaf_get_handled_forms_list() {
return;
$forms = array();
$text = \Drupal::config('asaf_settings.config')
->get('asaf_forms');
$lines = explode("\n", $text);
foreach ($lines as $line) {
$parts = explode('@', trim($line));
$form_id = isset($parts[0]) ? $parts[0] : '';
$buttons = isset($parts[1]) ? $parts[1] : '';
$form_id = trim($form_id);
$buttons = trim($buttons);
if ($form_id) {
$forms[$form_id] = array();
if ($buttons) {
$forms[$form_id] = array(
'included' => array(),
'excluded' => array(),
);
if ($buttons[0] != '+' && $buttons[0] != '-') {
$buttons = '+' . $buttons;
}
preg_match_all('/([\\+\\-][^\\+\\-]+)/', $buttons, $matches);
if (isset($matches[0]) && is_array($matches[0])) {
foreach ($matches[0] as $button) {
$op = $button[0];
$button = substr($button, 1);
$forms[$form_id][$op == '+' ? 'included' : 'excluded'][$button] = $button;
}
}
if (!empty($forms[$form_id]['included'])) {
unset($forms[$form_id]['excluded']);
}
else {
unset($forms[$form_id]['included']);
}
}
}
}
drupal_alter('asaf_forms_list', $forms);
return $forms;
}
function asaf_get_area_wrapper_id($area, &$form_state) {
return asaf_get_id($area . '-area-wrapper', $form_state);
}
function asaf_get_id($id, &$form_state) {
asaf_init_prefix($form_state);
return $form_state['asaf']['id_prefix'] . '-' . $id;
}
function asaf_init_prefix(&$form_state) {
if (!isset($form_state['asaf']['id_prefix'])) {
$form_state['asaf']['id_prefix'] = asaf_get_prefix();
}
}
function asaf_get_prefix() {
$prefixes =& drupal_static(__FUNCTION__, array());
$i = 0;
do {
$prefix = 'asaf-' . time() . ($i ? '-' . $i : '');
$i++;
} while (isset($prefixes[$prefix]));
$prefixes[$prefix] = $prefix;
return $prefix;
}
function asaf_is_asaf() {
$is_asaf = isset($_POST['form_build_id']) && isset($_POST['asaf_form']);
return $is_asaf;
}
function asaf_get_security_token($form_id, $form_build_id) {
return md5('asaf-' . $form_id . '-form-' . $form_build_id . '-security-token');
}
function asaf_get_form_stuff($form, $form_state, $form_id) {
$stuff = _asaf_get_form_parent_module($form_id, $form_state);
return is_array($stuff) && !empty($stuff) ? $stuff : array();
}
function _asaf_get_form_parent_module($form_id, $form_state) {
$parent = array();
// If in the same folder we well find a few modules we will load all of them
$constructor = _asaf_get_form_constructor($form_id, $form_state);
if (function_exists($constructor) && class_exists('ReflectionFunction')) {
$reflection = new ReflectionFunction($constructor);
$file = $reflection
->getFileName();
$directory = dirname($file);
$pattern = '/.module$/';
$i = 20;
// just in case :)
while (empty($parents) && $directory != DRUPAL_ROOT && $i) {
$matches = array_keys(file_scan_directory($directory, $pattern, array(
'recurse' => FALSE,
)));
if (is_array($matches)) {
foreach ($matches as $module) {
$info = pathinfo($module);
$parent[] = $info['filename'];
}
}
$directory = dirname($directory);
$i--;
}
}
return !empty($parent) ? $parent : FALSE;
}
function _asaf_get_form_constructor($form_id, $form_state) {
if (!function_exists($form_id) && isset($form_state['build_info']['base_form_id'])) {
$form_id = $form_state['build_info']['base_form_id'];
}
return function_exists($form_id) ? $form_id : FALSE;
}
/*
* Getting $form and $form_state from stacktrace
*
* @return
* An array containing the $form and $form_state.
*/
function _asaf_get_form_details_from_stacktrace() {
static $form;
static $form_state;
if (!$form || !$form_state) {
$stacktrace = debug_backtrace();
foreach ($stacktrace as $step) {
$args = isset($step['args']) ? $step['args'] : FALSE;
if (is_array($args)) {
foreach ($args as $arg) {
if (!$form && is_array($arg) && isset($arg['#type']) && $arg['#type'] == 'form') {
$form = $arg;
}
if (!$form_state && is_array($arg) && isset($arg['build_info']) && is_array($arg['build_info'])) {
$form_state = $arg;
}
}
if ($form && $form_state) {
break;
}
}
}
}
return array(
$form,
$form_state,
);
}
function asaf_ajax_command_redirect($href, $window = 'current') {
if (is_array($href)) {
$href = call_user_func_array('url', $href);
}
elseif (strpos($href, '://') === FALSE) {
$href = url($href, array(
'absolute' => TRUE,
));
}
return array(
'command' => 'asafRedirect',
'href' => $href,
'window' => $window,
);
}
function asaf_ajax_command_reload($window = 'current') {
return array(
'command' => 'asafReload',
'window' => $window,
);
}