Adding Saburly Headless WP theme inspired by postlights serverless solution

This commit is contained in:
Moris Zen
2018-06-25 00:06:37 +02:00
parent f069f6782f
commit 9d7f3de76a
12 changed files with 431 additions and 0 deletions

View File

@@ -0,0 +1,18 @@
<?php
// Logging functions
require_once 'inc/log.php';
// CORS handling
require_once 'inc/cors.php';
// Admin modifications
require_once 'inc/admin.php';
// Add Menus
require_once 'inc/menus.php';
// Add Headless Settings area
require_once 'inc/acf-options.php';
// Add custom API endpoints
require_once 'inc/api-routes.php';

View File

@@ -0,0 +1,31 @@
<?php
/**
* By default, in Add/Edit Post, WordPress moves checked categories to the top of the list and unchecked to the bottom.
* When you have subcategories that you want to keep below their parents at all times, this makes no sense.
* This function removes automatic reordering so the categories widget retains its order regardless of checked state.
* Thanks to https://stackoverflow.com/a/12586404
*
* @param arr $args Array of arguments.
* @return arr
*/
function taxonomy_checklist_checked_ontop_filter( $args ) {
$args['checked_ontop'] = false;
return $args;
}
add_filter( 'wp_terms_checklist_args', 'taxonomy_checklist_checked_ontop_filter' );
/**
* Customize the preview button in the WordPress admin to point to the headless client.
*
* @param str $link The WordPress preview link.
* @return str The headless WordPress preview link.
*/
function set_headless_preview_link( $link ) {
return get_frontend_origin() . '/'
. '_preview/'
. get_the_ID() . '/'
. wp_create_nonce( 'wp_rest' );
}
add_filter( 'preview_post_link', 'set_headless_preview_link' );

View File

@@ -0,0 +1,239 @@
<?php
/**
* Register custom REST API routes.
*/
add_action(
'rest_api_init',
function () {
// Define API endpoint arguments
$slug_arg = [
'validate_callback' => function ( $param, $request, $key ) {
return( is_string( $param ) );
}
,
];
$post_slug_arg = array_merge(
$slug_arg,
[
'description' => 'String representing a valid WordPress post slug',
]
);
$page_slug_arg = array_merge(
$slug_arg,
[
'description' => 'String representing a valid WordPress page slug',
]
);
// Register routes
register_rest_route( 'api/v1', '/post', [
'methods' => 'GET',
'callback' => 'rest_get_post',
'args' => [
'slug' => array_merge(
$post_slug_arg,
[
'required' => true,
]
),
],
] );
register_rest_route( 'api/v1', '/page', [
'methods' => 'GET',
'callback' => 'rest_get_page',
'args' => [
'slug' => array_merge(
$page_slug_arg,
[
'required' => true,
]
),
],
] );
register_rest_route('api/v1', '/post/preview', [
'methods' => 'GET',
'callback' => 'rest_get_post_preview',
'args' => [
'id' => [
'validate_callback' => function ( $param, $request, $key ) {
return ( is_numeric( $param ) );
}
,
'required' => true,
'description' => 'Valid WordPress post ID',
],
],
'permission_callback' => function () {
return current_user_can( 'edit_posts' );
}
,
] );
}
);
/**
* Respond to a REST API request to get post data.
*
* @param WP_REST_Request $request Request.
* @return WP_REST_Response
*/
function rest_get_post( WP_REST_Request $request ) {
return rest_get_content( $request, 'post', __FUNCTION__ );
}
/**
* Respond to a REST API request to get page data.
*
* @param WP_REST_Request $request Request.
* @return WP_REST_Response
*/
function rest_get_page( WP_REST_Request $request ) {
return rest_get_content( $request, 'page', __FUNCTION__ );
}
/**
* Respond to a REST API request to get post or page data.
* * Handles changed slugs
* * Doesn't return posts whose status isn't published
* * Redirects to the admin when an edit parameter is present
*
* @param WP_REST_Request $request Request
* @param str $type Type
* @param str $function_name Function name
* @return WP_REST_Response
*/
function rest_get_content( WP_REST_Request $request, $type, $function_name ) {
$content_in_array = in_array(
$type,
[
'post',
'page',
],
true
);
if ( ! $content_in_array ) {
$type = 'post';
}
$slug = $request->get_param( 'slug' );
$post = get_content_by_slug( $slug, $type );
if ( ! $post ) {
return new WP_Error(
$function_name,
$slug . ' ' . $type . ' does not exist',
[
'status' => 404,
]
);
}
;
// Shortcut to WP admin page editor
$edit = $request->get_param( 'edit' );
if ( 'true' === $edit ) {
header( 'Location: /wp-admin/post.php?post=' . $post->ID . '&action=edit' );
exit;
}
$controller = new WP_REST_Posts_Controller( 'post' );
$data = $controller->prepare_item_for_response( $post, $request );
$response = $controller->prepare_response_for_collection( $data );
return new WP_REST_Response( $response );
}
/**
* Returns a post or page given a slug. Returns false if no post matches.
*
* @param str $slug Slug
* @param str $type Valid values are 'post' or 'page'
* @return Post
*/
function get_content_by_slug( $slug, $type = 'post' ) {
$content_in_array = in_array(
$type,
[
'post',
'page',
],
true
);
if ( ! $content_in_array ) {
$type = 'post';
}
$args = [
'name' => $slug,
'post_type' => $type,
'post_status' => 'publish',
'numberposts' => 1,
];
// phpcs:ignore WordPress.VIP.RestrictedFunctions.get_posts_get_posts
$post_search_results = get_posts( $args );
if ( !$post_search_results ) {
// Maybe the slug changed
// check wp_postmeta table for old slug
$args = [
// phpcs:ignore WordPress.VIP.SlowDBQuery.slow_db_query_meta_query
'meta_query' => [
[
'key' => '_wp_old_slug',
'value' => $post_slug,
'compare' => '=',
],
],
];
$query = new WP_Query( $args );
$post_search_results = $query->posts;
}
if ( isset( $post_search_results[0] ) ) {
return $post_search_results[0];
}
return false;
}
/**
* Respond to a REST API request to get a post's latest revision.
* * Requires a valid _wpnonce on the query string
* * User must have 'edit_posts' rights
* * Will return draft revisions of even published posts
*
* @param WP_REST_Request $request Rest request.
* @return WP_REST_Response
*/
function rest_get_post_preview( WP_REST_Request $request ) {
$post_id = $request->get_param( 'id' );
// Revisions are drafts so here we remove the default 'publish' status
remove_action( 'pre_get_posts', 'set_default_status_to_publish' );
$check_enabled = [
'check_enabled' => false,
];
if ( $revisions = wp_get_post_revisions( $post_id, $check_enabled ) ) {
$last_revision = reset( $revisions );
$rev_post = wp_get_post_revision( $last_revision->ID );
$controller = new WP_REST_Posts_Controller( 'post' );
$data = $controller->prepare_item_for_response( $rev_post, $request );
} elseif ( $post = get_post( $post_id ) ) { // There are no revisions, just return the saved parent post
$controller = new WP_REST_Posts_Controller( 'post' );
$data = $controller->prepare_item_for_response( $post, $request );
} else {
$not_found = [
'status' => 404,
];
$error = new WP_Error(
'rest_get_post_preview',
'Post ' . $post_id . ' does not exist',
$not_found
);
return $error;
}
$response = $controller->prepare_response_for_collection( $data );
return new WP_REST_Response( $response );
}

View File

@@ -0,0 +1,16 @@
<?php
/**
* Allow GET requests from * origin
* Thanks to https://joshpress.net/access-control-headers-for-the-wordpress-rest-api/
*/
add_action( 'rest_api_init', function () {
remove_filter( 'rest_pre_serve_request', 'rest_send_cors_headers' );
add_filter( 'rest_pre_serve_request', function ( $value ) {
header( 'Access-Control-Allow-Origin: ' . get_frontend_origin() );
header( 'Access-Control-Allow-Methods: GET' );
header( 'Access-Control-Allow-Credentials: true' );
return $value;
});
}, 15 );

View File

@@ -0,0 +1,12 @@
<?php
/**
* Placeholder function for determining the frontend origin.
*
* @TODO Determine the headless client's URL based on the current environment.
*
* @return str Frontend origin URL, i.e., http://localhost:3000.
*/
function get_frontend_origin() {
return 'http://localhost:3000';
}

View File

@@ -0,0 +1,85 @@
<?php
/**
* Logs messages/variables/data to browser console from within php
* Thanks to https://codeinphp.github.io/post/outputting-php-to-browser-console/
*
* To use this, in your PHP template inside PHP tags, add a line like this:
* log_console('$mobile_image_size_inline_style var', $mobile_image_size_inline_style, true);
*
* @param str $name message to be shown for optional data/vars
* @param str $data variable (scalar/mixed) arrays/objects, etc to be logged
* @param bool $js_eval whether to apply JS eval() to arrays/objects
*
* @return none
* @author Sarfraz
*/
function log_console( $name, $data = null, $js_eval = false ) {
if ( ! $name ) {
return false;
}
$is_evaled = false;
$type = ( $data || gettype( $data ) ) ? 'Type: ' . gettype( $data ) : '';
if ( $js_eval && ( is_array( $data ) || is_object( $data ) ) ) {
$data = 'eval(' . preg_replace( '#[\s\r\n\t\0\x0B]+#', '', wp_json_encode( $data ) ) . ')';
$is_evaled = true;
} else {
$data = wp_json_encode( $data );
}
// sanitize
$data = $data ? $data : '';
$search_array = [ "#'#", '#""#', "#''#", "#\n#", "#\r\n#" ];
$replace_array = [ '"', '', '', '\\n', '\\n' ];
$data = preg_replace( $search_array, $replace_array, $data );
$data = ltrim( rtrim( $data, '"' ), '"' );
$data = $is_evaled ? $data : ( "'" === $data[0] ) ? $data : "'" . $data . "'";
$js = <<<JSCODE
\n<script>
// fallback - to deal with IE (or browsers that don't have console)
if (! window.console) console = {};
console.log = console.log || function(name, data){};
// end of fallback
console.log('$name');
console.log('------------------------------------------');
console.log('$type');
console.log($data);
console.log('\\n');
</script>
JSCODE;
echo $js; // phpcs:ignore WordPress.XSS.EscapeOutput.OutputNotEscaped
}
/**
* Log a value in wp-content/debug.log.
* To turn on, add the following to wp-config.php:
*
* define( 'WP_DEBUG', true );
* define( 'WP_DEBUG_LOG', true ); // Turn logging to wp-content/debug.log ON
* define( 'WP_DEBUG_DISPLAY', false ); // Keep JSON response valid
*
* @ini_set( 'display_errors', 0 ); // Keep JSON responses valid
*
* NOT INTENDED FOR PRODUCTION USE.
*
* @param str $message Message
* @param str $file Filename, defaults to __FILE__
* @param str $line Line number, defaults to __LINE__
* @return void
*/
function log_it( $message, $file = __FILE__, $line = __LINE__ ) {
// phpcs:disable WordPress
if ( WP_DEBUG === true ) {
if ( is_array( $message ) || is_object( $message ) ) {
error_log( $file . 'L' . $line . ' ' . ( print_r( $message, true ) ) );
} else {
error_log( $file . 'L' . $line . ' ' . $message );
}
}
// phpcs:enable WordPress
}

View File

@@ -0,0 +1,12 @@
<?php
/**
* Register navigation menu.
*
* @return void
*/
function register_menus() {
register_nav_menu( 'header-menu', __( 'Header Menu', 'saburly-wp' ) );
}
add_action( 'after_setup_theme', 'register_menus' );

View File

@@ -0,0 +1,9 @@
<?php
// Redirect individual post and pages to the REST API endpoint
if ( is_single() ) {
header( 'Location: /wp-json/wp/v2/posts/' . get_post()->ID );
} elseif ( is_page() ) {
header( 'Location: /wp-json/wp/v2/pages/' . get_queried_object()->ID );
} else {
header( 'Location: /wp-json/' );
}

View File

@@ -0,0 +1 @@
Welcome to WordPress. This is your first post. <a href="http://localhost:8080/wp-json/api/v1/post?slug=sample-post&edit=true" target="_new">Edit</a> or delete it, then start writing!

View File

@@ -0,0 +1 @@
You've successfully installed wordpress

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -0,0 +1,7 @@
/*
Theme Name: Saburly Website
Theme URI: https://www.saburly.com
Author: Saburly
Author URI: https://saburly.com
Description: Headless Wordpress API for Saburly.com
*/