Render .twig file when shortcode called in WordPress? - php

I've been firefighting this for a while and I'm struggling with getting this shortcode to render this Twig partial when it is called. Anyone have any advice?
Thanks!
Here's what I have so far:
<?php namespace loc\wp\lib;
use \Timber as Timber;
use \Twig_SimpleFunction as Twig_SimpleFunction;
class LOCShortcodes {
public function addSlider() {
add_shortcode('feature-slider', 'feature_slider');
function feature_slider() {
return Timber::render('template-feature-slider.twig');
}
}
}

When you work with hooks and filters (or shortcodes in your case) in a class context, you need to define the callback a little differently.
The last example in https://codex.wordpress.org/Function_Reference/add_shortcode shows you how you use shortcodes with classes:
<?php
class MyPlugin {
public static function baztag_func( $atts, $content = "" ) {
return "content = $content";
}
}
add_shortcode( 'baztag', array( 'MyPlugin', 'baztag_func' ) );
As you can see, the shortcode is added outside of the class. If you want to add it inside your class, then you don’t have to explicitly use the name of the class, but you can use $this:
<?php
class MyPlugin {
public function __construct() {
add_shortcode( 'baztag', array( $this, 'baztag_func' ) );
}
public static function baztag_func( $atts, $content = "" ) {
return "content = $content";
}
}
In your case, you could do it like this:
<?php
class LOCShortcodes {
public function __construct() {
add_shortcode( 'feature-slider', array( $this, 'feature_slider' ) );
}
public function feature_slider() {
return Timber::compile( 'template-feature-slider.twig' );
}
}
Don’t forget to use Timber::compile() instead of Timber::render(), because the render() function is echoing the output, while for shortcodes, the output should be returned. This is also mentioned in the Notes section in the Codex:
Note that the function called by the shortcode should never produce output of any kind. Shortcode functions should return the text that is to be used to replace the shortcode. Producing the output directly will lead to unexpected results. […]
Also make sure to read the wiki section about Shortcodes in Timber.

Related

PHPUnit mocking WordPress function calls

I am building a simple WordPress plugin and I am looking to add unit tests using PHPUnit, I have the following class code:
<?php
namespace App;
class MyPlugin {
public function __construct()
{
add_action('admin_enqueue_scripts', [$this, 'enqueueAdminScripts']);
add_action('admin_menu', [$this, 'createAdminMenu']);
add_action('admin_init', [$this, 'settings']);
}
public function enqueueAdminScripts()
{
// Code here
}
public function createAdminMenu()
{
// Code here
}
public function settings()
{
// Code here
}
}
And this is my test file:
<?php
use App\MyPlugin;
use PHPUnit\Framework\TestCase;
class MyPlugin extends TestCase {
protected $myPlugin;
protected function setUp(): void
{
$this->myPlugin = new MyPlugin();
}
public function testRegister()
{
$this->assertInstanceOf('\App\MyPlugin', $this->myPlugin);
}
}
However I am always getting the error Error: Call to undefined function App\add_action()
What is the best way of mocking the add_action calls purely within PHPUnit without having to rely on a third party solution?
Any help with this would be greatly appreciated.
Below is a very simple mock of the hook system. It doesn't take into account priorities, and it is more modernized for expected usage so it might not support some previous edge cases.
You'll notice that add_action actually calls the exact same code as add_filter, and that's what core actually does.
The gist is that there's a global array. You could store this in your base class if you wanted, too. WordPress backs their stuff with objects, too, but I'm simplifying things here.
global $wp_test_hooks;
if(!is_array($wp_test_hooks)) {
$wp_test_hooks = [];
}
if(!function_exists('add_filter')) {
function add_filter( $hook_name, $callback, $priority = 10, $accepted_args = 1 ) {
global $wp_test_hooks;
$wp_test_hooks[$hook_name][] = [
$callback,
$priority,
$accepted_args,
];
return true;
}
}
if(!function_exists('add_action')) {
function add_action( $hook_name, $callback, $priority = 10, $accepted_args = 1 ) {
return add_filter( $hook_name, $callback, $priority, $accepted_args );
}
}
if(!function_exists('apply_filters')){
function apply_filters( $hook_name, ...$values ) {
$value = array_shift($values);
global $wp_test_hooks;
if(array_key_exists($hook_name, $wp_test_hooks)){
foreach($wp_test_hooks[$hook_name] as $parts){
list($callback, $priority, $accepted_args) = $parts;
$value = $callback($value, ...$values);
}
}
return $value;
}
}
if(!function_exists('do_action')){
function do_action( $hook_name, ...$arg ) {
global $wp_test_hooks;
if(array_key_exists($hook_name, $wp_test_hooks)){
foreach($wp_test_hooks[$hook_name] as $parts){
list($callback, $priority, $accepted_args) = $parts;
$callback(...$arg);
}
}
}
}
You can see it in action by using normal WordPress code:
add_action('name1', static function(){echo 'In the Action';});
add_filter('name2', static function($originalValue){return 'In the Filter';});
do_action('name1', 'test', 'stuff');
echo PHP_EOL;
echo apply_filters('name2', 'test', 'stuff');
Demo here: https://3v4l.org/DAVl0
All that said, if you don't manually call do_action or apply_filters in your code, don't both with the bulk of this. You can use the functions, just leave their bodies empty. More often than not, it is WordPress or a plugin that is invoking those methods, and if you aren't invoking WordPress or calling those functions, there's no need for logic or even the global array. What you probably want is:
if(!function_exists('add_filter')) {
function add_filter( $hook_name, $callback, $priority = 10, $accepted_args = 1 ) { }
}
if(!function_exists('add_action')) {
function add_action( $hook_name, $callback, $priority = 10, $accepted_args = 1 ) { }
}
if(!function_exists('apply_filters')){
function apply_filters( $hook_name, ...$values ) { }
}
if(!function_exists('do_action')){
function do_action( $hook_name, ...$arg ) { }
}
I'd say this depends a lot what you'd actually want to test (and therefore call).
Reading your question you would like to put Wordpress itself out of the equation to essentially test your code.
That is testing a plugin not as an integration test.
Now Wordpress does not make it exactly easy to test but you could try to get as far as possible. I'd say the issue you describe in the question lays in the constructor:
public function __construct()
{
add_action('admin_enqueue_scripts', [$this, 'enqueueAdminScripts']);
add_action('admin_menu', [$this, 'createAdminMenu']);
add_action('admin_init', [$this, 'settings']);
}
This constructor only works within Wordpress. But now you want to test it without. Well, easy:
public function __construct()
{
}
Problem sovled. Now you'd say, this is overly clever, right? The plugin needs to be initialized. True:
public static function setup(): MyPlugin
{
$plugin = new self();
add_action('admin_enqueue_scripts', [$plugin, 'enqueueAdminScripts']);
add_action('admin_menu', [$plugin, 'createAdminMenu']);
add_action('admin_init', [$plugin, 'settings']);
return $plugin;
}
With this, you still have one central way to setup your plugin while you're able in your unit-tests to test calling the concrete functions. The new operator (in your test) and the way you've written the constructor does not stand in your way any longer and the code became testable.
If those as well depend on Wordpress functions, you'll run into the same problem thought. However the functionality you actually may want to test here might not entirely, so extracting the code that does not into methods of its own, allow actual unit-tests of those independent methods (those that have no side-effects on the global scope which Wordpress uses).
And I'd say your nose is good here: At the end of the day you want as many of these pure methods (without side-effects) and less those that depend on global state and Wordpress as a dependency.
You could do other tests (e.g. integrating with Wordpress) with the Wordpress base test case class to not reinvent the wheel. To only do that for certain methods, group your tests in different directories, e.g. test/unit and test/wordpress.
Then you can decide what kind of test you want to write.

Passing argument to a method in callback array in add_action() - WordPress

I am trying to pass an argument (say $arg) to the second parameter/callback of add_action() using do_action() in Wordpress.
do_action( 'my_hook', $arg);
the 'my_hook' in do_action() will refer to the below add_action()
class My_Class {
public function __construct () {
add_action( 'my_hook', array( $this, 'my_method ($arg)' ) );
}
public function my_method ($arg) {
some_function ($arg);
// some code here....
}
}
Note: I am not trying this in fuctions.php. Trying to modify core files. I know modifying core files is not recommended. But, I want to explore the horizons (I will return back to the home though)
Please provide a way to pass the argument into my_method(). Thanks in advance.
Hm I think this should work if you take out the ($arg) from the add_action call.
class My_Class {
public function __construct () {
add_action( 'my_hook', array( $this, 'my_method' ) );
}
public function my_method ($arg) {
some_function ($arg);
// some code here....
}
}
// make sure you are creating a new object so __construct() is called
$instance = new My_Class();
do_action( 'my_hook', $arg );
If you look at the documentation, add_action lets you specify the number of arguments an action accepts. It defaults to 1.
https://developer.wordpress.org/reference/functions/add_action/#more-information

PHP function inside another function [ class ]

I have the following code, perform a global function within a class to fill the functions of wordpress, the problem is that the only way that I could get a variable public class is as follows
class Core {
public $notice;
function __construct(){
$this->core_function();
}
function core_function(){
global $globalvar;
$globalvar = $this;
function notice_global(){
global $globalvar;
return $globalvar->notice;
}
}
function set_notice(){
$this->notice = array('Warning');
}
}
$GP = new Core();
$GP->set_notice();
var_dump(notice_global());
Any other ideas or suggestions, this code is correct or not?
As you said in the comments, you need global function due to wordpress hook method (for a plugin, I suppose).
This is not necessary: there is a way to pass an object method (not a whole object) to wordpress.
You can try in this way:
class Core {
public $notice;
function get_notice()
{ return $this->notice; }
function set_notice()
{ $this->notice = array('Warning'); }
}
$GP = new Core();
$GP->set_notice();
add_action( 'save_post', array( $GP, 'get_notice' ) );
Or - for a better flexibility - in this way:
class Core {
public $notice;
function get_notice()
{ return $this->notice; }
function set_notice()
{ $this->notice = array('Warning'); }
function add_wp_action( $hook, $method )
{ add_action( $hook, array( $this, $method ) ); }
}
$GP = new Core();
$GP->set_notice();
$GP->add_wp_action( 'save_post', 'get_notice' );
By this way, you can directly set all your wp hooks in the class and call they directly with an object method, without using globals variables or function tricks.
I'm not sure if I'm understanding you right, but notice_global can be moved out of that class.
Globals have scope outside of classes
There is no need for these functions. You've defined $notice as public property. You can access it like this: $GP->notice;
You might also want to read documentation on visibility of methods and properties.

Wordpress plugin: Call to undefined function add_menu_page()

I need some help, I've been through so many of these "similar" issues on stackoverflow and all over the web but nothing seems to be helping.
I'm new to wordpress and started creating a plugin using classes. when I run the plugin it throws the error "Call to undefined function add_menu_page()". I've checked the support docs on wordpress.org but that doesn't help either (https://codex.wordpress.org/Adding_Administration_Menus#Inserting_the_Pages)
heres my code below please can you tell me what I'm doing wrong:
class SetupAdminMenu {
public static function add_menu($menu)
{
add_action('admin_menu', self::load_menu($menu) );
//I've used it according to the docs as well
//add_action('admin_menu', array(self, self::load_menu($menu) );
}
public static function load_menu($menu)
{
$page = add_menu_page( $menu['page_title'], $menu['menu_title'], $menu['capability'], $menu['menu_slug'],
array(self,self::load_view($menu['function'])), $menu['icon_url'], $menu['position'] );
self::load_page($page);
}
protected static function load_view($view)
{
if ( !current_user_can( 'activate_plugins' ) ) {
wp_die( __( 'You do not have sufficient permissions to access this page.' ) );
}
//The actual page to display
include_once($view);
}
protected static function load_page($page)
{
add_action( 'admin_print_styles-' . $page, 'admin_styles' );
}
}
Thanks peeps
Couple of things going on here. First to just clarify, add_menu_page is to add an administration menu page for managing settings, etc.
Your add action should be as follows:
public static function add_menu($menu)
{
add_action('admin_menu', array('SetupAdminMenu', 'load_menu'));
}
The way WordPress handles actions requires you to format your add_action as follows for static class methods:
add_action('admin_menu', array('ClassName', 'methodname'), $priority, $numVars);
There are two optional variables:
$priority = integer, If there are multiple actions to a hook, the priority will determine the order of load. Default is 10. Use 99 if you want to react to any earlier actions, use 1 if you want to define things before further actions
$numVars = interger, if the method or function requires more then one variable, put the number here. WordPress core hooks don't require this to be defined and will pass variables according to the documentation.
Next, the class method with "add_menu_page" is pretty off. To my knowledge admin_menu hook does not return any variables by default and even if it did, why do you want the existing menu when trying to add new items? You pass those variables yourself:
public static function load_menu()
{
$page_title = 'My New Page';
$menu_title = 'New Page';
$capability= 'manage_options'; //An administrator only capability
$menu_slug = 'my-new-page';
$callback = array('SetupAdminMenu', 'load_view');
$menu_icon = ''; //use default
$menu_position = 81 //After settings
add_menu_page( $page_title, $menu_title, $capability, $menu_slug,
$callback, $menu_icon , $menu_position);
}
Your self::load_page($page); is redundant. The action you use in that function does nothing. The callback is what is displayed when the menu item is clicked. No reason to make that protected, it would likely fail anyway because of the way class methods are called from hooks within WordPress.
public static function load_view() // Removed $view because you need to define this yourself.
{
if ( !current_user_can( 'activate_plugins' ) ) {
wp_die( __( 'You do not have sufficient permissions to access this page.' ) );
}
//The actual page to display
//include_once('/path/to/your/template');
//which should have php, html for what you want to have appear.
//To test that the rest of the code is working... just put:
echo 'Hello World!';
}
This link has a list of a bunch of "how tos" on WP Plugin development.
alright, so I've done some more reading and playing and below is my answer, might help some.
class SetupAdminMenu {
private static $menu = array();
public static function add_menu()
{
add_action('admin_menu', array('SetupAdminMenu', 'load_menu') );
}
public static function add_sub_menu()
{
add_action('admin_menu', array('SetupAdminMenu', 'load_sub_menu') );
}
the method below is what I was missing initially, because you cannot pass an argument from the call back in add_action()
public static function setupPage($params)
{
self::$menu = $params;
}
public static function load_menu()
{
$page = add_menu_page( self::$menu['page_title'], self::$menu['menu_title'], self::$menu['capability'],
self::$menu['menu_slug'], array('SetupAdminMenu', 'load_view'),
self::$menu['icon_url'], self::$menu['position'] );
self::load_page($page);
}
protected static function load_sub_menu()
{
$page = add_submenu_page( self::$menu['parent_slug'], self::$menu['page_title'], self::$menu['menu_title'],
self::$menu['capability'], self::$menu['menu_slug'], 'load_view');
self::load_page($page);
}
public static function load_view()
{
if ( !current_user_can( 'activate_plugins' ) ) {
wp_die( __( 'You do not have sufficient permissions to access this page.' ) );
}
//The actual page to display
include_once(self::$menu['page']);
}
protected static function load_page($page)
{
add_action( 'admin_print_styles-' . $page, array('SetupAdminMenu', 'admin_styles') );
}
public static function admin_styles()
{
wp_register_style( 'admin_styles', plugins_url('css/admin.css', __FILE__) );
wp_enqueue_style( 'admin_styles' );
}
}
because add_action() is used so many times I've removed add_menu() and add_sub_menu() from this class and added the method below to may base class to be called with the necessary parameters for any actions
public static function addAction($action, $callback)
{
add_action($action, $callback);
}
might not be 100% the best way to do it yet but the above currently works.

Object function as callback for add_shortcode( ) in WordPress

I created a class to build custom post types and I'm trying to include a way to insert specific shortcodes for that post type, but I can't seem to get the callback function for the add_shortcode() to get a function from inside the created object.
I have something similar to this:
class custom_post_type {
public $post_type_name;
function __construct($id,$options) {
//builds the custom post type, etc
$this->post_type_name = $id;
$shortcode = new build_shortcode($id,$options);
}
public function shortcode_handler($atts) {
return $this->post_type_name;
}
}
class build_shortcode {
function __construct($id,$options) {
add_shortcode( $options['shortcode_name'] , array( $id ,'shortcode_handler') );
}
}
$options = array(); // a bunch of variables would be defined here to use in the classes
$post_type = new custom_post_type('post_type',$options);
But when I use the [show-cpt-name] shortcode, it doesn't run the function.
I know in the Codex they give and example to use a class, like:
add_shortcode( $options['shortcode_name'] , array( 'custom_post_type' ,'shortcode_handler'));
But this is not what I am looking for, since I wanted the callback function to use values from that particular object created.
Is this possible to achieve?

Categories