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.
Related
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.
Looking for a flexible way to allow other developers to extend render methods for a templating system, basically allowing them to generate their own render::whatever([ 'params' ]) methods.
Current set-up work well from a single developer point of view, I have a number of classes set-up based on context ( post, media, taxonomy etc ), with a __callStatic method collecting the calling function which checks if the method_exists within the class, and if so, extracts any passed arguments and renders the output.
quick example ( pseudo-code ):
-- view/page.php
render::title('<div>{{ title }}</div>');
-- app/render.php
class render {
public static function __callStatic( $function, $args ) {
// check if method exists
if ( method_exists( __CLASS__, $function ){
self::{ $function }( $args );
}
}
public static function title( $args ) {
// do something with the passed args...
}
}
I want to allow developers to extend the available methods from their own included class - so they could create for example render::date( $args ); and pass this to their logic to gather data, before rendering the results to the template.
The questions is, what approach would work best and be performant - errors are safety are not a big concern at this point, that can come later.
EDIT --
I am already making this work by doing the following ( pseudo-code again.. ):
-- app/render.php
class render {
public static function __callStatic( $function, $args ) {
// check if method exists
if (
method_exists( __CLASS__, $function
){
self::{ $function }( $args );
}
// check if method exists in extended class
if (
method_exists( __CLASS__.'_extend', $function
){
__CLASS__.'_extend'::{ $function }( $args );
}
}
public static function title( $args ) {
// do something with the passed args...
}
}
-- child_app/render_extend.php
class render_extend {
public static function date( $args = null ) {
// do some dating..
}
}
The issue here is that this is limited to one extension of the base render() class.
A common way (used by Twig and Smarty, for a couple of examples), is to require developers to manually register their extensions as callables. The render class keeps a record of them, and then as well as checking its own internal methods, also checks this list from _callStatic.
Based on what you have already, this might look like this:
class render
{
/** #var array */
private static $extensions;
public static function __callStatic($function, $args)
{
// check if method exists in class methods...
if ( method_exists( __CLASS__, $function )) {
self::{$function}(self::$args);
}
// and also in registry
elseif (isset(self::$extensions[$function])) {
(self::$extensions[$function])($args);
}
}
public static function title($args)
{
// do something with the passed args...
}
public static function register(string $name, callable $callback)
{
self::$extensions[$name] = $callback;
}
}
A developer would make use of this like so:
render::register('date', function($args) {
// Do something to do with dates
});
Full demo here: https://3v4l.org/oOiN6
I'm learning to code Wordpress Multisite the OOP way, and since I'm pretty new to OOP now I am in a situation that I can't seem to solve on my own.
Specifically, I'm creating some classes to create admin pages (both at the network and subsite level) with an OOP approach. Here's my simplified code:
class AdminPage {
public function __construct( $args ) {
add_action( 'admin_menu', array( $this, 'add_admin_page' ) );
}
public function add_admin_page() {
add_menu_page( // arguments );
}
}
class AdminNetworkPage extends AdminPage {
public function __construct( $args ) {
add_action( 'network_admin_menu', array( $this, 'add_admin_page' ) );
}
}
The code works, but as you can see I have to extend the AdminPage class with the sole purpose of changing the hook in the constructor ( I need admin_network_menu for admin pages at network level instead of admin_menu).
Is there a better way to do this? A way to have one class, put both hooks in the constructor of that class and then selectively call either one or the other?
Unfortunately, creating a new instance of the class ($page = new AdminPage) and then calling a method on it (e.g. $page->add_admin_page() ) won't work in this case, because then I get an error saying add_menu_page is undefined... It all has to happen in the constructor.
You could do something like this:
class AdminPage {
public function __construct( $args, $networkPage = false ) {
if($networkPage) {
add_action( 'network_admin_menu', array( $this, 'add_admin_page' ) );
} else {
add_action( 'admin_menu', array( $this, 'add_admin_page' ) );
}
}
public function add_admin_page() {
add_menu_page( // arguments );
}
}
And then pass a second parameter to the class to change which action you add, e.g.
$page = new AdminPage($args);
$networkPage = new AdminPage($args, true);
Whether or not this is better than just extending the class is debatable; I personally think there's nothing wrong with your initial setup, the point of OOP isn't to create as few classes as possible - if your code make more sense as two separate classes (with one extending the other) then there's nothing wrong with that.
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.
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.