View source
<?php
define('JS_MENU_NOT_FOUND', 2);
define('JS_MENU_ACCESS_DENIED', 3);
define('JS_MENU_SITE_OFFLINE', 4);
define('JS_MENU_SITE_ONLINE', 5);
define('JS_MENU_METHOD_NOT_ALLOWED', 6);
function js_hook_info() {
$group = array(
'group' => 'js',
);
$hooks['js_callback_filter_xss'] = $group;
$hooks['js_captured_content'] = $group;
$hooks['js_info'] = $group;
$hooks['js_info_alter'] = $group;
$hooks['js_server_info'] = $group;
$hooks['js_server_info_alter'] = $group;
return $hooks;
}
function js_menu() {
$items['admin/config/system/js'] = array(
'title' => 'JS Callback handler',
'description' => 'Configure JavaScript callback handler.',
'page callback' => 'drupal_get_form',
'page arguments' => array(
'js_configure_form',
),
'access arguments' => array(
'administer js',
),
'file' => 'js.admin.inc',
'type' => MENU_NORMAL_ITEM,
);
return $items;
}
function js_permission() {
return array(
'administer js' => array(
'title' => t('Administer JavaScript callback handler settings'),
),
);
}
function js_library() {
$libraries['js'] = array(
'title' => 'JS AJAX Handler',
'version' => '2.0.0',
'js' => array(
drupal_get_path('module', 'js') . '/js.js' => array(
'weight' => -100,
),
0 => array(
'type' => 'setting',
'data' => array(
'jsEndpoint' => variable_get('js_endpoint', 'js'),
),
),
),
'dependencies' => array(
array(
'system',
'jquery',
),
),
);
return $libraries;
}
function js_preprocess_html() {
drupal_add_js(array(
'js' => array(
'tokens' => js_get_token(),
),
), 'setting');
}
function js_custom_theme() {
global $_js;
if (!empty($_js['module']) && !empty($_js['callback'])) {
$info = js_get_callback($_js['module'], $_js['callback']);
if (!$info['skip init'] && $info['dependencies']) {
$implementations =& drupal_static('module_implements');
if (!isset($implementations['init'])) {
module_implements('init');
}
$implementations['init'] = array_diff_key($implementations['init'], array_flip($info['dependencies']));
}
}
if (!empty($_js['theme'])) {
return $_js['theme'];
}
}
function js_js_info() {
$callbacks['form'] = array(
'token' => FALSE,
'xss' => FALSE,
);
return $callbacks;
}
function js_js_server_info() {
$base_path = preg_quote(base_path());
$endpoint = preg_quote(variable_get('js_endpoint', 'js'));
$regexp = '(?:[a-z]{2}(?:-[A-Za-z]{2,4})?/)?(?:' . $endpoint . '|' . $endpoint . '/.*)';
$header = array(
'###',
'### Support for https://www.drupal.org/project/js module.',
'###',
);
$servers['apache'] = array(
'label' => 'Apache',
'description' => t('Add the above lines before any existing rewrite rules inside this site\'s Apache <code>.htaccess</code> file.'),
'rewrite' => $header,
);
$servers['apache']['rewrite'][] = 'RewriteCond %{REQUEST_URI} ^' . str_replace('/', '\\/', $base_path) . str_replace('/', '\\/', $regexp) . '$';
$servers['apache']['rewrite'][] = 'RewriteRule ^(.*)$ js.php?q=$1 [L,QSA]';
$servers['apache']['rewrite'][] = 'RewriteCond %{QUERY_STRING} (^|&)q=' . str_replace('/', '\\/', $regexp);
$servers['apache']['rewrite'][] = 'RewriteRule .* js.php [L]';
$servers['nginx'] = array(
'label' => 'Nginx',
'description' => t('Add the above lines before any existing rewrite rules inside this site\'s Nginx <code>server { }</code> block.'),
'rewrite' => $header,
);
$servers['nginx']['rewrite'][] = '### PHP-FPM (using https://github.com/perusio/drupal-with-nginx)';
$servers['nginx']['rewrite'][] = '###';
$servers['nginx']['rewrite'][] = '### 1. Copy `apps/drupal/fastcgi_drupal.conf` to `apps/drupal/fastcgi_js.conf`.';
$servers['nginx']['rewrite'][] = '### 2. Inside `fastcgi_js.conf`, rename all cases of `index.php` to `js.php`.';
$servers['nginx']['rewrite'][] = '###';
$servers['nginx']['rewrite'][] = 'location ~* "^' . $base_path . $regexp . '$" {';
$servers['nginx']['rewrite'][] = ' rewrite ^/(.*)$ /js.php?q=$1 last;';
$servers['nginx']['rewrite'][] = '}';
$servers['nginx']['rewrite'][] = 'location ^~ /js.php {';
$servers['nginx']['rewrite'][] = ' tcp_nopush off;';
$servers['nginx']['rewrite'][] = ' keepalive_requests 0;';
$servers['nginx']['rewrite'][] = ' access_log off;';
$servers['nginx']['rewrite'][] = ' try_files $uri =404; ### check for existence of php file first';
$servers['nginx']['rewrite'][] = ' include apps/drupal/fastcgi_js.conf;';
$servers['nginx']['rewrite'][] = ' fastcgi_pass phpcgi;';
$servers['nginx']['rewrite'][] = '}';
$servers['nginx']['rewrite'][] = '';
$servers['nginx']['rewrite'][] = '### Non-clean URLs (query based, only uncomment if needed).';
$servers['nginx']['rewrite'][] = '# if ($query_string ~ "(?:^|&)q=(' . $regexp . ')") {';
$servers['nginx']['rewrite'][] = '# rewrite ^' . $base_path . '(.*)$ /js.php?q=$1 last;';
$servers['nginx']['rewrite'][] = '#}';
return $servers;
}
function js_server_info($server = NULL, $reset = FALSE) {
static $drupal_static_fast;
if (!isset($drupal_static_fast)) {
$drupal_static_fast['servers'] =& drupal_static(__FUNCTION__);
}
$servers =& $drupal_static_fast['servers'];
if ($reset || !isset($servers)) {
$servers = array();
$cid = 'js:servers';
if (!$reset && ($cache = cache_get($cid)) && $cache->data) {
$servers = $cache->data;
}
else {
foreach (module_implements('js_server_info', FALSE, $reset) as $module) {
$results = module_invoke($module, 'js_server_info');
foreach ($results as $name => $info) {
$servers[$name] = (array) $info;
$servers[$name] += array(
'description' => '',
'label' => $name,
'regexp' => "/{$name}/i",
'rewrite' => '',
);
$servers[$name]['name'] = $name;
$servers[$name]['module'] = $module;
if (is_array($servers[$name]['rewrite'])) {
$servers[$name]['rewrite'] = implode("\n", $servers[$name]['rewrite']);
}
}
}
drupal_alter('js_server_info', $servers);
cache_set($cid, $servers);
}
}
if (isset($server)) {
return !empty($servers[$server]) ? $servers[$server] : FALSE;
}
return $servers;
}
function js_module_implements_alter(&$implementations, $hook) {
global $_js;
if (!empty($_js['module']) && !empty($_js['callback'])) {
$global_implementations =& drupal_static('module_implements');
unset($global_implementations['#write_cache']);
}
if ($hook === 'server_info' && !empty($implementations['js'])) {
unset($implementations['js']);
}
}
function js_js_callback_form() {
module_load_include('inc', 'js', 'includes/get');
return js_get_page();
}
function js_drupal_goto_alter(&$path, &$options, &$http_response_code) {
global $_js;
if ($_js && in_array($http_response_code, array(
301,
302,
303,
307,
))) {
module_load_include('inc', 'js', 'includes/json');
$json = js_http_response($http_response_code);
$options['absolute'] = TRUE;
$json['response']['url'] = url($path, $options);
if (!empty($options['force'])) {
$json['response']['force'] = TRUE;
}
js_deliver_json($json);
}
}
function js_element_info_alter(&$type) {
foreach ($type as $name => $element) {
if (!isset($type[$name]['#pre_render'])) {
$type[$name]['#pre_render'] = array();
}
array_unshift($type[$name]['#pre_render'], 'js_pre_render_element');
if (isset($type[$name]['#process']) && in_array('form_process_autocomplete', $type[$name]['#process'])) {
$type[$name]['#process'][] = 'js_process_autocomplete';
}
}
}
function js_process_autocomplete($element) {
if ($element['#autocomplete_path'] && !empty($element['#autocomplete_input']['#url_value']) && isset($element['#js_callback']) && is_array($element['#js_callback'])) {
$module = key($element['#js_callback']);
$callback = reset($element['#js_callback']);
$info = js_get_callback($module, $callback);
$options = array(
'absolute' => TRUE,
'script' => 'js.php',
'query' => array(
'js_module' => $module,
'js_callback' => $callback,
'q' => $element['#autocomplete_path'],
),
);
if ($info['token']) {
$options['query']['js_token'] = js_get_token($module, $callback);
}
$current_clean_url = isset($GLOBALS['conf']['clean_url']) ? $GLOBALS['conf']['clean_url'] : NULL;
$GLOBALS['conf']['clean_url'] = 0;
$base_url = url('<front>', $options);
$drupal_parsed_url = drupal_parse_url($base_url);
$autocomplete_path = drupal_parse_url(url($element['#autocomplete_path']));
$drupal_parsed_url['query']['q'] = $autocomplete_path['path'];
$parsed = parse_url($base_url);
$parsed['query'] = drupal_http_build_query($drupal_parsed_url['query']);
$element['#autocomplete_input']['#url_value'] = _js_http_build_url($parsed);
$GLOBALS['conf']['clean_url'] = $current_clean_url;
}
return $element;
}
function _js_http_build_url(array $parsed) {
$uri = '';
if (isset($parsed['scheme'])) {
switch (strtolower($parsed['scheme'])) {
case 'mailto':
$uri .= $parsed['scheme'] . ':';
break;
case '//':
$uri .= $parsed['scheme'];
break;
default:
$uri .= $parsed['scheme'] . '://';
}
}
$uri .= isset($parsed['user']) ? $parsed['user'] . (isset($parsed['pass']) ? ':' . $parsed['pass'] : '') . '@' : '';
$uri .= isset($parsed['host']) ? $parsed['host'] : '';
$uri .= !empty($parsed['port']) ? ':' . $parsed['port'] : '';
if (isset($parsed['path'])) {
$uri .= substr($parsed['path'], 0, 1) === '/' ? $parsed['path'] : (!empty($uri) ? '/' : '') . $parsed['path'];
}
$uri .= isset($parsed['query']) ? '?' . $parsed['query'] : '';
$uri .= isset($parsed['fragment']) ? '#' . $parsed['fragment'] : '';
return $uri;
}
function js_pre_render_element($element) {
if (isset($element['#js_callback']) && is_array($element['#js_callback'])) {
$callback = reset($element['#js_callback']);
$module = key($element['#js_callback']);
$info = js_get_callback($module, $callback);
if (!empty($module) && !empty($callback)) {
$element['#attributes']['data-js-module'] = $module;
$element['#attributes']['data-js-callback'] = $callback;
if ($info['token']) {
$element['#attributes']['data-js-token'] = js_get_token($module, $callback);
}
}
}
return $element;
}
function _js_error_handler($error_level, $message) {
if ($error_level & error_reporting()) {
require_once DRUPAL_ROOT . '/includes/errors.inc';
$types = drupal_error_levels();
list($severity_msg, $severity_level) = $types[$error_level];
$backtrace = debug_backtrace();
$caller = _drupal_get_last_caller($backtrace);
_js_log_php_error(array(
'%type' => isset($types[$error_level]) ? $severity_msg : 'Unknown error',
'!message' => filter_xss_admin($message),
'%function' => $caller['function'],
'%file' => $caller['file'],
'%line' => $caller['line'],
'severity_level' => $severity_level,
), $error_level == E_RECOVERABLE_ERROR);
}
}
function _js_exception_handler($exception) {
require_once DRUPAL_ROOT . '/includes/errors.inc';
try {
_js_log_php_error(_drupal_decode_exception($exception), TRUE);
} catch (\Throwable $uncaught) {
} catch (\Exception $uncaught) {
}
if (isset($uncaught)) {
$message = '<h1>Additional uncaught exception thrown while handling exception.</h1>';
$message .= '<h2>Original</h2><p>' . _drupal_render_exception_safe($exception) . '</p>';
$message .= '<h2>Additional</h2><p>' . _drupal_render_exception_safe($uncaught) . '</p>';
$backtrace = debug_backtrace();
$caller = _drupal_get_last_caller($backtrace);
_js_log_php_error(array(
'%type' => 'Unknown error',
'!message' => filter_xss_admin($message),
'%function' => $caller['function'],
'%file' => $caller['file'],
'%line' => $caller['line'],
'severity_level' => WATCHDOG_ERROR,
), TRUE);
}
}
function _js_fatal_error_handler() {
if ($error = error_get_last()) {
require_once DRUPAL_ROOT . '/includes/errors.inc';
_js_log_php_error(array(
'%type' => 'Fatal Error',
'!message' => filter_xss_admin($error['message']),
'%file' => $error['file'],
'%line' => $error['line'],
'severity_level' => WATCHDOG_ERROR,
), TRUE);
}
}
function _js_log_php_error(array $error, $fatal = FALSE) {
watchdog('php', '%type: !message in %function (line %line of %file).', $error, $error['severity_level']);
if (error_displayable($error)) {
if (!isset($error['%function'])) {
drupal_set_message(t('%type: !message (line %line of %file).', $error), 'error');
}
else {
drupal_set_message(t('%type: !message in %function (line %line of %file).', $error), 'error');
}
}
if ($fatal) {
js_deliver(js_http_response(500));
}
}
function js_execute_request() {
global $_js;
global $conf;
ob_start();
if (empty($conf['js_silence_php_errors'])) {
set_error_handler('_js_error_handler');
set_exception_handler('_js_exception_handler');
register_shutdown_function('_js_fatal_error_handler');
}
_js_cache_initialize();
static $method;
if (!isset($method)) {
$method = $_SERVER['REQUEST_METHOD'];
}
$_js['module'] = FALSE;
$_js['callback'] = FALSE;
$_js['token'] = FALSE;
$_js['theme'] = FALSE;
$global_method = '_' . strtoupper($method);
foreach ($_js as $key => $value) {
if (isset($GLOBALS[$global_method]["js_{$key}"])) {
$_js[$key] = check_plain($GLOBALS[$global_method]["js_{$key}"]);
unset($GLOBALS[$global_method]["js_{$key}"]);
}
}
$GLOBALS['devel_shutdown'] = FALSE;
$_js['args'] = explode('/', $_GET['q']);
$endpoint = variable_get('js_endpoint', 'js');
$_js['lang'] = FALSE;
if (!empty($_js['args'][0]) && !empty($_js['args'][1]) && $_js['args'][1] === $endpoint) {
$_js['lang'] = check_plain(array_shift($_js['args']));
}
if (!empty($_js['args'][0]) && $_js['args'][0] === $endpoint) {
array_shift($_js['args']);
}
module_load_include('inc', 'js', 'includes/common');
$info = NULL;
$request_result = JS_MENU_NOT_FOUND;
if (!$_js['module'] || !$_js['callback']) {
module_load_include('inc', 'js', 'includes/get');
$request_result = js_get_page();
}
else {
$info = js_get_callback($_js['module'], $_js['callback']);
if (!$info) {
drupal_set_message(t('The requested callback "%callback" defined by the "%module" module could not be loaded. Please check your configuration and try again.', array(
'%callback' => $_js['callback'],
'%module' => $_js['module'],
)), 'error', FALSE);
}
elseif (!in_array($method, $info['methods'])) {
$request_result = JS_MENU_METHOD_NOT_ALLOWED;
}
else {
js_delivery_callback($info['delivery callback']);
$token_valid = FALSE;
$validate_token = $info['token'] !== FALSE;
if ($validate_token) {
js_bootstrap(DRUPAL_BOOTSTRAP_SESSION);
drupal_load('module', 'user');
$token_valid = !user_is_anonymous() && drupal_valid_token($_js['token'], 'js-' . $_js['module'] . '-' . $_js['callback']);
}
if ($validate_token && !$token_valid) {
$request_result = JS_MENU_ACCESS_DENIED;
drupal_set_message(t('Cannot complete request. The token provided was either missing or invalid. Please refresh this page or try logging out and back in again.'), 'error', FALSE);
}
else {
module_load_include('inc', 'js', 'includes/callback');
$request_result = js_callback_execute($info);
}
}
}
js_deliver($request_result, $info);
}
function _js_cache_initialize() {
global $conf;
module_load_include('php', 'js', 'src/JsProxyCache');
$default_key = JsProxyCache::DEFAULT_BIN_KEY;
$cache_bin_keys = array_values(array_filter(array_keys($conf), function ($key) {
return strpos($key, 'cache_class_') === 0;
}));
$cache_bin_keys[] = $default_key;
$cache_conf = array();
$default_class = isset($conf[$default_key]) ? $conf[$default_key] : 'DrupalDatabaseCache';
foreach ($cache_bin_keys as $bin_key) {
$cache_conf[$bin_key] = isset($conf[$bin_key]) ? $conf[$bin_key] : $default_class;
$conf[$bin_key] = 'JsProxyCache';
}
JsProxyCache::setConf($cache_conf);
$excluded_conf = !empty($conf['js_excluded_cache_classes']) ? $conf['js_excluded_cache_classes'] : array(
'DrupalFakeCache',
);
JsProxyCache::setExcludedConf($excluded_conf);
if (!empty($cache_conf[$default_key]) && $cache_conf[$default_key] === 'MemCacheDrupal') {
js_bootstrap(DRUPAL_BOOTSTRAP_VARIABLES);
}
}
function js_deliver($result, array $info = NULL) {
$captured = ob_get_clean();
if (!isset($info) || !empty($info['capture'])) {
drupal_alter('js_captured_content', $result, $captured);
}
else {
print $captured;
}
$delivery_callback = js_delivery_callback();
if ($delivery_callback === 'js_deliver_json') {
module_load_include('inc', 'js', 'includes/json');
}
call_user_func_array($delivery_callback, array(
$result,
$info,
));
}
function js_bootstrap($phase = NULL, $new_phase = TRUE) {
if ($phase >= DRUPAL_BOOTSTRAP_LANGUAGE) {
js_update_path();
}
return drupal_bootstrap($phase, $new_phase);
}
function js_update_path() {
global $_js;
static $processed;
if (!$processed) {
$processed = TRUE;
$path = implode('/', $_js['args']);
if (!empty($_js['lang'])) {
$path = $_js['lang'] . '/' . $path;
}
$query_array = array();
foreach (array_diff_key($_GET, array(
'q' => FALSE,
)) as $param => $value) {
$query_array[] = $param . '=' . urlencode($value);
}
$query_params = implode('&', $query_array);
$_GET['q'] = $path;
if (isset($_SERVER['REQUEST_URI'])) {
$_SERVER['REQUEST_URI'] = '/' . $path . ($query_params ? '?' . $query_params : '');
}
else {
if (isset($_SERVER['argv'])) {
$_SERVER['argv'][0] = $path . ($query_params ? '?' . $query_params : '');
}
elseif (isset($_SERVER['QUERY_STRING'])) {
$_SERVER['QUERY_STRING'] = 'q=' . $path . ($query_params ? '&' . $query_params : '');
}
}
}
}
function js_get_token($module = NULL, $callback = NULL) {
static $drupal_static_fast;
if (!isset($drupal_static_fast)) {
$drupal_static_fast['tokens'] =& drupal_static(__FUNCTION__, array());
}
$tokens =& $drupal_static_fast['tokens'];
if (!empty($module) && !empty($callback)) {
if (!user_is_anonymous()) {
return $tokens["{$module}-{$callback}"] = drupal_get_token("js-{$module}-{$callback}");
}
else {
return FALSE;
}
}
return $tokens;
}
function js_get_callback($module = NULL, $callback = NULL, $reset = FALSE) {
global $_js;
static $drupal_static_fast;
if (!isset($drupal_static_fast)) {
$drupal_static_fast['callbacks'] =& drupal_static(__FUNCTION__);
}
$callbacks =& $drupal_static_fast['callbacks'];
if ($reset || !isset($callbacks)) {
$cid = 'js:callbacks';
if (!$reset && ($cache = cache_get($cid)) && $cache->data) {
$callbacks = $cache->data;
}
else {
if ($_js) {
js_bootstrap(DRUPAL_BOOTSTRAP_FULL);
}
foreach (module_implements('js_info', FALSE, $reset) as $_module) {
$results = module_invoke($_module, 'js_info');
foreach ($results as $_callback => $info) {
$callbacks[$_module][$_callback] = (array) $info;
$callbacks[$_module][$_callback] += array(
'access arguments' => array(),
'access callback' => FALSE,
'bootstrap' => DRUPAL_BOOTSTRAP_DATABASE,
'cache' => TRUE,
'callback function' => $_module . '_js_callback_' . $_callback,
'callback arguments' => array(),
'capture' => TRUE,
'delivery callback' => 'js_deliver_json',
'dependencies' => array(),
'includes' => array(),
'lang' => FALSE,
'load arguments' => array(),
'methods' => array(
'POST',
),
'module' => $_module,
'process request' => TRUE,
'skip init' => FALSE,
'token' => TRUE,
'xhprof' => FALSE,
'xss' => TRUE,
);
}
}
drupal_alter('js_info', $callbacks);
cache_set($cid, $callbacks);
}
}
if (isset($module) && isset($callback)) {
return !empty($callbacks[$module][$callback]) ? $callbacks[$module][$callback] : FALSE;
}
elseif (isset($module)) {
return !empty($callbacks[$module]) ? $callbacks[$module] : FALSE;
}
return !empty($callbacks) ? $callbacks : FALSE;
}
function js_array_replace_recursive($array, $array1) {
if (function_exists('array_replace_recursive')) {
return call_user_func_array('array_replace_recursive', func_get_args());
}
function recurse($array, $array1) {
foreach ($array1 as $key => $value) {
if (!isset($array[$key]) || isset($array[$key]) && !is_array($array[$key])) {
$array[$key] = array();
}
if (is_array($value)) {
$value = recurse($array[$key], $value);
}
$array[$key] = $value;
}
return $array;
}
$args = func_get_args();
$array = $args[0];
if (!is_array($array)) {
return $array;
}
for ($i = 1; $i < count($args); $i++) {
if (is_array($args[$i])) {
$array = recurse($array, $args[$i]);
}
}
return $array;
}