Do not send Woocommerce new customer email if a condition is true - wordpress

If i add a new customer to Woo using the REST API i need to avoid sending the new customer email.
The REST API docs don't talk about this and there's no parameter to set that can prevent the "WC_Email_Customer_New_Account" email
I've tried about 10 different things, I'll list the most recent ones
Editing the Woo source directly class-wc-emails.php. Not even that works, because when i collect the user meta it's still blank and only has the user ID and nice name
Creating a plugin that checks an external API and if a condition is met does remove_action('woocommerce_created_customer_notification', array($email_class->emails['WC_Email_Customer_New_Account'], 'trigger'));
Processing everything inside the plugin but i have the same problem as 1.

I think I managed to pull it off. My goal was to create a user (customer) via WooCommerce REST API (Customers endpoint) and not to trigger the New Account email.
The first suspect is the woocommerce_email_enabled_customer_new_account filter. It gives us the \WP_User and \WC_Email_Customer_Completed_Order objects. The first idea obviously is to just run get_user_meta() against the user.
add_filter( 'woocommerce_email_enabled_customer_new_account', function( $enabled, $user, $email ) {
/**
* #var bool $enabled
* #var \WP_User $user
* #var \WC_Email_Customer_Completed_Order $email
*/
$isOurGuy = 'foobar' === get_user_meta( $user->ID, 'meta_key_is_so_meta', true );
if ( $isOurGuy ) {
return false;
}
return $enabled;
}, 10, 3 );
As it turns out, the REST endpoint handler can only push metadata after the user is created, and email notification is triggered implicitly via a hook in \WC_Emails(). It means when our code is running during that email hook, no metadata is available yet.
Next thing to check was to go to the moment before the user is pushed anywhere at all. That would be woocommerce_before_data_object_save, an action this time. It gives us two objects, \WC_Customer and \WC_Customer_Data_Store. The \WC_Customer object has our metadata, yay! Let's enclose the existing code into another handler.
add_action( 'woocommerce_before_data_object_save', function( $customer, $store ) {
/**
* #var \WC_Customer $customer
* #var \WC_Customer_Data_Store $store
*/
if ( !( $customer instanceof \WC_Customer ) ) { // I guess other object types may end up here, let's make sure we only get to work with customers.
return;
}
$isOurGuy = 'foobar' === $customer->get_meta( 'meta_key_is_so_meta', true );
if ( !$isOurGuy ) {
return;
}
add_filter( 'woocommerce_email_enabled_customer_new_account', function( $enabled, $user, $email ) use ( $customer ) {
/**
* #var bool $enabled
* #var \WP_User $user
* #var \WC_Email_Customer_Completed_Order $email
*/
if ( $customer->get_id() !== $user->ID ) { // Is it our guy?
return $enabled;
}
return false;
}, 10, 3 );
}, 10, 2 );
It still does not work. Because this is before object save, the user did not exist at the moment when we captured $customer into our scope. So $customer->get_id() returns 0 (zero). It seems that we overshot in time a bit - need to roll forward. woocommerce_created_customer seems like a good candidate. It gives us among other things the new user ID.
Let's compile everything together.
add_action( 'woocommerce_before_data_object_save', function( $customer, $store ) {
/**
* #var \WC_Customer $customer
* #var \WC_Customer_Data_Store $store
*/
if ( !( $customer instanceof \WC_Customer ) ) { // I guess other object types may end up here, let's make sure we only get to work with customers.
return;
}
$isOurGuy = 'foobar' === $customer->get_meta( 'meta_key_is_so_meta', true );
if ( !$isOurGuy ) {
return;
}
/**
* Hook into the Customer Created event to capture the new customer ID.
*/
add_action( 'woocommerce_created_customer', function( $customerId ) {
add_filter( 'woocommerce_email_enabled_customer_new_account', function( $enabled, $user, $email ) use ( $customerId ) {
/**
* #var bool $enabled
* #var \WP_User $user
* #var \WC_Email_Customer_Completed_Order $email
*/
if ( $customerId !== $user->ID ) { // Is it our guy?
return $enabled;
}
return false;
}, 10, 3 );
}, 1 ); // NB: Email is also hooked here with priority 10.
}, 10, 2 );
Now let's recap. We hook into the data store and capture the moment a \WC_Customer object is saved. We perform custom metadata-based logic to decide whether to proceed. Then we skip to the moment when the user is created to retrieve their ID. Then we hop a little further in time during the "email enabled?" check to actually disable the notification for the given user.

Try this
Go to:
WooCommerce -> Settings -> Email tab
You can find the Options are "New account". Click the manage button.
Uncheck the "Enable this email notification".

Related

Getting customer adress in custom shipping method

I'm currently working on a custom shipping method for WooCommerce. The goal is to calculate the shipping based on the adress the customer enters in the order process. We currently have an API that receives a storeId and the adress of a customer and returns back the exact amount of shipping we need to apply to the order.
For this, I've started to write a new custom shipping order plugin. I am already receiving the $packages array with the order, however I can't seem to find a way to receive the customers adress in the calculate_shipping method. There also will be a useraction required to select the prefered warehouse on the reviewOrderBeforePayment page and I am not quite sure how to access that data yet.
<?php
/*
* Plugin Name: Warehouses Integration
* Description: WooCommerce integration of the Warehouses API
* Version: 1.1.0
* Author: -
* Author URI: -
* Developer: Laura Heimann
* Developer URI: -
* Text Domain: woocommerce-extension
* WC requires at least: 4.0.1
* WC tested up to: 4.0.1
*/
// Security Measure
if (!defined('ABSPATH')) {
exit;
}
// Check if WooCommerce is installed
if (!in_array('woocommerce/woocommerce.php', apply_filters('active_plugins', get_option( 'active_plugins')))) {
alert("WooCommerce is not installed!");
}
// Main Render Function
// This will be hooked into the WooCommerce Order Page
function wcfw_render() {
// TODO: Show List of possible stores, let user select and save selected store somehow
echo "WCFWRENDER";
}
function wcfw_renderThankYou() {
echo "WCFWTHANKYOU";
}
function wcfw_renderReviewOrder() {
// TODO: Show warning if items from different stores
echo "WCFWREVIEWORDER";
}
function wcfw_shippingMethodInit() {
if ( ! class_exists( 'WC_WCFW_Shipping_Method' ) ) {
class WC_WCFW_Shipping_Method extends WC_Shipping_Method {
/**
* Constructor for your shipping class
*
* #access public
* #return void
*/
public function __construct() {
$this->id = 'wcfw_shipping_method';
$this->title = __( 'Warehouses Shipping' );
$this->method_description = __( 'Shipping Method through the Warehouse' );
$this->enabled = "yes";
$this->init();
}
/**
* Init your settings
*
* #access public
* #return void
*/
function init() {
// Load the settings API
$this->init_form_fields(); // This is part of the settings API. Override the method to add your own settings
$this->init_settings(); // This is part of the settings API. Loads settings you previously init.
// Save settings in admin if you have any defined
add_action( 'woocommerce_update_options_shipping_' . $this->id, array( $this, 'process_admin_options' ) );
}
/**
* Settings
*/
function init_form_fields() {
$this->form_fields = array(
'apiBase' => array(
'title' => __('Api Base'),
'type' => 'text',
'description' => __('The base URL for API-Calls.'),
'default' => __('---')
),
);
}
/**
* calculate_shipping function.
*
* #access public
* #param mixed $package
* #return void
*/
public function calculate_shipping( $package = array() ) {
var_dump( $package );
// TODO: Get Customers Adress and selected store
// TODO: Send data to API and apply provided shipping
$rate = array(
'label' => __('Shipping by Truck'),
'cost' => '10.99',
'calc_tax' => 'per_item'
);
// Register the rate
$this->add_rate( $rate );
}
}
}
}
function add_wcfw_shipping_method( $methods ) {
$methods[] = 'WC_WCFW_Shipping_Method';
return $methods;
}
// Hooks
add_action('woocommerce_review_order_before_payment', 'wcfw_render', 10);
add_action('woocommerce_thankyou', 'wcfw_renderThankYou', 10);
add_action('woocommerce_after_cart_contents', 'wcfw_renderReviewOrder', 10);
add_action('woocommerce_shipping_init', 'wcfw_shippingMethodInit');
add_filter('woocommerce_shipping_methods', 'add_wcfw_shipping_method');

Trigger UniqueEntity after persisting and before flushing data

I'm importing a kind of csv data via doctrine entity manger, however, I have a loop that performs a batch processing as it is mentioned at the level of the doc.
https://www.doctrine-project.org/projects/doctrine-orm/en/2.7/reference/batch-processing.html#bulk-inserts
$validationErrors = [];
foreach($data as $itrationNumber => $item) {
/** #var User|null $user **/
$user = $this->em->getRepository(User::class)->findOnBy(['email' => $item['email']]);
$user = ($user) ? $user : new User();
$user->setName('test');
$errors = $this->validator->validate($user);
if ($errors->count() === 0) {
$this->persist($user);
} else {
$validationErrors[] = $errors;
}
if ($itratioNumber% 100 === 0) {
$this->em->flush();
}
}
return $validationErrors;
Here is my User class, which has a unique constraint on the email field:
/**
* #ORM\Entity
* #UniqueEntity("email")
*/
class User
{
/**
* #ORM\Column(name="email", type="string", length=255, unique=true)
* #Assert\Email
*/
protected $email;
}
Unfortunately, if there is more than one row in my data that has an identical email address, UniqueEntity validation will not be triggered, simply, because users are persisted but are not flushed into the database.
Solution 1: Avoid the batch and do a flush at each iteration which is very violating and may provoke connection closed doctrine or kind of memory leak.
Solution 2: It was to create a custom constraint, which is inspired by UniqueEntity("email") and then check for each item's email address if there's a user already persisted with the same mail.
The problem that if the user already exists in the database, and we call em-> persist(), I cannot find any persisted object neither in $entityManager->getUnitOfWork()->getScheduledEntityInsertions() nor in $entityManager->getUnitOfWork()->getScheduledEntityUpdates().
Only during a new insertion that I hole the object persisted in the response of the function ->getScheduledEntityInsertions()
I would be very grateful if anyone has any idea how I could recover the entities persisted after the $entityManager->persist() step.
Or simply a 3rd solution which allows me to trigger a validation on the uniqueness of the email even in a batch context.
You should keep a second bit of information to see if an e-mail address was already processed:
$emails = [];
foreach($data as $itrationNumber => $item) {
if (isset($emails[$item['email']])) {
continue;
}
/** #var User|null $user **/
$user = $this->em->getRepository(User::class)->findOnBy(['email' => $item['email']]);
$user = ($user) ? $user : new User();
$user->setName('test');
if ($this->validator->validate($user)->count() === 0) {
$this->persist($user);
$emails[$item['email']] = true;
}
if ($itratioNumber% 100 === 0) {
$this->em->flush();
}
}
This code snippet does not normalize the e-mail address, this should be done first (strtolower and maybe removal of +foo gmail local parts for example).
Except if yo are WILLING to load the whole emails in a memory cache and play around that with REDIS or direct arrays, I can't see a second way but using the DB constraint of uniqueness (i.e doctrine flush)
Regarding your fear of memory overload, you can simply detach the persisted entity from the managed pool by calling the clear method. So your code would look something like this:
foreach($data as $itrationNumber => $item) {
/** #var User|null $user **/
$user = $this->em->getRepository(User::class)->findOnBy(['email' => $item['email']]);
$user = ($user) ? $user : new User();
$user->setName('test');
if ($this->validator->validate($user)->count() === 0) {
$this->persist($user);
$this->em->flush();
}
$this->em->clear();
}
I personnaly used this solution for a batch processing of huge CSV flow, and got inspired from this doctrine documentation section.
P.S I would think about DB transactions but might need more tricks.

Argument 2 passed to App\Providers\AuthServiceProvider::App\Providers\{closure}() must be an instance of App\Post, string given,

Am getting the above error when i try to edit a blog post on my laravel 5.7 App. Any ideas on how to resolve this?
My Post Controller;
public function edit($id)
{
$post = Post::find($id);
return view('posts.edit')->withPost($post);
}
/**
* Update the specified resource in storage.
*
* #param \Illuminate\Http\Request $request
* #param int $id
* #return \Illuminate\Http\Response
*/
public function update(Request $request, $id)
{
// Form validation
$this->validate($request, array(
'title' =>'required|max:190',
'category' =>'required|max:190',
'body' =>'required'
));
//save in db
$post = Post::find($id);
$post->title=$request->input('title');
$post->category=$request->input('category');
$post->body=Purifier::clean($request->input('body'));
$post->save();
Session::flash('success','Blog successfuly updated!');
return redirect()->route('posts.show',$post->id);
}
My AuthServiceProvider;
public function registerPostPolicies()
{
Gate::define('create-post', function($user){
$user->hasAccess(['create-post']);
});
Gate::define('update-post', function($user, Post $post){
$user->hasAccess(['update-post']) or $user ->id ==$post -> user_id;
});
Gate::define('delete-post', function($user, Post $post){
$user->hasAccess(['delete-post']) or $user ->id ==$post -> user_id;
});
}
I have spent several hours trying to resolve the issue, but i dont seem to be making any progress.
Any help will be highly appreciated

Woocommerce Adding Multiple shipping methods

I used the code below (as provided by woocommerce API) to add custom Shipping Method and it is working but now I want to add another shipping method I tried copy pasting same code with different class name but it doesn't work actually the second method is replacing the first one
I want to know how can I create another Shipping method?
Thank you
function your_shipping_method_init() {
if ( ! class_exists( 'WC_Your_Shipping_Method' ) ) {
class WC_Your_Shipping_Method extends WC_Shipping_Method {
/**
* Constructor for your shipping class
*
* #access public
* #return void
*/
public function __construct() {
$this->id = 'vip_rate'; // Id for your shipping method. Should be uunique.
$this->method_title = __( 'VIP Shipping Rate' ); // Title shown in admin
$this->method_description = __( '$35 flate rate' ); // Description shown in admin
$this->enabled = "yes"; // This can be added as an setting but for this example its forced enabled
$this->title = "VIP Shipping rate"; // This can be added as an setting but for this example its forced.
$this->init();
}
/**
* Init your settings
*
* #access public
* #return void
*/
function init() {
// Load the settings API
$this->init_form_fields(); // This is part of the settings API. Override the method to add your own settings
$this->init_settings(); // This is part of the settings API. Loads settings you previously init.
// Save settings in admin if you have any defined
add_action( 'woocommerce_update_options_shipping_' . $this->id, array( $this, 'process_admin_options' ) );
}
/**
* calculate_shipping function.
*
* #access public
* #param mixed $package
* #return void
*/
public function calculate_shipping( $package ) {
$cost=35;
$rate = array(
'id' => $this->id,
'label' => $this->title,
'cost' => round($cost,2),
'calc_tax' => 'per_item'
);
// Register the rate
$this->add_rate( $rate );
}
}
}
}
add_action( 'woocommerce_shipping_init', 'your_shipping_method_init' );
function add_your_shipping_method( $methods ) {
$methods[] = 'WC_Your_Shipping_Method';
return $methods;
}
add_filter( 'woocommerce_shipping_methods', 'add_your_shipping_method' );
I'm having the same issue , my solution was made a new class who extends from WC_Shipping_Method and keep all the same code there and i made 3 new classes extending the new one , any one with their own ID and mothod type
Its not the best solution but its more legant than duplicate N times the same code of the class
OK I have succeeded in adding another Shipping method by renaming Class name.Previously I may be doing something wrong
However I would like to know if there was some better way of doing it because I have copy pasted whole chunk of code twice , My background is not in OOP however I think it is not the proper way of doing this thing

wordpress - how can I block email sending by role?

I have an educational Wordpress site where students have the role 'child' and adults have the role 'subscriber'. I need to prevent emails being sent to the 'child' users (by Woocommerce - but I think they are sent via the mail function in Wordpress).
Is there a line I can add in functions.php to stop mail being sent to specific roles?
Thanks in advance
Maria
I think it might be possible to adjust the email recipients via a filter in the get_recipient() method.
/**
* get_recipient function.
*
* #return string
*/
public function get_recipient() {
return apply_filters( 'woocommerce_email_recipient_' . $this->id, $this->recipient, $this->object );
}
Let's take for example the new order email. Here's it's trigger() method:
/**
* trigger function.
*
* #access public
* #return void
*/
function trigger( $order_id ) {
if ( $order_id ) {
$this->object = wc_get_order( $order_id );
$this->find['order-date'] = '{order_date}';
$this->find['order-number'] = '{order_number}';
$this->replace['order-date'] = date_i18n( wc_date_format(), strtotime( $this->object->order_date ) );
$this->replace['order-number'] = $this->object->get_order_number();
}
if ( ! $this->is_enabled() || ! $this->get_recipient() ) {
return;
}
$this->send( $this->get_recipient(), $this->get_subject(), $this->get_content(), $this->get_headers(), $this->get_attachments() );
}
Specifically
if ( ! $this->is_enabled() || ! $this->get_recipient() ) {
which says if there is no recipient then the email won't send. Also $this->object = wc_get_order( $order_id ); tells us that the $order object is passed to the get_recipient_$id filter.
The id of the new order email is "customer_completed_order" as shown in the class constructor for the email.
SO, putting that all together we can filter the recipient for new order emails:
add_filter( 'so_29896856_block_emails', 'woocommerce_email_recipient_customer_completed_order', 10, 2 );
function so_29896856_block_emails( $recipient, $order ) {
if( isset( $order->customer_user ) ){
$user = new WP_User( $customer_user );
if ( in_array( 'child', (array) $user->roles ) ) {
$recipient = false;
}
}
return $recipient;
}
However, this assumes that the recipient is a single string (if an array it would kill all the recipients and not just the child... though by the default the new order email is sent to the billing email address.
Also, note that I didn't test this at all, so your mileage may vary.

Resources