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;
}
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 );
Currently the URL, under the WooCommerce 'My orders' tab, for the button when you view an order in detail is equal to /my-account/view-order/ORDER-ID/
I would like to change this url to /my-account/orders/ORDER-ID/ - So:
current url: /my-account/view-order/ORDER-ID/
new url: /my-account/orders/ORDER-ID/
Therefore, via the WooCommerce settings I changed the view-order endpoint to orders
This works, the url of the button to view the order in detail has effectively been changed, only I get the following message on the order detail page
'No order has been made yet'.
I believe through the following code, which can be found in /plugins/woocommerce/includes/class-wc-order.php | line 1675
/**
* Generates a URL to view an order from the my account page.
*
* #return string
*/
public function get_view_order_url() {
return apply_filters( 'woocommerce_get_view_order_url', wc_get_endpoint_url( 'view-order', $this->get_id(), wc_get_page_permalink( 'myaccount' ) ), $this );
}
I can achieve my goal, but I could use some advice. Anyone who can guide me?
Changing the /my-account/view-order/ORDER-ID/ url to /my-account/orders/ORDER-ID/ can be done by applying a few steps.
Step 1) change the view order endpoint
The URL for each endpoint can be customized in WooCommerce > Settings > Advanced in the page setup section
OR you use the woocommerce_get_endpoint_url filter hook instead
function filter_woocommerce_get_endpoint_url( $url, $endpoint, $value, $permalink ) {
// Specific endpoint
if ( $endpoint === 'view-order' ) {
// New URL
$url = $permalink . 'orders/' . $value;
}
return $url;
}
add_filter( 'woocommerce_get_endpoint_url', 'filter_woocommerce_get_endpoint_url', 10, 4 );
While the endpoint is now changed, you will see the following message when viewing the new url:
'No order has been made yet'.
This is because the /myaccount/orders.php template file is loaded, while this should be the /myaccount/view-order.php template
Step 2) provide that the correct template file is loaded, this for both the /my-account/orders/ and /my-account/orders/ORDER-ID/ url.
Therefore we need to overwrite the existing woocommerce_account_orders() function, which can be found in the /includes/wc-template-functions.php file. So that our custom function is executed
function woocommerce_account_orders( $current_page ) {
global $wp;
// For view-order template file
if ( isset( $wp->query_vars['orders'] ) && is_numeric( $wp->query_vars['orders'] ) ) {
$order_id = $wp->query_vars['orders'];
$order = wc_get_order( $order_id );
if ( ! $order || ! current_user_can( 'view_order', $order_id ) ) {
echo '<div class="woocommerce-error">' . esc_html__( 'Invalid order.', 'woocommerce' ) . ' ' . esc_html__( 'My account', 'woocommerce' ) . '</div>';
return;
}
// Backwards compatibility.
$status = new stdClass();
$status->name = wc_get_order_status_name( $order->get_status() );
wc_get_template(
'myaccount/view-order.php',
array(
'status' => $status, // #deprecated 2.2.
'order' => $order,
'order_id' => $order->get_id(),
)
);
} else {
$current_page = empty( $current_page ) ? 1 : absint( $current_page );
$customer_orders = wc_get_orders(
apply_filters(
'woocommerce_my_account_my_orders_query',
array(
'customer' => get_current_user_id(),
'page' => $current_page,
'paginate' => true,
)
)
);
wc_get_template(
'myaccount/orders.php',
array(
'current_page' => absint( $current_page ),
'customer_orders' => $customer_orders,
'has_orders' => 0 < $customer_orders->total,
)
);
}
}
Important: it is not the intention to change this function by changing the core file!! you can simply add this to functions.php, this is because this function is embedded by if ( ! function_exists( 'woocommerce_account_orders' ) ) {
Step 3) this step is optional. This ensures that the /my-account/view-order/ORDER-ID/ url (which is now the old url) is no longer reachable
function action_woocommerce_account_view_order_endpoint() {
remove_action( 'woocommerce_account_view-order_endpoint', 'woocommerce_account_view_order' );
}
add_action( 'woocommerce_account_view-order_endpoint', 'action_woocommerce_account_view_order_endpoint', 1 );
Code from step 1, 2 and 3 goes in functions.php file of the active child theme (or active theme).
Related: Change title custom "view-order" endpoint in WooCommerce My account
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 would like to add a custom icon to my payment gateway. I have read the WOO gateway API and have zero help. This is my code below. please help me find a functional way to include the icon so I have an icon on my front end. Thanks
<?php if ( ! defined( 'ABSPATH' ) ) { exit; }
add_filter( 'woocommerce_payment_gateways', 'init_wpuw_gateway' );
function init_wpuw_gateway ( $methods )
{
$methods[] = 'WC_Gateway_WPUW';
return $methods;
}
if( class_exists('WC_Payment_Gateway') ):
class WC_Gateway_WPUW extends WC_Payment_Gateway {
/**
* Constructor for the gateway.
*/
public function __construct() {
$plugin_dir = plugin_dir_url(__FILE__);
$this->id = 'wpuw';
//If you want to show an image next to the gateway’s name on the frontend, enter a URL to an image.
$this->icon = apply_filters( 'woocommerce_gateway_icon', ''.$plugin_dir.'/assets/paysecure.png' );
$this->method_title = __( 'User Wallet', 'woocommerce' );
$this->method_description = __( 'Have your customers pay with their user wallet balance.', 'woocommerce' );
$this->has_fields = false;
Try with backslash instead of slash without concatenating the initial empty string with the variable $plugin_dir
$this->icon = apply_filters( 'woocommerce_gateway_icon', $plugin_dir.'\assets\paysecure.png' );
Using the WooCommerce filter woocommerce_gateway_icon you can add the icon to the payment gateway this way:
/**
* Add Custom Icon
*/
function custom_gateway_icon( $icon, $id ) {
if ( $id === 'custom' ) {
return '<img src="' . plugins_url( 'img/custom.png', __FILE__ ) . '" > ';
} else {
return $icon;
}
}
add_filter( 'woocommerce_gateway_icon', 'custom_gateway_icon', 10, 2 );
I would suggest woocommerce_available_payment_gateways filter.
/**
Add Custom Icon For Cash On Delivery
**/
function cod_gateway_icon( $gateways ) {
if ( isset( $gateways['cod'] ) ) {
$gateways['cod']->icon = get_stylesheet_directory_uri() . '/images/cod.png';
}
return $gateways;
}
add_filter( 'woocommerce_available_payment_gateways', 'cod_gateway_icon' );
try this:
$this->icon = trailingslashit( WP_PLUGIN_URL ) . plugin_basename( dirname( __FILE__ ) ) . '/assets/paysecure.png';
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;
}