Where to start with extending WP-API - php

I'm trying to use and extend the WP-API for Wordpress.
Now I might just be stupid but I really can't figure out where to start even though I've read the link above over and over. And I'm not talking code-wise but the very basics, where do I put the code? In a plugin? If so, what do I need to include to get it to work? Or is it enough to extend the class?
Sorry but I just find the info on the page to be way too little... Or have I totaly missed an perfectly structured example from top to bottom?
Here it is on GitHub.
Thanks for any help!

I don't know if the documentation was out of date or something but it's pretty simple to extend WP-API. You'll need to write a plugin first.
In the plugin, where you register the hooks like scripts and styles (functions.php, bootstrap.php) you add a new hook to register the routes.
add_filter( 'json_endpoints', array( $this, 'registerRoutes' ) );
public function registerRoutes($routes){
$editorService = $this->container["editorService"];
$routes['/newsletters'] = array(
array( array( $editorService, 'create'), \WP_JSON_Server::CREATABLE | \WP_JSON_Server::ACCEPT_JSON ),
);
$routes['/newsletters/(?P<id>\d+)'] = array(
array( array( $editorService, 'get'), \WP_JSON_Server::READABLE )
);
return $routes;
}
If you read the documentation you'll see that newsletter is the entity. In this example i inject a service and call it in the routes. Very probably you use a different approach and if you've difficulties in this point you'll have to figure out how to structure the plugin, which patterns apply, write or not your own framework, etc.
If thats the case check this skeleton, it's a great approach to MVC https://github.com/iandunn/WordPress-Plugin-Skeleton
If you want to call a function inside the same class you would do:
public function registerRoutes(){
$routes['/newsletters'] = array(
array( array( $this, 'createNewsletter'), \WP_JSON_Server::CREATABLE | \WP_JSON_Server::ACCEPT_JSON )
);
}
public function createNewsletter() {
$wpdb->prepare(); // etc etc
}

Related

How do can I extend woocommerce api endpoints?

I'm trying to extend the customer endpoint in woocommerce api to include some customfields I've created in my functions.php.
But I can't understand how to do it.
I have copied the class-wc-rest-customers-controller.php from the woocommerce plugin (woocommerce/includes/api/) to my woocommerce-folder in my theme as you should do with woocommerce files when you want to edit them.
This is how my class-wc-rest-customers-controller.php looks like:
https://www.pastebucket.com/561405
My plan now was to edit this copy of the file to include my custom_fields I've added in functions.php. But I can't solve this part.
This is the code from my functions.php that I added my custom fields with:
https://www.pastebucket.com/561406
It feels like it is in the function at line 475 in class-wc-rest-customers-controller.php, but I'm not sure.
So I'm wondering where and how should I add my custom fields to this class-wc-rest-customers-controller.php or I'm all wrong about this?
The file overrides apply AFAIK for templates only. In this case you are trying to override a class, which is different.
As you wrote, it's not a good idea to make your changes directly to the file inside WooCommerce directory. In fact, I wouldn't recommend changing the native endpoint at all, except through actions & filters.
One good, re-usable and future-proof way to change the behavior of a WC REST API endpoint would be to create your own endpoint which simply extends the Woocommerce controller class, and overrides the methods which need to be customized. Preferably, try not to override the entire method, but include the call to the parent method in your custom method.
Example solution: (haven't tested this particular one, but made a very similar one recently)
wp-content/plugins/
woocommerce/
your-plugin/
includes/
class-your-plugin.php
your-plugin.php
your-plugin.php
<?php
defined( 'ABSPATH' ) || exit;
add_action('rest_api_init', function(){
require_once __DIR__ . '/includes/class-your-plugin.php';
$controller = new Your_Custom_Class();
$controller->register_routes();
} );
includes/class-your-plugin.php
<?php
defined( 'ABSPATH' ) || exit;
/**
* Class Your_Custom_Class
*/
class Your_Custom_Class extends WC_REST_Customers_Controller {
/**
* Endpoint namespace.
* Use the wc- prefix to make use of WooCommerce authentication for third-parties.
* #see /wp-content/plugins/woocommerce/includes/api/class-wc-rest-authentication.php:63
*
* #var string
*/
protected $namespace = 'wc-your-route/v1';
public function register_routes() {
register_rest_route(
$this->namespace,
'/' . $this->rest_base . '/your-custom-route',
array(
array(
'methods' => WP_REST_Server::EDITABLE,
'callback' => array( $this, 'your_customized_function' ),
'permission_callback' => array( $this, 'get_items_permissions_check' ),
'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
),
'schema' => array( $this, 'get_item_schema' ),
)
);
}
/**
* Your custom function
*/
protected function your_customized_function( $arg1, $arg2) {
/*******************************
// IMPLEMENT CUSTOM CODE HERE
*******************************/
parent::the_name_of_the_original_function( $arg1, $arg2 );
/*******************************
// IMPLEMENT CUSTOM CODE HERE
*******************************/
}
This way you can freely extend the API for your own needs, take advantage of all current and future features of the WC API, and preserve the native API... well.. native.
While this should be a clean and "correct" solution, I would not recommend going for this path without a solid understanding of PHP classes and inheritance, as well as a good IDE to work with.
So it seems I managed to solve it.
I couldn't overwrite the class-wc-rest-customers-controller.php by copying it to my-theme-folder/woccomerce/includes/api/
So instead I just overwrote it and kept a backup of the original. But I now also need to keep an backup of of the class-wc-rest-customers-controller.php file I overwrote with.
This isn't the correct way of doing it, but this was the only way I could sole my problem.
UPDATE: Seems like I cannot update these values through the api. So this was not a solution at all.

Can't do the CakePHP autocomplete tutorial

In CakePHP, I am trying to create a autocomplete function from a textbox, using this tutorial: http://bakery.cakephp.org/articles/matt_1/2011/08/07/yet_another_jquery_autocomplete_helper_2
I keep getting a
Error: AutoCompleteHelper could not be found.
Undefined index: autoCompleteText
I am unsure how to fix this as I have tried relocating the files and still the same error.
I create the below files and copy the code from the tutorial,
create the auto_complete.php in the view/helpers directory,
and create webroot/js/views/helpers/auto_complete.js.
controller - Tests
function auto_complete
class TestsController extends AppController {
public $helpers = array('Html', 'Form', 'Session','Js','AutoComplete');
public function auto_complete() {
$this->loadModel('Tutor');
debug( $this->params);
$terms = $this->Tutor->find('all', array(
'conditions' => array(
'Tutor.first_name LIKE' => $this->params['url']['autoCompleteText'].'%'
),
'fields' => array('Tutor.first_name'),
'limit' => 3,
'recursive'=>-1,
));
$terms = Set::Extract($terms,'{n}.Tutor.first_name');
$this->set('terms', $terms);
$this->layout = '';
}
view
auto_complete.ctp
<?php
echo $this->AutoComplete->input(
'Tutor.first_name',
array(
'autoCompleteUrl'=>$this->Html->url(
array(
'controller'=>'tests',
'action'=>'auto_complete',
)
),
'autoCompleteRequestItem'=>'autoCompleteText',
)
);
if(isset($terms)) {
echo $this->Js->object($terms);
}
echo $this->Form->create('');
echo $this->Form->input('type', array('label' => 'Choose' ));
echo $this->Form->end('send');
?>
I had a good look around previous posts and I really couldnt solve the problem.
Cakephp form input with autocomplete
The tutorial you're looking at is for CakePHP 1.x (it's from August 2011, Cake 2 came out in October '11), but you're running Cake 2.5. The naming standards have changed between 1.x and 2.x, so Cake can't see your helper.
First: Your main problem is that you're naming your helper auto_complete.php when it should be AutoCompleteHelper.php.
Second, you are placing it in the wrong directory. In Cake 1.x, helpers lived under /view/helpers/. In 2.x, they live under /View/Helper/.
Third, regarding the undefined index error, you need to understand how this helper works in the first place, and why you're implementing it incorrectly.
The controller action auto_complete should not have a view. It's just a data source. When you use the helper, it loads the Javascript, which does its magic by polling /auto_complete?autoCompleteText=stringgoeshere.
You might notice that you removed $this->layout = 'ajax'; from the controller action, probably because your toolbars weren't rendering. They weren't supposed to. It's just a datasource, it NEEDS the ajax layout, and there should be no view on that action.
Because you were trying to put a form on your auto_complete action and not on another action, the helper was looking for the query parameter autoCompleteText, not finding it, and displaying an error.
You can solve this by changing the auto_complete action back as close as possible to how it was in the example, deleting auto_complete.ctp, and by trying to use the helper in the correct view, like add.ctp.
Fourth: You shouldn't be using jsHelper anymore. It's been deprecated in 2.5 and is going to disappear in the future. Basically, the tutorial is obsolete.
However, it's really easy to just replace $this->set('terms', $terms); with return json_encode($terms) and write your own piece of JS to make an AJAX call. I suggest incorporating Typeahead.js.
Finally: You should consult the 1.x -> 2.x migration guide, and as well, once you get your helper running you've got some other typos in your code that will break it anyway- "Ttuor.first_name", for example. Good luck!

Implement a search using zend

I have site which is managed using CMS entirely developed on zend. Now I have to Implement a search feature too. I'vent done anything related to search in zend. Some suggestions that I received is to implement a spider. The site will have plenty of links(and it will keep on adding). I'm totally confused and I don't know where to start from. Will zend_search_lucene do the trick?
You probably aren't going to find something completely turnkey for this. If your content is all public, and you are fine with just using a crawler, the easiest thing to implement could be Google Site Search.
http://www.google.com/enterprise/search/products_gss.html
If you need to get different functionality out of the search that this wouldn't offer, you'll likely be stuck doing some code. The Zend Lucene link that Alvar posted is good. One of the ugly things about Zend_Lucene, if I am not mistaken, is that it's relying on the text based lucene indexes without any Java. It's just slower and more cumbersome to manage.
A more robust Lucene based approach is Solr. It's Java based, and runs on it's own service with an API. It scales well, and there's a PHP Pecl out now that will help you communicate with it.
See http://php.net/manual/en/book.solr.php
Another option is Sphinx. This search engine bolts directly to your database, so indexing might be a little more intuitive.
http://sphinxsearch.com/
Good luck to you!
Lucene is strange, i never got it to work properly and developed my own search logic, but maybe this helps:
http://devzone.zend.com/397/roll-your-own-search-engine-with-zend_search_lucene/
Because you are using a home grown product you'll likely be better served by keeping things as simple as possible, at least in the beginning. Also because you're product is home grown you should have a pretty good handle on the data structure.
Building a simple query based search may be something appropriate for starters.
I started with a simple search form:
<?php
class Application_Form_Search extends Zend_Form
{
public function init() {
$this->setMethod('POST');
$this->setDecorators(array(
array('ViewScript', array(
'viewScript' => '_searchForm.phtml'
))
));
// create new element
$query = $this->createElement('text', 'query');
// element options
$query->setLabel('Search Keywords');
$query->setAttribs(array('placeholder' => 'Title',
'size' => 27,
));
// add the element to the form
$this->addElement($query);
$submit = $this->createElement('submit', 'search');
$submit->setLabel('Search Site');
$submit->setDecorators(array('ViewHelper'));
$this->addElement($submit);
}
}
then I built a simple action helper to display and route the form:
<?php
class Library_Controller_Action_Helper_Search extends Zend_Controller_Action_Helper_Abstract
{
public function direct($action, $label = null, $placeHolder = null)
{
$form = new Application_Form_Search();
$form->setAction($action);
$form->search->setLabel($label);
$form->query->setAttribs(array('placeholder' => $placeHolder,
'size' => 27,
));
return $form;
}
}
then I added a placeholder for the search form in my layout.phtml
<?php echo $this->layout()->search ?>
then in the controllers that need to use the search function I add the helper to predispatch():
public function preDispatch()
{
//assign search action helper to view placeholder
$this->_helper->layout()->search = $this->_helper->search(
'url_for_action', 'Submit button label', 'placeholder text'
);
}
then I use a simple mapper method to perform the appropriate query and I usually return a paginator adapter:
public function fetchPagedMoviesByTitle($title)
{
$select = $this->getGateway()->select();
$select->where(new Zend_Db_Expr("title LIKE '%$title%'"));
$select->order('title', 'ASC');
//create a new instance of the paginator adapter and return it
$adapter = new Video_Model_Paginator_Video($select);
return $adapter;
}
This is simple way to implement a search function and is adaptable to most types of queries. I find that a switch statment and a couple of simple database queries and almost any information I need is available.
Good Luck.

Changing the Page Labels in Yii?

I would like to change the the labels of pages in Yii.
I used Zii.widegt.CListView to show the list of items. The default structure of yii pagination is [previous] 1 2 4 5 6 7 [next] required structure is < 1....10 11 12 13 14 ....40 >.
I read "How can I customize the labels for the pager in Yii?" which is helpful, but how can I show the firstPageLabel as page number 1 instead of << and lastPageLabel as 40 instead of >>.
If you can't find a way to pass in the total item count (i.e. 40) to the lastPageLabel override, you will need to override the CLinkPager class to have this work automatically. The $lastPageLabel is static in the current implementation and does not provide access to variables like "itemCount". You can see the code:
$buttons[]=$this->createPageButton($this->lastPageLabel,$pageCount-1,self::CSS_LAST_PAGE,$currentPage>=$pageCount-1,false);
It just echos $this->lastPageLabel, which is static text.
If you make a new pager (called, say, MyLinkPager), use it like so:
$this->widget('zii.widgets.CListView', array(
'dataProvider' => $categoryProjects,
'itemView' => '_itemDetailsView',
'ajaxUpdate' => false,
'pager' => array(
'class' => 'MyLinkPager', // here is your pager
'firstPageLabel' => '<<',
'prevPageLabel' => '<',
'nextPageLabel' => '>',
'lastPageLabel' => '>>',
),
));
You will have to create your own class that derives from CLinkPager. Ultimately, what you want to achieve is to change the line that thaddeusmt mentions, inside CLinkPager::createPageButtons:
$buttons[]=$this->createPageButton($this->lastPageLabel /* the rest doesn't matter */);
to do the equivalent of
$buttons[]=$this->createPageButton($pageCount /* the rest doesn't matter */);
Now obviously the direct way of doing this is by overriding createPageButtons, but that's not a trivial method and if you do override it completely, you risk your pager becoming "out of sync" with code on later versions of Yii. So let's look for alternatives.
Alternatives
(you might want to skip this part if you 're only interested in the solution)
One alternative would be to override the method, have it call the standard implementation and then simply change what you need to change:
protected function createPageButtons() {
$buttons = parent::createPageButtons(); // Yii's implementation
array_pop($buttons); // remove last item, which is the link for the last page
$buttons[]=$this->createPageButton($this->getPageCount() /* the rest unchanged */);
return $buttons;
}
That's better, but it still involves copy/pasting code so your implementation needs to keep that part in sync with future Yii releases. Can we do better than that? It turns out that yes. Here's the method CLinkPager::run:
public function run()
{
$this->registerClientScript();
$buttons=$this->createPageButtons();
if(empty($buttons))
return;
echo $this->header;
echo CHtml::tag('ul',$this->htmlOptions,implode("\n",$buttons));
echo $this->footer;
}
As you see, CLinkPager doesn't really do a lot other than call createPageButtons. So you could override run and dynamically set the value of $this->lastPageLabel before letting Yii's code run, like this:
public function run()
{
$this->lastPageLabel = $this->getPageCount();
parent::run();
}
Well, this is nice. We managed to achieve the goal by overriding just one method and writing two lines of code. As an added bonus, there's nothing in our code that needs to be kept in sync with Yii if the implementation of CLinkPager changes in the future.
On the other hand, all of these solutions introduce an impurity that could be problematic: when someone writes a view that uses our custom pager class, they might not know that we are actually overriding the value of lastPageLabel! Imagine the "why is it not outputting the label I 'm telling it to?" confusion.
A really nice solution
Fortunately, you can have your pie and eat it too by overriding CLinkPager::init like this:
public function init()
{
// "Hijack" the default values for properties that the user did not set.
// This allows the user to still override this if they want to.
if($this->nextPageLabel===null)
$this->nextPageLabel='<';
if($this->prevPageLabel===null)
$this->prevPageLabel='>';
if($this->firstPageLabel===null)
$this->firstPageLabel='1';
if($this->lastPageLabel===null)
$this->lastPageLabel=$this->getPageCount();
// and let Yii do the rest like it always does
parent::init();
}
You can then configure your view to use this pager, and everything will work just fine without any further ado:
'pager' => array('class' => 'CustomLinkPager'),

Can you Create your Own Hook in Drupal?

Is it possible to create your own hook in a Drupal module for other Drupal modules to consume? If not, is there a mechanism in Drupal for third party developers to provide hooks? If everything's been a no so far, where in the core are the list of hooks implemented?
As I understand things, Drupal modules work on a event like system called hooks. When you create a new module, you create functions that implement a hook. For example, there's a hook_delete hook. If you implement a function in your module
function mymodule_delete($node)
{
}
this function will be called whenever a node is deleted.
What I want to know is, is there a way or me, as a third party module developer, to create my own hooks. Say, something like hook_alanskickbutthook so that other module developers could subscribe to this hook.
If this is possible, how do you do it? I've looked around the official docs and haven't found much there, and I still get a little dizzy when I start poking around the Drupal source code (I understand recursion, but don't spend enough time thinking about recursive problems). Full solutions are welcome, but I'm happy to just be pointed in the right direction.
Module_invoke_all() is your ticket to creating your own hooks:
see the API:
http://api.drupal.org/api/drupal/includes--module.inc/function/module_invoke_all
and then look at this great writeup:
http://web.archive.org/web/20101227170201/http://himerus.com/blog/himerus/creating-hooks-your-drupal-modules
(edit: was at http://himerus.com/blog/himerus/creating-hooks-your-drupal-modules but this is now gone)
Once you've made your hook, it can be called in another module using:
/**
* Implementation of hook_myhookname()
*/
function THISMODULENAME_myhookname(args){
//do stuff
}
For example, say you wanted to create hook_my_custom_goodness() for others to use. Then just place code like this in your module at the point where you want the hook to happen:
$variables['msg'] = 'foo';
// Make sure at least one module implements our hook.
if (sizeof(module_implements('my_custom_goodness')) > 0) {
// Call modules that implement the hook, and let them change $variables.
$variables = module_invoke_all('my_custom_goodness', $variables);
}
drupal_set_message($variables['msg']); // Will display 'bar' instead.
Now, if anybody wanted to use your hook, then they could do so in their own module like this:
/**
* Implements hook_my_custom_goodness().
*/
function SOME_OTHER_MODULE_my_custom_goodness($variables) {
$variables['msg'] = 'bar';
return $variables;
}
There is a more complete explanation here:
http://tylerfrankenstein.com/code/drupal-create-custom-hook-for-other-modules
You need to implement two hooks 1. hook_token_info() & 2. hook_tokens() in your module file Below i have given code to create my custom token "query-param-all" and used that token in views- Textarea field.....
/**
* Implements hook_token_info().
*/
function mycustom_token_info() {
$type = [
'name' => ('Custom Token'),
'description' => ('Tokens for custom things.'),
];
$node['query-param-all'] = [
'name' => ("Get all URL query string"),
'description' => ('Get all URL query string'),
];
return [
'types' => ['customtoken' => $type],
'tokens' => ['customtoken' => $node],
];
}
/**
* Implements hook_tokens().
*/
function mycustom_tokens($type, $tokens, array $data, array $options, \Drupal\Core\Render\BubbleableMetadata $bubbleable_metadata) {
$replacements = [];
//print '<pre>'; print_r($data);exit;
$current_path = \Drupal::request()->query->all();
$query_param = '';
if( count($current_path) > 0) {
$amper = '';
$query_param = '?';
foreach($current_path as $key => $value){
$query_param .= $amper.$key.'='.$value;
$amper = '&';
}
}
if ($type == 'customtoken') {
foreach ($tokens as $name => $original) {
switch ($name) {
case 'query-param-all':
$replacements[$original] = $query_param;
break;
}
}
}
return $replacements;
}
If i recall... http://api.drupal.org/api/drupal/modules--node--node.api.php/function/hook_delete/7
does ths help? been a while since I messed with Drupal.
To create/offer custom Drupal hook, you must implement in a ways such that calling the hook with module_invoke or module_invoke_all does not make any conflicts with other module hooks. The name of the hook should be unique and it should offer all/specific feature in such a general way that it doesn't require any type of adjustments with code. All the configuration must go on admin pages and should store those configurations in a separate table or any existing tables create by Drupal or modules on which your modules depends. The hook should be easy to implment by other modules and it should not be much complex to implement. When you create custom hooks, your module(s) act(s) as API provider.
For Drupal 6 & 7, drupal_alter() is probably the best option.
As stated in the module_invoke_all() documentation,
All arguments are passed by value. Use drupal_alter() if you need to
pass arguments by reference.
In Drupal 8, use ModuleHandler::alter.
Passes alterable variables to specific hook_TYPE_alter()
implementations.

Categories