From 9d7f3de76aae6328f2da47b3426d8cddeacbc00c Mon Sep 17 00:00:00 2001 From: Moris Zen Date: Mon, 25 Jun 2018 00:06:37 +0200 Subject: [PATCH] Adding Saburly Headless WP theme inspired by postlights serverless solution --- .../themes/saburly-headless/functions.php | 18 ++ .../themes/saburly-headless/inc/admin.php | 31 +++ .../saburly-headless/inc/api-routes.php | 239 ++++++++++++++++++ .../themes/saburly-headless/inc/cors.php | 16 ++ .../saburly-headless/inc/frontend-origin.php | 12 + .../themes/saburly-headless/inc/log.php | 85 +++++++ .../themes/saburly-headless/inc/menus.php | 12 + .../themes/saburly-headless/index.php | 9 + .../post-content/sample-post.txt | 1 + .../saburly-headless/post-content/welcome.txt | 1 + .../themes/saburly-headless/screenshot.png | Bin 0 -> 14181 bytes .../themes/saburly-headless/style.css | 7 + 12 files changed, 431 insertions(+) create mode 100644 wordpress/wp-content/themes/saburly-headless/functions.php create mode 100644 wordpress/wp-content/themes/saburly-headless/inc/admin.php create mode 100644 wordpress/wp-content/themes/saburly-headless/inc/api-routes.php create mode 100644 wordpress/wp-content/themes/saburly-headless/inc/cors.php create mode 100644 wordpress/wp-content/themes/saburly-headless/inc/frontend-origin.php create mode 100644 wordpress/wp-content/themes/saburly-headless/inc/log.php create mode 100644 wordpress/wp-content/themes/saburly-headless/inc/menus.php create mode 100644 wordpress/wp-content/themes/saburly-headless/index.php create mode 100644 wordpress/wp-content/themes/saburly-headless/post-content/sample-post.txt create mode 100644 wordpress/wp-content/themes/saburly-headless/post-content/welcome.txt create mode 100644 wordpress/wp-content/themes/saburly-headless/screenshot.png create mode 100644 wordpress/wp-content/themes/saburly-headless/style.css diff --git a/wordpress/wp-content/themes/saburly-headless/functions.php b/wordpress/wp-content/themes/saburly-headless/functions.php new file mode 100644 index 0000000..a24d55a --- /dev/null +++ b/wordpress/wp-content/themes/saburly-headless/functions.php @@ -0,0 +1,18 @@ + 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 ); +} diff --git a/wordpress/wp-content/themes/saburly-headless/inc/cors.php b/wordpress/wp-content/themes/saburly-headless/inc/cors.php new file mode 100644 index 0000000..faf987e --- /dev/null +++ b/wordpress/wp-content/themes/saburly-headless/inc/cors.php @@ -0,0 +1,16 @@ + + // 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'); + +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 +} diff --git a/wordpress/wp-content/themes/saburly-headless/inc/menus.php b/wordpress/wp-content/themes/saburly-headless/inc/menus.php new file mode 100644 index 0000000..adc0277 --- /dev/null +++ b/wordpress/wp-content/themes/saburly-headless/inc/menus.php @@ -0,0 +1,12 @@ +ID ); +} elseif ( is_page() ) { + header( 'Location: /wp-json/wp/v2/pages/' . get_queried_object()->ID ); +} else { + header( 'Location: /wp-json/' ); +} diff --git a/wordpress/wp-content/themes/saburly-headless/post-content/sample-post.txt b/wordpress/wp-content/themes/saburly-headless/post-content/sample-post.txt new file mode 100644 index 0000000..6fe6613 --- /dev/null +++ b/wordpress/wp-content/themes/saburly-headless/post-content/sample-post.txt @@ -0,0 +1 @@ +Welcome to WordPress. This is your first post. Edit or delete it, then start writing! \ No newline at end of file diff --git a/wordpress/wp-content/themes/saburly-headless/post-content/welcome.txt b/wordpress/wp-content/themes/saburly-headless/post-content/welcome.txt new file mode 100644 index 0000000..6d0dc22 --- /dev/null +++ b/wordpress/wp-content/themes/saburly-headless/post-content/welcome.txt @@ -0,0 +1 @@ +You've successfully installed wordpress \ No newline at end of file diff --git a/wordpress/wp-content/themes/saburly-headless/screenshot.png b/wordpress/wp-content/themes/saburly-headless/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..d19300d1165fe19e4f19f46e279e37e5fbe64d0c GIT binary patch literal 14181 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