Adding Saburly Headless WP theme inspired by postlights serverless solution
This commit is contained in:
18
wordpress/wp-content/themes/saburly-headless/functions.php
Normal file
18
wordpress/wp-content/themes/saburly-headless/functions.php
Normal 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';
|
||||
31
wordpress/wp-content/themes/saburly-headless/inc/admin.php
Normal file
31
wordpress/wp-content/themes/saburly-headless/inc/admin.php
Normal 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' );
|
||||
239
wordpress/wp-content/themes/saburly-headless/inc/api-routes.php
Normal file
239
wordpress/wp-content/themes/saburly-headless/inc/api-routes.php
Normal 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 );
|
||||
}
|
||||
16
wordpress/wp-content/themes/saburly-headless/inc/cors.php
Normal file
16
wordpress/wp-content/themes/saburly-headless/inc/cors.php
Normal 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 );
|
||||
@@ -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';
|
||||
}
|
||||
85
wordpress/wp-content/themes/saburly-headless/inc/log.php
Normal file
85
wordpress/wp-content/themes/saburly-headless/inc/log.php
Normal 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
|
||||
}
|
||||
12
wordpress/wp-content/themes/saburly-headless/inc/menus.php
Normal file
12
wordpress/wp-content/themes/saburly-headless/inc/menus.php
Normal 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' );
|
||||
9
wordpress/wp-content/themes/saburly-headless/index.php
Normal file
9
wordpress/wp-content/themes/saburly-headless/index.php
Normal 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/' );
|
||||
}
|
||||
@@ -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!
|
||||
@@ -0,0 +1 @@
|
||||
You've successfully installed wordpress
|
||||
BIN
wordpress/wp-content/themes/saburly-headless/screenshot.png
Normal file
BIN
wordpress/wp-content/themes/saburly-headless/screenshot.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
7
wordpress/wp-content/themes/saburly-headless/style.css
Normal file
7
wordpress/wp-content/themes/saburly-headless/style.css
Normal 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
|
||||
*/
|
||||
Reference in New Issue
Block a user