Why does my save_post action not trigger? - php

I'm writing a plug-in for wordpress in classes and I'm adding an action hook to 'save_post' in the constructor of a class. However it does not seem to fire. Am I using it in the right way?
EDIT 25-05-2014 - I wrote a new (tested) minimal example that definitely reproduces the problem for me.
If I use the save_post in a procedural way (like directly in the index.php) it does work, but obviously that's not helpful when I'm structuring everything in classes.
/*
File index.php
This file handles the installation and bootstrapping of the plugin
*/
define("XX_POST_TYPE", 'testposttype');
if( !class_exists('MyPlugin') ):
class MyPlugin {
var $savecontroller;
public function __construct(){
add_action('init', array($this, 'init'), 1);
//include stuff before activation of theme
$this->include_before_theme();
}
//Include these before loading theme
private function include_before_theme(){
include_once("controllers/savecontroller.php");
}
public function init(){
register_post_type( XX_POST_TYPE,
array(
'labels' => array(
'name' => __('Tests'),
'singular_name' => __('Test'),
'add_new' => __('Add new test'),
'add_new_item' => __('Add new test')
),
'public' => true,
'has_archive' => true,
'hierarchical' => true
)
);
add_action('add_meta_boxes', function(){
$this->savecontroller = new SaveController();
});
}
}
function startup(){
global $myPlugin;
if( !isset($myPlugin) ){
$myPlugin = new MyPlugin();
}
return $myPlugin;
}
//Initialize
startup();
endif;
?>
The save actions happen in a different class and file.
<?php
// file savecontroller.php
class SaveController{
public function __construct(){
add_meta_box('xx_field_box', 'Field', array($this, 'setup_field'), XX_POST_TYPE);
}
public function setup_field( $post ){
?>
<input type="text" name="xx_custom_field" id="xx_custom_field" value="">
<?php
add_action('save_post', array($this, 'save_my_post'), 1, 1);
}
public function save_my_post($post_id){
if(isset($_POST['xx_custom_field'])){
update_post_meta($post_id, 'xx_custom_field', $_POST['xx_custom_field']);
}
}
}
?>
It does create my custom posttype and field so I know the classes are working. But the save_post is not triggered. It does not 'die()' and it does not do the 'update_post_meta()'. The custom field does appear in the POST request so the isset() checks out.
It's probably something dumb, but I can't get it to work.

You're trying to add the hook save_post inside the add_meta_box callback, and that's not the place for it.
To solve it, change the init method to
public function init(){
register_post_type( $args );
$this->savecontroller = new SaveController();
}
And modify the SaveController to
class SaveController{
public function __construct(){
add_action( 'add_meta_boxes', array( $this, 'meta_box' ) );
add_action( 'save_post', array( $this, 'save_my_post'), 10, 2 );
}
public function meta_box(){
add_meta_box( 'xx_field_box', 'Field', array($this, 'setup_field'), XX_POST_TYPE );
}
public function setup_field( $post ){
?>
<input type="text" name="xx_custom_field" id="xx_custom_field" value="">
<?php
}
public function save_my_post( $post_id, $post_object ){
wp_die( '<pre>'. print_r( $post_object, true) . '</pre>' );
}
}
Note that the save_post action takes two parameters and the priority can be default (10). You can find lots of examples for meta boxes and save_post here.

Related

Wordpress Plugin Boilerplate - How to use WP_List_Table class correctly?

I'm using the Wordpress Plugin Boilerplate as foundation for my own plugin. In the admin area I intend to display data using the WP_List_Table class of Wordpress. I know I have to create my own child class to access it. Doing so is not the issue, however I get the following error:
Fatal error: Uncaught Error: Call to a member function render_screen_reader_content() on
During my research I discovered a few cases with the same issue (1, 2), but none of the solutions worked in my case.
Using the structure of the boilerplate, I created the child class in a file inside the includes folder:
if ( !class_exists( 'WP_List_Table' ) ) {
require_once( ABSPATH . 'wp-admin/includes/class-wp-list-table.php' );
}
if ( !class_exists( 'Hedwig_tables' ) ) {
class Hedwig_tables extends WP_List_Table {
private array $hd_columns;
private array $hd_data;
private array $hd_hidden;
private array $hd_sortable;
private array $hd_column_names;
public function __construct() {
//parent::__construct();
}
public function set_column_names(array $column_names) {
$this->hd_column_names = $column_names;
}
public function set_columns(array $columns) {
$this->hd_columns = $columns;
}
public function set_data(array $data) {
$this->hd_data = $data;
}
public function set_hidden(array $hidden) {
$this->hd_hidden = $hidden;
}
public function set_sortable(array $sortable) {
$this->hd_sortable = $sortable;
}
public function prepare_items() {
$this->_column_headers = array($this->hd_columns, $this->hd_hidden, $this->hd_sortable);
$this->items = $this->hd_data;
}
public function column_default( $item, $column_name ): mixed {
if (in_array($column_name, $this->hd_column_names)) {
return $item[ $column_name ];
}
return print_r($item, true);
}
}
}
The file is then loaded in the boilerplate's load_dependencies() function in class-plugin-name.php inside the includes folder.
In the boilerplate's class-plugin-name-admin.php (inside the admin folder) I created a function which generates the admin menu entry.
public function add_hedwig_page() {
$this->plugin_screen_hook_suffix = add_menu_page(
__( 'Hedwig Settings', 'Hedwig' ),
__( 'Hedwig Settings', 'Hedwig' ),
'manage_options',
$this->plugin_name,
array( $this, 'hedwig_admin_display_page' ),
'dashicons-buddicons-activity'
);
$this->plugin_screen_hook_suffix = add_submenu_page(
$this->plugin_name,
__( 'Hedwig Settings', 'Hedwig' ),
__( 'Hedwig Settings', 'Hedwig' ),
'manage_options',
$this->plugin_name,
array( $this, 'hedwig_admin_display_page' )
);
public function hedwig_admin_display_page() {
include_once 'partials/hedwig-admin-display.php';
}
Inside the display.php a function is called which I created inside the class-plugin-name-admin.php which creates the object for the child class of WP_List_Table.
public function get_data() {
$hedwig_list_table = new Hedwig_tables();
$sql = "SELECT id, value FROM y";
$results = $this->wpdb->get_results($sql, ARRAY_A);
if (count($results)<=0) {
?>
<div class="hedwig-msg-error"><?php _e('No data found.','Hedwig');?></div>
<?php
return false;
}
$hedwig_list_table->set_columns(
array(
'id' => __('ID','Hedwig'),
'value' => __('Art','Hedwig')
)
);
$hedwig_list_table->set_column_names(
array(
'id',
'value'
)
);
$hedwig_list_table->set_data($results);
$hedwig_list_table->set_hidden(array());
$hedwig_list_table->set_sortable(array());
$hedwig_list_table->prepare_items();
$hedwig_list_table->display();
return true;
}
Based on my aforementioned research the issue must be somewhere along the line of when the object for the child class is created (see this answer). I tried using add_actions() at different places (on __construct of the admin class, inside the run() function of the plugin-name.php trying to either load it after the menu items are generated or loading the class as a $GLOBALS. Everything I came up with failed. I used to create some smaller plugins without a boilerplate, but in this project I actually want to do the switch to this OOP and get new plugins kickstarted this way.
Update #1
Still got no solution, but I stumbled upon another solution which looked promising. However, using a function when creating the menu item for initialising the child class doesn't work either.
public function add_hedwig_page() {
$this->plugin_screen_hook_suffix = add_menu_page(
__( 'Hedwig Settings', 'Hedwig' ),
__( 'Hedwig Settings', 'Hedwig' ),
'manage_options',
$this->plugin_name,
function() {
$this->hedwig_list_table = new Hedwig_tables();
$this->hedwig_admin_display_page();
},
'dashicons-buddicons-activity'
);
$this->plugin_screen_hook_suffix = add_submenu_page(
$this->plugin_name,
__( 'Hedwig Settings', 'Hedwig' ),
__( 'Hedwig Settings', 'Hedwig' ),
'manage_options',
$this->plugin_name,
function() {
$this->hedwig_list_table = new Hedwig_tables();
$this->hedwig_admin_display_page();
}
);
public function hedwig_admin_display_page() {
include_once 'partials/hedwig-admin-display.php';
}
You shouldn't load this class like your custom classes since this is an extension of a core class and it has many more dependencies to rely on. Don't use add_action or $GLOBALS to initialize this class at all.
If you load it in boilerplate's load_dependencies() function in class-plugin-name.php inside the includes folder it will be instantiated too early and it will not function properly.
Instead, call it only when needed, inside your partials/hedwig-admin-display.php that you will use to output your markup for that page.
Something like this should work in your example:
<?php $table = new Hedwig_tables(); ?>
<h1>Hedwig_tables</h1>
<div class="wrap">
<div id="poststuff">
<div id="post-body" class="metabox-holder">
<div id="post-body-content">
<div class="meta-box-sortables ui-sortable">
<?php $table->prepare_items();?>
<form method="get">
<?php $table->display(); ?>
</form>
</div>
</div>
</div>
<br class="clear">
</div>
</div>

WordPress AJAX is_admin is true, causing issues.

i'm trying to make a plugin for WordPress, which is has got an admin section for some basic settings, and also registers some shortcode to display some HTML, which is basically a form.
Here is my main plugin file, plugins/my-plugin/my-plugin.php:
/**
* Plugin Name: Pathway
* Plugin URI: http://www.martynleeball.com/
* Description: Pathway integration.
* Version: 1.0
* Author: Martyn Lee Ball
* Author URI: https://www.martynleeball.com/
**/
define('PATHWAY_VERSION', '0.0.8');
define('PATHWAY_AUTHOR', 'Martyn Lee Ball');
define('PATHWAY__MINIMUM_WP_VERSION', '4.*');
define('PATHWAY_CONTACT', 'martynleeball#gmail.com');
add_action(
'plugins_loaded',
array ( Pathway::get_instance(), 'plugin_setup' )
);
class Pathway
{
protected static $instance = NULL;
public $plugin_url = '';
private $cpt = 'post'; # Adjust the CPT
public function __construct() {}
public static function get_instance()
{
NULL === self::$instance and self::$instance = new self;
return self::$instance;
}
public function plugin_setup()
{
$this->plugin_url = '';
add_action( 'wp_enqueue_scripts', array( $this, 'enqueue' ) );
// if (is_admin()) {
//
// require_once( $this->plugin_url . 'admin/index.php' );
//
// register_activation_hook( __FILE__, 'install' );
//
// return;
// }
add_shortcode( 'pathway', array($this, 'shortcode'));
add_action( 'wp_ajax_ajax_login', array( $this, 'ajax_login' ) );
add_action( 'wp_ajax_nopriv_ajax_login', array( $this, 'ajax_login' ) );
add_action( 'wp_ajax_ajax_register', array( $this, 'ajax_register' ) );
add_action( 'wp_ajax_nopriv_ajax_register', array( $this, 'ajax_register' ) );
}
public function enqueue()
{
wp_enqueue_script( 'vuejs', 'https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js' );
wp_enqueue_script(
'ajax-handle-form',
"{$this->plugin_url}/wp-content/plugins/pathway/frontend/js/scripts.js"
);
wp_localize_script(
'ajax-handle-form',
'wp_ajax',
array(
'ajaxurl' => admin_url( 'admin-ajax.php' ),
'ajaxnonce' => wp_create_nonce( 'ajax_post_validation' )
)
);
}
public function ajax_login()
{
echo 'login';exit;
}
public function ajax_register()
{
echo 'register';exit;
}
public function shortcode()
{
if (!isset($_SESSION['pathway_login'])) {
self::view('forms/login');
}
}
public static function view( $name, array $args = array() ) {
foreach ( $args AS $key => $val ) {
$$key = $val;
}
// $file = $this->plugin_url . 'views/'. $name . '.php';
$file = 'views/'. $name . '.php';
include( $file );
}
}
Please correct me if i'm going wrong somewhere, there's so many mixed guides online showing different ways. Within this file i'm basically:
Adding my scripts and assigning the PHP values.
I would be then starting the admin section however has to comment this out for the AJAX call, this is my issue.
Registering my shortcode.
Adding the actions for the AJAX form submit.
Obviously my issue is that when I hit the is_admin from the AJAX call it is returning true, when it should be false as an public visitor can submit this form. The wp_ajax_nopriv action doesn't appear to work which would solve the issue, this is probably due to me being logged into WordPress.
I have tried logging out of WordPress but the is_admin still returns true!
Can someone advise?
is_admin will return true on all ajax calls.
It is not actually a useful function to check the user as it checks the uri rather than the user details, i.e. if on a admin page = true, if not false.
Now I was a little confused about your question here, it appears you want the is_admin to return false if its actually an ajax call?
if ( is_admin() && ! wp_doing_ajax() ) {}
It will return false on ajax calls.
If you are checking there is an "admin" logged in, as in can edit posts, see the other capabilities here
if ( current_user_can( 'edit_post' ) ) {}
The no_priv hook will not work when logged in, its not called.

wp_enqueue_script not working when dependent on query string value in custom template.

So for some reason wp_enqueue_script does not execute when dependent on query string value. If I change function check to simply return true, it works. Why is this? Looking at the WordPress initiation order, wp_enqueue_script fires after parse_query which means it must be available. My goal is to only load the scripts if template foo is requested.
class Car {
public function __construct() {
if ( $this->check() ) {
add_action('template_include', array( $this, 'get_template') );
// Does not work
add_action( 'wp_enqueue_scripts', array( $this, 'get_scripts') );
add_action( 'wp_enqueue_scripts', array( $this, 'get_styles') );
}
// Works
add_action( 'wp_enqueue_scripts', array( $this, 'get_scripts') );
add_action( 'wp_enqueue_scripts', array( $this, 'get_styles') );
}
public function check() {
return ( isset( $_GET['foo'] ) && $_GET['foo'] == true );
}
public function get_template() {
return locate_template( array( 'foo.php' ) );
}
}
$car = new Car();
I suspect that it's ignoring your 'foo' parameter because to use custom query vars, they need to be registered with WordPress, using the 'query_vars' filter.
Like this:
function themeslug_query_vars( $qvars ) {
$qvars[] = 'foo';
return $qvars;
}
add_filter( 'query_vars', 'themeslug_query_vars' , 10, 1 );

WooCommerce Webhooks Custom/Action not saving

Every time you try and set a custom/action topic within webhooks (from WooCommerce > Settings > Webhooks) it would unset it as soon as you update your changes to the webhook. In other words, it will undo your custom topic and return it back to 'Select an option' for the topic dropdown.
Any help at all is appreciated. Thank you very much!
edit: In addition to my comment below, I've also attempted to create my own custom topic via the following filter woocommerce_webhook_topic_hooks, however, it doesn't show within the dropdown list as an option.
The below code runs from functions.php as with any WordPress hook..
Code
function custom_woocommerce_webhook_topics( $topic ) {
$topic['order.refunded'] = array(
'woocommerce_process_shop_order_meta',
'woocommerce_api_edit_order',
'woocommerce_order_edit_status',
'woocommerce_order_status_changed'
);
return $topic;
}
add_filter( 'woocommerce_webhook_topic_hooks', 'custom_woocommerce_webhook_topics', 10, 1 );
edit 2: Added more context
I was having the same issue. Selecting any of the built-in topics worked fine. But selection Action and entering any WooCommerce Subscription actions kept reverting. I had also tried creating a new custom topic in the same file (wp-content/plugins/woocommerce-subscriptions/includes/class-wcs-webhooks.php) that the built-in topics are created, mirroring 1:1 the code of one of the topics that 'stick' (e.g subscription.created) for a new 'subscription.paymentcomplete' topic. It appears in the drop down, but after saving the drop-down reverts to the default 'Selection an option...'.
//wp-content/plugins/woocommerce-subscriptions/includes/class-wcs-webhooks.php
public static function init() {
...
add_action( 'woocommerce_subscription_created_for_order', __CLASS__ . '::add_subscription_created_callback', 10, 2 );
add_action( 'woocommerce_subscription_payment_complete', __CLASS__ . '::add_subscription_payment_complete_callback', 10, );
...
}
public static function add_topics( $topic_hooks, $webhook ) {
if ( 'subscription' == $webhook->get_resource() ) {
$topic_hooks = apply_filters( 'woocommerce_subscriptions_webhook_topics', array(
'subscription.created' => array(
'wcs_api_subscription_created',
'wcs_webhook_subscription_created',
'woocommerce_process_shop_subscription_meta',
),
'subscription.paymentcomplete' => array(
'wcs_webhook_subscription_payment_complete'
'woocommerce_process_shop_subscription_meta',
),
...
), $webhook );
}
return $topic_hooks;
}
public static function add_topics_admin_menu( $topics ) {
$front_end_topics = array(
'subscription.created' => __( ' Subscription Created', 'woocommerce-subscriptions' ),
'subscription.paymentcomplete' => __( ' Subscription Payment Complete', 'woocommerce-subscriptions' ),
...
);
return array_merge( $topics, $front_end_topics );
}
public static function add_subscription_created_callback( $order, $subscription ) {
do_action( 'wcs_webhook_subscription_created', $subscription->id );
}
public static function add_subscription_payment_complete_callback( $order, $subscription ) {
do_action( 'wcs_webhook_subscription_payment_complete', $subscription->id );
}
The solution was:
add_filter( 'woocommerce_valid_webhook_events', __CLASS__ . '::add_event', 10, 1 );
public static function add_event( $events) {
$events[] = 'paymentcomplete';
return $events;
}

Removing action from function inside class wordpress returning false

I am trying to remove a function from the init filter which is located inside a class, and tried based on what i have read.
wp-content/plugins/plugin_name/includes/class-wps-deals-public-pages.php
class Wps_Deals_Public_Pages {
public function wps_deals_register() {
// some content here
}
public function add_hooks() {
add_action( 'init', array( $this, 'wps_deals_register' ), 100 );
}
}
Main file of plugin wp-content/plugins/plugin_name/deals-engine.php
require_once( WPS_DEALS_DIR . '/includes/class-wps-deals-public-pages.php' );
$wps_deals_public = new Wps_Deals_Public_Pages();
$wps_deals_public->add_hooks(); // add_hooks is getting called here
wp-content/themes/my_theme/overriding_template_that_overrides_the_plugin_template/create_account.php
global $wps_deals_message, $wps_deals_public;
var_dump(remove_action( 'init', array( 'Wps_Deals_Public_Pages', 'wps_deals_register' ), 1000 ));
The var_dump() returns false instead of true, and tested and indeed is not removing the function from the hook, I also tried
var_dump(remove_action( 'init', array( $wps_deals_public, 'wps_deals_register' ), 1000 ));
$wps_deals_public => this is the object from the specified instantiated class Wps_Deals_Public_Pages.

Categories