restore_all(); } function async_widgets_deactivate(){ $aw = new AsyncWidget(); $aw->deactivate_all(); } /***************************************** * * SESSION INIT * ****************************************/ /* * We store the widget parameters in $_SESSION, * rather than in the database, since the * parameter may be different from user to * user (or may they not?). */ add_action('template_redirect', 'aw_setup_session'); function aw_setup_session(){ if( ! session_id() ){ $succ = session_start(); } } /********************************************* * * MAIN HOOK * ********************************************/ /* * Priority probably needs to be as high as possible -- but I'm not sure. * For now prio set to be higher than at least prio used by WP Widget Cache plugin. */ if( ! defined('AW_HOOK_PRIORITY') ){ define('AW_HOOK_PRIORITY', 1 << 17); } $aw = new AsyncWidget(); /* * Register the action that'll proxy all necessary widget callbacks. */ add_action("wp_head", array(&$aw, 'setup_widget_callback_intercept'), AW_HOOK_PRIORITY); /********************************************* * * AJAX AND JAVASCRIPT GOODIES * *********************************************/ add_action('wp_enqueue_scripts', 'aw_cond_output_js_loader'); /** * Conditionally insert the JavaScript that'll perform * the asynchronous loading. * "Conditionally", for if we aren't set up to async any widget, * we don't output the code. * * @return void */ function aw_cond_output_js_loader(){ $aw = new AsyncWidget(); if( $aw->get_async_widget_count() == 0 ){ /* * nothing to do: don't pollute the page with yet more script. */ return; } /* * See AsyncWidget::async_widget_proxy_callback for * an explanation of the identifiers used here. */ wp_register_script( "async-widgets", plugins_url( 'js/async-widgets.js' , __FILE__ ), array('jquery') ); wp_enqueue_script( "async-widgets" ); wp_localize_script( "async-widgets" , "async_widgets", array( "ajax_url" => admin_url('admin-ajax.php') ) ); } add_action('wp_ajax_aw_get_widget', 'async_widgets_get_callback'); add_action('wp_ajax_nopriv_aw_get_widget', 'async_widgets_get_callback'); /** * This is the function called from the AJAX query. * * Serves the real widget content. */ function async_widgets_get_callback(){ if( ! isset($_POST['widget_id']) ){ header("HTTP/1.1 500 Internal Server Error", true, 500); echo "Error: widget_id not found\n"; die(0xff); } if( ! isset($_POST['session_id']) ){ header("HTTP/1.1 500 Internal Server Error", true, 500); echo "Error: session_id not found\n"; die(0xff); } $widgetid = $_POST['widget_id']; $sid = $_POST['session_id']; session_id($sid); session_start(); // restore GET and POST from the main page // for plugins that use them. $_GET += $_SESSION['aw_get_params']; $_POST += $_SESSION['aw_post_params']; $aw = new AsyncWidget(); $aw->get_widget_content($widgetid); die(); } /*********************************************** * * UTILITY CLASS * ***********************************************/ /** * Helper class. Hardly modelling anything (don't let the name mislead you). */ class AsyncWidget { protected $widgetstore = array(); public function __construct() { /* * restore from persistence. */ $stored = get_option("async_widgets_store"); if( $stored ){ $this->widgetstore = &$stored; } } /** * Returns the number of widgets we are currently enabled for. * * @return int the number of widgets we are currently enabled for. */ public function get_async_widget_count(){ return count($this->widgetstore); } /** * Returns a boolean indicating whether or not the async mechanism is * enabled for the widget with the given ID. * * @param string $widget_id * @return boolean */ public function is_async_enabled($widget_id){ return isset($this->widgetstore[$widget_id]); } /** * Persistence */ protected function save_widget_store(){ update_option("async_widgets_store", $this->widgetstore); } /** * Enable the async mechanism for the widget with the given ID. * * @global $wp_registered_widgets * @param string $widget_id * @return true, if the async mechanism could be enabled, false otherwise. */ public function enable_async($widget_id){ global $wp_registered_widgets; if( ! isset($wp_registered_widgets[$widget_id]) ){ return false; } $this->widgetstore[$widget_id] = array(); $this->save_widget_store(); return true; } /** * Disable the async mechanism for the widget with the given ID. * * @global $wp_registered_widgets * @param string $widget_id * @return false, if the async mechanism wasn't enabled for whe widget with * the given ID, true otherwise. */ public function disable_async($widget_id){ global $wp_registered_widgets; if( ! isset($this->widgetstore[$widget_id]) ){ return false; } unset($this->widgetstore[$widget_id]); $this->save_widget_store(); return true; } /** * This is called when the plugin is deactivated. */ public function deactivate_all(){ //nothing for now. Maybe later. } /** * This is called when the plugin is activated. */ public function restore_all(){ //nothing for now. Maybe later. } private function check_async_widget_enabled_or_throw($widgetid){ if( ! $this->is_async_enabled($widgetid) ){ throw new Exception("Async loading not enabled for widget: $widget_id"); } } /** * Serve the real widget content. This is called from the AJAX query. *

* If the async mechanism isn't enabled for whe widget * with the given ID, an Exception is thrown. * * @param string $widget_id */ public function get_widget_content($widget_id){ $this->check_async_widget_enabled_or_throw($widget_id); $real_callback = $this->widgetstore[$widget_id]['callback']; if( isset( $_SESSION['aw_'.$widget_id]['params'] ) ){ $params = $_SESSION['aw_'.$widget_id]['params']; //won't be needed anymore unset($_SESSION['aw_'.$widget_id]); } else { /* * If Super Cache is enabled, we don't have * the parameters in the session, and have * to load them from the DB instead. */ $params = $this->widgetstore[$widget_id]['params']; } //see function doc $this->restore_widget_cache_redirected_callback($widget_id); call_user_func_array($real_callback, $params); } /** * This is an interoperability hack for the WP Widget Cache plugin -- we need to make the changes * it opers persistent. * * @global $wp_registered_widgets * @param string $widgetid */ private function restore_widget_cache_redirected_callback($widgetid){ global $wp_registered_widgets; if( isset($this->widgetstore[$widgetid]['callback_wc_redirect']) ){ $wp_registered_widgets[$widgetid]['callback_wc_redirect'] = $this->widgetstore[$widgetid]['callback_wc_redirect']; } } /** * Called on each GET of a normal page (wp_head). * * Here we set up our proxy callbacks for all the widgets we are configured to. */ public function setup_widget_callback_intercept(){ global $wp_registered_widgets; $widgetids = array_keys($this->widgetstore); $need_save = false; foreach($widgetids as $id){ if( ! isset($wp_registered_widgets[$id]) ){ //WTF!? continue; } $callback = $wp_registered_widgets[$id]['callback']; if( ! is_callable($callback) ){ //Just skip. continue; } //replace the callback with our own if( $this->widgetstore[$id]['callback'] !== $callback ){ $this->widgetstore[$id]['callback'] = $callback; $need_save = true; } $wp_registered_widgets[$id]['callback']=array(&$this, 'async_widget_proxy_callback'); /* * WP Widget Cache interoperability hack */ if( isset($wp_registered_widgets[$id]['callback_wc_redirect']) ){ //store the WidgetCache redirect callback -- it's transient. $wc_callback = $wp_registered_widgets[$id]['callback_wc_redirect']; $this->widgetstore[$id]['callback_wc_redirect'] = $wc_callback; $need_save = true; } else if ( isset( $this->widgetstore[$id]['callback_wc_redirect'] )){ unset( $this->widgetstore[$id]['callback_wc_redirect'] ); $need_save = true; } } if( $need_save ){ $this->save_widget_store(); } //store the GET and POST params, in case //a plugin is relying on those (we'll restore //them in the AJAX call $_SESSION['aw_get_params'] = $_GET; $_SESSION['aw_post_params'] = $_POST; } /** * This is the proxy callback we've replaced the * registered widget callbacks with. * * onload, client-side JS will parse widget_id and replace * the placeholder element (see below). * * @todo I18N of noscript message. * @uses aw_cond_output_js_loader */ function async_widget_proxy_callback(){ global $wp_registered_widgets; $args = func_get_args(); //there's a bit of guesswork involved //here, but it appears to work. $widgetsettings = $args[0]; $id = $widgetsettings['widget_id']; /* * Allow other plugins to hook on this action */ do_action('async_widgets_output_proxy_html', $widgetsettings); $this->async_widgets_output_proxy_html($widgetsettings); /* * Store things. * * If the page is using the Super Cache * plugin, we will have to store things * more persistently, because in Super Cache * delivered pages, the code doesn't go through * this hook anymore -- only if the page isn't * in the cache yet. * So put it in the DB. Can't say I'm enthousiastic * about this, but I don't see any other way. Super * Cache and this plugin are kind of conflicting, * anyway. */ if( $GLOBALS['super_cache_enabled'] ){ $this->widgetstore[$id]['params'] = $args; $this->save_widget_store(); } else{ //clean up if( isset( $this->widgetstore[$id]['params'] ) ){ unset( $this->widgetstore[$id]['params'] ); $this->save_widget_store(); } $_SESSION['aw_'.$id]['params'] = $args; } } function async_widgets_output_proxy_html($widgetsettings){ /* * Allow other plugins to filter on this action */ $widgetsettings = apply_filters( 'async_widgets_output_proxy_html_filter', $widgetsettings ); if( ! is_array($widgetsettings) || ! isset($widgetsettings['id']) ){ return; } $id = $widgetsettings['widget_id']; $name = $widgetsettings['widget_name']; $sessionid = session_id(); ?>

widgetstore); global $wp_registered_widgets; $this->widgetstore = array_intersect_key($this->widgetstore, $wp_registered_widgets); if( count($this->widgetstore) != $count_pre ){ echo "Cleaned ".($count_pre - count($this->widgetstore))." stale key(s)\n"; $this->save_widget_store(); } } } /************************************************* * * ADMIN INTEGRATION * ************************************************/ add_action("admin_init", "aw_admin_init"); add_action("admin_menu", "aw_admin_plugin_menu"); function aw_admin_init(){ add_action('wp_ajax_aw_toggle_async_widget', 'aw_toggle_async_widget_callback'); wp_register_style('aw_admin_styles', plugins_url( "css/async-widgets-admin.css", __FILE__ ) ); } function aw_admin_plugin_menu(){ $page = add_options_page( "Async Widgets", "Async Widgets", "administrator", "aw_plugin_config", "init_aw_plugin_config_page" ); /* Using registered $page handle to hook stylesheet loading */ //going per codex sample. Man, is this convoluted! add_action('admin_print_styles-' . $page, 'aw_enqueue_admin_styles'); } function aw_enqueue_admin_styles(){ wp_enqueue_style("aw_admin_styles"); } function init_aw_plugin_config_page(){ $aw = new AsyncWidget(); $aw->clean_store(); include 'aw_plugin_config.php'; } function aw_toggle_async_widget_callback(){ $aw = new AsyncWidget(); $nonce = $_POST['_ajax_nonce']; if( ! wp_verify_nonce($nonce, 'aw_toggle_async_widget') ){ header("HTTP/1.1 401 Authorization Required", true, 401); die(0xff); } $widgetid = $_POST['widget_id']; /* * Here be dragons! * * The client-side acts on the returned data; * specifically, it must be "1" (and only that) * to indicate that the async for that widget * is now ENabled. Anything else will be interpreted * as that it's DISabled. * (see JavaScript in aw_plugin_config.php (aw_toggle_async_widget)). */ try { if( $aw->is_async_enabled($widgetid) ){ $aw->disable_async($widgetid); echo "0"; } else { $aw->enable_async($widgetid); echo "1"; } } catch (Exception $ex){ header("HTTP/1.1 500 Internal Error", true, 500); echo $ex->getTraceAsString(); } die(); } ?>