Skip to content

Instantly share code, notes, and snippets.

@wpsmith
Created August 13, 2015 21:14
Show Gist options
  • Save wpsmith/6fd1ae9c87ec53d762bd to your computer and use it in GitHub Desktop.
Save wpsmith/6fd1ae9c87ec53d762bd to your computer and use it in GitHub Desktop.
PHP/JS: WordPress Ajax as OOP.
<?php
/**
* Plugin Name: TGM API Helper
* Plugin URI: https://thomasgriffin.io
* Description: Whitelists the plugins to be loaded during API requests to reduce overhead.
* Author: Thomas Griffin
* Author URI: https://thomasgriffin.io
* Version: 1.0.0
*/
// Make sure we can target a valid URI endpoint.
if ( ! isset( $_SERVER['REQUEST_URI'] ) ) {
return;
}
// Make sure that the endpoint being requested matches a valid API endpoint.
if ( strpos( stripslashes( $_SERVER['REQUEST_URI'] ), '/api/v1/' ) === false ) {
return;
}
// Define a constant that we can make use of in any API plugins we create.
define( 'TGM_API_REQUEST', true );
// Now that we know this is an API request, let's filter available plugins.
add_filter( 'pre_option_active_plugins', 'tgm_whitelist_active_plugins' );
/**
* Filters the plugins that should be loaded during this request to reduce overhead.
*
* since 1.0.0
*
* @param array $plugins The active plugins to be loaded in the request.
* @retun array $whitelist Whitelisted plugins that should only be loaded during the request.
*/
function tgm_whitelist_active_plugins( $plugins ) {
// This is my base API plugin to handle all API routes. I want this loaded before everything else.
$whitelist = array(
'api-routes/api-routes.php'
);
// You can easily conditionally load plugins based on the URI path. Here is an example for an API endpoint called "update".
if ( strpos( stripslashes( $_SERVER['REQUEST_URI'] ), '/api/v1/update/' ) !== false ) {
$whitelist[] = 'api-update-helper/api-update-helper.php';
}
// Finally, we can load any API plugin that depends on all the others last.
$whitelist[] = 'api-depends/api-depends.php';
// Return the whitelist instead of the default plugins.
return $whitelist;
}
<?php
/**
* WP Ajax Abstract Class.
*
* @package WPS_Core
* @author Travis Smith <t@wpsmith.net>
* @copyright 2014 Travis Smith
* @license GPL-2.0+
*/
/**
* WPS_Ajax class.
*
* @package WPS_Core
* @author Travis Smith <t@wpsmith.net>
*/
class WPS_Ajax {
/**
* WP Nounce
*
* @var string
*/
protected $nonce = '';
/**
* WP AJAX Name
*
* @var string
*/
protected $name = '';
/**
* Whether to hook AJAX callback into front-end.
*
* @var string
*/
protected $nopriv = false;
/**
* Hook for scripts.
* Could be: wp_enqueue_scripts or login_enqueue_scripts or admin_enqueue_scripts
*
* @var string
*/
protected $script_hook = 'admin_enqueue_scripts';
/**
* Whether to keep the WP Heartbeat script.
*
* @var string
*/
protected $heartbeat = true;
/**
* AJAX callback function name.
*
* @var string
*/
protected $callback;
/**
* Constructor
*
* @since 1.0.0
* @author Travis Smith <t@wpsmith.net>
*
* @param string $name Name (lower-case, without spaces, use underscore) of the WP Action
* @param array $script Array of script information: url, src (path), data (localized info).
* @return void.
*/
public function __construct( $name, $script = array(), $callback ) {
$this->name = str_replace( ' ', '_', strtolower( $name ) );
// if not doing ajax, load script
if ( ( defined( 'DOING_AJAX' ) && !DOING_AJAX ) || !defined( 'DOING_AJAX' ) ) {
$this->maybe_do_action( 'plugins_loaded', 'script' );
}
// Hook up AJAX Action
$this->maybe_do_action( 'plugins_loaded', 'init' );
// Hook into secured callback
add_action( "{$this->name}_wp_ajax_action", $callback );
}
/**
* Sets object parameters.
* Available parameters: nopriv, script_hook, callback
*
* @since 1.0.0
* @author Travis Smith <t@wpsmith.net>
*
* @param string $param Parameter name.
* @param mixed $value Value of parameter.
* @return void.
*/
public function set( $param, $value ) {
switch ( $param ) {
case 'nopriv':
$this->nopriv = (bool) $value;
break;
case 'heartbeat':
$this->heartbeat = (bool) $value;
break;
case 'script_hook':
$hooks = array(
'wp_enqueue_scripts',
'login_enqueue_scripts',
'admin_enqueue_scripts',
);
if ( in_array( $value, $hooks ) ) {
$this->script_hook = strtolower( $value );
}
break;
case 'callback':
if ( is_callable( $value ) ) {
$this->callback = $value;
}
break;
}
}
/**
* Gets object parameter.
* Available parameters: nopriv, script_hook, callback
*
* @since 1.0.0
* @author Travis Smith <t@wpsmith.net>
*
* @param string $param Parameter name.
* @return mixed Value of parameter.
*/
public function get( $param ) {
switch ( $param ) {
case 'nopriv':
return (bool)$this->nopriv;
case 'heartbeat':
return (bool)$this->heartbeat;
case 'script_hook':
return (string)$this->script_hook;
case 'callback':
return (string)$this->callback;
default:
return null;
}
}
/**
* Hooks up AJAX Action
*
* @since 1.0.0
* @author Travis Smith <t@wpsmith.net>
*
* @return void.
*/
protected function init() {
add_action( "wp_ajax_{$this->name}_action", array( $this, 'callback' ) );
if ( $this->nopriv ) {
add_action( "wp_ajax_nopriv_{$this->name}_action", array( $this, 'callback' ) );
}
}
/**
* Hooks action or executes action.
*
* @since 1.0.0
* @author Travis Smith <t@wpsmith.net>
*
* @param string WordPress action to be checked with did_action().
* @param string|array Function name/array to be called.
* @return void.
*/
private function maybe_do_action( $hook, $action ) {
if ( !did_action( $hook ) ) {
add_action( $hook, array( $this, $action ) );
} elseif ( is_callable( $action ) ) {
call_user_func( $action );
}
}
/**
* Performs script operations: register, localize, and enqueue.
*
* @since 1.0.0
* @author Travis Smith <t@wpsmith.net>
*
* @return void.
*/
public function script() {
// Register Script
add_action( 'wp_loaded', array( $this, 'register' ) );
// Make sure we hook scripts in proper place & prevent user error.
if ( $this->nopriv && 'admin_enqueue_scripts' === $this->script_hook ) {
$this->script_hook = 'wp_enqueue_scripts';
}
// Go SCRIPT!
add_action( $this->script_hook, array( $this, 'localize' ) );
add_action( $this->script_hook, array( $this, 'enqueue' ) );
if ( !$this->heartbeat ) {
add_action( 'admin_enqueue_scripts', array( $this, 'no_heartbeat' ) );
}
}
/**
* Properly Registers AJAX script.
*
* @since 1.0.0
* @author Travis Smith <t@wpsmith.net>
*
* @return void.
*/
public function register() {
wp_register_script(
$this->name,
$this->script['url'],
$this->script['deps'],
filemtime( $this->script['src'] ),
true
);
}
/**
* Properly Enqueues AJAX script.
*
* @since 1.0.0
* @author Travis Smith <t@wpsmith.net>
*
* @return void.
*/
public function enqueue() {
wp_enqueue_script( $this->name );
}
/**
* Properly provides localized data for the action.
*
* @since 1.0.0
* @author Travis Smith <t@wpsmith.net>
*
* @return void.
*/
public function localize() {
// Get JS object name
$object = isset( $this->script['data_object'] ) ? $this->script['data_object'] : $this->name;
$this->nonce = wp_create_nonce( "{$this->name}_nonce" );
$data = wp_parse_args( $this->script['data'], array(
'ajaxurl' => admin_url( 'admin-ajax.php' ),
'_ajax_nonce' => $this->nonce,
'action' => "{$this->name}_action",
'screen_id' => get_current_screen()->id,
));
wp_localize_script( $this->name, "$object", $data );
}
public function no_heartbeat() {
wp_deregister_script('heartbeat');
wp_register_script('heartbeat', false);
}
/**
* Does proper AJAX security check & then calls "{$this->name}_wp_ajax_action" action.
*
* @since 1.0.0
* @author Travis Smith <t@wpsmith.net>
*
* @return void.
*/
public function _callback() {
$data = array_map( 'esc_attr', $_GET );
! check_ajax_referer( $data['action'], "_ajax_nonce", false )
AND wp_send_json_error();
do_action( "{$this->name}_wp_ajax_action", $data );
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment