Block users from REST API endpoint while still grabbing data from it - php

I have created 2 REST API endpoint to store the CPT data and their custom fields:
Archive Data: xyz.com/wp-json/wl/v2/businesses
Single Business data: xyz.com/wp-json/wl/v2/businesses/<ID>
Both these endpoints have been registered with the permission callbacks;
register_rest_route( 'wl/v2', '/businesses', array(
'methods' => WP_REST_Server::READABLE,
'callback' => 'wl_businesses_posts',
'permission_callback' => '__return_true'
));
It is a business directory website where a common dashboard ( xyz.com/dashboard ) for each 'business' client exists and this dashboard page pulls in data for that 'business' from the Single Business data REST API endpoint above and fills the input fields on the page.
There is also another page accessible to the non-logged in visitors( xyz.com/business1 ) that is common for all businesses and is a read-only page where visitors can check that business' details. This page too pulls data from the Single Business data REST API endpoint mentioned above.
What I am trying to accomplish is that no one except the Admin should be able to peep into the Archive Data or the Single Business data endpoints directly which displays that tall JSON data, to avoid stealing info of all the businesses registered with the site. But at the same time, I would want these endpoints to be accessible by the wp_remote_retrieve_body( wp_remote_get( $url ) );code to populate the dashboard and single business info pages.
I tried this code but it obviously also blocks the requests made by the page code to pull and populate data in the pages.
add_filter( 'rest_authentication_errors', function( $result ) {
if ( ! empty( $result ) ) {
return $result;
}
if ( ! is_user_logged_in() ) {
return new WP_Error( 'rest_not_logged_in', 'You are not currently logged in.', array( 'status' => 401 ) );
}
return $result;
});
I am not necessarily looking for a code, just the direction about how to solve this problem.
------------------------------UPDATE----------------------------:
Like I said, since it is a business directory, even non logged-in users should see the archive pages and single business pages of the CPT. I've learned recently about the 'permission callbacks' in register_rest_route arguments, so how would I pass a custom 'key' argument in the wp_remote_get function and receive and validate it in the permission callback?

Ok I've managed to secure my endpoint by passing a param in the request, and only if it matches in the permission callback of the rest endpoint, then the data is retrieved:
$userData = wp_remote_retrieve_body( wp_remote_get( $url, array(
'body' => array(
'param' => '1'
),
'sslverify' => false,
) ) );
And in the register_rest_route:
'permission_callback' => function( WP_REST_Request $request ) {
if ( '1' == $request->get_param( 'param' ) ) {
return true;
} else {
return false;
}
}
I'm so happy I wont have to use a plugin for this :D

Related

WP Admin: Include custom post type archive in link search results

I've been searching for examples of this online and through the WP documentation on filters but I can't find a suitable hook, so apologies for posting a question without a good example of what I'm trying to do!
When you add a link to text or to a button in the editor, you can search for the page/post you want to link to. What you can't search for is a post type archive link.
I want to be able to type the name of a post type into the search box (pictured below), and include the post type archive link in the search results. In this example, I have a post type called members that I'd like to link to.
I find the need to do this a lot, and I always end up just typing /post-type-link into the box and leaving it at that, but I don't think this is an elegant solution and is clunky for users.
I have tried to write some code, but I don't believe I have the right hook:
function include_cpt_search( $query ) {
if ( is_admin() && is_single() ) {
$query->set( 'post_type', array( 'services' ) );
}
return $query;
}
add_filter( 'pre_get_posts', 'include_cpt_search' );
Has anyone done this before? Know of a filter or hook I could work with? Anything really!
The Link format in the RichText toolbar uses the Search REST API endpoint at /wp/v2/search, so although that endpoint doesn't provide a specialized hook for filtering the response, you can use rest_post_dispatch to add custom links to the search results returned via /wp/v2/search.
So in the examples below, I'm checking if the route is /wp/v2/search and if so, then we add the (custom) post type's archive link. Also note that, you should provide an array with the items mentioned here (the LinkControl component used by the Link format).
Basic Example
Includes only the (i.e. one) post type whereby its name matched exactly the search keyword.
add_filter( 'rest_post_dispatch', 'so_62472641', 10, 3 );
function so_62472641( $response, $server, $request ) {
// Don't modify the data if the REST API route is not /wp/v2/search
if ( 'post' !== $request->get_param( 'type' ) ||
'/wp/v2/search' !== $request->get_route() ) {
return $response;
}
// Let's see if there's a post type that matched the search keyword.
$search = $request->get_param( 'search' );
if ( ! $post_type = get_post_type_object( $search ) ) {
return $response;
}
// Now add the post type archive URL, if any, to the response data.
if ( $url = get_post_type_archive_link( $search ) ) {
$data = (array) $response->get_data();
$data[] = [
'id' => 'post_type-' . $search,
'type' => 'Post Type Archive',
'title' => $post_type->label,
'url' => $url,
];
$response->set_data( $data );
}
return $response;
}
Extended Example
Includes all post types whereby the name/label matched the search keyword.
add_filter( 'rest_post_dispatch', 'so_62472641', 10, 3 );
function so_62472641( $response, $server, $request ) {
// Don't modify the data if the REST API route is not /wp/v2/search
if ( 'post' !== $request->get_param( 'type' ) ||
'/wp/v2/search' !== $request->get_route() ) {
return $response;
}
$search = $request->get_param( 'search' );
$post_types = get_post_types( [], 'objects' );
$extra_data = [];
// Let's see if there's a post type that matched the search keyword.
foreach ( $post_types as $obj ) {
if ( $search === $obj->name ||
// look for the search keyword in the post type name/slug and labels (plural & singular)
false !== stripos( "{$obj->name} {$obj->label} {$obj->labels->singular_name}", $search )
) {
if ( $url = get_post_type_archive_link( $obj->name ) ) {
$extra_data[] = [
'id' => 'post_type-' . $obj->name,
'type' => 'Post Type Archive',
'title' => $obj->label,
'url' => $url,
];
}
}
}
// Now add the post type archive links, if any, to the response data.
if ( ! empty( $extra_data ) ) {
$response->set_data( array_merge( (array) $response->get_data(), $extra_data ) );
}
return $response;
}
Sample Output (for the second example above)
Note: The above is a screenshot of a real response, but I deliberately (via PHP) changed the domain name to example.com (i.e. the actual domain name is different).
And the examples were both tried & tested working on WordPress 5.5.1 (latest release as of writing). Also, you can exclude the default post post type, if you want to.
Additional Notes
It should be noted that the examples do not take into account the pagination, which means, if there were 10 post types that matched the search keyword, then they would always be included in the response (on page 1, 2, 3, etc.). So you might want to just use the first example because at least it always includes at most 1 post type only. However, with the second example, you can actually limit the $extra_data to, say, 5 items (per page - but it's up to you on how to distribute the items per page).
You can also use a custom search handler class, e.g. one that extends the default class (WP_REST_Post_Search_Handler) and use the wp_rest_search_handlers hook to add your class to the list. Here's a very basic example...
In your-class.php:
class My_WP_REST_Post_Search_Handler extends WP_REST_Post_Search_Handler {
// ... you'll need to actually write the code on your own..
}
return new My_WP_REST_Post_Search_Handler;
In the theme's functions.php file or somewhere in your plugin:
add_filter( 'wp_rest_search_handlers', 'my_wp_rest_search_handlers' );
function my_wp_rest_search_handlers( $search_handlers ) {
$search_handlers[] = include_once '/path/to/the/your-class.php';
return $search_handlers;
}

Change/restructure output of Wordpress REST API to include more data

I want to use Wordpress as headless CMS for my single page React app
(+axios). The website is kind of portfolio, so the photos attached to each post as galleries are the most important for me.
The WP REST API returns all necessary data, but the images links are only available after sending new request to [.../wp-json/wp/v2/media?parent=X]. I would like to optimize it and automatically attach all image urls to regular output, so I could get one complex response.
I believe it is possible to manipulate API response, however I am not so into Wordpress.. Would appreciate for any tips and links how to achieve this. Thanks in advance.
This is to return featured image for posts route
example posts route https://your-wp-site.com/wp-json/wp/v2/posts
function wp_featured_image( $object, $field_name, $request ) {
if( $object['featured_media'] ){
$img = wp_get_attachment_image_src(
$object['featured_media'],
'thegoodartisan-big'//default: 'thumbnail' or create customize sizes -> add_image_size( 'thegoodartisan-thumb', 220, 180, true ); //(cropped)
);
return $img[0];
}
return false;
}
function featured_media_posts_api(){
register_rest_field( array('post'),//name of post type 'post', 'page'
'thegoodartisan_featured_media',//name of field to display
array(
'get_callback' => 'wp_featured_image',
'update_callback' => null,
'schema' => null,
)
);
}
add_action('rest_api_init', 'featured_media_posts_api' );
posts route request with returned featured image

WooCommerce Memberships: Conditional to check a page access

I have a Wordpress Memberships website that is built on WooCommerce with WooCommerce Memberships plugin to restrict certain pages to members only.
Some of those pages are "drip-fed"... ie. Access to those pages opens 3 days after purchase, etc. I have set this up in WooMemberships.
I am trying to simply do a PHP conditional check to see if the current user has access to a certain page.
I have found this code piece in the docs: wc_memberships_is_post_content_restricted()
However, I have been unable to make it work.
Is there a code snippet which will basically do a PHP IF statement on whether the current user has access to a certain page (using page ID)?
eg:
if ( current_user_has_access(page_ID) ) { DO SOMETHING } else { DON'T }
Thanks.
I'm not sure if this helps but here is my take on it. I first go through all of the users active membership and then I check the content $rules to see if the restricted plans are a part of the users membership plans (in_array)
function can_user_access_content($user_id,$post_id){
//check if there's a force public on this content
if(get_post_meta($post_id,'_wc_memberships_force_public',true)=='yes') return true;
$args = array( 'status' => array( 'active' ));
$plans = wc_memberships_get_user_memberships( $user_id, $args );
$user_plans = array();
foreach($plans as $plan){
array_push($user_plans,$plan->plan_id);
}
$rules = wc_memberships()->get_rules_instance()->get_post_content_restriction_rules( $post_id );
foreach($rules as $rule){
if(in_array($rule->get_membership_plan_id(), $user_plans)){
return true;
}
}
return false;
}
Usage would be something like:
if(can_user_access_content(get_current_user_id(),$post->ID)){
//do whatever here
}
I'm dealing with the same issue at StoryMoment.com (we produce audio + eBook story series for kids).
This is how I've dealt with it. I use the following in a page template to show or hide page elements based on access. The wc_memberships_view_delayed_post_content would change based on the type of content.
You can see the other options in the file:
class-wc-memberships-capabilities.php
<?php
$has_access = current_user_can( 'wc_memberships_view_delayed_post_content', $post->ID );
if ($has_access) {
//do something
} else {
//do something else
}
?>
You will have to Replace (in the conditions):
$page_id by your page ID number (for example: is_page(42))
$membership_plan by the slug of the plan ('plan_slug') or related post ID.
The conditions:
wc_memberships_is_post_content_restricted($page_id) => true if $page_id is retracted.
is_page($page_id) => true if is actual $page_id.
wc_memberships_is_user_active_member( $membership_plan ) => true actual user is an active member for this $membership_plan plan. In that case the access to the page is granted by the suscription plan of the user.
You can remove some of the conditions, if not needed, and fine tune for your needs.
if( wc_memberships_is_post_content_restricted() && is_page($page_id) && wc_memberships_is_user_active_member( $membership_plan ) ) {
// do something
} else {
// don't
}
--- Update ---
The only function related to restriction and (or) time access are:
1) wc_memberships_restrict( $content, $membership_plans, $delay, $exclude_trial ) just like shortcode [wcm_restrict] (so not useful)…
2) wc_memberships_get_user_access_time( $user_id, $target, $action, $gmt ): Parameters
$user_id // for a logged 'user ID'
$target : array('post' => id, 'product' => id) // content_type and content_id
$action : 'view' or 'purchase' // Type of access (products only)<br>
$gmt => : true or false // (selection of the time zone)
// Returns user access start timestamp (in site timezone) for content or a product
Reference: WooCommerce Memberships Function Reference

Wordpress custom page template and WP-API

My Wordpress site uses custom template pages like this one:
<php
/* 
Template Name : project_costs
/*
get_header ();
// set default vars or user received
require("my_forms.php");
// pull data from db and calculate
require("project_calculation. php");
// create page
require("content. php");
...
My custom page project_costs.php performing the steps :
Receive and set user entered vars from page forms (POST/GET).
Pull data from database.
Do some calculations and changes.
Creates page for user. 
I want to integrate angular.js with the WP-API plugin. The plugin just pulls raw data from database (step 2) and sends it to front end (step 4). So pages and templates not in use as page didn't reload.
I want to pass data to my php class first (step 3), then pass changed data to WP-API.
Is there any function in WP-API to call my PHP file or function? 
Any advice, samples or links would be highly appreciated.
Thanks.
So I'm working on a huge project which includes several API/Angular replacement parts for #WordPress. One file is a custom endpoint-boilerplate.php. It works like a charm so far but any input would be appreciated.
Just follow the structure and use the my_awesome_function to return do anything you'd like. Then the namespace and route from the hook will be available, using data from my_awesome_func.
<?php
/* ------------------------------------------------------------------------ *
A great example of custom endpoints is the PHP in wp-api-menus plugin
* ------------------------------------------------------------------------ */
// hook into rest_api_init and register a new api route
add_action( 'rest_api_init', function () {
register_rest_route(
'custom-endpoint/v2', // namespace
'/author/(?P<id>\d+)', // route
array( // options
'methods' => 'GET',
'callback' => 'my_awesome_func',
// 'args' => array(
// 'context' => array(
// 'default' => 'view',
// ),
// )
)
);
});
function my_awesome_func( $data ) {
$posts = get_posts( array(
'author' => $data['id'],
) );
if ( empty( $posts ) ) {
return null;
}
return $posts[0]->post_title;
}
So your call will then be a get to http://yourproject.com/wp-json/custom-endpoint/v2/author/1

set post author depending on condition via frontend post submit

I use wordpress cms and I allow users to post from frontend. There are three kinds of users. Myself as admin, a junior ( given a role of an author) and anyone from public as non-logged-in user. Currently if any non-logged-in user posts from frontend, I am assigned as an author with this code. Currently my wp_insert_post array looks something like this :
'post_title' => $final_title,
'post_content' => $about_stuff,
'post_status' => 'draft',
'post_author' => '20',
'post_type' => 'post',
etc....
wherein my author id is '20'. All this works fine. Now, what I would like to achieve is when my junior logs-in and creates post in the frontend, I would like him to be the post author, and not me (as currently set). I understand that the post_author will need to be a variable example $content_creator. Given below is the code I have written till now, but I am confused how to put it together in order to achieve what I need. To be specific I am confused how do I make the variable $content_creator work with rest of the code.
if ( is_user_logged_in() ) {
$current_user = wp_get_current_user();
if ( 19 == $current_user->ID ) {
'post_author' => '19';
} else {
'post_author' => '20',
}
}
The code is pretty self-explanatory and basic which says check if the user if logged in, if yes check the user-id. If user id equals 19 set 'post_author' => '19' (user id of my junior) otherwise set the author as admin. Two things I wanted to ask, do I also need global $post before my code and should I be using another filter wp_update_post instead. Please help.
Final scenario must be, when the admin or anyone else creates a post the post-author must be set as admin (me) but when my junior creates a post he must be set as the post-author. This would have been unnecessary if we were creating posts in the backend but due to some reason we prefer to create posts in frontend.
Turns out it was pretty simple with wp_update_post. Although, now it works fine. Let me know if you find something funny. Here is the code I am using after wp_insert_post and did not make any change in the array.
if ( is_user_logged_in() ) {
$current_user = wp_get_current_user();
if ( 19 == $current_user->ID ) {
$update_creator = array(
'ID' => $pid, //created post ID
'post_author' => '19'
);
wp_update_post( $update_creator );
}
}

Categories