Skip to content

Commit

Permalink
Added rest api logic and settings to fetch the customizer link - see #2
Browse files Browse the repository at this point in the history
  • Loading branch information
Vlad Olaru committed Oct 8, 2019
1 parent 10cdc17 commit fd451ea
Show file tree
Hide file tree
Showing 6 changed files with 292 additions and 10 deletions.
7 changes: 6 additions & 1 deletion assets/css/admin.css
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,9 @@ fieldset.group {

#backstage_setup_page .cmb2-metabox-description {
color: #959595;
}
}

#backstage_setup_page .cmb2-wrap .cmb-type-title.cmb2-id-backstage-rest-route-url-title {
margin-top: 0;
padding-left: 230px;
}
25 changes: 19 additions & 6 deletions extras.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@
/**
* Get the link for visitors to auto-login and access the Customizer.
*
* @param string|bool $return_url Optional. The return URL to use for the Customizer back button. Defaults to current frontend URL.
* @param string|bool $button_text Optional. Custom button text to use for the Customizer back button.
* @return string
*/
function backstage_get_customizer_link() {
function backstage_get_customizer_link( $return_url = false, $button_text = false) {
global $wp;

// This should not be used in the admin.
Expand All @@ -25,16 +27,27 @@ function backstage_get_customizer_link() {
$auto_login_key = Backstage::$default_auto_login_key;
}

// First, get the current frontend URL.
$current_url = home_url( add_query_arg( array(), $wp->request ) );
if ( empty( $return_url ) ) {
// Get the current frontend URL.
$return_url = home_url( add_query_arg( array(), $wp->request ) );
}

// Now get the Customizer URL.
$link = wp_customize_url();
// Next we need to add our own parameters to it, including the return URL (the current page).
$auto_login_hash = wp_hash( $current_url );
$auto_login_hash = wp_hash( $return_url );

$link = add_query_arg( $auto_login_key, urlencode( $auto_login_hash ), $link );
$link = add_query_arg( 'return_url', urlencode( $current_url ), $link );
$link = add_query_arg( 'return_url', urlencode( $return_url ), $link );

if ( ! empty( $button_text ) ) {
// Sanitize it
$button_text = esc_html( $button_text );
if ( ! empty( $button_text ) ) {
// Put it in the link
$link = add_query_arg( 'button_text', urlencode( $button_text ), $link );
}
}

return apply_filters( 'backstage_get_customizer_link', $link );
}
Expand Down Expand Up @@ -450,4 +463,4 @@ function backstage_get_css_class( $class = '', $location = '', $prefix = '', $su
$classes = array_map( 'esc_attr', $classes );

return array_unique( $classes );
} // function
} // function
14 changes: 12 additions & 2 deletions includes/class-Backstage.php
Original file line number Diff line number Diff line change
Expand Up @@ -377,7 +377,17 @@ public function maybe_auto_login() {

do_action( 'backstage_did_auto_login' );

wp_safe_redirect( esc_url( add_query_arg( 'url', $_GET['return_url'], admin_url( 'customize.php' ) ) ) );
// We will put the return URL in the url key because the return_url key is used by the Customizer logic and we want to avoid collisions.
$url = add_query_arg( 'url', $_GET['return_url'], admin_url( 'customize.php' ) );

if ( ! empty( $_GET['button_text'] ) ) {
$url = add_query_arg( 'button_text', $_GET['button_text'], $url );
}

// Allow others to have say in this.
$url = apply_filters( 'backstage_auto_login_redirect_url', $url );

wp_safe_redirect( esc_url( $url ) );
die();
}
}
Expand Down Expand Up @@ -557,7 +567,7 @@ public function enqueue_admin_customizer_scripts() {
wp_enqueue_script( backstage_prefix( 'customizer' ) );

wp_localize_script( backstage_prefix( 'customizer' ), 'backstage', apply_filters( 'backstage_customizer_localized_data', array(
'button_text' => esc_html( Backstage_Plugin()->settings->get_option( 'customizer_back_button_text', __( 'Back to Demo', 'backstage' ) ) ),
'button_text' => esc_html( ! empty( $_GET['button_text'] ) ? $_GET['button_text'] : Backstage_Plugin()->settings->get_option( 'customizer_back_button_text', __( 'Back to Demo', 'backstage' ) ) ),
'button_link' => esc_url( ! empty( $_GET['url'] ) ? $_GET['url'] : get_home_url() ),
'notice_type' => esc_attr( trim( Backstage_Plugin()->settings->get_option( 'customizer_notice_type', 'info' ) ) ),
'notice_text' => trim( Backstage_Plugin()->settings->get_option( 'customizer_notice_text', __( '<b>Demo Mode</b><p>You can\'t upload images and save settings.</p>', 'backstage' ) ) ),
Expand Down
16 changes: 16 additions & 0 deletions includes/class-Backstage_Plugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,14 @@ final class Backstage_Plugin extends Backstage_Plugin_Init {
*/
public $backstage = null;

/**
* REST API class class object.
* @var Backstage_REST_Controller_v1
* @access public
* @since 1.0.0
*/
public $rest_api = null;


/**
* The main plugin file.
Expand Down Expand Up @@ -152,6 +160,12 @@ private function init() {
$this->backstage = Backstage::getInstance( $this );
}

/* Initialize the REST API logic. */
require_once( trailingslashit( $this->plugin_basepath ) . 'includes/class-Backstage_REST_Controller_v1.php' );
if ( is_null( $this->rest_api ) ) {
$this->rest_api = new Backstage_REST_Controller_v1();
}

// Register all the needed hooks
$this->register_hooks();
}
Expand All @@ -172,6 +186,8 @@ public function register_hooks() {

/* Handle localisation. */
add_action( 'init', array( $this, 'load_localisation' ), 0 );

add_action( 'rest_api_init', array( $this->rest_api, 'register_routes' ), 5 );
}

/**
Expand Down
144 changes: 144 additions & 0 deletions includes/class-Backstage_REST_Controller_v1.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
<?php

class Backstage_REST_Controller_v1 extends WP_REST_Controller {
const REST_NAMESPACE = 'backstage';
const REST_FRONT_BASE = 'front';

public function register_routes() {

// This route should only be registered if it is enabled in the settings.
if ( ! empty( Backstage_Plugin()->settings->get_option( 'enable_rest_api' ) ) ) {

$args = array(
'return_url' => array(
'required' => true,
'type' => 'string',
'sanitize_callback' => array( 'Backstage_REST_Controller_v1', 'sanitize_string' ),
//'validate_callback' => ...
'description' => esc_html__( 'The return URL to use for the back button in the Customizer.', 'backstage' ),
),
'button_text' => array(
'required' => false,
'type' => 'string',
'sanitize_callback' => array( 'Backstage_REST_Controller_v1', 'sanitize_string' ),
'description' => esc_html__( 'A custom text for the back button in the Customizer, to overwrite the one set in the plugin\'s settings just when using this link.', 'backstage' ),
'default' => '',
),
);

// Add the required secret_key param if the secret key check is enabled.
if ( Backstage_Plugin()->settings->get_option( 'enable_rest_api_secret_key' ) == 'on' ) {
$args['secret_key'] = array(
'required' => true,
'type' => 'string',
'sanitize_callback' => array( 'Backstage_REST_Controller_v1', 'sanitize_string' ),
'description' => esc_html__( 'The secret key as set in the Backstage settings.', 'backstage' ),
);
}

register_rest_route( Backstage_REST_Controller_v1::REST_NAMESPACE, '/' . Backstage_REST_Controller_v1::REST_FRONT_BASE . '/customizer_link', array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_customizer_link' ),
'permission_callback' => array( $this, 'get_customizer_link_permissions_check' ),
'args' => $args,
// This should be available in the index since it is public (API discovery)
'show_in_index' => true,
) );
}
}

public function get_customizer_link_route_url() {
return get_rest_url( null, Backstage_REST_Controller_v1::REST_NAMESPACE . '/' . Backstage_REST_Controller_v1::REST_FRONT_BASE . '/customizer_link' );
}

/**
* Check and verify the get customizer link request.
*
* @param WP_REST_Request $request
* @return false|int
*/
public function get_customizer_link_permissions_check( $request ) {
$params = $request->get_params();

// If the secret key check is enabled in the plugin settings, we need to receive it in a parameter and it must match.
if ( Backstage_Plugin()->settings->get_option( 'enable_rest_api_secret_key' ) == 'on' ) {
$secret_key = trim( Backstage_Plugin()->settings->get_option( 'rest_api_secret_key' ) );
if ( ! isset( $params['secret_key'] ) || empty( $secret_key ) || $params['secret_key'] !== $secret_key ) {
return false;
}
}

return true;
}

/**
* @param WP_REST_Request $request
*
* @return WP_REST_Response
*/
public function get_customizer_link( $request ) {

$params = $request->get_params();

if ( empty( $params['return_url'] ) ) {
return rest_ensure_response( array(
'code' => 'missing_return_url',
'message' => 'You need to provide a return URL.',
'data' => array(),
) );
}

$return_url = $params['return_url'];
$button_text = false;
if ( ! empty( $params['button_text'] ) ) {
$button_text = $params['button_text'];
}

return rest_ensure_response( array(
'code' => 'success',
'message' => '',
'data' => array(
'url' => backstage_get_customizer_link( $return_url, $button_text ),
),
) );
}

/**
* Jut a little bit of trimming.
*
* @param $string
* @param $request
* @param $param
*
* @return string
*/
public static function sanitize_string( $string, $request, $param ) {
return trim( $string );
}

/**
* Make sure that we get an int.
*
* @param $int
* @param $request
* @param $param
*
* @return string
*/
public static function sanitize_int( $int, $request, $param ) {
return intval( $int );
}

/**
* Make sure that we get a safe text.
*
* @param $text
* @param $request
* @param $param
*
* @return string
*/
public static function sanitize_text_field( $text, $request, $param ) {
return sanitize_text_field( $text );
}
}
96 changes: 95 additions & 1 deletion includes/class-Backstage_Settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,7 @@ public function cmb2_init() {

$cmb->add_field( array(
'name' => esc_html__( 'Advanced', 'backstage' ),
'desc' => 'Advanced options that you should take extra care when modifying them.',
'desc' => esc_html__( 'Advanced options that you should take extra care when modifying them.', 'backstage' ),
'id' => $this->prefix( 'advanced_title' ),
'type' => 'title',
) );
Expand All @@ -360,6 +360,74 @@ public function cmb2_init() {
),
) );

$cmb->add_field( array(
'name' => esc_html__( 'Enable REST API Endpoint', 'backstage' ),
'desc' => esc_html__( 'Enable a REST API endpoint for dynamically fetching the secure URL to access the Customizer.', 'backstage' ),
'id' => $this->prefix( 'enable_rest_api' ),
'type' => 'checkbox',
) );

if ( Backstage_Plugin()->is_plugin_network_activated() ) {
$rest_route_url_details = __( '<p>The plugin is network activated, but each blog in the network will have its own REST endpoint for fetching the customizer link.</p><p>Here is an example endpoint URL: <code>' . Backstage_Plugin()->rest_api->get_customizer_link_route_url() . '</code></p><p>To get the link for each blog in the network, replace the part before <code>/wp-json/</code> with the home URL of that blog.</p>', 'backstage');
} else {
$rest_route_url_details = __( '<p>Here is the endpoint URL to use: <code>' . Backstage_Plugin()->rest_api->get_customizer_link_route_url() . '</code></p>', 'backstage');
}
$rest_route_url_details .= __( '<p>Use the "GET" HTTP method. The successful response will be a JSON, with the following structure:</p>', 'backstage' );
$rest_route_url_details .= '<p><pre>
{
"code":"success",
"message":"",
"data":{
"url":"the-customizer-url-ready-to-use"
}
}
</pre></p>';

$cmb->add_field( array(
'name' => '',
'desc' => wp_kses_post( $rest_route_url_details ),
'id' => $this->prefix( 'rest_route_url_title' ),
'type' => 'title',
'attributes' => array(
'data-conditional-id' => $this->prefix( 'enable_rest_api' ),
'data-conditional-value' => 'on',
),
) );

$cmb->add_field( array(
'name' => esc_html__( 'REST API Secret Key Check', 'backstage' ),
'desc' => esc_html__( 'Enable this to only provide the customizer link response to REST API requests that provide the secret key bellow.', 'backstage' ),
'id' => $this->prefix( 'enable_rest_api_secret_key' ),
'type' => 'checkbox',
'attributes' => array(
'data-conditional-id' => $this->prefix( 'enable_rest_api' ),
'data-conditional-value' => 'on',
),
) );

$cmb->add_field( array(
'name' => esc_html__( 'Secret Key', 'backstage' ),
'desc' => esc_html__( 'Set a secret (private) key that must be provided in each REST API request (under the name `secret_key`). In a Multisite installation, with the plugin network activated, this key is shared across all blogs in the network.', 'backstage' ),
'id' => $this->prefix( 'rest_api_secret_key' ),
'type' => 'text',
'default' => $this->get_rest_api_default_secret_key(),
'attributes' => array(
'data-conditional-id' => $this->prefix( 'enable_rest_api_secret_key' ),
'data-conditional-value' => 'on',
),
) );

}

protected function get_rest_api_default_secret_key() {
$default_secret_key = $this->get_option( 'rest_api_default_secret_key' );
if ( empty( $default_secret_key ) ) {
// We will save the default in the DB so it stays the same, but unique to each installation.
$default_secret_key = wp_generate_password( 20, false, false );
$this->update_option( 'rest_api_default_secret_key', $default_secret_key );
}

return $default_secret_key;
}

public function is_settings_page() {
Expand Down Expand Up @@ -419,6 +487,32 @@ public function get_option( $id, $default = false ) {
return $default;
}

/**
* Update an option, even before the CMB2 has loaded.
*
* @since 1.3.0
*
* @param string $id
* @param mixed $value
*/
public function update_option( $id, $value ) {
if ( Backstage_Plugin()->is_plugin_network_activated() ) {
$options = get_site_option( self::$key );
if ( empty( $options ) ) {
$options = array();
}
$options[ $this->prefix( $id ) ] = $value;
update_site_option( self::$key, $options );
} else {
$options = get_option( self::$key );
if ( empty( $options ) ) {
$options = array();
}
$options[ $this->prefix( $id ) ] = $value;
update_option( self::$key, $options );
}
}

/**
* Public getter method for retrieving protected/private properties.
*
Expand Down

0 comments on commit fd451ea

Please sign in to comment.