How does array(__CLASS__, work in wordpress? - php

I'm trying to mod a wordpress plugin to take custom categories. So when the random_post_link is called, I can add the custom category by using random_post_link('Random Link',3). 3 being the category name.
How does the plugin below create a new object of class Random_Post_Link? I thought you created new objects by doing something like:
$a = new random_post_link;
But I don't see that in the plugin. I think it creates the new object in the init function by using a hook:
add_action('init', array(CLASS, 'jump'));
If that's the case, how can I add parameter to jump function?
I think I know how add_action works, the second parameter should be the function name, how does
" array(CLASS, 'jump')" work?
Here's the full code for the plugin:
function random_post_link($text = 'Random Post',$the_cat = 36) {
printf('%s', get_random_post_url(), $text);
$the_category = $the_cat;
}
function get_random_post_url() {
return trailingslashit(get_bloginfo('url')) . '?' . Random_Post_Link::query_var;
}
class Random_Post_Link {
const query_var = 'random';
const name = 'wp_random_posts';
public $the_category;
function init() {
add_action('init', array(__CLASS__, 'jump'));
// Fire just after post selection
add_action('wp', array(__CLASS__, 'manage_cookie'));
}
// Jump to a random post
function jump() {
if ( ! isset($_GET[self::query_var]) )
return;
$args = apply_filters('random_post_args', array(
'post__not_in' => self::read_cookie(),
));
$args = array_merge($args, array(
'orderby' => 'rand',
'cat' => $the_category,
'showposts' => 1,
));
$posts = get_posts($args);
if ( empty($posts) ) {
self::update_cookie(array());
unset($args['post__not_in']);
$posts = get_posts($args);
}
if ( empty($posts) )
wp_redirect(get_bloginfo('url'));
$id = $posts[0]->ID;
wp_redirect(get_permalink($id));
die;
}
// Collect post ids that the user has already seen
function manage_cookie() {
if ( ! is_single() )
return;
$ids = self::read_cookie();
$id = $GLOBALS['posts'][0]->ID;
if ( count($ids) > 200 )
$ids = array($id);
elseif ( ! in_array($id, $ids) )
$ids[] = $id;
self::update_cookie($ids);
}
private function read_cookie() {
return explode(' ', #$_COOKIE[self::name]);
}
private function update_cookie($ids) {
setcookie(self::name, trim(implode(' ', $ids)), 0, '/');
}
}
Random_Post_Link::init();

Some WordPress authors use the Class structure in PHP to basically reduce global vars. The class is meant as a 'singleton' of sorts, and so it isn't usually instantiated (that code calls Random_Post_Link::init(); at the bottom instead). The class functions are treated as Class members, not instance members, sort of like Math.max() in other languages, for example.
The __CLASS__ php keyword is simply a token for the current class, so when passed, the callable becomes Random_Post_Link::method() or array( 'Random_Post_Link', 'method' )
If you need to unhook, try remove_action( 'init', array( 'Random_Post_Link, 'jump' ) );
(Also note, that methods used that way should be declared static function jump() {...})
P.S. To further clarify:
http://php.net/manual/en/language.types.callable.php
The array('class', 'function') syntax is a PHP thing, while WordPress actions expect a callable which could be any of the PHP callable things.

Related

How to override a class with constructor on a WordPress plugin?

I'm trying to override a plugin class on a WordPress plugin.
Here is the original plugin class :
class WCV_Vendor_Dashboard
{
/**
* __construct()
*/
function __construct()
{
add_shortcode( 'wcv_shop_settings', array( $this, 'display_vendor_settings' ) );
add_shortcode( 'wcv_vendor_dashboard', array( $this, 'display_vendor_products' ) );
add_action( 'template_redirect', array( $this, 'check_access' ) );
add_action( 'init', array( $this, 'save_vendor_settings' ) );
}
public function save_vendor_settings(){
//some codes here
}
}
Here is what I'm trying (in functions.php), but it doesn't work :
$wcv_vendor_dashboard = new WCV_Vendor_Dashboard();
global $wcv_vendor_dashboard;
remove_action( 'init', array( $wcv_vendor_dashboard , 'save_vendor_settings' ) );
How to remove it correctly and how to create the replacement?
Additional info:
I did similar thing on the WooCommerce core. When I want to override a class / function, I use this (for example):
remove_action( 'template_redirect', array( 'WC_Form_Handler', 'save_account_details' ) );
function new_save_account_details() {
//custom code here
}
add_action( 'template_redirect', 'new_save_account_details' );
It's working properly on WooCommerce core. I tried something similar on WCV_Vendor_Dashboard but it doesn't work.
Why it was working with woocommerce, but it does not work in this case?
When you attach a function on to a specific action, WoredPress creates unique id for that callback and stores that in global $wp_filter array ( in fact, it was an array before, but now it is an object ). For object method callbacks ( like array( $this, 'save_vendor_settings' ) ), the id is generated with spl_object_hash php function. For the above example,
spl_object_hash( $this ) . 'save_vendor_settings'
, and it looks like 000000001c0af63f000000006d7fe83asave_vendor_settings.
To "legally" remove the object method with remove_action() you will need to have access to the original object that was used to attach the function in the first place. If the object exists in global namespace:
global $wcv;
remove_action( 'init', array( $wcv, 'save_vendor_settings' ) );
Creating another class instance will not work because the generated id is unique for each object, even if they are instances of the same class.
In the case of WooCommerce I guess it was about static class methods. Different logic is used to generate the ids for static class methods, functions and static method callbacks are just returned as strings. For your example it would be:
'WC_Form_Handler' . '::' . 'save_account_details'
You see why it works for one case, but not for the other.
There is a hack to replace functions attached by replacing them directly in global $wp_filter object, but it's not 100% reliable. Since we do not have access to the original object, we can only filter $wp_filter by the name of the function, if there are identical names for the same action it will replace wrong handlers.
global $wp_filter;
foreach ( $wp_filter['init']->callbacks as $priority => &$callbacks ) {
foreach ( $callbacks as $id => &$callback ) {
if ( substr( $id, -strlen( 'save_vendor_settings' ) ) === 'save_vendor_settings' ) {
// replace the callback with new function
$callback['function'] = 'new_save_vendor_settings';
}
}
}
I hope it will work, greetings.
Example for child class
class WCV_Vendor_Dashboard_Child extends WCV_Vendor_Dashboard
{
/**
* __construct()
*/
function __construct()
{
parent::__construct();
}
public function new_save_vendor_settings(){
//some codes here
}
}

Looping through arrays within classes to create wordpress metaboxes

I have a class sitting in my Wordpress functions.php. Eventually it's going to end up in the plugins folder but one step at a time. Below is a foreshortened version of it:
class metaboxClass {
$them_meta_boxes = array (
array (
"1a_myplugin_box_id_1",
"1b_Custom Meta Box Title 1"
),
array (
"2a_myplugin_box_id_2",
"2b_Custom Meta Box Title 2"
)
);
public function __construct() {
add_action( 'add_meta_boxes', array( $this, 'add_meta_box' ) );
}
public function add_meta_box( $post_type ) {
$post_types = array( 'page', 'my_cpt' );
if ( in_array( $post_type, $post_types )) { // *** IF $POST_TYPE IS IN THE ARRAY $POST_TYPES
foreach ($this->them_meta_boxes as $level_1) {
add_meta_box (
foreach ($this->level_1 as $level_2) {
echo $level_1 . ",";
}
array( $this, 'render_form'),
$post_type
)
}
}
}
}
As you can see from the above, I'm trying to construct various iterations of the add_meta_boxes function using the information in the array.
I've a feeling that there are a number of issues here and I'm kind of going through them one at a time but the first is that when an object is instantiated from the class I get: "syntax error, unexpected 'foreach' ". I know this is usually caused by a missing semi-colon. In this case the semi colon is present and correct. I've a feeling it's something to do with the placement of the array but I'm getting similar problems when it's placed outside. Can anyone give me any pointers - I'm pretty new to the world of OO PHP and also to really getting my hands dirty with the wordpress backend so any pointers would be appreciated.
Thanks in advance,
Stef
You can't pass a foreach loop as a parameter to a function. Construct your argument string first and then pass the constructed string as an argument to your add_meta_box function.
Even then though, I'm not sure what you are trying to call since your add_meta_box function only takes one argument.
Got it sorted for the record... ended up something like this:
class initialise_meta_boxes {
public $meta_boxes_array = array (
array (
"1a_myplugin_box_id_1",
"1b_Custom Meta Box Title 1",
"render_dropdown"
),
array (
"2a_myplugin_box_id_2",
"2b_Custom Meta Box Title 2",
"render_dropdown"
)
);
public function __construct() {
add_action( 'add_meta_boxes', array( $this, 'add_meta_box' ) );
add_action( 'save_post', array( $this, 'save' ) );
}
public function make_meta_box($meta_id, $meta_title, $meta_callback) {
return add_meta_box ($meta_id, $meta_title, $meta_callback, $post_type );
}
public function add_meta_box( $post_type ) { // *** $post_type is global variable!!!
$post_types = array( 'page', 'my_cpt' );
if ( in_array( $post_type, $post_types )) { // *** IF $POST_TYPE IS IN THE ARRAY $POST_TYPES
foreach ($this->meta_boxes_array as $value) {
$this->make_meta_box($value[0], $value[1], array( $this, $value[2]));
}
}
}
}

WordPress query_var by domain

I'd like to add a query variable to all queries coming from a certain domain.
For example, mydomain.com and proxydomain.com both show the same WordPress site, but for users visiting via proxydomain.com, I'd like to be able to handle their queries differently.
Additionally, I'd like to apply some different CSS styles for visitors coming through proxydomain.com.
I was thinking I could check for the query_var and apply classes based on the presence of that variable.
This is the code to add to your functions.php file:
add_filter( 'body_class', 'domain_as_body_class' );
function domain_as_body_class( $classes ) {
$classes[] = sanitize_title( $_SERVER['SERVER_NAME'] );
return $classes;
}
It adds the sanitized domain of your site (i.e. mydomain-com or proxydomain-com) as class of the body tag of your pages, so you can target the relative class for custom styles.
Update
For the queries you could add a function again in functions.php like:
function is_proxydomain() {
return 'proxydomain.com' == $_SERVER['SERVER_NAME'];
}
And then use it when needed on a query:
if( is_proxydomain() ) {
$args = array(
// arguments for proxydomain.com
);
} else {
$args = array(
// arguments for mydomain.com
);
}
$query = new WP_Query( $args );
I like the answer of d79 for the first part.
For the queries, I think it would be better to extend WP_Query class ( i.e. WP_Query_Custom ) and have one copy for each domain. Then you can load the file you need based on the domain in the functions.php file, and so you don't need to change in the future your calls everywhere you use WP_Query_Custom, even if you need to add more domains and different versions of WP_Query_Custom.
//in functions.php
$mydomain = str_replace('.', '_', $_SERVER['SERVER_NAME']);
require_once("path/to/my/classes/$mydomain/WP_Query_Custom.php");
//In each path/to/my/classes/$mydomain/WP_Query_Custom.php
class WP_Query_Custom extends WP_Query {
function __construct( $args = array() ) {
// Force these args
$args = array_merge( $args, array(
'post_type' => 'my_custom_post_type',
'posts_per_page' => -1, // Turn off paging
'no_found_rows' => true // Optimize query for no paging
) );
add_filter( 'posts_where', array( $this, 'posts_where' ) );
parent::__construct( $args );
// Make sure these filters don't affect any other queries
remove_filter( 'posts_where', array( $this, 'posts_where' ) );
}
function posts_where( $sql ) {
global $wpdb;
return $sql . " AND $wpdb->term_taxonomy.taxonomy = 'my_taxonomy'";
}
}
The example class is copied from extending WP_Query

Accessing $args when it is an object when using 'do_action_ref_array' in WordPress?

I'm trying to write a callback function and hook it to an action hook I have in my plugin. For example,
do_action_ref_array( 'my_action', array( &$args ) );
And my callback is:
function my_callback( &$args ) {
// Do something.
}
add_action( 'my_action', 'my_callback' );
If $args were a simple array, lets say $args = array( 'arg_1', true, 'foo', 'arg_4' ), then I could access the values with $args[0], $args[1] etc.
My question: When $args is an object, how would I access it in my callback?
After reading the documentation, I was under the impression $args[0]->property would work but it doesn't. However, $args->property does work. Hoping someone can explain why?
after a look at the doc of do_action_ref_array if you give it an array($obj1,$obj2)
you should be able to acces them like so
function my_callback( &$args ) {
// $args[0] would be $obj1
$args[0]->propertyX;
$args[0]->methodY();
//and so on
}

PHP: __call not working properly

I'm having a problem creating a class called API_Widgets in WordPress using the magic __call function. If I simple rename the file to derp.php and the class to API_Derp then it works without problems.
The following example has been stripped of everything unimportant to this issue (so if there is any error other than the specific fatal error specified in the third code block, ignore it).
Please keep in mind that I know the core.php or API class' __call works, as renaming the widgets.php or invoking another class works just fine.
core.php:
class API {
function __call( $method, $args ) {
$rmethod = "API_{$method}";
if ( !class_exists( $rmethod ) ) {
$lmethod = strtolower( $method );
require_once( "{$lmethod}.php" );
}
return new $rmethod( $args );
}
}
widgets.php:
class API_Widgets {
private $widgets = array();
function Add( $widgets ) {
if ( is_array( $widgets ) ) {
foreach ( $widgets as $widget ) {
if ( !in_array( $widget, $this->widgets ) )
$this->widgets[] = $widget;
}
} else
$this->widgets[] = $widgets;
}
}
api.php:
$api = new API();
$widgets = $api->Widgets(); // Fatal error: Class 'API_Widgets' not found in /home4/goldencr/public_html/wp-content/plugins/minecraft-api/api/core.php on line 25
//$widgets->Add( 'some_widget' );
Extending from comment:
Though not hinted at your question, it seems you may not actually included the widgets.php. Try to use absolute path to fix that:
require_once( __DIR__.DIRECTORY_SEPARATOR.$lmethod.".php" );

Categories