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;
}
Related
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 );
I am trying to add an include a template file located in my active child theme:
childtheme/woocommerce/myaccount/order-a-kit.php
The function use also echo "Hello World" which is displayed successfully, but not the included php template file.
I have tried those:
include($_SERVER['DOCUMENT_ROOT']."twentyseventeen-child/woocommerce/myaccount/order-a-kit.php");
include($get_stylesheet_directory_uri()."twentyseventeen-child/woocommerce/myaccount/order-a-kit.php");
include 'twentyseventeen-child/woocommerce/myaccount/order-a-kit.php';
The content of the order-a-kit.php is super simple, I am just trying to include that file:
<?php
?>
<div>
<p>
Look at me
</p>
</div>
This is my function.php section and everything is doing as it should except the include function towards the bottom:
add_filter( 'woocommerce_account_menu_items', 'add_my_menu_items', 99, 1 );
function add_my_menu_items( $items ) {
$my_items = array(
// endpoint => label
'order-a-kit' => __( 'Order A Kit', 'woocommerce'),
'orders' => __( 'Order History', 'my_plugin' ),
);
$my_items = array_slice( $items, 0, 1, true ) +
$my_items +
array_slice( $items, 1, count( $items ), true );
return $my_items;
}
//adding custom endpoint
function my_custom_endpoints() {
add_rewrite_endpoint( 'order-a-kit', EP_ROOT | EP_PAGES );
}
add_action( 'init', 'my_custom_endpoints' );
function my_custom_query_vars( $vars ) {
$vars[] = 'order-a-kit';
return $vars;
}
add_filter( 'query_vars', 'my_custom_query_vars', 0 );
function my_custom_flush_rewrite_rules() {
flush_rewrite_rules();
}
add_action( 'wp_loaded', 'my_custom_flush_rewrite_rules' );
//including custom endpoint
function my_custom_endpoint_content() {
include 'twentyseventeen-child/woocommerce/myaccount/order-a-kit.php';
echo '<p>Hello World!</p>';
}
add_action( 'woocommerce_account_order-a-kit_endpoint', 'my_custom_endpoint_content' );
?>
Any help is greatly appreciated.
As the "woocommerce" folder is inside your theme as the function.php file you just need to use:
include 'woocommerce/myaccount/order-a-kit.php';
See this related answer: WooCommerce: Adding custom template to customer account pages
Try this
require_once plugin_dir_path( dirname( __FILE__ ) ) . 'woocommerce/myaccount/order-a-kit.php';
You can add this code to your theme's function.php:
class My_Custom_My_Account_Endpoint {
/**
* Custom endpoint name.
*
* #var string
*/
public static $endpoint = 'Your Desired Link';
/**
* Plugin actions.
*/
public function __construct() {
// Actions used to insert a new endpoint in the WordPress.
add_action( 'init', array( $this, 'add_endpoints' ) );
add_filter( 'query_vars', array( $this, 'add_query_vars' ), 0 );
// Change the My Accout page title.
add_filter( 'the_title', array( $this, 'endpoint_title' ) );
// Insering your new tab/page into the My Account page.
add_filter( 'woocommerce_account_menu_items', array( $this, 'new_menu_items' ) );
add_action( 'woocommerce_account_' . self::$endpoint . '_endpoint', array( $this, 'endpoint_content' ) );
}
/**
* Register new endpoint to use inside My Account page.
*
* #see https://developer.wordpress.org/reference/functions/add_rewrite_endpoint/
*/
public function add_endpoints() {
add_rewrite_endpoint( self::$endpoint, EP_ROOT | EP_PAGES );
}
/**
* Add new query var.
*
* #param array $vars
* #return array
*/
public function add_query_vars( $vars ) {
$vars[] = self::$endpoint;
return $vars;
}
/**
* Set endpoint title.
*
* #param string $title
* #return string
*/
public function endpoint_title( $title ) {
global $wp_query;
$is_endpoint = isset( $wp_query->query_vars[ self::$endpoint ] );
if ( $is_endpoint && ! is_admin() && is_main_query() && in_the_loop() && is_account_page() ) {
// New page title.
$title = __( 'Your Item Name', 'woocommerce' );
remove_filter( 'the_title', array( $this, 'endpoint_title' ) );
}
return $title;
}
/**
* Insert the new endpoint into the My Account menu.
*
* #param array $items
* #return array
*/
public function new_menu_items( $items ) {
// Remove the logout menu item.
$logout = $items['customer-logout'];
unset( $items['customer-logout'] );
// Insert your custom endpoint.
$items[ self::$endpoint ] = __( 'Your Item Name', 'woocommerce' );
// Insert back the logout item.
$items['customer-logout'] = $logout;
return $items;
}
/**
* Endpoint HTML content.
*/
public function endpoint_content() {
//example include('woocommerce/myaccount/Your-File.php');
include('Path-To-Your-File.php');
}
/**
* Plugin install action.
* Flush rewrite rules to make our custom endpoint available.
*/
public static function install() {
flush_rewrite_rules();
}
}
new My_Custom_My_Account_Endpoint();
// Flush rewrite rules on plugin activation.
register_activation_hook( __FILE__, array( 'My_Custom_My_Account_Endpoint', 'install' ) );
You have to simply set "Your Desired Link"x1 and "Your Item Name"x2 and "Path-To-Your-File.php"x1 in this source.
If you don't know where is your theme's function.php:
1.Log in to the WordPress Admin interface
2.In the left sidebar, hover over Appearances, then click Theme Editor
3.In the right sidebar, click functions.php
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()
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
}
}
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;
}