Extending the woocommerce rest api - php

I would like to extend the woocommerce rest api to include data of its 'booking' extension plugin. Currently this extension does not have default endpoints provided by the rest api.
So far I have created a plugin, and I've added the following code;
add_filter( 'woocommerce_rest_prepare_product', 'custom_data');
function custom_data($response, $object) {
if( empty( $response->data ) )
return $response;
$response->data['meta_data'] = get_post_meta( $object[ID], 'availability', true);
return $response;
}
When I call the end point /products only the default data outlined by woocommerce is still called my little add on is no where to be found.
I don't even know where to find the above filter as I just saw this posted on a webpage and I tried to get it to do what I wanted, don't know if this is the correct direction to go down either. Webpage: https://francescocarlucci.com/woocommerce/woocommerce-api-custom-data-default-endpoints/#more-96
The above was me trying to extend the api but I also decided to try making a custom endpoint to see if I can get my desired outcome but so far I've just made a endpoint which calls but I have no idea what to write to retrieve the data I want.
custom end point code:
function register_custom_route() {
register_rest_route( 'ce/v1', '/bookable',
array(
'methods' => 'GET',
'callback' => 'get_bookable'
)
);
}
function get_bookable( ) {
return array( 'custom' => 'woocommerce here' );
//What code do I write here :(
}
Is there anyway I can achieve what I want under one of the above methods?
I'm quite new to dev and I'm familiar with javascript not PHP hence my need to want to use the rest api as I would like to use wordpress/woocommerce as a headless cms.
So far the closets example I've come to has been shown on this question Creating WooCommerce Custom API

Alternatively you can try the below code to extend product response of WooCommerce REST API without doing additionally, as your above hook "woocommerce_rest_prepare_product" is for v1 but currently it was v3 so the hook for the latest is below one(the below hook is from v2 controller of rest api which is extended by v3).
add_filter('woocommerce_rest_prepare_product_object', 'so54387226_custom_data', 10, 3);
function so54387226_custom_data($response, $object, $request) {
if (empty($response->data))
return $response;
$id = $object->get_id(); //it will fetch product id
$response->data['booking_meta_data'] = get_post_meta($id, 'availability', true);
return $response;
}
I tested the above code it works perfectly fine. Hope it may helps to someone who search for similar solution.

this is only part of my code. some variable not defined. and this just concept. hopefully, you can modify as per your requirement.
public function __construct() {
$this->template_url = apply_filters( 'woocommerce_template_url', 'woocommerce/'
);
$this->api_namespace = 'wc/v';
$this->base = 'home';
$this->api_version = '2';
add_action( 'woocommerce_loaded', array( $this, 'register_hooks' ) );
}
$namespace = $this->api_namespace . $this->api_version;
register_rest_route(
$namespace, '/wclogin/',
array(
'methods' => 'GET',
'callback' => array( $this, 'wc_login'),
)
);
function wc_login($request){
$user = get_user_by('email', $request["email"]);
//bad email
if(!$user){
$error = new WP_Error();
$error->add('invalid', __('<strong>ERROR</strong>: Either the email or password you entered is invalid.'));
return $error;
}
else{ //check password
if(!wp_check_password($request["password"], $user->user_pass, $user->ID)){ //bad password
$error = new WP_Error();
$error->add('invalid', __('<strong>ERROR</strong>: Either the email or password you entered is invalid.'));
return $error;
}else{
return $user; //passed
}
}
}

Just an update for those that come to this question. Now, there is a rest api for the booking extension: https://docs.woocommerce.com/document/bookings-rest-api-reference/

Related

How do I use WP_REST_Server::CREATABLE to insert into the MySQL db?

I'm having trouble using the WP_REST_Server::CREATABLE, for the WP REST API POST.
I'm trying to insert into the database via POST, but it doesn't work. I was able to get it working via GET but not POST:
<?php
// Register REST API endpoints
class GenerateWP_Custom_REST_API_Endpoints {
/**
* Register the routes for the objects of the controller.
*/
public static function register_endpoints() {
register_rest_route( 'ibl/api/interview', '/greeting', array(
'methods' => WP_REST_Server::CREATABLE,
'callback' => array( 'GenerateWP_Custom_REST_API_Endpoints', 'create_greeting' ),
) );
}
/**
* Add a new greeting
*
* #param WP_REST_Request $request Full data about the request.
* #return List
*/
public static function create_greeting( $request ) {
global $wpdb;
$item = $request->get_json_params();
$fields = array();
$values = array();
foreach($item as $key => $val) {
array_push($fields, preg_replace("/[^A-Za-z0-9]/", '', $key));
array_push($values, $wpdb->prepare('%s', $val));
}
$fields = implode(", ", $fields);
$values = $_GET["greeting"];
$query = "INSERT INTO wp_api (GREETING) VALUES ('$values')";
$list = $wpdb->get_results($query);
return $list;
}
}
add_action( 'rest_api_init', array( 'GenerateWP_Custom_REST_API_Endpoints', 'register_endpoints' ) );
?greeting=ititit
Thank you
Maybe Im i litle late but I think your problem would be fixed adding a nonce.
Wordpress has a security system in REST APIs based in nonces. Maybe is clear for you that you could use it in plugins forms and settings by using the function wp_create_nonce() where the first argument is the name of the nonce.
You could Enqueue a JS script and then declare a 'global variable' by using wp_localize_script()
Then you could declare a nonce as following:
wp_enqueue_script('mm_main_js',plugin_dir_url( __FILE__).'js/main.js',array('jquery'), '1.0', true);
wp_localize_script( 'mm_main_js', 'ajax_requests', array(
'site_url'=>site_url(),
'my_new_nonce'=>wp_create_nonce( 'any_nonce_name' )
));
Ok, the thing is that when you are using the native Worpress REST API and you create a custom route or even you are using the API for creating posts, deleting them, etc., you MUST use the nonce name 'wp_rest'; it is mandatory.
So you must change the previous snipet by:
'my_new_nonce'=>wp_create_nonce( 'wp_rest' )
So let's supose you have the following JS script for posting some information in the custom route.
async deleteRequest(dataId, deleteUrl){
const deleteRequest = await fetch(deleteUrl, {
method: "POST",
credentials: "same-origin",
headers: {
"Content-Type": "application/json",
"X-WP-Nonce": ajax_requests.my_new_nonce, //It is important to send the nonce in this format and on the headers request section
},
body:JSON.stringify({
//Your data
})
})
return deleteRequest;
}
Remember that ajax_requests is the name of the array created by wp_localize_script and, as any other object or array in JS you access to any value by coding ajax_scripts.variable, in this case, the nonce created.
Hope it works for you.

Use register_rest_field inside a custom route

I'm currently trying to create an API which will be used to create an app
To do this I need a custom endpoint which should be accessed only from a logged user, which is currently done using this code:
register_rest_route( 'wp/v2', 'private/me',array(
'methods' => WP_REST_Server::READABLE,
'callback' => 'get_private'
));
function get_private($request) {
$user = (array) wp_get_current_user();
$user["data"] = (array) $user["data"];
unset($user['data']['user_pass']);
// $user = get_user_by('id', 13);
if (empty($user)) {
return new WP_Error( 'empty_category', 'there is no post in this category', array('status' => 404) );
}
$response = new WP_REST_Response($user['data']);
$response->set_status(200);
return $response;
}
Currently it return an user object without 'user_pass'
Then when I use the 'register_rest_field' function, I get nothing (the function work if set on any default api user endpoint
register_rest_field('private/me',
'rank_number',
array(
'get_callback' => 'get_rank_number',
'update_callback' => null,
'schema' => null
)
);
function get_rank_number ( $user ) {
return (int) get_user_meta($user['id'], 'ck_user_ranking_score_number', true);
}
The thing is, I don't know how to make the route read the list of the registered rest fields nor if it's supposed to be done like that
Currently, I'm trying to make it by extending the WP_REST_Controller class since it looks like it could work
Can someone help me understand how the API work or how it is supposed to be used ? :/
In case anyone have the same problem, a wordpress user answered my post on their forum,
Solution:
The var $wp_rest_additional_fields, I can't find anything about it in the documentation, but at least the var really exist in the global scope
So with this code everything's ok
global $wp_rest_additional_fields;
foreach($wp_rest_additional_fields['private/me'] as $key => $value){
$user_data[$key] = call_user_func($value['get_callback'], $user['data']);
}

zendframework routing based on the GET parameter

i have a page in my website (zendframework 1) that parses the GET parameter and query its data from the database to show it to the user.
-> my current url : https://example.com/index.php/product/info?id=123
i want my url to be more human readable
-> https://example.com/index.php/product/iphone-7s-white
so basicaly i want to parse the GET parameter in the url and query the name of the product from the database in order to make it appear as the page name in the url.
i came across some solutions, one of them is achieved by looping through the database (in bootstrap.php) and adding a route for each product, but this seems like a mess, (products can reach 200k or maybe more than that).
is there a better solution for my problem ? thanks in advance
So basically, ZF1 provides a default route that leads to the controller/action of the names from the url.
You can add custom routes from the application/Bootstrap.php file by adding a function there:
/**
* This method loads URL routes defined in /application/configs/routes.ini
* #return Zend_Router
*/
protected function _initRouter() {
$this->bootstrap('frontController');
$front = $this->getResource('frontController');
$router = $front->getRouter();
$router->addRoute(
'user',
new Zend_Controller_Router_Route('product/:slug', array(
'controller' => 'product',
'action' => 'details',
), array(
'slug' => '[A-Za-z0-9-]+',
))
);
return $router;
}
And here you go!
As described by Chris, you need to change your controller code to handle the request. Another solution would be to use an extra action.
final class ProductController
{
public function infoAction()
{
$product = $table->find($this->_param('id'));
$this->view->product = $product;
}
public function detailsAction()
{
$product = $table->fetch(['slug' => $this->_param('slug')]);
$this->view->product = $product;
$this->render('info');
}
}
Now, assuming you do a lot of processing in infoAction, you could go with a forward:
final class ProductController
{
public function infoAction()
{
$product = $table->find($this->_param('id'));
$this->view->product = $product;
}
public function detailsAction()
{
$product = $table->fetch(['slug' => $this->_param('slug')]);
$this->forward('info', 'product', [
'id' => $product->id,
]);
}
}
It is less efficient (2 requests instead of one), but allows you to reuse your code.

Wordpress: Saving Form Data and POST external

I'm creating a Booking form in Wordpress which will send the data to an external CRM (Airship in this case) whilst also storing the data inside the Wordpress CMS that can then be emailed automatically.
Currently I've tried Contact Form 7 and a few other plugins, but this requires its own action="/?page_id=1327&preview=true#wpcf7-f1326-p1327-o1" (just as a page preview whilst building).
Airship CRM also has it's own action="http://atwbar.com/linkitajax.php" required to submit data.
Any suggestions/advice would be HUGELY appreciated!
when I want to save some special data, process a form to a crm or just redirect all my forms, I use the Contact form 7 hook : wpcf7_before_send_mail
Here is an example to redirect any form to a page (extract from an utility plugin I've done, so, don't take care about unset and session lines, options...).
add_action('wpcf7_before_send_mail', 'mail_send_redirection');
function mail_send_redirection($contactform){
$submission = WPCF7_Submission::get_instance();
if($options['_redirect_all_forms'] == 'false' && $contact_form->prop( 'redirection_settings' ) == 'false'){
return;
}
$redirection_form_id = $contact_form->prop( 'redirection_settings' );
$redirection_page_id = (empty($redirection_form_id)) ? $options['_thank_you_url'] : $redirection_form_id;
$nonce = wp_create_nonce('redirect-user-action');
if($contact_form->prop( 'redirection_message' ) != ''){
$args = array(
'html' => false,
'exclude_blank' => false );
$message = wpcf7_mail_replace_tags( $contact_form->prop( 'redirection_message' ), $args );
unset($_SESSION['bcf7u_nonce']);
unset($_SESSION['bcf7u_message']);
unset($_SESSION['bcf7u_pageid']);
$_SESSION['_nonce'] = $nonce;
$_SESSION['_pageid'] = $redirection_page_id;
$_SESSION['_message'] = $message;
}
$contact_form->skip_mail = false;
$contact_form->set_properties(
array(
'additional_settings' => "on_sent_ok: \"location.replace('" . get_permalink($redirection_page_id) . "/?nonce=" . $nonce . "');\""));
}
To save a form field, just use update_post_meta.
There are other method to send a request base on wp_ajax_no_priv_{$action} action and js.
Tell me if it's helps you, or if you need some more hints !

Perform validation only on create using php-activerecord

I am creating a User Model using Codeigniter and php-activerecord and the wiki says I can use 'on' => 'create' to have a validation only run when a new record is created, like this,
static $validates_presence_of = array(
array('title', 'message' => 'cannot be blank on a book!', 'on' => 'create')
);
It also states that we have access to "save", "update" and "delete"...
None of these are working for me though and I can figure out why, here is my code,
// Validations
static $validates_presence_of = array(
array('email', 'message' => 'Please enter a valid email address.'),
array('password', 'message' => 'Password must be provided.', 'on' => 'create')
);
I want to set it up like this so that when a user updates their profile, they can leave their password blank to keep their current one.
I would appreciate any help or guidance! Thanks!
The reason for this is most likely because it's not been implemented.
Relevant classes are lib/Model.php and lib/Validations.php
From a purely abstract standpoint, you would need to track the mode of operation between save and create. To do this, I created a public property (public $validation_mode) within lib/Model.php and set that property to 'create' or 'save' in private methods Model::insert() and Model::update() respectively. These values match the 'on' property you are trying to use.
Then within lib/Validations.php, I modified the following methods:
Validations::validates_presence_of()
public function validates_presence_of($attrs)
{
$configuration = array_merge(self::$DEFAULT_VALIDATION_OPTIONS, array('message' => Errors::$DEFAULT_ERROR_MESSAGES['blank'], 'on' => 'save'));
foreach ($attrs as $attr)
{
$options = array_merge($configuration, $attr);
$this->record->add_on_blank($options[0], $options['message'], $options);
}
}
Errors::add_on_blank()
public function add_on_blank($attribute, $msg, $options = array())
{
if (!$msg)
$msg = self::$DEFAULT_ERROR_MESSAGES['blank'];
if (($value = $this->model->$attribute) === '' || $value === null)
{
if(array_key_exists('on', $options))
{
if($options['on'] == $this->model->validation_mode)
{
$this->add($attribute, $msg);
}
} else {
$this->add($attribute, $msg);
}
}
}
What this does basically is passes ALL the $options specified in your model (including the 'on' property) down to the Errors::add_on_blank() method where it now has enough information to differentiate between 'on' => 'create' and the default ('on' => 'save'). Using the public $validation_mode property from the Model class ($this->model->validation_mode), we can determine what the current mode of operation is and whether or not we wish to continue adding the error message or skip it this time around.
Obviously you would want to document any changes you make and test thoroughly. According to the documentation, all validation methods supposedly support this "common option" along side allow_null, allow_blank but again, if it's not implemented, you will have to make it happen yourself by making these necessary changes.
should be call the validation method like this:
#example
$your_obj = new Instace();
if($your_obj->is_valid()) {
// if all is correct your logical code
}
else {
// your logical to show the error messages
}
//doesnt work if you write
if(!$your_obj->is_valid())
//the other way you must be use the next method
if($your_obj->is_invalid())
I'm find a answer for your question without edit library.
Add the before_validation callback and add in this callback a validation rule. It works for me.
static $before_validation_on_create = array('before_validation_on_create');
static $validates_presence_of = array(
array('login'),
);
public function before_validation_on_create() {
self::$validates_presence_of[] = array('password');
}

Categories