WooCommerce Webhooks Custom/Action not saving - php

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;
}

Related

disabling the action function of a class in woocommerce subscriptions

i use the woocommerce subscriptions plugin, i have a class class WC_Subscriptions_Cart {}, inside the function class is the following :
/**
* Display the recurring totals for items in the cart
*
* #since 2.0
*/
public static function display_recurring_totals() {
if ( self::cart_contains_subscription() ) {
// We only want shipping for recurring amounts, and they need to be calculated again here
self::$calculation_type = 'recurring_total';
$carts_with_multiple_payments = 0;
foreach ( WC()->cart->recurring_carts as $recurring_cart ) {
// Cart contains more than one payment
if ( 0 != $recurring_cart->next_payment_date ) {
$carts_with_multiple_payments++;
}
}
if ( apply_filters( 'woocommerce_subscriptions_display_recurring_totals', $carts_with_multiple_payments >= 1 ) ) {
wc_get_template(
'checkout/recurring-totals.php',
array(
'shipping_methods' => array(),
'recurring_carts' => WC()->cart->recurring_carts,
'carts_with_multiple_payments' => $carts_with_multiple_payments,
),
'',
WC_Subscriptions_Core_Plugin::instance()->get_subscriptions_core_directory( 'templates/' )
);
}
self::$calculation_type = 'none';
}
}
its action is as follows :
public static function init() {
add_action( 'woocommerce_cart_totals_after_order_total', __CLASS__ . '::display_recurring_totals' );
}
i want to disable this action, my code is not working :
function remove_my_class_action(){
remove_action( 'woocommerce_cart_totals_after_order_total', array( 'WC_Subscriptions_Cart', 'display_recurring_totals' ) );
} add_action( 'woocommerce_cart_totals_after_order_total', 'remove_my_class_action' );
how can i disable this action through the function.php file? i appreciate any reply from you.
after repeated attempts, i got the answer :
remove_action( 'woocommerce_cart_totals_after_order_total', array( 'WC_Subscriptions_Cart', 'display_recurring_totals' ), 10 );

Extending WooCommerce COD payment gateway in a plugin

I would like to understand the sequence various classes are loaded in Wordpress.
There are many plugins available in Wordpress, then who will be loaded earlier than another.
Consider I would like to develop a plugin that will use some existing class of Woocommerce. Basically my custom plugin will extend some class of Woocommerce (for example : WC_Gateway_COD)
How I can ensure the existing class ‘WC_Gateway_COD’ is already defined & loaded before it execute the below statement ?
if (class_exists('WC_Gateway_COD')) {
class WC_my_custom_class extends WC_Gateway_COD {
…..
….
}
}
To make a custom gateway based on an existing WooCommerce payment method as COD, It's recommended to copy the source code from WC_Gateway_COD Class in a plugin (adapting the code for your needs) like:
defined( 'ABSPATH' ) or exit;
// Make sure WooCommerce is active
if ( ! in_array( 'woocommerce/woocommerce.php', apply_filters( 'active_plugins', get_option( 'active_plugins' ) ) ) ) {
return;
}
add_filter( 'woocommerce_payment_gateways', 'wc_custom_add_to_gateways' );
function wc_custom_add_to_gateways( $gateways ) {
$gateways[] = 'WC_Gateway_COD2';
return $gateways;
}
add_filter( 'plugin_action_links_' . plugin_basename( __FILE__ ), 'wc_gateway_custom_plugin_links' );
function wc_gateway_custom_plugin_links( $links ) {
$plugin_links = array(
'' . __( 'Configure', 'payment_cod2' ) . ''
);
return array_merge( $plugin_links, $links );
}
add_action( 'plugins_loaded', 'wc_gateway_cod2_init', 11 );
function wc_gateway_cod2_init() {
class WC_Gateway_COD2 extends WC_Payment_Gateway {
public $domain; // The text domain (optional)
/**
* Constructor for the gateway.
*/
public function __construct() {
$this->domain = 'payment_cod2'; // text domain name (for translations)
// Setup general properties.
$this->setup_properties();
// Load the settings.
$this->init_form_fields();
$this->init_settings();
// Get settings.
$this->title = $this->get_option( 'title' );
$this->description = $this->get_option( 'description' );
$this->instructions = $this->get_option( 'instructions' );
$this->enable_for_methods = $this->get_option( 'enable_for_methods', array() );
$this->enable_for_virtual = $this->get_option( 'enable_for_virtual', 'yes' ) === 'yes';
add_action( 'woocommerce_update_options_payment_gateways_' . $this->id, array( $this, 'process_admin_options' ) );
add_action( 'woocommerce_thankyou_' . $this->id, array( $this, 'thankyou_page' ) );
add_filter( 'woocommerce_payment_complete_order_status', array( $this, 'change_payment_complete_order_status' ), 10, 3 );
// Customer Emails.
add_action( 'woocommerce_email_before_order_table', array( $this, 'email_instructions' ), 10, 3 );
}
/**
* Setup general properties for the gateway.
*/
protected function setup_properties() {
$this->id = 'cod2';
$this->icon = apply_filters( 'woocommerce_cod2_icon', '' );
$this->method_title = __( 'Cash on delivery', 'woocommerce' );
$this->method_description = __( 'Have your customers pay with cash (or by other means) upon delivery.', 'woocommerce' );
$this->has_fields = false;
}
// and so on (the rest of the code)…
}
}
You can unset / remove original COD payment gateway changing the 1st function like:
add_filter( 'woocommerce_payment_gateways', 'wc_custom_add_to_gateways' );
function wc_custom_add_to_gateways( $gateways ) {
$gateways[] = 'WC_Gateway_COD2';
unset($gateways['WC_Gateway_COD']; // Remove COD gateway
return $gateways;
}

How to use WC_Order class properly?

I'm writing a Woocommerce plugin and I'm trying to get order information from the class WC_Order, and I get two different types of error.
In the first case, I'm using the following code:
public function woocommerce_loaded() {
global $woocommerce, $post;
$order = new WC_Order($post->ID);
}
And I get this:
PHP Fatal error: Uncaught Error: Class 'WC_Order' not found in C:\wamp64\www\mysite\wp-content\plugins\tutorial-plugin\tutorial-plugin.php
In the second case, I'm using this code:
public function woocommerce_loaded() {
global $woocommerce, $post;
$order = $woocommerce -> WC_Order($post->ID);
}
And I get the following error message:
PHP Fatal error: Uncaught Error: Call to a member function WC_Order() on null in C:\wamp64\www\mysite\wp-content\plugins\tutorial-plugin\tutorial-plugin.php
And for both cases, I'm hooking my function at my __construct() method with the following code:
add_action( 'woocommerce_init', array( $this, 'woocommerce_loaded' ) );
I also tried using this class with my orders id that I have access in the Woocommerce Orders page, but it didn't work either.
I'm new in this area, I don't know what I am doing wrong. I could use some help!
Here's the complete version of my plugin code:
<?php
//Make sure we don't expose any information if called directly
if ( ! defined( 'ABSPATH' ) ) exit;
//Check if Woocommerce is active
if ( in_array( 'woocommerce/woocommerce.php', apply_filters( 'active_plugins', get_option( 'active_plugins' ) ) ) ) {
//Check if a class with the same name doesn’t already exist
if ( ! class_exists( 'WC_tutorial_plugin' ) ) {
//Provide translation files
load_plugin_textdomain( 'tutorial-plugin', false, dirname( plugin_basename( __FILE__ ) ) . '/' );
//Plugin code
class WC_tutorial_plugin {
/**
* Constructor for the WC_tutorial_plugin class
*
* #access public
* #return void
*/
public function __construct() {
$this->id = 'tutorial-plugin'; // Id of the class
$this->method_title = __( 'Tutorial plugin' ); // Title shown in admin
$this->init();
global $woocommerce;
$this->woocommerce_loaded();
// called just before the woocommerce template functions are included
add_action( 'init', array( $this, 'include_template_functions' ), 20 );
// called only after woocommerce has finished loading
add_action( 'woocommerce_init', array( $this, 'woocommerce_loaded' ) );
// called after all plugins have loaded
add_action( 'plugins_loaded', array( $this, 'plugins_loaded' ) );
add_action( 'plugins_loaded', array( $this, 'init' ) );
}
/**
* Initialize settings
*
* #access public
* #return void
*/
public function init() {
// Load the settings API
$this->init_form_fields(); // This is part of the settings API.
}
function activate(){
//Flush rewrite rules
flush_rewrite_rules();
}
function deactivate(){
//Flush rewrite rules
flush_rewrite_rules();
}
/**
* Take care of anything that needs woocommerce to be loaded.
* For instance, if you need access to the $woocommerce global
*/
public function woocommerce_loaded() {
global $woocommerce;
$order = $woocommerce -> WC_Order(17);
}
/**
* Take care of anything that needs all plugins to be loaded
*/
public function plugins_loaded() {
do_action('woocommerce_init', array($this, 'woocommerce_loaded'));
}
}
//Finally instantiate our plugin class and add it to the set of globals
if ( class_exists('WC_tutorial_plugin')){
$GLOBALS['tutorial-plugin'] = new WC_tutorial_plugin();
$plugin = new WC_tutorial_plugin(__FILE__ );
$plugin -> woocommerce_loaded();
}
}
}
//Activation
register_activation_hook( __FILE__, array($plugin, 'activate'));
//Deactivation
register_deactivation_hook( __FILE__, array($plugin, 'deactivate'));
I tried a lot to find the solution for this, and in the end I found two things that can be useful:
we can use the init hook and put the priority to 100, and then use our function in it, if you can use your code this way then it is for you:
add_action('init', 'my_init', 1);
function my_init(){
$order = new WC_Order(5273);
}
The 2nd way is to fetch the order details directly from the database using the $wpdb. the order is stored in the post table and order details like all the default order meta are stored in the post_meta table
global $wpdb;
// Get all customer orders
$subscriptions = get_posts(array(
'numberposts' => -1,
'post_type' => 'shop_subscription', // Subscription post type
'post_status' => 'wc-active', // Active subscription
'orderby' => 'post_date', // ordered by date
'order' => 'ASC',
'date_query' => array( // Start & end date
array(
'after' => $from_date,
'before' => $to_date,
'inclusive' => true,
),
),
));
I found this way much useful and also if you need to get the order custom meta fields then those fields are stored in the woocommerce_order_itemmeta table but to fetch these you need order_item_meta id which you can get from the table woocommerce_order_items, you can use the code below giving it the orderIds array containing the order ids:
foreach ($orderIds as $orderId) {
$result = $wpdb->get_results($wpdb->prepare(
"
SELECT m.meta_value,i.order_item_name
FROM {$wpdb->prefix}woocommerce_order_itemmeta as m
INNER JOIN {$wpdb->prefix}woocommerce_order_items as i ON
m.order_item_id = i.order_item_id
WHERE `order_id` = %s and i.order_item_type = 'line_item'
and m.meta_key = '_wapf_meta'
",
"$orderId"
), object);
$results[] = $result;
}

Remove product variation message in Woocommerce points & rewards plugin

I'm trying to create a remove action for the woocommerce points and rewards plugin but I cannot get it to work.
I haven't tried to remove an action that's like this before so any help would be appreciated :)
Thank you for reading
My remove_action is :
remove_action( 'woocommerce_before_add_to_cart_button', 'add_variation_message_to_product_summary', 25 );
The original add_actions in their class:
class WC_Points_Rewards_Product {
/**
* Add product-related hooks / filters
*
* #since 1.0
*/
public function __construct() {
// add single product message immediately after product excerpt
add_action( 'woocommerce_before_add_to_cart_button', array( $this, 'render_product_message' ), 15 );
// add variation message before the price is displayed
add_action( 'woocommerce_before_add_to_cart_button', array( $this, 'add_variation_message_to_product_summary' ), 25 );
// add the points message just before the variation price.
add_filter( 'woocommerce_variation_price_html', array( $this, 'render_variation_m
essage' ), 10, 2 );
add_filter( 'woocommerce_variation_sale_price_html', array( $this, 'render_variation_message' ), 10, 2 );
add_filter( 'woocommerce_available_variation', array( $this, 'render_available_variation_message' ), 10, 3 );
add_filter( 'woocommerce_show_variation_price', '__return_true' );
// delete transients
add_action( 'woocommerce_delete_product_transients', array( $this, 'delete_transients' ) );
}
and the function which im trying to remove:
/**
* Add a message about the points to the product summary
*
* #since 1.2.6
*/
public function add_variation_message_to_product_summary() {
global $product;
// make sure the product has variations (otherwise it's probably a simple product)
if ( method_exists( $product, 'get_available_variations' ) ) {
// get variations
$variations = $product->get_available_variations();
// find the variation with the most points
$points = $this->get_highest_points_variation( $variations, $product->get_id() );
$message = '';
// if we have a points value let's create a message; other wise don't print anything
if ( $points ) {
$message = $this->create_variation_message_to_product_summary( $points );
}
echo $message;
}
}
EDIT
I forgot to mention that if you comment out this then the info im trying to remove is actually removed.
//add_action( 'woocommerce_before_add_to_cart_button', array( $this, 'add_variation_message_to_product_summary' ), 25 );
Additional info
I didn't mention this before because i thought it would only bloat the question but the goal is to remove it from where it is (before add to cart) and add it back in under the price, so effectively moving it up the page.
I didn't include this before as i thought it would be a simple remove add action.
add_filter( 'wc_points_rewards_single_product_message',
'remove_rewards_earn_points_message_single', 15, 2 );
function remove_rewards_earn_points_message_single( $message, $data ) {
if( is_product() )
return '';
return $message;
}
Can be removed by this, but i don`t know how to add it back to another location (

remove_action From PHP Class in WooCommerce Memberships

I have previously used a solution described here: remove_action From PHP Class for removing an action in the WooCommerce membership plugin.
However, the solution no longer works, as WooComemerce have changed the code behind the membership plugin.
So this is the new code.
Main woocommerce-memberships.php
public function includes() {
// load post types
require_once( $this->get_plugin_path() . '/includes/class-wc-memberships-post-types.php' );
// load user messages helper
require_once( $this->get_plugin_path() . '/includes/class-wc-memberships-user-messages.php' );
// load helper functions
require_once( $this->get_plugin_path() . '/includes/functions/wc-memberships-functions.php' );
// init general classes
$this->rules = $this->load_class( '/includes/class-wc-memberships-rules.php', 'WC_Memberships_Rules' );
$this->plans = $this->load_class( '/includes/class-wc-memberships-membership-plans.php', 'WC_Memberships_Membership_Plans' );
$this->emails = $this->load_class( '/includes/class-wc-memberships-emails.php', 'WC_Memberships_Emails' );
$this->user_memberships = $this->load_class( '/includes/class-wc-memberships-user-memberships.php', 'WC_Memberships_User_Memberships' );
$this->capabilities = $this->load_class( '/includes/class-wc-memberships-capabilities.php', 'WC_Memberships_Capabilities' );
$this->member_discounts = $this->load_class( '/includes/class-wc-memberships-member-discounts.php', 'WC_Memberships_Member_Discounts' );
$this->restrictions = $this->load_class( '/includes/class-wc-memberships-restrictions.php', 'WC_Memberships_Restrictions' );
Main instance
function wc_memberships() {
return WC_Memberships::instance();
}
From included class-wc-memberships-restrictions.php file
/**
* Returns the general content restrictions handler.
*
* #since 1.9.0
*
* #return null|\WC_Memberships_Posts_Restrictions
*/
public function get_posts_restrictions_instance() {
if ( ! $this->posts_restrictions instanceof WC_Memberships_Posts_Restrictions ) {
$this->posts_restrictions = wc_memberships()->load_class( '/includes/frontend/class-wc-memberships-posts-restrictions.php', 'WC_Memberships_Posts_Restrictions' );
}
return $this->posts_restrictions;
}
Then in class-wc-memberships-posts-restrictions.php
public function __construct() {
// decide whether attempting to access restricted content has to be redirected
add_action( 'wp', array( $this, 'handle_restriction_modes' ) );
// restrict the post by filtering the post object and replacing the content with a message and maybe excerpt
add_action( 'the_post', array( $this, 'restrict_post' ), 0 );
How do i remove the 'the_post' action?
So far i have the following in functions.php theme file:
function weteach_remove_actions(){
if(is_singular( 'post' )) {
if( function_exists( 'wc_memberships' ) ){
remove_action( 'the_post', array( wc_memberships()->restrictions, 'restrict_post' ));
}
}
return;
}
add_action( 'the_post', 'weteach_remove_actions', 1 );
Which gives me a "blank-page"-error.
Could you tell us what the error message was? My guess is that restrictions and post_restrictions aren't the same property and so you aren't finding the restrict_post method in the right class.
Edited now that I have looked at Memberships, this seems to work for me:
function so_41431558_remove_membership_post_restrictions(){
if( function_exists( 'wc_memberships' ) && version_compare( WC_Memberships::VERSION, '1.9.0', '>=' ) && is_singular( 'post' ) ){
remove_action( 'the_post', array( wc_memberships()->get_restrictions_instance()->get_posts_restrictions_instance(), 'restrict_post' ), 0 );
}
}
add_action( 'wp_head', 'so_41431558_remove_membership_post_restrictions', 1 );
Your add_action attempt is happening on priority 1, which is after the function has already run the Memberships method on priority 0, so even if the rest of your code was correct it would be too late.
So 1. I think we need to go to an earlier hook.
And 2. I think we need to use the new method for accessing the post restrictions class instance.
edited to add
and 3. I've switched to a direct version compare condition
and 4. I misread where the get_posts_restrictions_instance() method was... it is accessed via wc_memberships()->get_restrictions_instance()->get_posts_restrictions_instance()

Categories