Related
I have been stuck on this for weeks now so I'm dropping my question here, hopefully someone can help me. I have a website where people can sell their car. You buy an advertisement and you can upload your vehicle. I have integrated Mollie Payment API so people can pay with iDeal, but it doesn't seem to read the payment status.
When the payment has been successful, my system doesn't give out an advertisement. How can I check if the payment has been successful and then automatically give out an advertisement if the order has been paid?
This is the code that processes the payments:
<?php
namespace AutoListingsFrontend;
require_once __DIR__ . "/../mollie-api-php/vendor/autoload.php";
require_once __DIR__ . "/../mollie-api-php/examples/functions.php";
class Checkout {
/**
* Hold our entire Purchase data
*/
public $purchase_data = array();
public function __construct() {
add_action( 'init', array( $this, 'process_purchase' ) );
add_action( 'auto_listings_mark_as_pending', array( $this, 'pending_payment' ) );
add_action( 'auto_listings_send_to_gateway', array( $this, 'send_to_gateway' ) );
add_action( 'auto_listings_payment_successful', array( $this, 'payment_successful' ), 10, 2 );
add_action( 'auto_listings_insert_payment_note', array( $this, 'insert_payment_note' ), 10, 2 );
add_action( 'auto_listings_update_payment_status', array( $this, 'update_payment_status' ), 10, 2 );
//add_action( 'auto_listings_gateway_paypal', 'auto_listings_process_paypal_purchase' );
}
public function sanitize_post_data() {
// simple sanitizing
foreach ( $_POST as $key => $value ) {
$key = str_replace( 'auto-listings-', '', $key );
if( $key == 'user-id' ) {
$sanitized[ $key ] = absint( $value );
} else {
$sanitized[ $key ] = sanitize_text_field( $value );
}
}
return $sanitized;
}
/**
* Process Purchase Form
*
* Handles the purchase form process.
*/
public function process_purchase() {
do_action( 'auto_listings_pre_process_purchase' );
// Check if there is $_POST
if ( empty( $_POST ) ) return false;
if ( ! isset( $_POST['auto-listings-gateway'] ) || empty( $_POST['auto-listings-gateway'] ) )
return false;
if ( ! isset( $_POST['auto-listings-package'] ) || empty( $_POST['auto-listings-package'] ) )
return false;
if ( ! isset( $_POST['auto-listings-user-id'] ) || empty( $_POST['auto-listings-user-id'] ) )
return false;
$data = $this->sanitize_post_data();
// Verify there is a user_ID
if ( $data['user-id'] > 0 ) {
// Get the logged in user data
$user = get_userdata( $data['user-id'] );
// Verify data
if ( ! $user ) {
return false;
}
}
// Setup user information
$user_info = array(
'id' => $user->ID,
'email' => $user->user_email,
'first_name' => $user->first_name,
'last_name' => $user->last_name,
);
// Setup package information
$package_info = auto_listings_get_package( $data['package'] );
// Set up the unique purchase key.
$key = strtolower( md5( $user_info['id'] . date( 'Y-m-d H:i:s' ) . uniqid( 'auto', true ) ) );
// Setup purchase information
$purchase_data = array(
'package' => stripslashes_deep( $package_info ),
'purchase_key' => $key,
'user' => stripslashes_deep( $user_info ),
'date' => date( 'Y-m-d H:i:s', current_time( 'timestamp' ) ),
'gateway' => $data['gateway'],
);
// If the total amount in the cart is 0, send to the manual gateway.
if ( ! $purchase_data['package']['price'] ) {
$purchase_data['gateway'] = 'manual';
}
// Allow the purchase data to be modified before it is sent to the gateway
$this->purchase_data = apply_filters(
'auto_listings_purchase_data_before_gateway',
$purchase_data,
$data
);
// Send info to create the pending payment
// Send info to the gateway for payment processing
do_action( 'auto_listings_mark_as_pending' );
do_action( 'auto_listings_send_to_gateway' );
}
/**
* Sends all the payment data to the specified gateway.
*/
public function send_to_gateway() {
$this->purchase_data['gateway_nonce'] = wp_create_nonce( 'auto-listings-gateway' );
// $gateway must match the ID used when registering the gateway
do_action( 'auto_listings_gateway_' . $this->purchase_data['gateway'], $this->purchase_data );
}
/**
* Insert Pending Payment
*
* #param array $payment_data Payment data to process
* #return int|bool Payment ID if payment is inserted, false otherwise
*/
public function pending_payment() {
if ( empty( $this->purchase_data ) ) {
return false;
}
$payment_title = $this->purchase_data['user']['email'];
if ( $purchase_data['gateway'] = 'ideal' ) {
/*
* Initialize the Mollie API library with your API key.
*w
* See: https://www.mollie.com/dashboard/developers/api-keys
*/
$mollie = new \Mollie\Api\MollieApiClient();
$mollie->setApiKey("MOLLIE_APIKEY"); // change to LIVE key when done
$packages = auto_listings_get_packages();
foreach ( $packages as $package_id => $package ){
$orderId = $payment->id;
$prijs = '';
if(isset($_POST["auto-listings-purchase"])) {
$package_id = $_POST['auto-listings-package'];
if($package_id == "3769") {
$prijs = "10.00";
} elseif ($package_id == "3767") {
$prijs = "40.00";
} else {
echo "Something went wrong. Please contact our staff via: info#vagplace.nl.";
}
}
// Create Mollie payment
$payment = $mollie->payments->create([
"amount" => [
"currency" => "EUR",
"value" => $prijs,
],
"description" => "VAGplace order: ".$orderId,
"redirectUrl" => "https://vagplace.nl/mijn-autos/?payment=success&gateway=ideal&order_id=".$orderId,
"webhookUrl" => "https://vagplace.nl/mollie-webhook",
"method" => \Mollie\Api\Types\PaymentMethod::IDEAL,
"metadata" => [
"order_id" => $orderId,
],
]);
database_write($orderId, $payment->status);
/*
* Send the customer off to complete the payment.
* This request should always be a GET, thus we enforce 303 http response code
*/
header("Location: " . $payment->getCheckoutUrl(), true, 303);
}
}
$payment_post = array(
'post_title' => $payment_title,
'post_status' => 'pending',
'post_date' => $this->purchase_data['date'],
'post_type' => 'package-payment',
'post_content' => '',
'meta_input' => array(
'_al_payment_data' => stripslashes_deep( $this->purchase_data ),
'_al_payment_package_status' => 'pending',
'_al_payment_user_id' => $this->purchase_data['user']['id'],
),
);
$payment_id = wp_insert_post( $payment_post );
if ( ! empty( $payment_id ) ) {
$this->purchase_data['payment_id'] = $payment_id;
}
// Return false if no payment was inserted
return false;
}
/**
* Add a note to a payment
*
* #param int $payment_id The payment ID to store a note for
* #param string $note The note to store
* #return int The new note ID
*/
public function insert_payment_note( $payment_id = 0, $note = '' ) {
if ( empty( $payment_id ) )
return false;
$existing_data = get_post_meta( $payment_id, '_al_payment_data', true );
do_action( 'auto_listings_pre_insert_payment_note', $payment_id, $note );
$commentdata = array(
'comment_post_ID' => $payment_id, // to which post the comment will show up
'comment_author' => '', //fixed value - can be dynamic
'comment_author_email' => $existing_data['user']['email'], //fixed value - can be dynamic
'comment_author_url' => '', //fixed value - can be dynamic
'comment_content' => $note['heading'] . ' - ' . $note['content'], //fixed value - can be dynamic
'comment_type' => '', //empty for regular comments, 'pingback' for pingbacks, 'trackback' for trackbacks
'comment_parent' => 0, //0 if it's not a reply to another comment; if it's a reply, mention the parent comment ID here
'user_id' => get_current_user_id() ? get_current_user_id() : 1, //passing current user ID or any predefined as per the demand
);
//Insert new comment and get the comment ID
$note_id = wp_new_comment( $commentdata );
return $note_id;
}
/**
* Updates a payment status.
*
* #param int $payment_id Payment ID
* #param string $new_status New Payment Status (default: publish)
* #return bool If the payment was successfully updated
*/
public function update_payment_status( $payment_id = 0, $new_status = 'publish' ) {
if ( empty( $payment_id ) ) {
return false;
}
if ( empty( $data ) ) {
return false;
}
//Trying to verify payment
//$payment = $mollie->payments->get($payment->id);
$payment = $mollie->payments->get($_POST["id"]);
$orderId = $payment->metadata->order_id;
if ($payment->isPaid())
{
echo "Payment received.";
}
$post_arr = array(
'ID' => $payment_id,
'post_status' => $new_status,
);
$updated = wp_update_post( $post_arr );
return $updated;
}
/**
* What to do when a payment completes successfully
*
* #param array $data payment success data, sent from gateway
* #return bool If the payment was successfully updated
*/
public function payment_successful( $data ) {
$start_time = current_time( 'timestamp', $gmt = 0 );
$end_time = null;
if( $data['package_id']['duration'] > 0 ) {
$end_time = strtotime( '+' . $data['package_id']['duration'] . ' days', date( 'Y-m-d H:i:s', $start_time ) );
}
update_post_meta( $data['payment_id'], '_al_payment_package_time_start', $start_time );
update_post_meta( $data['payment_id'], '_al_payment_package_time_end', $end_time );
update_post_meta( $data['payment_id'], '_al_payment_package_status', 'active' );
update_post_meta( $data['payment_id'], '_al_payment_package_listings', $data['package_id']['listings'] );
update_post_meta( $data['payment_id'], '_al_payment_package_listings_used', '0' );
}
}
Solved. I used the Mollie Webhook to check the status of the payment: https://github.com/mollie/mollie-api-php/blob/master/examples/payments/webhook.php.
I've built my first shipping method plugin based on flat rate with a few extra fields.
I have done the following:
1. Installed and activated the plugin
2. Added 2 instances of the shipping method to the UK zone
I can see in the top sub menu in the shipping section there appears to be some kind of "default" instance of the shipping plugin in a menu labelled "UK Flat Rate"
I was wondering if there's a way to remove this and ONLY have the plugin work in the shipping zones section.
The reason I ask is that then in checkout if I enter a UK address I see the 2 UK methods defined and then underneath them both there is also a radio button for UK Flat Rate which I'm trying to get rid of. It shows the default values based on the values entered in the sub-section.
if (!defined('ABSPATH')) {
exit;
}
if (in_array('woocommerce/woocommerce.php', apply_filters('active_plugins', get_option('active_plugins')))) {
add_action( 'woocommerce_shipping_init', 'uk_shipping_method');
function uk_shipping_method() {
if (!class_exists('UK_WC_Shipping_Flat_Rate')) {
class UK_WC_Shipping_Flat_Rate extends WC_Shipping_Method {
/** #var string cost passed to [fee] shortcode */
protected $fee_cost = '';
/**
* Constructor.
*
* #param int $instance_id
*/
public function __construct($instance_id = 1) {
$this->id = 'uk_flat_rate';
$this->instance_id = absint($instance_id);
$this->enabled = "yes"; // This can be added as an setting but for this example its forced enabled
$this->method_title = __('UK Flat Rate');
$this->title = 'UK Flat Rate';
$this->method_description = __('Lets you charge a fixed rate for shipping but flags for UK Status Update.');
$this->supports = array(
'shipping-zones',
'instance-settings',
'instance-settings-modal',
);
$this->init();
add_action('woocommerce_update_options_shipping_' . $this->id, array( $this, 'process_admin_options'));
}
/**
* init user set variables.
*/
public function init() {
$this->instance_form_fields = include('includes/settings-flat-rate.php');
$this->title = $this->get_option( 'title' );
$this->tax_status = $this->get_option( 'tax_status' );
$this->cost = $this->get_option( 'cost' );
$this->type = $this->get_option( 'type', 'class' );
}
/**
* Evaluate a cost from a sum/string.
* #param string $sum
* #param array $args
* #return string
*/
protected function evaluate_cost( $sum, $args = array() ) {
include_once( WC()->plugin_path() . '/includes/libraries/class-wc-eval-math.php' );
// Allow 3rd parties to process shipping cost arguments
$args = apply_filters( 'woocommerce_evaluate_shipping_cost_args', $args, $sum, $this );
$locale = localeconv();
$decimals = array( wc_get_price_decimal_separator(), $locale['decimal_point'], $locale['mon_decimal_point'], ',' );
$this->fee_cost = $args['cost'];
// Expand shortcodes
add_shortcode( 'fee', array( $this, 'fee' ) );
$sum = do_shortcode( str_replace(
array(
'[qty]',
'[cost]',
),
array(
$args['qty'],
$args['cost'],
),
$sum
) );
remove_shortcode( 'fee', array( $this, 'fee' ) );
// Remove whitespace from string
$sum = preg_replace( '/\s+/', '', $sum );
// Remove locale from string
$sum = str_replace( $decimals, '.', $sum );
// Trim invalid start/end characters
$sum = rtrim( ltrim( $sum, "\t\n\r\0\x0B+*/" ), "\t\n\r\0\x0B+-*/" );
// Do the math
return $sum ? WC_Eval_Math::evaluate( $sum ) : 0;
}
/**
* Work out fee (shortcode).
* #param array $atts
* #return string
*/
public function fee( $atts ) {
$atts = shortcode_atts( array(
'percent' => '',
'min_fee' => '',
'max_fee' => '',
), $atts, 'fee' );
$calculated_fee = 0;
if ( $atts['percent'] ) {
$calculated_fee = $this->fee_cost * ( floatval( $atts['percent'] ) / 100 );
}
if ( $atts['min_fee'] && $calculated_fee < $atts['min_fee'] ) {
$calculated_fee = $atts['min_fee'];
}
if ( $atts['max_fee'] && $calculated_fee > $atts['max_fee'] ) {
$calculated_fee = $atts['max_fee'];
}
return $calculated_fee;
}
/**
* calculate_shipping function.
*
* #param array $package (default: array())
*/
public function calculate_shipping( $package = array() ) {
$rate = array(
'id' => $this->get_rate_id(),
'label' => $this->title,
'cost' => 0,
'package' => $package,
);
// Calculate the costs
$has_costs = false; // True when a cost is set. False if all costs are blank strings.
$cost = $this->get_option('cost');
if ( '' !== $cost ) {
$has_costs = true;
$rate['cost'] = $this->evaluate_cost( $cost, array(
'qty' => $this->get_package_item_qty( $package ),
'cost' => $package['contents_cost'],
) );
}
// Add shipping class costs.
$shipping_classes = WC()->shipping->get_shipping_classes();
if ( ! empty( $shipping_classes ) ) {
$found_shipping_classes = $this->find_shipping_classes( $package );
$highest_class_cost = 0;
foreach ( $found_shipping_classes as $shipping_class => $products ) {
// Also handles BW compatibility when slugs were used instead of ids
$shipping_class_term = get_term_by( 'slug', $shipping_class, 'product_shipping_class' );
$class_cost_string = $shipping_class_term && $shipping_class_term->term_id ? $this->get_option( 'class_cost_' . $shipping_class_term->term_id, $this->get_option( 'class_cost_' . $shipping_class, '' ) ) : $this->get_option( 'no_class_cost', '' );
if ( '' === $class_cost_string ) {
continue;
}
$has_costs = true;
$class_cost = $this->evaluate_cost( $class_cost_string, array(
'qty' => array_sum( wp_list_pluck( $products, 'quantity' ) ),
'cost' => array_sum( wp_list_pluck( $products, 'line_total' ) ),
) );
if ( 'class' === $this->type ) {
$rate['cost'] += $class_cost;
} else {
$highest_class_cost = $class_cost > $highest_class_cost ? $class_cost : $highest_class_cost;
}
}
if ( 'order' === $this->type && $highest_class_cost ) {
$rate['cost'] += $highest_class_cost;
}
}
// Add the rate
if ( $has_costs ) {
$this->add_rate( $rate );
}
/**
* Developers can add additional flat rates based on this one via this action since #version 2.4.
*
* Previously there were (overly complex) options to add additional rates however this was not user.
* friendly and goes against what Flat Rate Shipping was originally intended for.
*
* This example shows how you can add an extra rate based on this flat rate via custom function:
*
* add_action( 'woocommerce_flat_rate_shipping_add_rate', 'add_another_custom_flat_rate', 10, 2 );
*
* function add_another_custom_flat_rate( $method, $rate ) {
* $new_rate = $rate;
* $new_rate['id'] .= ':' . 'custom_rate_name'; // Append a custom ID.
* $new_rate['label'] = 'Rushed Shipping'; // Rename to 'Rushed Shipping'.
* $new_rate['cost'] += 2; // Add $2 to the cost.
*
* // Add it to WC.
* $method->add_rate( $new_rate );
* }.
*/
do_action( 'woocommerce_' . $this->id . '_shipping_add_rate', $this, $rate );
}
/**
* Get items in package.
* #param array $package
* #return int
*/
public function get_package_item_qty( $package ) {
$total_quantity = 0;
foreach ( $package['contents'] as $item_id => $values ) {
if ( $values['quantity'] > 0 && $values['data']->needs_shipping() ) {
$total_quantity += $values['quantity'];
}
}
return $total_quantity;
}
/**
* Finds and returns shipping classes and the products with said class.
* #param mixed $package
* #return array
*/
public function find_shipping_classes( $package ) {
$found_shipping_classes = array();
foreach ( $package['contents'] as $item_id => $values ) {
if ( $values['data']->needs_shipping() ) {
$found_class = $values['data']->get_shipping_class();
if ( ! isset( $found_shipping_classes[ $found_class ] ) ) {
$found_shipping_classes[ $found_class ] = array();
}
$found_shipping_classes[ $found_class ][ $item_id ] = $values;
}
}
return $found_shipping_classes;
}
}
}
function add_uk_shipping_method( $methods ) {
$methods['uk_flat_rate'] = 'UK_WC_Shipping_Flat_Rate';
return $methods;
}
add_filter( 'woocommerce_shipping_methods', 'add_uk_shipping_method' );
}
}
Is there a setting in the plugin I'm missing to enforce the method is only zones based?
Turns out I needed to add filters to remove the section and the first instance of the method from shipping like so
woocommerce_shipping_option_remove( $section ) {
unset($section['uk_flat_rate']);
return $section;
}
add_filter( 'woocommerce_get_sections_shipping', 'woocommerce_shipping_option_remove' ,1 );
function woocommerce_shipping_remove_method( $rates )
{
unset($rates['uk_flat_rate:1']);
return $rates;
}
add_filter('woocommerce_package_rates','woocommerce_shipping_remove_method', 100 );
}
}
I'm trying to bypass the single product page so I've created a custom template page for my subscriptions. On this page I'm generating the buttons to either allow users to signup for a specific subscription or switch their subscription. The problem I'm running into is getting the Switch Subscription URL to go to cart instead of the single product page.
The function below will test whether the user is logged in, if they are not show the add subscription to cart URL. If they are show the switch subscription url which I'm trying to just add it to cart ( or go straight to checkout ).
/**
* Get Subscription URL ( Initial or Switch Subscription ) by Subscription ID
*
* #param Integer $subscription_id
*
* #return void
*/
function woo_subscriptions_checkout_url( $subscription_id, $echo = true ) {
$subscription_id = intval( $subscription_id );
$subscription_url = do_shortcode( '[add_to_cart_url id="' . $subscription_id . '"]' );
if( is_user_logged_in() && function_exists( 'wcs_get_users_subscriptions' ) ) {
$user_subscriptions = wcs_get_users_subscriptions();
if( ! empty( $user_subscriptions ) ) {
foreach( $user_subscriptions as $subscription ) {
$subscription_order_id = $subscription->get_parent_id();
$subscription_key = wcs_get_old_subscription_key( $subscription );
if( ! empty( $subscription_key ) ) {
$plan_parent_id = wp_get_post_parent_id( $subscription_id );
$subscription_url = WC_Subscriptions_Switcher::add_switch_query_arg_post_link( get_permalink( wc_get_page_id( 'subscriptions' ) ), $plan_parent_id );
// Failed Test, Goes to Product
// $subscription_url = WC_Subscriptions_Switcher::get_switch_url( $subscription_order_id, array( 'product_id' => $plan_parent_id ), $subscription );
}
}
}
}
if( $echo ) {
echo $subscription_url;
} else {
return $subscription_url;
}
}
Additional information: There is 1 product with subscriptions as variations. I'm passing the Variation ID to this function in hopes of generating the correct URL.
Here's what we got. We can add it to cart with specific URL arguments: Product, User Subscription ID, New Subscription ID, Subscription Order Line Number, and a nonce. Do not we did this on a single product with subscriptions as variations. This may need to be tweaked in other subscription cases but hopefully it's helpful to someone down the road:
/**
* Generate cart button based on subscription variation ID
*
* #param Array $args
*
* #return void
*/
function prefix_subscriptions_checkout_button( $args = array() ) {
$button_arr = wp_parse_args( $args, array(
'variation_id' => 0,
'btn_class' => array( 'button', 'button-primary' ),
'btn_text' => __( 'Sign Up' ),
'btn_atts' => array(),
) );
$button_arr['btn_url'] = do_shortcode( '[add_to_cart_url id="' . intval( $button_arr['variation_id'] ) . '"]' );
if( is_user_logged_in() && function_exists( 'wcs_get_users_subscriptions' ) ) {
// Grab an array of user subscriptions
$user_subscriptions = wcs_get_users_subscriptions();
if( ! empty( $user_subscriptions ) ) {
// Array( 'Subscription ID' => WC_Subscriptions Object );
foreach( $user_subscriptions as $user_subscription_id => $subscription ) {
// Loop through the users subscription order items to get the subscription order line item
foreach( $subscription->get_items() as $item_line_number => $item_arr ) {
if( $user_subscription_id == $item_arr['order_id'] ) {
if( $item_arr['variation_id'] == $button_arr['variation_id'] ) {
// Change button based on status
switch( $subscription->get_status() ) {
case 'on-hold':
$button_arr['btn_text'] = __( 'On Hold' );
$button_arr['btn_class'] = array( 'button', 'button-secondary' );
$button_arr['btn_url'] = 'javascript:void(0);';
break;
case 'active':
$button_arr['btn_text'] = __( 'Current' );
$button_arr['btn_class'] = array( 'button', 'button-secondary' );
$button_arr['btn_url'] = 'javascript:void(0);';
break;
default:
$button_arr['btn_url'] = add_query_arg( array(
'add-to-cart' => $item_arr['product_id'],
'switch-subscription' => $user_subscription_id,
'variation_id' => $button_arr['variation_id'],
'item' => $item_line_number,
'_wcsnonce' => wp_create_nonce( 'wcs_switch_request' )
),
wc_get_cart_url()
);
}
}
}
}
}
}
}
// Create button attributes
$button_atts = '';
if( ! empty( $button_arr['btn_atts'] ) && is_array( $button_arr['btn_atts'] ) ) {
foreach( $button_arr['btn_atts'] as $attribute => $value ) {
$button_atts .= sprintf( ' %1$s="%2$s"', esc_attr( $attribute ), esc_attr( $value ) );
}
}
// Create button Classes
if( ! empty( $button_arr['btn_class'] ) && is_array( $button_arr['btn_class'] ) ) {
array_walk( $button_arr['btn_class'], 'esc_attr' );
$button_arr['btn_class'] = implode( ' ', $button_arr['btn_class'] );
}
// Display Button
printf( '<a href="%1$s" class="%2$s"%3$s>%4$s</a>',
$button_arr['btn_url'],
esc_attr( $button_arr['btn_class'] ),
( ! empty( $button_atts ) ) ? $button_atts : '',
$button_arr['btn_text']
);
}
I'm using WooCommerce in a marketplace website and I am looking for a solution to disable the "downloadable product" functionality. Mainly I want that it doesn't appear in vendor's backend.
By Claudio Sanches (#claudiosanches):
Go to WooCommerce > Settings > Account and clean the downloads endpoint field.
This will disable the downloads page.
I got this answer here By Christophvh .
Go to WooCommerce > Settings > Advanced and remove the entry for Downloads in the Account endpoints section, just leave it Blank. And the menu will not be visible anymore. Just take a look my Attached Image.
function CM_woocommerce_account_menu_items_callback($items) {
unset( $items['downloads'] );
return $items;
}
add_filter('woocommerce_account_menu_items', 'CM_woocommerce_account_menu_items_callback', 10, 1);
Used this in place of the above
This code worked for me. I got it from the Woocommerce Support.
https://wordpress.org/support/topic/remove-virtual-downloadable-products-selection/
function my_remove_product_type_options( $options ) {
// uncomment this if you want to remove virtual too.
// if ( isset( $options['virtual'] ) ) {
// unset( $options['virtual'] );
// }
if ( isset( $options['downloadable'] ) ) {
unset( $options['downloadable'] );
}
return $options;
}
add_filter( 'product_type_options', 'my_remove_product_type_options' );
Not sure if I understood it correctly but if you are willing to remove "Downloads" navigation option from the "My Account" page then continue reading :)
Create Child Theme to your currently used theme. If you are not well known what it is read this: https://codex.wordpress.org/Child_Themes
Now copy navigation.php from ...\wp-content\plugins\woocommerce\templates\myaccount\ to the Child Theme folder ...\wp-content\themes\yourtheme-child\woocommerce\myaccount\
Open navigation.php in your Child theme folder. Find line with function wc_get_account_menu_items() and rename the function to for example wc_get_account_menu_items_custom()
Open functions.php in your Child theme folder. Paste inside the file below function. Save the file and that's all. Now the "My Account" page is without "Downloads" navigation option.
function wc_get_account_menu_items_custom() {
$endpoints = array(
'orders' => get_option( 'woocommerce_myaccount_orders_endpoint', 'orders' ),
'edit-address' => get_option( 'woocommerce_myaccount_edit_address_endpoint', 'edit-address' ),
'payment-methods' => get_option( 'woocommerce_myaccount_payment_methods_endpoint', 'payment-methods' ),
'edit-account' => get_option( 'woocommerce_myaccount_edit_account_endpoint', 'edit-account' ),
'customer-logout' => get_option( 'woocommerce_logout_endpoint', 'customer-logout' ),
);
$items = array(
'dashboard' => __( 'Dashboard', 'woocommerce' ),
'orders' => __( 'Orders', 'woocommerce' ),
'edit-address' => __( 'Addresses', 'woocommerce' ),
'payment-methods' => __( 'Payment Methods', 'woocommerce' ),
'edit-account' => __( 'Account Details', 'woocommerce' ),
'customer-logout' => __( 'Logout', 'woocommerce' ),
);
// Remove missing endpoints.
foreach ( $endpoints as $endpoint_id => $endpoint ) {
if ( empty( $endpoint ) ) {
unset( $items[ $endpoint_id ] );
}
}
// Check if payment gateways support add new payment methods.
if ( isset( $items['payment-methods'] ) ) {
$support_payment_methods = false;
foreach ( WC()->payment_gateways->get_available_payment_gateways() as $gateway ) {
if ( $gateway->supports( 'add_payment_method' ) || $gateway->supports( 'tokenization' ) ) {
$support_payment_methods = true;
break;
}
}
if ( ! $support_payment_methods ) {
unset( $items['payment-methods'] );
}
}
return apply_filters( 'woocommerce_account_menu_items_custom', $items );
}
Note: This is edited original WooCommerce function. There are just deleted array fields mentioning "Downloads" option.
Hope this helps.
Ok so based on all of the answers above, I've put together an "all-in-one" solution which disables:
The download endpoint.
The checkbox options from the admin product editor.
The dropdown options from Product Type Filter.
The downloads metabox from orders.
File: class-disablewoocommerceproducttypes.php
class DisableWooCommerceProductTypes {
/**
* #var array Product types in this property will be disabled.
*/
public $disabled = [
'virtual',
'downloadable'
];
/**
* #var array WooCommerce uses different references for the same product types.
*/
private $aliases = [
'downloadable' => [ 'downloads' ]
];
/**
* #var int The priority of these overrides.
*/
public $priority = PHP_INT_MAX;
/**
* #var null|string|array $product_types Accepts a string or array of 'virtual' and/or 'downloadable' product types.
*/
public function __construct( $product_types = null ) {
if ( $product_types ) {
$this->disabled = (array)$product_types;
}
add_filter( 'product_type_options', [ $this, 'disable_admin_options' ], $this->priority );
add_filter( 'woocommerce_account_menu_items', [ $this, 'disable_frontend_nav_items' ], $this->priority );
add_filter( 'add_meta_boxes', [ $this, 'disable_admin_metabox' ], $this->priority );
add_filter( 'woocommerce_products_admin_list_table_filters', [ $this, 'disable_admin_filters' ], $this->priority );
// Disable the downloads endpoint
if ( $this->is_disabled( 'downloadable' ) ) {
add_filter( 'default_option_woocommerce_myaccount_downloads_endpoint', '__return_null' );
add_filter( 'option_woocommerce_myaccount_downloads_endpoint', '__return_null' );
}
}
/**
* Quickly check if a product type is disabled. Returns primary key if $type is an alias and disabled.
*
* #param string $type
* #param bool $aliases Check for aliases.
* #return bool|string
*/
private function is_disabled( string $type, bool $aliases = true ) {
$basic_check = in_array( $type, $this->disabled );
if ( $aliases && !$basic_check ) {
foreach ( $this->aliases as $_type => $_aliases ) {
if ( in_array( $type, $_aliases ) && $this->is_disabled( $_type, false ) ) {
return $_type;
}
}
}
return $basic_check;
}
/**
* Remove product type checkboxes from product editor.
*
* #param array $types
* #return array
*/
public function disable_admin_options( $types ) {
foreach ( $types as $key => $value ) {
if ( $this->is_disabled( $key ) ) {
unset( $types[ $key ] );
}
}
return $types;
}
/**
* Removes product type page from `wc_get_account_menu_items()`
*
* #param array $items
* #return array
*/
public function disable_frontend_nav_items( $items ) {
foreach ( $items as $key => $value ) {
if ( $this->is_disabled( $key ) ) {
unset( $items[ $key ] );
}
}
return $items;
}
/**
* Removes the downloads metabox from orders.
*/
public function disable_admin_metabox() {
if ( $this->is_disabled( 'downloadable' ) ) {
remove_meta_box( 'woocommerce-order-downloads', 'shop_order', 'normal' );
}
}
/**
* Add our admin product table filter modifier.
*
* #param array $filters
* #return array
*/
public function disable_admin_filters( $filters ) {
if ( isset( $filters[ 'product_type' ] ) ) {
$filters[ 'product_type' ] = [ $this, 'disable_product_type_filters' ];
}
return $filters;
}
/**
* Remove disabled product types from the admin products table filters.
*/
public function disable_product_type_filters() {
$current_product_type = isset( $_REQUEST['product_type'] ) ? wc_clean( wp_unslash( $_REQUEST['product_type'] ) ) : false; // WPCS: input var ok, sanitization ok.
$output = '<select name="product_type" id="dropdown_product_type"><option value="">' . esc_html__( 'Filter by product type', 'woocommerce' ) . '</option>';
foreach ( wc_get_product_types() as $value => $label ) {
$output .= '<option value="' . esc_attr( $value ) . '" ';
$output .= selected( $value, $current_product_type, false );
$output .= '>' . esc_html( $label ) . '</option>';
if ( 'simple' === $value ) {
if ( !$this->is_disabled( 'downloadable' ) ) {
$output .= '<option value="downloadable" ';
$output .= selected( 'downloadable', $current_product_type, false );
$output .= '> ' . ( is_rtl() ? '←' : '→' ) . ' ' . esc_html__( 'Downloadable', 'woocommerce' ) . '</option>';
}
if ( !$this->is_disabled( 'virtual' ) ) {
$output .= '<option value="virtual" ';
$output .= selected( 'virtual', $current_product_type, false );
$output .= '> ' . ( is_rtl() ? '←' : '→' ) . ' ' . esc_html__( 'Virtual', 'woocommerce' ) . '</option>';
}
}
}
$output .= '</select>';
echo $output; // WPCS: XSS ok.
}
}
File: functions.php
include_once get_theme_file_path( 'path/to/class-disablewoocommerceproducttypes.php' );
new DisableWooCommerceProductTypes();
By default the above will disable both downloadable and virtual product types.
To disable a single product type simply pass the type to the class constructor...
// Example usage for just `downloadable` product type
new DisableWooCommerceProductTypes( 'downloadable' );
// Example usage for just `virtual` product type
new DisableWooCommerceProductTypes( 'virtual' );
There are a couple of extra steps to completely remove the functionality.
Reviewing them all:
as per Osmar Sanches and MD Ashik answers, clear the endpoint on WooCommerce > Settings > Advanced > Downloads
add the filter product_type_options as per Maher Aldous answer
remove the "Dropdown Options from Product Type Filter" as per this blog post by Misha Rudrasyth:
add_filter( 'woocommerce_products_admin_list_table_filters', function( $filters ) {
if( isset( $filters[ 'product_type' ] ) ) {
$filters[ 'product_type' ] = 'misha_product_type_callback';
}
return $filters;
});
function misha_product_type_callback(){
$current_product_type = isset( $_REQUEST['product_type'] ) ? wc_clean( wp_unslash( $_REQUEST['product_type'] ) ) : false;
$output = '<select name="product_type" id="dropdown_product_type"><option value="">Filter by product type</option>';
foreach ( wc_get_product_types() as $value => $label ) {
$output .= '<option value="' . esc_attr( $value ) . '" ';
$output .= selected( $value, $current_product_type, false );
$output .= '>' . esc_html( $label ) . '</option>';
}
$output .= '</select>';
echo $output;
}
Remove the metabox "Downloadable product permissions" from Orders (edit and new):
add_filter('add_meta_boxes', function() {
remove_meta_box('woocommerce-order-downloads', 'shop_order', 'normal');
}, 99 );
CSS fix... no tampering with the functions.
.woocommerce-MyAccount-navigation-link--downloads {
display: none;
}
Was having the same problem and just fixed it.
Open this file:
...\www\Your_website_folder\wp-content\plugins\woocommerce\includes\wc_account-functions.php
now search for the wc_get_account_menu_items() function (line 78)
now replace this line (line 91)
'downloads' => __( 'Downloads', 'woocommerce' ),
with this one
/* 'downloads' => __( 'Downloads', 'woocommerce' ),*/
That's it.
To get the ball rolling I used this sitepoint template to put together a custom table to use in a plugin on an admin options page...So far so good, but the stock plugin is designed to live on its own page...the thing is I need it to live on an existing page...though when I tried to move the code over into the specific file but it doesn't seem to fire at all. There is more code of course, I'm just listing the relevant portion that creates the menu page in the admin menu....so I ask how do I add this into an existing page/action rather than create a new page?
...
class SP_Plugin {
// class instance
static $instance;
// customer WP_List_Table object
public $customers_obj;
// class constructor
public function __construct() {
add_filter( 'set-screen-option', [ __CLASS__, 'set_screen' ], 10, 3 );
add_action( 'admin_menu', [ $this, 'plugin_menu' ] );
}
public static function set_screen( $status, $option, $value ) {
return $value;
}
public function plugin_menu() {
$hook = add_menu_page(
'Sitepoint WP_List_Table Example',
'SP WP_List_Table',
'manage_options',
'wp_list_table_class',
[ $this, 'plugin_settings_page' ]
);
add_action( "load-$hook", [ $this, 'screen_option' ] );
}
/**
* Plugin settings page
*/
public function plugin_settings_page() {
?>
<div class="wrap">
<h2>WP_List_Table Class Example</h2>
<div id="poststuff">
<div id="post-body" class="metabox-holder columns-2">
<div id="post-body-content">
<div class="meta-box-sortables ui-sortable">
<form method="post">
<?php
$this->customers_obj->prepare_items();
$this->customers_obj->display(); ?>
</form>
</div>
</div>
</div>
<br class="clear">
</div>
</div>
<?php
}
/**
* Screen options
*/
public function screen_option() {
$option = 'per_page';
$args = [
'label' => 'Records',
'default' => 5,
'option' => 'customers_per_page'
];
add_screen_option( $option, $args );
$this->customers_obj = new Customers_List();
}
/** Singleton instance */
public static function get_instance() {
if ( ! isset( self::$instance ) ) {
self::$instance = new self();
}
return self::$instance;
}
}
add_action( 'plugins_loaded', function () {
SP_Plugin::get_instance();
} );
The code used to initialize the my admin page for the plugin:
add_action('load_admin_faucet_page', 'plugin_settings_page');
function faucet_admin_page_container(){
require '../../../myfolder/myfile.php';
}
From there I thought it was going to be as simple as:
add_action('load_admin_faucet_page', 'plugin_settings_page')
But well...no dice and here we are.
Edit 1: as requested, here is the entire affected code portion that I was trying to load on myfile.php (not everything, just the class to extend wp_list_table - I would keep the actions on the main plugin file as it is now)...now I'm simply trying to keep it on the main plugin file and have it load on that specific page.
function load_admin_faucet_page(){
add_menu_page('Faucet Settings', 'Faucet', 'manage_options', 'bitcoinfaucet-settings', 'faucet_admin_page_container', 'dashicons-welcome-view-site', 1);
}
function faucet_admin_page_container(){
require '../../myfile.php';
}
...
add_action('admin_menu', 'load_admin_faucet_page');
add_action('load_admin_faucet_page', 'plugin_settings_page')
//allow redirection, even if my theme starts to send output to the browser
add_action('init', 'do_output_buffer');
function do_output_buffer() {
ob_start();
}
...
if ( ! class_exists( 'WP_List_Table' ) ) {
require_once('/wp-admin/includes/class-wp-list-table.php' );
}
class Customers_List extends WP_List_Table {
/** Class constructor */
public function __construct() {
parent::__construct( array(
'singular' => __( 'Record', 'sp' ), //singular name of the listed records
'plural' => __( 'Records', 'sp' ), //plural name of the listed records
'ajax' => false //does this table support ajax?
) );
}
/**
* Retrieve customers data from the database
*
* #param int $per_page
* #param int $page_number
*
* #return mixed
*/
public static function get_customers( $per_page = 5, $page_number = 1 ) {
global $wpdb;
$sql = "SELECT * FROM table";
if ( ! empty( $_REQUEST['orderby'] ) ) {
$sql .= ' ORDER BY ' . esc_sql( $_REQUEST['orderby'] );
$sql .= ! empty( $_REQUEST['order'] ) ? ' ' . esc_sql( $_REQUEST['order'] ) : ' ASC';
}
$sql .= " LIMIT $per_page";
$sql .= ' OFFSET ' . ( $page_number - 1 ) * $per_page;
$result = $wpdb->get_results( $sql, 'ARRAY_A' );
return $result;
}
/**
* Associative array of columns
*
* #return array
*/
function get_columns() {
$columns = array(
'cb' => '<input type="checkbox" />',
'id' => __( 'Id Number', 'sp' ),
'date' => __( 'Date', 'sp' ),
'user' => __( 'Address', 'sp' ),
'amount' => __( 'Amount', 'sp'),
'message' => __( 'Message', 'sp' ),
);
return $columns;
}
/**
* Columns to make sortable.
*
* #return array
*/
public function get_sortable_columns() {
$sortable_columns = array(
'user' => array( 'user', false ),
'date' => array( 'date', false ),
'id' => array( 'id', true )
);
return $sortable_columns;
}
/**
* Returns the count of records in the database.
*
* #return null|string
*/
public static function record_count() {
global $wpdb;
$sql = "SELECT COUNT(*) FROM table";
return $wpdb->get_var( $sql );
}
/** Text displayed when no customer data is available */
public function no_items() {
_e( 'No customers avaliable.', 'sp' );
}
/**
* Render a column when no column specific method exist.
*
* #param array $item
* #param string $column_name
*
* #return mixed
*/
public function column_default( $item, $column_name ) {
switch ( $column_name ) {
case 'id':
case 'user':
case 'amount':
case 'date':
case 'message':
return $item[ $column_name ];
default:
return print_r( $item, true ); //Show the whole array for troubleshooting purposes
}
}
/**
* Render the bulk edit checkbox
*
* #param array $item
*
* #return string
*/
function column_cb( $item ) {
return sprintf(
'<input type="checkbox" name="my_CheckBoxes[]" value="%s" />', $item['id']
);
}
/**
* Method for name column
*
* #param array $item an array of DB data
*
* #return string
*/
function column_name( $item ) {
$delete_nonce = wp_create_nonce( 'sp_delete_customer' );
$reset_nonce = wp_create_nonce( 'sp_reset_payouts' );
$title = '<strong>' . $item['name'] . '</strong>';
$actions = [
'delete' => sprintf( 'Delete', esc_attr( $_REQUEST['page'] ), 'delete', absint( $item['id'] ), $delete_nonce ),
'reset' => sprintf( 'Reset', esc_attr( $_REQUEST['page'] ), 'reset', absint( $item['id'] ), $reset_nonce )
];
return $title . $this->row_actions( $actions );
}
/**
* Returns an associative array containing the bulk action
*
* #return array
*/
public function get_bulk_actions() {
$actions = array(
'bulk-delete' => 'Delete',
'bulk-reset' => 'Reset'
);
return $actions;
}
/**
* Handles data query and filter, sorting, and pagination.
*/
public function prepare_items() {
$this->_column_headers = $this->get_column_info();
/** Process bulk action */
$this->process_bulk_action();
$per_page = $this->get_items_per_page( 'customers_per_page', 5 );
$current_page = $this->get_pagenum();
$total_items = self::record_count();
$this->set_pagination_args( [
'total_items' => $total_items, //WE have to calculate the total number of items
'per_page' => $per_page //WE have to determine how many items to show on a page
] );
$this->items = self::get_customers( $per_page, $current_page );
}
/**
* Delete a customer record.
*
* #param int $id customer ID
*/
public static function delete_customer( $id ) {
global $wpdb;
$wpdb->delete(
"table",
array( 'id' => $id ),
array('%d' )
);
}
/**
* Reset Message To Null.
*
* #param int $id customer ID
*/
public static function reset_record( $id ) {
global $wpdb;
$wpdb->update(
"table",
array('message' => NULL),
array('id' => $id )
);
}
public function process_bulk_action() {
//Detect when a bulk action is being triggered...
if ( 'delete' === $this->current_action()){
// In our file that handles the request, verify the nonce.
$nonce = esc_attr( $_REQUEST['_wpnonce'] );
if ( ! wp_verify_nonce( $nonce, 'sp_delete_customer' ) ) {
die( 'Go get a life script kiddies' );
}
else {
self::delete_customer( absint( $_GET['customer'] ) );
wp_redirect( esc_url( add_query_arg() ) );
exit;
}
}
//Detect when a Reset is being triggered...
if ( 'reset' === $this->current_action()){
// In our file that handles the request, verify the nonce.
$nonce = esc_attr( $_REQUEST['_wpnonce'] );
if ( ! wp_verify_nonce( $nonce, 'sp_reset_payouts' ) ) {
die( 'Go get a life script kiddies' );
}
else {
self::reset_record( absint ( $_GET['customer']));
wp_redirect( esc_url( add_query_arg() ) );
exit;
}
}
// // If the delete bulk action is triggered
if ( ( isset( $_POST['action'] ) && $_POST['action'] == 'bulk-delete' )
|| ( isset( $_POST['action2'] ) && $_POST['action2'] == 'bulk-delete' )
) {
$delete_ids = esc_sql( $_POST['my_CheckBoxes'] );
// loop over the array of record IDs and delete them
foreach ( $delete_ids as $id ) {
self::delete_customer( $id );
}
wp_redirect( esc_url( add_query_arg() ) );
exit;
}
//If the delete bulk action is triggered
if ( ( isset( $_POST['action'] ) && $_POST['action'] == 'bulk-reset' )
|| ( isset( $_POST['action2'] ) && $_POST['action2'] == 'bulk-reset' )
) {
$reset_ids = esc_sql( $_POST['my_CheckBoxes'] );
// loop over the array of record IDs and delete them
foreach ( $reset_ids as $id ) {
self::reset_record( $id );
}
wp_redirect( esc_url( add_query_arg() ) );
exit;
}
}
}
class SP_Plugin {
// class instance
static $instance;
// customer WP_List_Table object
public $customers_obj;
// class constructor
public function __construct() {
add_filter( 'set-screen-option', [ __CLASS__, 'set_screen' ], 10, 3 );
add_action( 'admin_menu', [ $this, 'plugin_menu' ] );
}
public static function set_screen( $status, $option, $value ) {
return $value;
}
public function plugin_menu() {
$hook = add_menu_page(
'Sitepoint WP_List_Table Example',
'SP WP_List_Table',
'manage_options',
'wp_list_table_class',
[ $this, 'plugin_settings_page' ]
);
add_action( "load-$hook", [ $this, 'screen_option' ] );
}
/**
* Plugin settings page
*/
public function plugin_settings_page() {
?>
<div class="wrap">
<h2>WP_List_Table Class Example</h2>
<div id="poststuff">
<div id="post-body" class="metabox-holder columns-2">
<div id="post-body-content">
<div class="meta-box-sortables ui-sortable">
<form method="post">
<?php
$this->customers_obj->prepare_items();
$this->customers_obj->display(); ?>
</form>
</div>
</div>
</div>
<br class="clear">
</div>
</div>
<?php
}
/**
* Screen options
*/
public function screen_option() {
$option = 'per_page';
$args = [
'label' => 'Records',
'default' => 5,
'option' => 'customers_per_page'
];
add_screen_option( $option, $args );
$this->customers_obj = new Customers_List();
}
/** Singleton instance */
public static function get_instance() {
if ( ! isset( self::$instance ) ) {
self::$instance = new self();
}
return self::$instance;
}
}
add_action( 'plugins_loaded', function () {
SP_Plugin::get_instance();
} );