first commit

This commit is contained in:
Rachit Bhargava
2023-07-21 17:12:10 -04:00
parent d0fe47dde4
commit 5d0f0734d8
14003 changed files with 2829464 additions and 0 deletions

View File

@@ -0,0 +1,93 @@
<?php
/**
* WooCommerce Admin: Add First Product.
*
* Adds a note (type `email`) to bring the client back to the store setup flow.
*/
namespace Automattic\WooCommerce\Internal\Admin\Notes;
defined( 'ABSPATH' ) || exit;
use Automattic\WooCommerce\Admin\Notes\Note;
use Automattic\WooCommerce\Admin\Notes\NoteTraits;
/**
* Add_First_Product.
*/
class AddFirstProduct {
/**
* Note traits.
*/
use NoteTraits;
/**
* Name of the note for use in the database.
*/
const NOTE_NAME = 'wc-admin-add-first-product-note';
/**
* Get the note.
*
* @return Note
*/
public static function get_note() {
if ( ! self::wc_admin_active_for( 2 * DAY_IN_SECONDS ) || self::wc_admin_active_for( 5 * DAY_IN_SECONDS ) ) {
return;
}
// Don't show if there is a product.
$query = new \WC_Product_Query(
array(
'limit' => 1,
'return' => 'ids',
'status' => array( 'publish' ),
)
);
$products = $query->get_products();
if ( 0 !== count( $products ) ) {
return;
}
// Don't show if there is an orders.
$args = array(
'limit' => 1,
'return' => 'ids',
);
$orders = wc_get_orders( $args );
if ( 0 !== count( $orders ) ) {
return;
}
// If you're updating the following please use sprintf to separate HTML tags.
// https://github.com/woocommerce/woocommerce-admin/pull/6617#discussion_r596889685.
$content_lines = array(
'{greetings}<br/><br/>',
/* translators: %s: line break */
sprintf( __( 'Nice one; you\'ve created a WooCommerce store! Now it\'s time to add your first product and get ready to start selling.%s', 'woocommerce' ), '<br/><br/>' ),
__( 'There are three ways to add your products: you can <strong>create products manually, import them at once via CSV file</strong>, or <strong>migrate them from another service</strong>.<br/><br/>', 'woocommerce' ),
/* translators: %1$s is an open anchor tag (<a>) and %2$s is a close link tag (</a>). */
sprintf( __( '%1$1sExplore our docs%2$2s for more information, or just get started!', 'woocommerce' ), '<a href="https://woocommerce.com/document/managing-products/?utm_source=help_panel&utm_medium=product">', '</a>' ),
);
$additional_data = array(
'role' => 'administrator',
);
$note = new Note();
$note->set_title( __( 'Add your first product', 'woocommerce' ) );
$note->set_content( implode( '', $content_lines ) );
$note->set_content_data( (object) $additional_data );
$note->set_image(
plugins_url(
'/images/admin_notes/dashboard-widget-setup.png',
WC_ADMIN_PLUGIN_FILE
)
);
$note->set_type( Note::E_WC_ADMIN_NOTE_EMAIL );
$note->set_name( self::NOTE_NAME );
$note->set_source( 'woocommerce-admin' );
$note->add_action( 'add-first-product', __( 'Add a product', 'woocommerce' ), admin_url( 'admin.php?page=wc-admin&task=products' ) );
return $note;
}
}

View File

@@ -0,0 +1,55 @@
<?php
/**
* WooCommerce Admin (Dashboard) choosing a theme note
*
* Adds notes to the merchant's inbox about choosing a theme.
*/
namespace Automattic\WooCommerce\Internal\Admin\Notes;
defined( 'ABSPATH' ) || exit;
use Automattic\WooCommerce\Admin\Notes\Note;
use Automattic\WooCommerce\Admin\Notes\NoteTraits;
/**
* Giving_Feedback_Notes
*/
class ChoosingTheme {
/**
* Note traits.
*/
use NoteTraits;
/**
* Name of the note for use in the database.
*/
const NOTE_NAME = 'wc-admin-choosing-a-theme';
/**
* Get the note.
*
* @return Note
*/
public static function get_note() {
// We need to show choosing a theme notification after 1 day of install.
if ( ! self::is_wc_admin_active_in_date_range( 'week-1', DAY_IN_SECONDS ) ) {
return;
}
// Otherwise, create our new note.
$note = new Note();
$note->set_title( __( 'Choosing a theme?', 'woocommerce' ) );
$note->set_content( __( 'Check out the themes that are compatible with WooCommerce and choose one aligned with your brand and business needs.', 'woocommerce' ) );
$note->set_content_data( (object) array() );
$note->set_type( Note::E_WC_ADMIN_NOTE_MARKETING );
$note->set_name( self::NOTE_NAME );
$note->set_source( 'woocommerce-admin' );
$note->add_action(
'visit-the-theme-marketplace',
__( 'Visit the theme marketplace', 'woocommerce' ),
'https://woocommerce.com/product-category/themes/?utm_source=inbox&utm_medium=product'
);
return $note;
}
}

View File

@@ -0,0 +1,157 @@
<?php
/**
* WooCommerce Admin Coupon Page Moved provider.
*
* Adds a notice when the store manager access the coupons page via the old WooCommerce > Coupons menu.
*/
namespace Automattic\WooCommerce\Internal\Admin\Notes;
defined( 'ABSPATH' ) || exit;
use Automattic\WooCommerce\Admin\Notes\Note;
use Automattic\WooCommerce\Admin\Notes\Notes;
use Automattic\WooCommerce\Admin\Notes\NoteTraits;
use Automattic\WooCommerce\Internal\Admin\CouponsMovedTrait;
use stdClass;
use WC_Data_Store;
/**
* Coupon_Page_Moved class.
*/
class CouponPageMoved {
use NoteTraits, CouponsMovedTrait;
const NOTE_NAME = 'wc-admin-coupon-page-moved';
/**
* Initialize our hooks.
*/
public function init() {
if ( ! wc_coupons_enabled() ) {
return;
}
add_action( 'admin_init', [ $this, 'possibly_add_note' ] );
add_action( 'admin_init', [ $this, 'redirect_to_coupons' ] );
add_action( 'woocommerce_admin_newly_installed', [ $this, 'disable_legacy_menu_for_new_install' ] );
}
/**
* Checks if a note can and should be added.
*
* @return bool
*/
public static function can_be_added() {
if ( ! wc_coupons_enabled() ) {
return false;
}
// Don't add the notice if the legacy coupon menu is already disabled.
if ( ! self::should_display_legacy_menu() ) {
return false;
}
// Don't add the notice if it's been hidden by the user before.
if ( self::has_dismissed_note() ) {
return false;
}
// If we already have a notice, don't add a new one.
if ( self::has_unactioned_note() ) {
return false;
}
return isset( $_GET[ self::$query_key ] ) && (bool) $_GET[ self::$query_key ]; // phpcs:ignore WordPress.Security.NonceVerification
}
/**
* Get the note object for this class.
*
* @return Note
*/
public static function get_note() {
$note = new Note();
$note->set_title( __( 'Coupon management has moved!', 'woocommerce' ) );
$note->set_content( __( 'Coupons can now be managed from Marketing > Coupons. Click the button below to remove the legacy WooCommerce > Coupons menu item.', 'woocommerce' ) );
$note->set_type( Note::E_WC_ADMIN_NOTE_UPDATE );
$note->set_name( self::NOTE_NAME );
$note->set_content_data( new stdClass() );
$note->set_source( 'woocommerce-admin' );
$note->add_action(
'remove-legacy-coupon-menu',
__( 'Remove legacy coupon menu', 'woocommerce' ),
wc_admin_url( '&action=remove-coupon-menu' ),
Note::E_WC_ADMIN_NOTE_ACTIONED
);
return $note;
}
/**
* Find notes that have not been actioned.
*
* @return bool
*/
protected static function has_unactioned_note() {
$note = Notes::get_note_by_name( self::NOTE_NAME );
if ( ! $note ) {
return false;
}
return $note->get_status() === 'unactioned';
}
/**
* Whether any notes have been dismissed by the user previously.
*
* @return bool
*/
protected static function has_dismissed_note() {
$note = Notes::get_note_by_name( self::NOTE_NAME );
if ( ! $note ) {
return false;
}
return ! $note->get_is_deleted();
}
/**
* Get the data store object.
*
* @return DataStore The data store object.
*/
protected static function get_data_store() {
return WC_Data_Store::load( 'admin-note' );
}
/**
* Safe redirect to the coupon page to force page refresh.
*/
public function redirect_to_coupons() {
/* phpcs:disable WordPress.Security.NonceVerification */
if (
! isset( $_GET['page'] ) ||
'wc-admin' !== $_GET['page'] ||
! isset( $_GET['action'] ) ||
'remove-coupon-menu' !== $_GET['action'] ||
! defined( 'WC_ADMIN_PLUGIN_FILE' )
) {
return;
}
/* phpcs:enable */
$this->display_legacy_menu( false );
wp_safe_redirect( self::get_management_url( 'coupons' ) );
exit;
}
/**
* Disable legacy coupon menu when installing for the first time.
*/
public function disable_legacy_menu_for_new_install() {
$this->display_legacy_menu( false );
}
}

View File

@@ -0,0 +1,86 @@
<?php
/**
* WooCommerce Admin: Customize your online store with WooCommerce blocks.
*
* Adds a note to customize the client online store with WooCommerce blocks.
*
* @package WooCommerce\Admin
*/
namespace Automattic\WooCommerce\Internal\Admin\Notes;
defined( 'ABSPATH' ) || exit;
use Automattic\WooCommerce\Admin\Notes\Note;
use Automattic\WooCommerce\Admin\Notes\NoteTraits;
/**
* Customize_Store_With_Blocks.
*/
class CustomizeStoreWithBlocks {
/**
* Note traits.
*/
use NoteTraits;
/**
* Name of the note for use in the database.
*/
const NOTE_NAME = 'wc-admin-customize-store-with-blocks';
/**
* Get the note.
*
* @return Note
*/
public static function get_note() {
$onboarding_profile = get_option( 'woocommerce_onboarding_profile', array() );
// Confirm that $onboarding_profile is set.
if ( empty( $onboarding_profile ) ) {
return;
}
// Make sure that the person who filled out the OBW was not setting up
// the store for their customer/client.
if (
! isset( $onboarding_profile['setup_client'] ) ||
$onboarding_profile['setup_client']
) {
return;
}
// We want to show the note after fourteen days.
if ( ! self::is_wc_admin_active_in_date_range( 'week-1-4', 14 * DAY_IN_SECONDS ) ) {
return;
}
// Don't show if there aren't products.
$query = new \WC_Product_Query(
array(
'limit' => 1,
'return' => 'ids',
'status' => array( 'publish' ),
)
);
$products = $query->get_products();
if ( 0 === count( $products ) ) {
return;
}
$note = new Note();
$note->set_title( __( 'Customize your online store with WooCommerce blocks', 'woocommerce' ) );
$note->set_content( __( 'With our blocks, you can select and display products, categories, filters, and more virtually anywhere on your site — no need to use shortcodes or edit lines of code. Learn more about how to use each one of them.', 'woocommerce' ) );
$note->set_type( Note::E_WC_ADMIN_NOTE_INFORMATIONAL );
$note->set_name( self::NOTE_NAME );
$note->set_content_data( (object) array() );
$note->set_source( 'woocommerce-admin' );
$note->add_action(
'customize-store-with-blocks',
__( 'Learn more', 'woocommerce' ),
'https://woocommerce.com/posts/how-to-customize-your-online-store-with-woocommerce-blocks/?utm_source=inbox&utm_medium=product',
Note::E_WC_ADMIN_NOTE_ACTIONED
);
return $note;
}
}

View File

@@ -0,0 +1,83 @@
<?php
/**
* WooCommerce Admin: How to customize your product catalog note provider
*
* Adds a note with a link to the customizer a day after adding the first product
*/
namespace Automattic\WooCommerce\Internal\Admin\Notes;
defined( 'ABSPATH' ) || exit;
use Automattic\WooCommerce\Admin\Notes\Note;
use Automattic\WooCommerce\Admin\Notes\NoteTraits;
/**
* Class CustomizingProductCatalog
*
* @package Automattic\WooCommerce\Admin\Notes
*/
class CustomizingProductCatalog {
/**
* Note traits.
*/
use NoteTraits;
/**
* Name of the note for use in the database.
*/
const NOTE_NAME = 'wc-admin-customizing-product-catalog';
/**
* Get the note.
*
* @return Note
*/
public static function get_note() {
$query = new \WC_Product_Query(
array(
'limit' => 1,
'paginate' => true,
'status' => array( 'publish' ),
'orderby' => 'post_date',
'order' => 'DESC',
)
);
$products = $query->get_products();
// we need at least 1 product.
if ( 0 === $products->total ) {
return;
}
$product = $products->products[0];
$created_timestamp = $product->get_date_created()->getTimestamp();
$is_a_day_old = ( time() - $created_timestamp ) >= DAY_IN_SECONDS;
// the product must be at least 1 day old.
if ( ! $is_a_day_old ) {
return;
}
// store must not been active more than 14 days.
if ( self::wc_admin_active_for( DAY_IN_SECONDS * 14 ) ) {
return;
}
$note = new Note();
$note->set_title( __( 'How to customize your product catalog', 'woocommerce' ) );
$note->set_content( __( 'You want your product catalog and images to look great and align with your brand. This guide will give you all the tips you need to get your products looking great in your store.', 'woocommerce' ) );
$note->set_type( Note::E_WC_ADMIN_NOTE_INFORMATIONAL );
$note->set_name( self::NOTE_NAME );
$note->set_content_data( (object) array() );
$note->set_source( 'woocommerce-admin' );
$note->add_action(
'day-after-first-product',
__( 'Learn more', 'woocommerce' ),
'https://woocommerce.com/document/woocommerce-customizer/?utm_source=inbox&utm_medium=product'
);
return $note;
}
}

View File

@@ -0,0 +1,62 @@
<?php
/**
* WooCommerce Admin: EU VAT Number Note.
*
* Adds a note for EU store to install the EU VAT Number extension.
*/
namespace Automattic\WooCommerce\Internal\Admin\Notes;
defined( 'ABSPATH' ) || exit;
use Automattic\WooCommerce\Admin\Notes\Note;
use Automattic\WooCommerce\Admin\Notes\NoteTraits;
/**
* EU_VAT_Number
*/
class EUVATNumber {
/**
* Note traits.
*/
use NoteTraits;
/**
* Name of the note for use in the database.
*/
const NOTE_NAME = 'wc-admin-eu-vat-number';
/**
* Get the note.
*
* @return Note
*/
public static function get_note() {
if ( 'yes' !== get_option( 'wc_connect_taxes_enabled', 'no' ) ) {
return;
}
$country_code = WC()->countries->get_base_country();
$eu_countries = WC()->countries->get_european_union_countries();
if ( ! in_array( $country_code, $eu_countries, true ) ) {
return;
}
$content = __( "If your store is based in the EU, we recommend using the EU VAT Number extension in addition to automated taxes. It provides your checkout with a field to collect and validate a customer's EU VAT number, if they have one.", 'woocommerce' );
$note = new Note();
$note->set_title( __( 'Collect and validate EU VAT numbers at checkout', 'woocommerce' ) );
$note->set_content( $content );
$note->set_content_data( (object) array() );
$note->set_type( Note::E_WC_ADMIN_NOTE_MARKETING );
$note->set_name( self::NOTE_NAME );
$note->set_source( 'woocommerce-admin' );
$note->add_action(
'learn-more',
__( 'Learn more', 'woocommerce' ),
'https://woocommerce.com/products/eu-vat-number/?utm_medium=product',
Note::E_WC_ADMIN_NOTE_ACTIONED
);
return $note;
}
}

View File

@@ -0,0 +1,71 @@
<?php
/**
* WooCommerce Admin Edit products on the move note.
*
* Adds a note to download the mobile app.
*/
namespace Automattic\WooCommerce\Internal\Admin\Notes;
defined( 'ABSPATH' ) || exit;
use Automattic\WooCommerce\Admin\Notes\Note;
use Automattic\WooCommerce\Admin\Notes\NoteTraits;
/**
* Edit_Products_On_The_Move
*/
class EditProductsOnTheMove {
/**
* Note traits.
*/
use NoteTraits;
/**
* Name of the note for use in the database.
*/
const NOTE_NAME = 'wc-admin-edit-products-on-the-move';
/**
* Get the note.
*
* @return Note
*/
public static function get_note() {
// Only add this note if this store is at least a year old.
$year_in_seconds = 365 * DAY_IN_SECONDS;
if ( ! self::wc_admin_active_for( $year_in_seconds ) ) {
return;
}
// Check that the previous mobile app notes have not been actioned.
if ( MobileApp::has_note_been_actioned() ) {
return;
}
if ( RealTimeOrderAlerts::has_note_been_actioned() ) {
return;
}
if ( ManageOrdersOnTheGo::has_note_been_actioned() ) {
return;
}
if ( PerformanceOnMobile::has_note_been_actioned() ) {
return;
}
$note = new Note();
$note->set_title( __( 'Edit products on the move', 'woocommerce' ) );
$note->set_content( __( 'Edit and create new products from your mobile devices with the Woo app', 'woocommerce' ) );
$note->set_content_data( (object) array() );
$note->set_type( Note::E_WC_ADMIN_NOTE_INFORMATIONAL );
$note->set_name( self::NOTE_NAME );
$note->set_source( 'woocommerce-admin' );
$note->add_action(
'learn-more',
__( 'Learn more', 'woocommerce' ),
'https://woocommerce.com/mobile/?utm_source=inbox&utm_medium=product'
);
return $note;
}
}

View File

@@ -0,0 +1,221 @@
<?php
/**
* Handles emailing user notes.
*/
namespace Automattic\WooCommerce\Internal\Admin\Notes;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
use Automattic\WooCommerce\Admin\Notes\Notes;
/**
* Include dependencies.
*/
if ( ! class_exists( 'WC_Email', false ) ) {
include_once WC_ABSPATH . 'includes/emails/class-wc-email.php';
}
/**
* EmailNotification Class.
*/
class EmailNotification extends \WC_Email {
/**
* Constructor.
*
* @param Note $note The notification to send.
*/
public function __construct( $note ) {
$this->note = $note;
$this->id = 'merchant_notification';
$this->template_base = WC_ADMIN_ABSPATH . 'includes/react-admin/emails/';
$this->placeholders = array(
'{greetings}' => __( 'Hi there,', 'woocommerce' ),
);
// Call parent constructor.
parent::__construct();
}
/**
* This email has no user-facing settings.
*/
public function init_form_fields() {}
/**
* This email has no user-facing settings.
*/
public function init_settings() {}
/**
* Return template filename.
*
* @param string $type Type of email to send.
* @return string
*/
public function get_template_filename( $type = 'html' ) {
if ( ! in_array( $type, array( 'html', 'plain' ), true ) ) {
return;
}
$content_data = $this->note->get_content_data();
$template_filename = "{$type}-merchant-notification.php";
if ( isset( $content_data->{"template_{$type}"} ) && file_exists( $this->template_base . $content_data->{ "template_{$type}" } ) ) {
$template_filename = $content_data[ "template_{$type}" ];
}
return $template_filename;
}
/**
* Return email type.
*
* @return string
*/
public function get_email_type() {
return class_exists( 'DOMDocument' ) ? 'html' : 'plain';
}
/**
* Get email heading.
*
* @return string
*/
public function get_default_heading() {
$content_data = $this->note->get_content_data();
if ( isset( $content_data->heading ) ) {
return $content_data->heading;
}
return $this->note->get_title();
}
/**
* Get email headers.
*
* @return string
*/
public function get_headers() {
$header = 'Content-Type: ' . $this->get_content_type() . "\r\n";
return apply_filters( 'woocommerce_email_headers', $header, $this->id, $this->object, $this );
}
/**
* Get email subject.
*
* @return string
*/
public function get_default_subject() {
return $this->note->get_title();
}
/**
* Get note content.
*
* @return string
*/
public function get_note_content() {
return $this->note->get_content();
}
/**
* Get note image.
*
* @return string
*/
public function get_image() {
return $this->note->get_image();
}
/**
* Get email action.
*
* @return stdClass
*/
public function get_actions() {
return $this->note->get_actions();
}
/**
* Get content html.
*
* @return string
*/
public function get_content_html() {
return wc_get_template_html(
$this->get_template_filename( 'html' ),
array(
'email_actions' => $this->get_actions(),
'email_content' => $this->format_string( $this->get_note_content() ),
'email_heading' => $this->format_string( $this->get_heading() ),
'email_image' => $this->get_image(),
'sent_to_admin' => true,
'plain_text' => false,
'email' => $this,
'opened_tracking_url' => $this->opened_tracking_url,
'trigger_note_action_url' => $this->trigger_note_action_url,
),
'',
$this->template_base
);
}
/**
* Get content plain.
*
* @return string
*/
public function get_content_plain() {
return wc_get_template_html(
$this->get_template_filename( 'plain' ),
array(
'email_heading' => $this->format_string( $this->get_heading() ),
'email_content' => $this->format_string( $this->get_note_content() ),
'email_actions' => $this->get_actions(),
'sent_to_admin' => true,
'plain_text' => true,
'email' => $this,
'trigger_note_action_url' => $this->trigger_note_action_url,
),
'',
$this->template_base
);
}
/**
* Trigger the sending of this email.
*
* @param string $user_email Email to send the note.
* @param int $user_id User id to to track the note.
* @param string $user_name User's name.
*/
public function trigger( $user_email, $user_id, $user_name ) {
$this->recipient = $user_email;
$this->opened_tracking_url = sprintf(
'%1$s/wp-json/wc-analytics/admin/notes/tracker/%2$d/user/%3$d',
site_url(),
$this->note->get_id(),
$user_id
);
$this->trigger_note_action_url = sprintf(
'%1$s&external_redirect=1&note=%2$d&user=%3$d&action=',
wc_admin_url(),
$this->note->get_id(),
$user_id
);
if ( $user_name ) {
/* translators: %s = merchant name */
$this->placeholders['{greetings}'] = sprintf( __( 'Hi %s,', 'woocommerce' ), $user_name );
}
$this->send(
$this->get_recipient(),
$this->get_subject(),
$this->get_content(),
$this->get_headers(),
$this->get_attachments()
);
Notes::record_tracks_event_with_user( $user_id, 'email_note_sent', array( 'note_name' => $this->note->get_name() ) );
}
}

View File

@@ -0,0 +1,86 @@
<?php
/**
* WooCommerce Admin: Do you need help with adding your first product?
*
* Adds a note to ask the client if they need help adding their first product.
*/
namespace Automattic\WooCommerce\Internal\Admin\Notes;
defined( 'ABSPATH' ) || exit;
use Automattic\WooCommerce\Admin\Notes\Note;
use Automattic\WooCommerce\Admin\Notes\NoteTraits;
/**
* First_Product.
*/
class FirstProduct {
/**
* Note traits.
*/
use NoteTraits;
/**
* Name of the note for use in the database.
*/
const NOTE_NAME = 'wc-admin-first-product';
/**
* Get the note.
*
* @return Note
*/
public static function get_note() {
// We want to show the note after seven days.
if ( ! self::is_wc_admin_active_in_date_range( 'week-1-4' ) ) {
return;
}
$onboarding_profile = get_option( 'woocommerce_onboarding_profile', array() );
// Confirm that $onboarding_profile is set.
if ( empty( $onboarding_profile ) ) {
return;
}
// Make sure that the person who filled out the OBW was not setting up
// the store for their customer/client.
if (
! isset( $onboarding_profile['setup_client'] ) ||
$onboarding_profile['setup_client']
) {
return;
}
// Don't show if there are products.
$query = new \WC_Product_Query(
array(
'limit' => 1,
'paginate' => true,
'return' => 'ids',
'status' => array( 'publish' ),
)
);
$products = $query->get_products();
$count = $products->total;
if ( 0 !== $count ) {
return;
}
$note = new Note();
$note->set_title( __( 'Do you need help with adding your first product?', 'woocommerce' ) );
$note->set_content( __( 'This video tutorial will help you go through the process of adding your first product in WooCommerce.', 'woocommerce' ) );
$note->set_type( Note::E_WC_ADMIN_NOTE_INFORMATIONAL );
$note->set_name( self::NOTE_NAME );
$note->set_content_data( (object) array() );
$note->set_source( 'woocommerce-admin' );
$note->add_action(
'first-product-watch-tutorial',
__( 'Watch tutorial', 'woocommerce' ),
'https://www.youtube.com/watch?v=sFtXa00Jf_o&list=PLHdG8zvZd0E575Ia8Mu3w1h750YLXNfsC&index=24'
);
return $note;
}
}

View File

@@ -0,0 +1,55 @@
<?php
/**
* WooCommerce Admin (Dashboard) Giving feedback notes provider
*
* Adds notes to the merchant's inbox about giving feedback.
*/
namespace Automattic\WooCommerce\Internal\Admin\Notes;
defined( 'ABSPATH' ) || exit;
use Automattic\WooCommerce\Admin\Notes\Note;
use Automattic\WooCommerce\Admin\Notes\NoteTraits;
use Automattic\WooCommerce\Internal\Admin\Survey;
/**
* Giving_Feedback_Notes
*/
class GivingFeedbackNotes {
/**
* Note traits.
*/
use NoteTraits;
/**
* Name of the note for use in the database.
*/
const NOTE_NAME = 'wc-admin-store-notice-giving-feedback-2';
/**
* Get the note.
*
* @return Note
*/
public static function get_note() {
if ( ! self::is_wc_admin_active_in_date_range( 'week-1-4' ) ) {
return;
}
// Otherwise, create our new note.
$note = new Note();
$note->set_title( __( 'You\'re invited to share your experience', 'woocommerce' ) );
$note->set_content( __( 'Now that youve chosen us as a partner, our goal is to make sure we\'re providing the right tools to meet your needs. We\'re looking forward to having your feedback on the store setup experience so we can improve it in the future.', 'woocommerce' ) );
$note->set_content_data( (object) array() );
$note->set_type( Note::E_WC_ADMIN_NOTE_INFORMATIONAL );
$note->set_name( self::NOTE_NAME );
$note->set_source( 'woocommerce-admin' );
$note->add_action(
'share-feedback',
__( 'Share feedback', 'woocommerce' ),
Survey::get_url( '/store-setup-survey' )
);
return $note;
}
}

View File

@@ -0,0 +1,145 @@
<?php
/**
* WooCommerce Admin Add Install Jetpack and WooCommerce Shipping & Tax Plugin Note Provider.
*
* Adds a note to the merchant's inbox prompting them to install the Jetpack
* and WooCommerce Shipping & Tax plugins after it fails to install during
* WooCommerce setup.
*/
namespace Automattic\WooCommerce\Internal\Admin\Notes;
defined( 'ABSPATH' ) || exit;
use Automattic\WooCommerce\Admin\Notes\Note;
use Automattic\WooCommerce\Admin\Notes\Notes;
use Automattic\WooCommerce\Admin\Notes\NoteTraits;
use Automattic\WooCommerce\Admin\PluginsHelper;
/**
* Install_JP_And_WCS_Plugins
*/
class InstallJPAndWCSPlugins {
/**
* Note traits.
*/
use NoteTraits;
/**
* Name of the note for use in the database.
*/
const NOTE_NAME = 'wc-admin-install-jp-and-wcs-plugins';
/**
* Constructor.
*/
public function __construct() {
add_action( 'woocommerce_note_action_install-jp-and-wcs-plugins', array( $this, 'install_jp_and_wcs_plugins' ) );
add_action( 'activated_plugin', array( $this, 'action_note' ) );
add_action( 'woocommerce_plugins_install_api_error', array( $this, 'on_install_error' ) );
add_action( 'woocommerce_plugins_install_error', array( $this, 'on_install_error' ) );
add_action( 'woocommerce_plugins_activate_error', array( $this, 'on_install_error' ) );
}
/**
* Get the note.
*
* @return Note
*/
public static function get_note() {
$content = __( 'We noticed that there was a problem during the Jetpack and WooCommerce Shipping & Tax install. Please try again and enjoy all the advantages of having the plugins connected to your store! Sorry for the inconvenience. The "Jetpack" and "WooCommerce Shipping & Tax" plugins will be installed & activated for free.', 'woocommerce' );
$note = new Note();
$note->set_title( __( 'Uh oh... There was a problem during the Jetpack and WooCommerce Shipping & Tax install. Please try again.', 'woocommerce' ) );
$note->set_content( $content );
$note->set_content_data( (object) array() );
$note->set_type( Note::E_WC_ADMIN_NOTE_INFORMATIONAL );
$note->set_name( self::NOTE_NAME );
$note->set_source( 'woocommerce-admin' );
$note->add_action(
'install-jp-and-wcs-plugins',
__( 'Install plugins', 'woocommerce' ),
false,
Note::E_WC_ADMIN_NOTE_ACTIONED
);
return $note;
}
/**
* Action the Install Jetpack and WooCommerce Shipping & Tax note, if any exists,
* and as long as both the Jetpack and WooCommerce Shipping & Tax plugins have been
* activated.
*/
public static function action_note() {
// Make sure that both plugins are active before actioning the note.
$active_plugin_slugs = PluginsHelper::get_active_plugin_slugs();
$jp_active = in_array( 'jetpack', $active_plugin_slugs, true );
$wcs_active = in_array( 'woocommerce-services', $active_plugin_slugs, true );
if ( ! $jp_active || ! $wcs_active ) {
return;
}
// Action any notes with a matching name.
$data_store = Notes::load_data_store();
$note_ids = $data_store->get_notes_with_name( self::NOTE_NAME );
foreach ( $note_ids as $note_id ) {
$note = Notes::get_note( $note_id );
if ( $note ) {
$note->set_status( Note::E_WC_ADMIN_NOTE_ACTIONED );
$note->save();
}
}
}
/**
* Install the Jetpack and WooCommerce Shipping & Tax plugins in response to the action
* being clicked in the admin note.
*
* @param Note $note The note being actioned.
*/
public function install_jp_and_wcs_plugins( $note ) {
if ( self::NOTE_NAME !== $note->get_name() ) {
return;
}
$this->install_and_activate_plugin( 'jetpack' );
$this->install_and_activate_plugin( 'woocommerce-services' );
}
/**
* Installs and activates the specified plugin.
*
* @param string $plugin The plugin slug.
*/
private function install_and_activate_plugin( $plugin ) {
$install_request = array( 'plugin' => $plugin );
$installer = new \Automattic\WooCommerce\Admin\API\OnboardingPlugins();
$result = $installer->install_plugin( $install_request );
// @todo Use the error statuses to decide whether or not to action the note.
if ( is_wp_error( $result ) ) {
return;
}
$activate_request = array( 'plugins' => $plugin );
$installer->activate_plugins( $activate_request );
}
/**
* Create an alert notification in response to an error installing a plugin.
*
* @param string $slug The slug of the plugin being installed.
*/
public function on_install_error( $slug ) {
// Exit early if we're not installing the Jetpack or the WooCommerce Shipping & Tax plugins.
if ( 'jetpack' !== $slug && 'woocommerce-services' !== $slug ) {
return;
}
self::possibly_add_note();
}
}

View File

@@ -0,0 +1,60 @@
<?php
/**
* WooCommerce Admin Launch Checklist Note.
*
* Adds a note to cover pre-launch checklist items for store owners.
*/
namespace Automattic\WooCommerce\Internal\Admin\Notes;
defined( 'ABSPATH' ) || exit;
use Automattic\WooCommerce\Admin\Notes\Note;
use Automattic\WooCommerce\Admin\Notes\NoteTraits;
/**
* Launch_Checklist
*/
class LaunchChecklist {
/**
* Note traits.
*/
use NoteTraits;
/**
* Name of the note for use in the database.
*/
const NOTE_NAME = 'wc-admin-launch-checklist';
/**
* Get the note.
*
* @return Note
*/
public static function get_note() {
// Only add this note if completing the task list or completed 3 tasks in 10 days.
$completed_tasks = get_option( 'woocommerce_task_list_tracked_completed_tasks', array() );
$ten_days_in_seconds = 10 * DAY_IN_SECONDS;
if (
! get_option( 'woocommerce_task_list_complete' ) &&
(
count( $completed_tasks ) < 3 ||
self::is_wc_admin_active_in_date_range( 'week-1-4', $ten_days_in_seconds )
)
) {
return;
}
$content = __( 'To make sure you never get that sinking "what did I forget" feeling, we\'ve put together the essential pre-launch checklist.', 'woocommerce' );
$note = new Note();
$note->set_title( __( 'Ready to launch your store?', 'woocommerce' ) );
$note->set_content( $content );
$note->set_content_data( (object) array() );
$note->set_type( Note::E_WC_ADMIN_NOTE_INFORMATIONAL );
$note->set_name( self::NOTE_NAME );
$note->set_source( 'woocommerce-admin' );
$note->add_action( 'learn-more', __( 'Learn more', 'woocommerce' ), 'https://woocommerce.com/posts/pre-launch-checklist-the-essentials/?utm_source=inbox&utm_medium=product' );
return $note;
}
}

View File

@@ -0,0 +1,100 @@
<?php
/**
* WooCommerce Admin note on how to migrate from Magento.
*/
namespace Automattic\WooCommerce\Internal\Admin\Notes;
use Automattic\WooCommerce\Internal\Admin\Onboarding\OnboardingProfile;
defined( 'ABSPATH' ) || exit;
use Automattic\WooCommerce\Admin\Features\Onboarding;
use Automattic\WooCommerce\Admin\Notes\Note;
use Automattic\WooCommerce\Admin\Notes\NoteTraits;
/**
* MagentoMigration
*/
class MagentoMigration {
/**
* Note traits.
*/
use NoteTraits;
/**
* Name of the note for use in the database.
*/
const NOTE_NAME = 'wc-admin-magento-migration';
/**
* Attach hooks.
*/
public function __construct() {
add_action( 'update_option_' . OnboardingProfile::DATA_OPTION, array( __CLASS__, 'possibly_add_note' ) );
add_action( 'woocommerce_admin_magento_migration_note', array( __CLASS__, 'save_note' ) );
}
/**
* Add the note if it passes predefined conditions.
*/
public static function possibly_add_note() {
$onboarding_profile = get_option( OnboardingProfile::DATA_OPTION, array() );
if ( empty( $onboarding_profile ) ) {
return;
}
if (
! isset( $onboarding_profile['other_platform'] ) ||
'magento' !== $onboarding_profile['other_platform']
) {
return;
}
if (
! isset( $onboarding_profile['setup_client'] ) ||
$onboarding_profile['setup_client']
) {
return;
}
WC()->queue()->schedule_single( time() + ( 5 * MINUTE_IN_SECONDS ), 'woocommerce_admin_magento_migration_note' );
}
/**
* Save the note to the database.
*/
public static function save_note() {
$note = self::get_note();
if ( self::note_exists() ) {
return;
}
$note->save();
}
/**
* Get the note.
*
* @return Note
*/
public static function get_note() {
$note = new Note();
$note->set_title( __( 'How to Migrate from Magento to WooCommerce', 'woocommerce' ) );
$note->set_content( __( 'Changing platforms might seem like a big hurdle to overcome, but it is easier than you might think to move your products, customers, and orders to WooCommerce. This article will help you with going through this process.', 'woocommerce' ) );
$note->set_content_data( (object) array() );
$note->set_type( Note::E_WC_ADMIN_NOTE_INFORMATIONAL );
$note->set_name( self::NOTE_NAME );
$note->set_source( 'woocommerce-admin' );
$note->add_action(
'learn-more',
__( 'Learn more', 'woocommerce' ),
'https://woocommerce.com/posts/how-migrate-from-magento-to-woocommerce/?utm_source=inbox'
);
return $note;
}
}

View File

@@ -0,0 +1,64 @@
<?php
/**
* WooCommerce Admin Manage orders on the go note.
*
* Adds a note to download the mobile app to manage orders on the go.
*/
namespace Automattic\WooCommerce\Internal\Admin\Notes;
defined( 'ABSPATH' ) || exit;
use Automattic\WooCommerce\Admin\Notes\Note;
use Automattic\WooCommerce\Admin\Notes\NoteTraits;
/**
* Manage_Orders_On_The_Go
*/
class ManageOrdersOnTheGo {
/**
* Note traits.
*/
use NoteTraits;
/**
* Name of the note for use in the database.
*/
const NOTE_NAME = 'wc-admin-manage-orders-on-the-go';
/**
* Get the note.
*
* @return Note|null
*/
public static function get_note() {
// Only add this note if this store is at least 6 months old.
if ( ! self::is_wc_admin_active_in_date_range( 'month-6+' ) ) {
return;
}
// Check that the previous mobile app notes have not been actioned.
if ( MobileApp::has_note_been_actioned() ) {
return;
}
if ( RealTimeOrderAlerts::has_note_been_actioned() ) {
return;
}
$note = new Note();
$note->set_title( __( 'Manage your orders on the go', 'woocommerce' ) );
$note->set_content( __( 'Look for orders, customer info, and process refunds in one click with the Woo app.', 'woocommerce' ) );
$note->set_content_data( (object) array() );
$note->set_type( Note::E_WC_ADMIN_NOTE_INFORMATIONAL );
$note->set_name( self::NOTE_NAME );
$note->set_source( 'woocommerce-admin' );
$note->add_action(
'learn-more',
__( 'Learn more', 'woocommerce' ),
'https://woocommerce.com/mobile/?utm_source=inbox&utm_medium=product'
);
return $note;
}
}

View File

@@ -0,0 +1,146 @@
<?php
/**
* WooCommerce Admin Jetpack Marketing Note Provider.
*
* Adds notes to the merchant's inbox concerning Jetpack Backup.
*/
namespace Automattic\WooCommerce\Internal\Admin\Notes;
defined( 'ABSPATH' ) || exit;
use Automattic\Jetpack\Constants;
use Automattic\WooCommerce\Admin\Notes\Note;
use Automattic\WooCommerce\Admin\Notes\Notes;
use Automattic\WooCommerce\Admin\Notes\NoteTraits;
use Automattic\WooCommerce\Admin\PluginsHelper;
/**
* Suggest Jetpack Backup to Woo users.
*
* Note: This should probably live in the Jetpack plugin in the future.
*
* @see https://developer.woocommerce.com/2020/10/16/using-the-admin-notes-inbox-in-woocommerce/
*/
class MarketingJetpack {
// Shared Note Traits.
use NoteTraits;
/**
* Name of the note for use in the database.
*/
const NOTE_NAME = 'wc-admin-marketing-jetpack-backup';
/**
* Product IDs that include Backup.
*/
const BACKUP_IDS = [
2010,
2011,
2012,
2013,
2014,
2015,
2100,
2101,
2102,
2103,
2005,
2006,
2000,
2003,
2001,
2004,
];
/**
* Maybe add a note on Jetpack Backups for Jetpack sites older than a week without Backups.
*/
public static function possibly_add_note() {
/**
* Check if Jetpack is installed.
*/
$installed_plugins = PluginsHelper::get_installed_plugin_slugs();
if ( ! in_array( 'jetpack', $installed_plugins, true ) ) {
return;
}
$data_store = \WC_Data_Store::load( 'admin-note' );
// Do we already have this note?
$note_ids = $data_store->get_notes_with_name( self::NOTE_NAME );
if ( ! empty( $note_ids ) ) {
$note_id = array_pop( $note_ids );
$note = Notes::get_note( $note_id );
if ( false === $note ) {
return;
}
// If Jetpack Backups was purchased after the note was created, mark this note as actioned.
if ( self::has_backups() && Note::E_WC_ADMIN_NOTE_ACTIONED !== $note->get_status() ) {
$note->set_status( Note::E_WC_ADMIN_NOTE_ACTIONED );
$note->save();
}
return;
}
// Check requirements.
if ( ! self::is_wc_admin_active_in_date_range( 'week-1-4', DAY_IN_SECONDS * 3 ) || ! self::can_be_added() || self::has_backups() ) {
return;
}
// Add note.
$note = self::get_note();
$note->save();
}
/**
* Get the note.
*/
public static function get_note() {
$note = new Note();
$note->set_title( __( 'Protect your WooCommerce Store with Jetpack Backup.', 'woocommerce' ) );
$note->set_content( __( 'Store downtime means lost sales. One-click restores get you back online quickly if something goes wrong.', 'woocommerce' ) );
$note->set_type( Note::E_WC_ADMIN_NOTE_MARKETING );
$note->set_name( self::NOTE_NAME );
$note->set_layout( 'thumbnail' );
$note->set_image(
WC_ADMIN_IMAGES_FOLDER_URL . '/admin_notes/marketing-jetpack-2x.png'
);
$note->set_content_data( (object) array() );
$note->set_source( 'woocommerce-admin-notes' );
$note->add_action(
'jetpack-backup-woocommerce',
__( 'Get backups', 'woocommerce' ),
esc_url( 'https://jetpack.com/upgrade/backup-woocommerce/?utm_source=inbox&utm_medium=automattic_referred&utm_campaign=jp_backup_to_woo' ),
Note::E_WC_ADMIN_NOTE_ACTIONED
);
return $note;
}
/**
* Check if this blog already has a Jetpack Backups product.
*
* @return boolean Whether or not this blog has backups.
*/
protected static function has_backups() {
$product_ids = [];
$plan = get_option( 'jetpack_active_plan' );
if ( ! empty( $plan ) ) {
$product_ids[] = $plan['product_id'];
}
$products = get_option( 'jetpack_site_products' );
if ( ! empty( $products ) ) {
foreach ( $products as $product ) {
$product_ids[] = $product['product_id'];
}
}
return (bool) array_intersect( self::BACKUP_IDS, $product_ids );
}
}

View File

@@ -0,0 +1,140 @@
<?php
/**
* Handles merchant email notifications
*/
namespace Automattic\WooCommerce\Internal\Admin\Notes;
use Automattic\WooCommerce\Admin\Notes\Note;
use Automattic\WooCommerce\Admin\Notes\Notes;
defined( 'ABSPATH' ) || exit;
/**
* Merchant email notifications.
* This gets all non-sent notes type `email` and sends them.
*/
class MerchantEmailNotifications {
/**
* Initialize the merchant email notifications.
*/
public static function init() {
add_action( 'admin_init', array( __CLASS__, 'trigger_notification_action' ) );
}
/**
* Trigger the note action.
*/
public static function trigger_notification_action() {
/* phpcs:disable WordPress.Security.NonceVerification */
if (
! isset( $_GET['external_redirect'] ) ||
1 !== intval( $_GET['external_redirect'] ) ||
! isset( $_GET['user'] ) ||
! isset( $_GET['note'] ) ||
! isset( $_GET['action'] )
) {
return;
}
$note_id = intval( $_GET['note'] );
$action_id = intval( $_GET['action'] );
$user_id = intval( $_GET['user'] );
/* phpcs:enable */
$note = Notes::get_note( $note_id );
if ( ! $note || Note::E_WC_ADMIN_NOTE_EMAIL !== $note->get_type() ) {
return;
}
$triggered_action = Notes::get_action_by_id( $note, $action_id );
if ( ! $triggered_action ) {
return;
}
Notes::trigger_note_action( $note, $triggered_action );
$url = $triggered_action->query;
// We will use "wp_safe_redirect" when it's an internal redirect.
if ( strpos( $url, 'http' ) === false ) {
wp_safe_redirect( $url );
} else {
header( 'Location: ' . $url );
}
exit();
}
/**
* Send all the notifications type `email`.
*/
public static function run() {
$data_store = Notes::load_data_store();
$notes = $data_store->get_notes(
array(
'type' => array( Note::E_WC_ADMIN_NOTE_EMAIL ),
'status' => array( 'unactioned' ),
)
);
foreach ( $notes as $note ) {
$note = Notes::get_note( $note->note_id );
if ( $note ) {
self::send_merchant_notification( $note );
$note->set_status( 'sent' );
$note->save();
}
}
}
/**
* Send the notification to the merchant.
*
* @param object $note The note to send.
*/
public static function send_merchant_notification( $note ) {
\WC_Emails::instance();
$users = self::get_notification_recipients( $note );
$email = new EmailNotification( $note );
foreach ( $users as $user ) {
if ( is_email( $user->user_email ) ) {
$name = self::get_merchant_preferred_name( $user );
$email->trigger( $user->user_email, $user->ID, $name );
}
}
}
/**
* Get the preferred name for user. First choice is
* the user's first name, and then display_name.
*
* @param WP_User $user Recipient to send the note to.
* @return string User's name.
*/
public static function get_merchant_preferred_name( $user ) {
$first_name = get_user_meta( $user->ID, 'first_name', true );
if ( $first_name ) {
return $first_name;
}
if ( $user->display_name ) {
return $user->display_name;
}
return '';
}
/**
* Get users by role to notify.
*
* @param object $note The note to send.
* @return array Users to notify
*/
public static function get_notification_recipients( $note ) {
$content_data = $note->get_content_data();
$role = 'administrator';
if ( isset( $content_data->role ) ) {
$role = $content_data->role;
}
$args = array( 'role' => $role );
return get_users( $args );
}
}

View File

@@ -0,0 +1,79 @@
<?php
/**
* WooCommerce Admin: Migrate from Shopify to WooCommerce.
*
* Adds a note to ask the client if they want to migrate from Shopify to WooCommerce.
*/
namespace Automattic\WooCommerce\Internal\Admin\Notes;
defined( 'ABSPATH' ) || exit;
use Automattic\WooCommerce\Admin\Notes\Note;
use Automattic\WooCommerce\Admin\Notes\NoteTraits;
/**
* Migrate_From_Shopify.
*/
class MigrateFromShopify {
/**
* Note traits.
*/
use NoteTraits;
/**
* Name of the note for use in the database.
*/
const NOTE_NAME = 'wc-admin-migrate-from-shopify';
/**
* Get the note.
*
* @return Note
*/
public static function get_note() {
// We want to show the note after two days.
$two_days = 2 * DAY_IN_SECONDS;
if ( ! self::is_wc_admin_active_in_date_range( 'week-1', $two_days ) ) {
return;
}
$onboarding_profile = get_option( 'woocommerce_onboarding_profile', array() );
if (
! isset( $onboarding_profile['setup_client'] ) ||
! isset( $onboarding_profile['selling_venues'] ) ||
! isset( $onboarding_profile['other_platform'] )
) {
return;
}
// Make sure the client is not setup.
if ( $onboarding_profile['setup_client'] ) {
return;
}
// We will show the notification when the client already is selling and is using Shopify.
if (
'other' !== $onboarding_profile['selling_venues'] ||
'shopify' !== $onboarding_profile['other_platform']
) {
return;
}
$note = new Note();
$note->set_title( __( 'Do you want to migrate from Shopify to WooCommerce?', 'woocommerce' ) );
$note->set_content( __( 'Changing eCommerce platforms might seem like a big hurdle to overcome, but it is easier than you might think to move your products, customers, and orders to WooCommerce. This article will help you with going through this process.', 'woocommerce' ) );
$note->set_type( Note::E_WC_ADMIN_NOTE_INFORMATIONAL );
$note->set_name( self::NOTE_NAME );
$note->set_content_data( (object) array() );
$note->set_source( 'woocommerce-admin' );
$note->add_action(
'migrate-from-shopify',
__( 'Learn more', 'woocommerce' ),
'https://woocommerce.com/posts/migrate-from-shopify-to-woocommerce/?utm_source=inbox&utm_medium=product',
Note::E_WC_ADMIN_NOTE_ACTIONED
);
return $note;
}
}

View File

@@ -0,0 +1,53 @@
<?php
/**
* WooCommerce Admin Mobile App Note Provider.
*
* Adds a note to the merchant's inbox showing the benefits of the mobile app.
*/
namespace Automattic\WooCommerce\Internal\Admin\Notes;
defined( 'ABSPATH' ) || exit;
use Automattic\WooCommerce\Admin\Notes\Note;
use Automattic\WooCommerce\Admin\Notes\NoteTraits;
/**
* Mobile_App
*/
class MobileApp {
/**
* Note traits.
*/
use NoteTraits;
/**
* Name of the note for use in the database.
*/
const NOTE_NAME = 'wc-admin-mobile-app';
/**
* Get the note.
*
* @return Note
*/
public static function get_note() {
// We want to show the mobile app note after day 2.
$two_days_in_seconds = 2 * DAY_IN_SECONDS;
if ( ! self::is_wc_admin_active_in_date_range( 'week-1', $two_days_in_seconds ) ) {
return;
}
$content = __( 'Install the WooCommerce mobile app to manage orders, receive sales notifications, and view key metrics — wherever you are.', 'woocommerce' );
$note = new Note();
$note->set_title( __( 'Install Woo mobile app', 'woocommerce' ) );
$note->set_content( $content );
$note->set_content_data( (object) array() );
$note->set_type( Note::E_WC_ADMIN_NOTE_INFORMATIONAL );
$note->set_name( self::NOTE_NAME );
$note->set_source( 'woocommerce-admin' );
$note->add_action( 'learn-more', __( 'Learn more', 'woocommerce' ), 'https://woocommerce.com/mobile/?utm_medium=product' );
return $note;
}
}

View File

@@ -0,0 +1,179 @@
<?php
/**
* WooCommerce Admin (Dashboard) New Sales Record Note Provider.
*
* Adds a note to the merchant's inbox when the previous day's sales are a new record.
*/
namespace Automattic\WooCommerce\Internal\Admin\Notes;
defined( 'ABSPATH' ) || exit;
use Automattic\WooCommerce\Admin\Notes\Note;
use Automattic\WooCommerce\Admin\Notes\Notes;
use Automattic\WooCommerce\Admin\Notes\NoteTraits;
/**
* New_Sales_Record
*/
class NewSalesRecord {
/**
* Note traits.
*/
use NoteTraits;
/**
* Name of the note for use in the database.
*/
const NOTE_NAME = 'wc-admin-new-sales-record';
/**
* Option name for the sales record date in ISO 8601 (YYYY-MM-DD) date.
*/
const RECORD_DATE_OPTION_KEY = 'woocommerce_sales_record_date';
/**
* Option name for the sales record amount.
*/
const RECORD_AMOUNT_OPTION_KEY = 'woocommerce_sales_record_amount';
/**
* Returns the total of yesterday's sales.
*
* @param string $date Date for sales to sum (i.e. YYYY-MM-DD).
* @return floatval
*/
public static function sum_sales_for_date( $date ) {
$order_query = new \WC_Order_Query( array( 'date_created' => $date ) );
$orders = $order_query->get_orders();
$total = 0;
foreach ( (array) $orders as $order ) {
$total += $order->get_total();
}
return $total;
}
/**
* Possibly add a sales record note.
*/
public static function possibly_add_note() {
/**
* Filter to allow for disabling sales record milestones.
*
* @since 3.7.0
*
* @param boolean default true
*/
$sales_record_notes_enabled = apply_filters( 'woocommerce_admin_sales_record_milestone_enabled', true );
if ( ! $sales_record_notes_enabled ) {
return;
}
$yesterday = gmdate( 'Y-m-d', current_time( 'timestamp', 0 ) - DAY_IN_SECONDS );
$total = self::sum_sales_for_date( $yesterday );
// No sales yesterday? Bail.
if ( 0 >= $total ) {
return;
}
$record_date = get_option( self::RECORD_DATE_OPTION_KEY, '' );
$record_amt = floatval( get_option( self::RECORD_AMOUNT_OPTION_KEY, 0 ) );
// No previous entry? Just enter what we have and return without generating a note.
if ( empty( $record_date ) ) {
update_option( self::RECORD_DATE_OPTION_KEY, $yesterday );
update_option( self::RECORD_AMOUNT_OPTION_KEY, $total );
return;
}
// Otherwise, if yesterdays total bested the record, update AND generate a note.
if ( $total > $record_amt ) {
update_option( self::RECORD_DATE_OPTION_KEY, $yesterday );
update_option( self::RECORD_AMOUNT_OPTION_KEY, $total );
// We only want one sales record note at any time in the inbox, so we delete any other first.
Notes::delete_notes_with_name( self::NOTE_NAME );
$note = self::get_note_with_record_data( $record_date, $record_amt, $yesterday, $total );
$note->save();
}
}
/**
* Get the note with record data.
*
* @param string $record_date record date Y-m-d.
* @param float $record_amt record amount.
* @param string $yesterday yesterday's date Y-m-d.
* @param string $total total sales for yesterday.
*
* @return Note
*/
public static function get_note_with_record_data( $record_date, $record_amt, $yesterday, $total ) {
// Use F jS (March 7th) format for English speaking countries.
if ( substr( get_user_locale(), 0, 2 ) === 'en' ) {
$date_format = 'F jS';
} else {
// otherwise, fallback to the system date format.
$date_format = get_option( 'date_format' );
}
$formatted_yesterday = date_i18n( $date_format, strtotime( $yesterday ) );
$formatted_total = html_entity_decode( wp_strip_all_tags( wc_price( $total ) ) );
$formatted_record_date = date_i18n( $date_format, strtotime( $record_date ) );
$formatted_record_amt = html_entity_decode( wp_strip_all_tags( wc_price( $record_amt ) ) );
$content = sprintf(
/* translators: 1 and 4: Date (e.g. October 16th), 2 and 3: Amount (e.g. $160.00) */
__( 'Woohoo, %1$s was your record day for sales! Net sales was %2$s beating the previous record of %3$s set on %4$s.', 'woocommerce' ),
$formatted_yesterday,
$formatted_total,
$formatted_record_amt,
$formatted_record_date
);
$content_data = (object) array(
'old_record_date' => $record_date,
'old_record_amt' => $record_amt,
'new_record_date' => $yesterday,
'new_record_amt' => $total,
);
$report_url = '?page=wc-admin&path=/analytics/revenue&period=custom&compare=previous_year&after=' . $yesterday . '&before=' . $yesterday;
// And now, create our new note.
$note = new Note();
$note->set_title( __( 'New sales record!', 'woocommerce' ) );
$note->set_content( $content );
$note->set_content_data( $content_data );
$note->set_type( Note::E_WC_ADMIN_NOTE_INFORMATIONAL );
$note->set_name( self::NOTE_NAME );
$note->set_source( 'woocommerce-admin' );
$note->add_action( 'view-report', __( 'View report', 'woocommerce' ), $report_url );
return $note;
}
/**
* Get the note. This is used for localizing the note.
*
* @return Note
*/
public static function get_note() {
$note = Notes::get_note_by_name( self::NOTE_NAME );
if ( ! $note ) {
return false;
}
$content_data = $note->get_content_data();
return self::get_note_with_record_data(
$content_data->old_record_date,
$content_data->old_record_amt,
$content_data->new_record_date,
$content_data->new_record_amt
);
}
}

View File

@@ -0,0 +1,68 @@
<?php
/**
* WooCommerce Admin: Payments reminder note.
*
* Adds a notes to complete the payment methods.
*/
namespace Automattic\WooCommerce\Internal\Admin\Notes;
defined( 'ABSPATH' ) || exit;
use Automattic\WooCommerce\Admin\Notes\Note;
use Automattic\WooCommerce\Admin\Notes\NoteTraits;
/**
* Onboarding_Payments.
*/
class OnboardingPayments {
/**
* Note traits.
*/
use NoteTraits;
/**
* Name of the note for use in the database.
*/
const NOTE_NAME = 'wc-admin-onboarding-payments-reminder';
/**
* Get the note.
*
* @return Note
*/
public static function get_note() {
// We want to show the note after five days.
if ( ! self::is_wc_admin_active_in_date_range( 'week-1-4', 5 * DAY_IN_SECONDS ) ) {
return;
}
// Check to see if any gateways have been added.
$gateways = WC()->payment_gateways->get_available_payment_gateways();
$enabled_gateways = array_filter(
$gateways,
function( $gateway ) {
return 'yes' === $gateway->enabled;
}
);
if ( ! empty( $enabled_gateways ) ) {
return;
}
$note = new Note();
$note->set_title( __( 'Start accepting payments on your store!', 'woocommerce' ) );
$note->set_content( __( 'Take payments with the provider thats right for you - choose from 100+ payment gateways for WooCommerce.', 'woocommerce' ) );
$note->set_type( Note::E_WC_ADMIN_NOTE_INFORMATIONAL );
$note->set_name( self::NOTE_NAME );
$note->set_content_data( (object) array() );
$note->set_source( 'woocommerce-admin' );
$note->add_action(
'view-payment-gateways',
__( 'Learn more', 'woocommerce' ),
'https://woocommerce.com/product-category/woocommerce-extensions/payment-gateways/?utm_medium=product',
Note::E_WC_ADMIN_NOTE_ACTIONED,
true
);
return $note;
}
}

View File

@@ -0,0 +1,98 @@
<?php
/**
* WooCommerce Admin: Start your online clothing store.
*
* Adds a note to ask the client if they are considering starting an online
* clothing store.
*/
namespace Automattic\WooCommerce\Internal\Admin\Notes;
defined( 'ABSPATH' ) || exit;
use Automattic\WooCommerce\Admin\Notes\Note;
use Automattic\WooCommerce\Admin\Notes\NoteTraits;
/**
* Online_Clothing_Store.
*/
class OnlineClothingStore {
/**
* Note traits.
*/
use NoteTraits;
/**
* Name of the note for use in the database.
*/
const NOTE_NAME = 'wc-admin-online-clothing-store';
/**
* Returns whether the industries includes fashion-apparel-accessories.
*
* @param array $industries The industries to search.
*
* @return bool Whether the industries includes fashion-apparel-accessories.
*/
private static function is_in_fashion_industry( $industries ) {
foreach ( $industries as $industry ) {
if ( 'fashion-apparel-accessories' === $industry['slug'] ) {
return true;
}
}
return false;
}
/**
* Get the note.
*
* @return Note
*/
public static function get_note() {
// We want to show the note after two days.
if ( ! self::is_wc_admin_active_in_date_range( 'week-1', 2 * DAY_IN_SECONDS ) ) {
return;
}
$onboarding_profile = get_option( 'woocommerce_onboarding_profile', array() );
// Confirm that $onboarding_profile is set.
if ( empty( $onboarding_profile ) ) {
return;
}
// Make sure that the person who filled out the OBW was not setting up
// the store for their customer/client.
if (
! isset( $onboarding_profile['setup_client'] ) ||
$onboarding_profile['setup_client']
) {
return;
}
// We need to show the notification when the industry is
// fashion/apparel/accessories.
if ( ! isset( $onboarding_profile['industry'] ) ) {
return;
}
if ( ! self::is_in_fashion_industry( $onboarding_profile['industry'] ) ) {
return;
}
$note = new Note();
$note->set_title( __( 'Start your online clothing store', 'woocommerce' ) );
$note->set_content( __( 'Starting a fashion website is exciting but it may seem overwhelming as well. In this article, we\'ll walk you through the setup process, teach you to create successful product listings, and show you how to market to your ideal audience.', 'woocommerce' ) );
$note->set_type( Note::E_WC_ADMIN_NOTE_INFORMATIONAL );
$note->set_name( self::NOTE_NAME );
$note->set_content_data( (object) array() );
$note->set_source( 'woocommerce-admin' );
$note->add_action(
'online-clothing-store',
__( 'Learn more', 'woocommerce' ),
'https://woocommerce.com/posts/starting-an-online-clothing-store/?utm_source=inbox&utm_medium=product',
Note::E_WC_ADMIN_NOTE_ACTIONED
);
return $note;
}
}

View File

@@ -0,0 +1,369 @@
<?php
/**
* WooCommerce Admin (Dashboard) Order Milestones Note Provider.
*
* Adds a note to the merchant's inbox when certain order milestones are reached.
*/
namespace Automattic\WooCommerce\Internal\Admin\Notes;
defined( 'ABSPATH' ) || exit;
use Automattic\WooCommerce\Admin\Notes\Note;
use Automattic\WooCommerce\Admin\Notes\Notes;
/**
* Order_Milestones
*/
class OrderMilestones {
/**
* Name of the "other milestones" note.
*/
const NOTE_NAME = 'wc-admin-orders-milestone';
/**
* Option key name to store last order milestone.
*/
const LAST_ORDER_MILESTONE_OPTION_KEY = 'woocommerce_admin_last_orders_milestone';
/**
* Hook to process order milestones.
*/
const PROCESS_ORDERS_MILESTONE_HOOK = 'wc_admin_process_orders_milestone';
/**
* Allowed order statuses for calculating milestones.
*
* @var array
*/
protected $allowed_statuses = array(
'pending',
'processing',
'completed',
);
/**
* Orders count cache.
*
* @var int
*/
protected $orders_count = null;
/**
* Further order milestone thresholds.
*
* @var array
*/
protected $milestones = array(
1,
10,
100,
250,
500,
1000,
5000,
10000,
500000,
1000000,
);
/**
* Delay hook attachment until after the WC post types have been registered.
*
* This is required for retrieving the order count.
*/
public function __construct() {
/**
* Filter Order statuses that will count towards milestones.
*
* @since 3.5.0
*
* @param array $allowed_statuses Order statuses that will count towards milestones.
*/
$this->allowed_statuses = apply_filters( 'woocommerce_admin_order_milestone_statuses', $this->allowed_statuses );
add_action( 'woocommerce_after_register_post_type', array( $this, 'init' ) );
register_deactivation_hook( WC_PLUGIN_FILE, array( $this, 'clear_scheduled_event' ) );
}
/**
* Hook everything up.
*/
public function init() {
if ( ! wp_next_scheduled( self::PROCESS_ORDERS_MILESTONE_HOOK ) ) {
wp_schedule_event( time(), 'hourly', self::PROCESS_ORDERS_MILESTONE_HOOK );
}
add_action( 'wc_admin_installed', array( $this, 'backfill_last_milestone' ) );
add_action( self::PROCESS_ORDERS_MILESTONE_HOOK, array( $this, 'possibly_add_note' ) );
}
/**
* Clear out our hourly milestone hook upon plugin deactivation.
*/
public function clear_scheduled_event() {
wp_clear_scheduled_hook( self::PROCESS_ORDERS_MILESTONE_HOOK );
}
/**
* Get the total count of orders (in the allowed statuses).
*
* @param bool $no_cache Optional. Skip cache.
* @return int Total orders count.
*/
public function get_orders_count( $no_cache = false ) {
if ( $no_cache || is_null( $this->orders_count ) ) {
$status_counts = array_map( 'wc_orders_count', $this->allowed_statuses );
$this->orders_count = array_sum( $status_counts );
}
return $this->orders_count;
}
/**
* Backfill the store's current milestone.
*
* Used to avoid celebrating milestones that were reached before plugin activation.
*/
public function backfill_last_milestone() {
// If the milestone notes have been disabled via filter, bail.
if ( ! $this->are_milestones_enabled() ) {
return;
}
$this->set_last_milestone( $this->get_current_milestone() );
}
/**
* Get the store's last milestone.
*
* @return int Last milestone reached.
*/
public function get_last_milestone() {
return get_option( self::LAST_ORDER_MILESTONE_OPTION_KEY, 0 );
}
/**
* Update the last reached milestone.
*
* @param int $milestone Last milestone reached.
*/
public function set_last_milestone( $milestone ) {
update_option( self::LAST_ORDER_MILESTONE_OPTION_KEY, $milestone );
}
/**
* Calculate the current orders milestone.
*
* Based on the threshold values in $this->milestones.
*
* @return int Current orders milestone.
*/
public function get_current_milestone() {
$milestone_reached = 0;
$orders_count = $this->get_orders_count();
foreach ( $this->milestones as $milestone ) {
if ( $milestone <= $orders_count ) {
$milestone_reached = $milestone;
}
}
return $milestone_reached;
}
/**
* Get the appropriate note title for a given milestone.
*
* @param int $milestone Order milestone.
* @return string Note title for the milestone.
*/
public static function get_note_title_for_milestone( $milestone ) {
switch ( $milestone ) {
case 1:
return __( 'First order received', 'woocommerce' );
case 10:
case 100:
case 250:
case 500:
case 1000:
case 5000:
case 10000:
case 500000:
case 1000000:
return sprintf(
/* translators: Number of orders processed. */
__( 'Congratulations on processing %s orders!', 'woocommerce' ),
wc_format_decimal( $milestone )
);
default:
return '';
}
}
/**
* Get the appropriate note content for a given milestone.
*
* @param int $milestone Order milestone.
* @return string Note content for the milestone.
*/
public static function get_note_content_for_milestone( $milestone ) {
switch ( $milestone ) {
case 1:
return __( 'Congratulations on getting your first order! Now is a great time to learn how to manage your orders.', 'woocommerce' );
case 10:
return __( "You've hit the 10 orders milestone! Look at you go. Browse some WooCommerce success stories for inspiration.", 'woocommerce' );
case 100:
case 250:
case 500:
case 1000:
case 5000:
case 10000:
case 500000:
case 1000000:
return __( 'Another order milestone! Take a look at your Orders Report to review your orders to date.', 'woocommerce' );
default:
return '';
}
}
/**
* Get the appropriate note action for a given milestone.
*
* @param int $milestone Order milestone.
* @return array Note actoion (name, label, query) for the milestone.
*/
public static function get_note_action_for_milestone( $milestone ) {
switch ( $milestone ) {
case 1:
return array(
'name' => 'learn-more',
'label' => __( 'Learn more', 'woocommerce' ),
'query' => 'https://woocommerce.com/document/managing-orders/?utm_source=inbox&utm_medium=product',
);
case 10:
return array(
'name' => 'browse',
'label' => __( 'Browse', 'woocommerce' ),
'query' => 'https://woocommerce.com/success-stories/?utm_source=inbox&utm_medium=product',
);
case 100:
case 250:
case 500:
case 1000:
case 5000:
case 10000:
case 500000:
case 1000000:
return array(
'name' => 'review-orders',
'label' => __( 'Review your orders', 'woocommerce' ),
'query' => '?page=wc-admin&path=/analytics/orders',
);
default:
return array(
'name' => '',
'label' => '',
'query' => '',
);
}
}
/**
* Convenience method to see if the milestone notes are enabled.
*
* @return boolean True if milestone notifications are enabled.
*/
public function are_milestones_enabled() {
/**
* Filter to allow for disabling order milestones.
*
* @since 3.7.0
*
* @param boolean default true
*/
$milestone_notes_enabled = apply_filters( 'woocommerce_admin_order_milestones_enabled', true );
return $milestone_notes_enabled;
}
/**
* Get the note. This is used for localizing the note.
*
* @return Note
*/
public static function get_note() {
$note = Notes::get_note_by_name( self::NOTE_NAME );
if ( ! $note ) {
return false;
}
$content_data = $note->get_content_data();
if ( ! isset( $content_data->current_milestone ) ) {
return false;
}
return self::get_note_by_milestone(
$content_data->current_milestone
);
}
/**
* Get the note by milestones.
*
* @param int $current_milestone Current milestone.
*
* @return Note
*/
public static function get_note_by_milestone( $current_milestone ) {
$content_data = (object) array(
'current_milestone' => $current_milestone,
);
$note = new Note();
$note->set_title( self::get_note_title_for_milestone( $current_milestone ) );
$note->set_content( self::get_note_content_for_milestone( $current_milestone ) );
$note->set_content_data( $content_data );
$note->set_type( Note::E_WC_ADMIN_NOTE_INFORMATIONAL );
$note->set_name( self::NOTE_NAME );
$note->set_source( 'woocommerce-admin' );
$note_action = self::get_note_action_for_milestone( $current_milestone );
$note->add_action( $note_action['name'], $note_action['label'], $note_action['query'] );
return $note;
}
/**
* Checks if a note can and should be added.
*
* @return bool
*/
public function can_be_added() {
// If the milestone notes have been disabled via filter, bail.
if ( ! $this->are_milestones_enabled() ) {
return false;
}
$last_milestone = $this->get_last_milestone();
$current_milestone = $this->get_current_milestone();
if ( $current_milestone <= $last_milestone ) {
return false;
}
return true;
}
/**
* Add milestone notes for other significant thresholds.
*/
public function possibly_add_note() {
if ( ! self::can_be_added() ) {
return;
}
$current_milestone = $this->get_current_milestone();
$this->set_last_milestone( $current_milestone );
// We only want one milestone note at any time.
Notes::delete_notes_with_name( self::NOTE_NAME );
$note = $this->get_note_by_milestone( $current_milestone );
$note->save();
}
}

View File

@@ -0,0 +1,84 @@
<?php
/**
* WooCommerce Admin Payments More Info Needed Inbox Note Provider
*/
namespace Automattic\WooCommerce\Internal\Admin\Notes;
use Automattic\WooCommerce\Admin\Notes\Note;
use Automattic\WooCommerce\Admin\Notes\NoteTraits;
use Automattic\WooCommerce\Admin\PluginsHelper;
defined( 'ABSPATH' ) || exit;
/**
* PaymentsMoreInfoNeeded
*/
class PaymentsMoreInfoNeeded {
/**
* Note traits.
*/
use NoteTraits;
/**
* Name of the note for use in the database.
*/
const NOTE_NAME = 'wc-admin-payments-more-info-needed';
/**
* Should this note exist?
*/
public static function is_applicable() {
return self::should_display_note();
}
/**
* Returns true if we should display the note.
*
* @return bool
*/
public static function should_display_note() {
// If user has installed WCPay, don't show this note.
$installed_plugins = PluginsHelper::get_installed_plugin_slugs();
if ( in_array( 'woocommerce-payments', $installed_plugins, true ) ) {
return false;
}
// User has dismissed the WCPay Welcome Page.
if ( 'yes' !== get_option( 'wc_calypso_bridge_payments_dismissed', 'no' ) ) {
return false;
}
// More than 30 days since viewing the welcome page.
$exit_survey_timestamp = get_option( 'wc_pay_exit_survey_more_info_needed_timestamp', false );
if ( ! $exit_survey_timestamp ||
( time() - $exit_survey_timestamp < 30 * DAY_IN_SECONDS )
) {
return false;
}
return true;
}
/**
* Get the note.
*
* @return Note
*/
public static function get_note() {
if ( ! self::should_display_note() ) {
return;
}
$content = __( 'We recently asked you if you wanted more information about WooCommerce Payments. Run your business and manage your payments in one place with the solution built and supported by WooCommerce.', 'woocommerce' );
$note = new Note();
$note->set_title( __( 'Payments made simple with WooCommerce Payments', 'woocommerce' ) );
$note->set_content( $content );
$note->set_content_data( (object) array() );
$note->set_type( Note::E_WC_ADMIN_NOTE_INFORMATIONAL );
$note->set_name( self::NOTE_NAME );
$note->set_source( 'woocommerce-admin' );
$note->add_action( 'learn-more', __( 'Learn more here', 'woocommerce' ), 'https://woocommerce.com/payments/' );
return $note;
}
}

View File

@@ -0,0 +1,83 @@
<?php
/**
* WooCommerce Admin Payment Reminder Me later
*/
namespace Automattic\WooCommerce\Internal\Admin\Notes;
use Automattic\WooCommerce\Admin\Notes\Note;
use Automattic\WooCommerce\Admin\Notes\NoteTraits;
use Automattic\WooCommerce\Admin\PluginsHelper;
defined( 'ABSPATH' ) || exit;
/**
* PaymentsRemindMeLater
*/
class PaymentsRemindMeLater {
/**
* Note traits.
*/
use NoteTraits;
/**
* Name of the note for use in the database.
*/
const NOTE_NAME = 'wc-admin-payments-remind-me-later';
/**
* Should this note exist?
*/
public static function is_applicable() {
return self::should_display_note();
}
/**
* Returns true if we should display the note.
*
* @return bool
*/
public static function should_display_note() {
// Installed WCPay.
$installed_plugins = PluginsHelper::get_installed_plugin_slugs();
if ( in_array( 'woocommerce-payments', $installed_plugins, true ) ) {
return false;
}
// Dismissed WCPay welcome page.
if ( 'yes' === get_option( 'wc_calypso_bridge_payments_dismissed', 'no' ) ) {
return false;
}
// Less than 3 days since viewing welcome page.
$view_timestamp = get_option( 'wc_pay_welcome_page_viewed_timestamp', false );
if ( ! $view_timestamp ||
( time() - $view_timestamp < 3 * DAY_IN_SECONDS )
) {
return false;
}
return true;
}
/**
* Get the note.
*
* @return Note
*/
public static function get_note() {
if ( ! self::should_display_note() ) {
return;
}
$content = __( 'Save up to $800 in fees by managing transactions with WooCommerce Payments. With WooCommerce Payments, you can securely accept major cards, Apple Pay, and payments in over 100 currencies.', 'woocommerce' );
$note = new Note();
$note->set_title( __( 'Save big with WooCommerce Payments', 'woocommerce' ) );
$note->set_content( $content );
$note->set_content_data( (object) array() );
$note->set_type( Note::E_WC_ADMIN_NOTE_INFORMATIONAL );
$note->set_name( self::NOTE_NAME );
$note->set_source( 'woocommerce-admin' );
$note->add_action( 'learn-more', __( 'Learn more', 'woocommerce' ), admin_url( 'admin.php?page=wc-admin&path=/wc-pay-welcome-page' ) );
return $note;
}
}

View File

@@ -0,0 +1,68 @@
<?php
/**
* WooCommerce Admin Performance on mobile note.
*
* Adds a note to download the mobile app, performance on mobile.
*/
namespace Automattic\WooCommerce\Internal\Admin\Notes;
defined( 'ABSPATH' ) || exit;
use Automattic\WooCommerce\Admin\Notes\Note;
use Automattic\WooCommerce\Admin\Notes\NoteTraits;
/**
* Performance_On_Mobile
*/
class PerformanceOnMobile {
/**
* Note traits.
*/
use NoteTraits;
/**
* Name of the note for use in the database.
*/
const NOTE_NAME = 'wc-admin-performance-on-mobile';
/**
* Get the note.
*
* @return Note
*/
public static function get_note() {
// Only add this note if this store is at least 9 months old.
$nine_months_in_seconds = MONTH_IN_SECONDS * 9;
if ( ! self::wc_admin_active_for( $nine_months_in_seconds ) ) {
return;
}
// Check that the previous mobile app notes have not been actioned.
if ( MobileApp::has_note_been_actioned() ) {
return;
}
if ( RealTimeOrderAlerts::has_note_been_actioned() ) {
return;
}
if ( ManageOrdersOnTheGo::has_note_been_actioned() ) {
return;
}
$note = new Note();
$note->set_title( __( 'Track your store performance on mobile', 'woocommerce' ) );
$note->set_content( __( 'Monitor your sales and high performing products with the Woo app.', 'woocommerce' ) );
$note->set_content_data( (object) array() );
$note->set_type( Note::E_WC_ADMIN_NOTE_INFORMATIONAL );
$note->set_name( self::NOTE_NAME );
$note->set_source( 'woocommerce-admin' );
$note->add_action(
'learn-more',
__( 'Learn more', 'woocommerce' ),
'https://woocommerce.com/mobile/?utm_source=inbox&utm_medium=product'
);
return $note;
}
}

View File

@@ -0,0 +1,63 @@
<?php
/**
* WooCommerce Admin Personalize Your Store Note Provider.
*
* Adds a note to the merchant's inbox prompting them to personalize their store.
*/
namespace Automattic\WooCommerce\Internal\Admin\Notes;
defined( 'ABSPATH' ) || exit;
use Automattic\WooCommerce\Admin\Notes\Note;
use Automattic\WooCommerce\Admin\Notes\NoteTraits;
/**
* Personalize_Store
*/
class PersonalizeStore {
/**
* Note traits.
*/
use NoteTraits;
/**
* Name of the note for use in the database.
*/
const NOTE_NAME = 'wc-admin-personalize-store';
/**
* Get the note.
*
* @return Note
*/
public static function get_note() {
// Only show the note to stores with homepage.
$homepage_id = get_option( 'woocommerce_onboarding_homepage_post_id', false );
if ( ! $homepage_id ) {
return;
}
// Show the note after task list is done.
$is_task_list_complete = get_option( 'woocommerce_task_list_complete', false );
// We want to show the note after day 5.
$five_days_in_seconds = 5 * DAY_IN_SECONDS;
if ( ! self::is_wc_admin_active_in_date_range( 'week-1-4', $five_days_in_seconds ) && ! $is_task_list_complete ) {
return;
}
$content = __( 'The homepage is one of the most important entry points in your store. When done right it can lead to higher conversions and engagement. Don\'t forget to personalize the homepage that we created for your store during the onboarding.', 'woocommerce' );
$note = new Note();
$note->set_title( __( 'Personalize your store\'s homepage', 'woocommerce' ) );
$note->set_content( $content );
$note->set_content_data( (object) array() );
$note->set_type( Note::E_WC_ADMIN_NOTE_INFORMATIONAL );
$note->set_name( self::NOTE_NAME );
$note->set_source( 'woocommerce-admin' );
$note->add_action( 'personalize-homepage', __( 'Personalize homepage', 'woocommerce' ), admin_url( 'post.php?post=' . $homepage_id . '&action=edit' ), Note::E_WC_ADMIN_NOTE_ACTIONED );
return $note;
}
}

View File

@@ -0,0 +1,57 @@
<?php
/**
* WooCommerce Admin Real Time Order Alerts Note.
*
* Adds a note to download the mobile app to monitor store activity.
*/
namespace Automattic\WooCommerce\Internal\Admin\Notes;
defined( 'ABSPATH' ) || exit;
use Automattic\WooCommerce\Admin\Notes\Note;
use Automattic\WooCommerce\Admin\Notes\NoteTraits;
/**
* Real_Time_Order_Alerts
*/
class RealTimeOrderAlerts {
/**
* Note traits.
*/
use NoteTraits;
/**
* Name of the note for use in the database.
*/
const NOTE_NAME = 'wc-admin-real-time-order-alerts';
/**
* Get the note.
*
* @return Note
*/
public static function get_note() {
// Only add this note if the store is 3 months old.
if ( ! self::is_wc_admin_active_in_date_range( 'month-3-6' ) ) {
return;
}
// Check that the previous mobile app note was not actioned.
if ( MobileApp::has_note_been_actioned() ) {
return;
}
$content = __( 'Get notifications about store activity, including new orders and product reviews directly on your mobile devices with the Woo app.', 'woocommerce' );
$note = new Note();
$note->set_title( __( 'Get real-time order alerts anywhere', 'woocommerce' ) );
$note->set_content( $content );
$note->set_content_data( (object) array() );
$note->set_type( Note::E_WC_ADMIN_NOTE_INFORMATIONAL );
$note->set_name( self::NOTE_NAME );
$note->set_source( 'woocommerce-admin' );
$note->add_action( 'learn-more', __( 'Learn more', 'woocommerce' ), 'https://woocommerce.com/mobile/?utm_source=inbox&utm_medium=product' );
return $note;
}
}

View File

@@ -0,0 +1,85 @@
<?php
/**
* WooCommerce Admin: Selling Online Courses note
*
* Adds a note to encourage selling online courses.
*/
namespace Automattic\WooCommerce\Internal\Admin\Notes;
defined( 'ABSPATH' ) || exit;
use Automattic\WooCommerce\Admin\Notes\Note;
use Automattic\WooCommerce\Admin\Notes\NoteTraits;
use Automattic\WooCommerce\Internal\Admin\Onboarding\OnboardingProfile;
/**
* Selling_Online_Courses
*/
class SellingOnlineCourses {
/**
* Note traits.
*/
use NoteTraits;
/**
* Name of the note for use in the database.
*/
const NOTE_NAME = 'wc-admin-selling-online-courses';
/**
* Attach hooks.
*/
public function __construct() {
add_action(
'update_option_' . OnboardingProfile::DATA_OPTION,
array( $this, 'check_onboarding_profile' ),
10,
3
);
}
/**
* Check to see if the profiler options match before possibly adding note.
*
* @param object $old_value The old option value.
* @param object $value The new option value.
* @param string $option The name of the option.
*/
public static function check_onboarding_profile( $old_value, $value, $option ) {
// Skip adding if this store is in the education/learning industry.
if ( ! isset( $value['industry'] ) ) {
return;
}
$industry_slugs = array_column( $value['industry'], 'slug' );
if ( ! in_array( 'education-and-learning', $industry_slugs, true ) ) {
return;
}
self::possibly_add_note();
}
/**
* Get the note.
*
* @return Note
*/
public static function get_note() {
$note = new Note();
$note->set_title( __( 'Do you want to sell online courses?', 'woocommerce' ) );
$note->set_content( __( 'Online courses are a great solution for any business that can teach a new skill. Since courses dont require physical product development or shipping, theyre affordable, fast to create, and can generate passive income for years to come. In this article, we provide you more information about selling courses using WooCommerce.', 'woocommerce' ) );
$note->set_content_data( (object) array() );
$note->set_type( Note::E_WC_ADMIN_NOTE_MARKETING );
$note->set_name( self::NOTE_NAME );
$note->set_source( 'woocommerce-admin' );
$note->add_action(
'learn-more',
__( 'Learn more', 'woocommerce' ),
'https://woocommerce.com/posts/how-to-sell-online-courses-wordpress/?utm_source=inbox&utm_medium=product',
Note::E_WC_ADMIN_NOTE_ACTIONED
);
return $note;
}
}

View File

@@ -0,0 +1,104 @@
<?php
/**
* WooCommerce Admin Test Checkout.
*
* Adds a note to remind the user to test their store checkout.
*
* @package WooCommerce\Admin
*/
namespace Automattic\WooCommerce\Internal\Admin\Notes;
defined( 'ABSPATH' ) || exit;
use Automattic\WooCommerce\Admin\Notes\Note;
use Automattic\WooCommerce\Admin\Notes\NoteTraits;
/**
* Test_Checkout
*/
class TestCheckout {
/**
* Note traits.
*/
use NoteTraits;
/**
* Name of the note for use in the database.
*/
const NOTE_NAME = 'wc-admin-test-checkout';
/**
* Completed tasks option name.
*/
const TASK_LIST_TRACKED_TASKS = 'woocommerce_task_list_tracked_completed_tasks';
/**
* Constructor.
*/
public function __construct() {
add_action( 'update_option_' . self::TASK_LIST_TRACKED_TASKS, array( $this, 'possibly_add_note' ) );
}
/**
* Get the note.
*
* @return Note|null
*/
public static function get_note() {
$onboarding_profile = get_option( 'woocommerce_onboarding_profile', array() );
// Confirm that $onboarding_profile is set.
if ( empty( $onboarding_profile ) ) {
return;
}
// Make sure that the person who filled out the OBW was not setting up
// the store for their customer/client.
if (
! isset( $onboarding_profile['setup_client'] ) ||
$onboarding_profile['setup_client']
) {
return;
}
// Make sure payments task was completed.
$completed_tasks = get_option( self::TASK_LIST_TRACKED_TASKS, array() );
if ( ! in_array( 'payments', $completed_tasks, true ) ) {
return;
}
// Make sure that products were added within the previous 1/2 hour.
$query = new \WC_Product_Query(
array(
'limit' => 1,
'status' => 'publish',
'orderby' => 'date',
'order' => 'ASC',
)
);
$products = $query->get_products();
if ( 0 === count( $products ) ) {
return;
}
$oldest_product_timestamp = $products[0]->get_date_created()->getTimestamp();
$half_hour_in_seconds = 30 * MINUTE_IN_SECONDS;
if ( ( time() - $oldest_product_timestamp ) > $half_hour_in_seconds ) {
return;
}
$content = __( 'Make sure that your checkout is working properly before you launch your store. Go through your checkout process in its entirety: from adding a product to your cart, choosing a shipping location, and making a payment.', 'woocommerce' );
$note = new Note();
$note->set_title( __( 'Don\'t forget to test your checkout', 'woocommerce' ) );
$note->set_content( $content );
$note->set_content_data( (object) array() );
$note->set_type( Note::E_WC_ADMIN_NOTE_INFORMATIONAL );
$note->set_name( self::NOTE_NAME );
$note->set_source( 'woocommerce-admin' );
$note->add_action( 'test-checkout', __( 'Test checkout', 'woocommerce' ), wc_get_page_permalink( 'shop' ) );
return $note;
}
}

View File

@@ -0,0 +1,89 @@
<?php
/**
* WooCommerce Admin Usage Tracking Opt In Note Provider.
*
* Adds a Usage Tracking Opt In extension note.
*/
namespace Automattic\WooCommerce\Internal\Admin\Notes;
defined( 'ABSPATH' ) || exit;
use Automattic\WooCommerce\Admin\Notes\Note;
use Automattic\WooCommerce\Admin\Notes\NoteTraits;
/**
* Tracking_Opt_In
*/
class TrackingOptIn {
/**
* Note traits.
*/
use NoteTraits;
/**
* Name of the note for use in the database.
*/
const NOTE_NAME = 'wc-admin-usage-tracking-opt-in';
/**
* Attach hooks.
*/
public function __construct() {
add_action( 'woocommerce_note_action_tracking-opt-in', array( $this, 'opt_in_to_tracking' ) );
}
/**
* Get the note.
*
* @return Note|null
*/
public static function get_note() {
// Only show this note to stores that are opted out.
if ( 'yes' === get_option( 'woocommerce_allow_tracking', 'no' ) ) {
return;
}
// We want to show the note after one week.
if ( ! self::is_wc_admin_active_in_date_range( 'week-1-4' ) ) {
return;
}
/* translators: 1: open link to WooCommerce.com settings, 2: open link to WooCommerce.com tracking documentation, 3: close link tag. */
$content_format = __(
'Gathering usage data allows us to improve WooCommerce. Your store will be considered as we evaluate new features, judge the quality of an update, or determine if an improvement makes sense. You can always visit the %1$sSettings%3$s and choose to stop sharing data. %2$sRead more%3$s about what data we collect.',
'woocommerce'
);
$note_content = sprintf(
$content_format,
'<a href="' . esc_url( admin_url( 'admin.php?page=wc-settings&tab=advanced&section=woocommerce_com' ) ) . '" target="_blank">',
'<a href="https://woocommerce.com/usage-tracking?utm_medium=product" target="_blank">',
'</a>'
);
$note = new Note();
$note->set_title( __( 'Help WooCommerce improve with usage tracking', 'woocommerce' ) );
$note->set_content( $note_content );
$note->set_content_data( (object) array() );
$note->set_type( Note::E_WC_ADMIN_NOTE_INFORMATIONAL );
$note->set_name( self::NOTE_NAME );
$note->set_source( 'woocommerce-admin' );
$note->add_action( 'tracking-opt-in', __( 'Activate usage tracking', 'woocommerce' ), false, Note::E_WC_ADMIN_NOTE_ACTIONED, true );
return $note;
}
/**
* Opt in to usage tracking when note is actioned.
*
* @param Note $note Note being acted upon.
*/
public function opt_in_to_tracking( $note ) {
if ( self::NOTE_NAME === $note->get_name() ) {
// Opt in to tracking and schedule the first data update.
// Same mechanism as in WC_Admin_Setup_Wizard::wc_setup_store_setup_save().
update_option( 'woocommerce_allow_tracking', 'yes' );
wp_schedule_single_event( time() + 10, 'woocommerce_tracker_send_event', array( true ) );
}
}
}

View File

@@ -0,0 +1,88 @@
<?php
/**
* WooCommerce Admin Unsecured Files Note.
*
* Adds a warning about potentially unsecured files.
*/
namespace Automattic\WooCommerce\Internal\Admin\Notes;
defined( 'ABSPATH' ) || exit;
use Automattic\WooCommerce\Admin\Notes\Note;
if ( ! class_exists( Note::class ) ) {
class_alias( WC_Admin_Note::class, Note::class );
}
/**
* Unsecured_Report_Files
*/
class UnsecuredReportFiles {
/**
* Name of the note for use in the database.
*/
const NOTE_NAME = 'wc-admin-remove-unsecured-report-files';
/**
* Get the note.
*
* @return Note|null
*/
public static function get_note() {
$note = new Note();
$note->set_title( __( 'Potentially unsecured files were found in your uploads directory', 'woocommerce' ) );
$note->set_content(
sprintf(
/* translators: 1: opening analytics docs link tag. 2: closing link tag */
__( 'Files that may contain %1$sstore analytics%2$s reports were found in your uploads directory - we recommend assessing and deleting any such files.', 'woocommerce' ),
'<a href="https://woocommerce.com/document/woocommerce-analytics/" target="_blank">',
'</a>'
)
);
$note->set_content_data( (object) array() );
$note->set_type( Note::E_WC_ADMIN_NOTE_ERROR );
$note->set_name( self::NOTE_NAME );
$note->set_source( 'woocommerce-admin' );
$note->add_action(
'learn-more',
__( 'Learn more', 'woocommerce' ),
'https://developer.woocommerce.com/?p=10410',
Note::E_WC_ADMIN_NOTE_UNACTIONED,
true
);
$note->add_action(
'dismiss',
__( 'Dismiss', 'woocommerce' ),
wc_admin_url(),
Note::E_WC_ADMIN_NOTE_ACTIONED,
false
);
return $note;
}
/**
* Add the note if it passes predefined conditions.
*/
public static function possibly_add_note() {
$note = self::get_note();
if ( self::note_exists() ) {
return;
}
$note->save();
}
/**
* Check if the note has been previously added.
*/
public static function note_exists() {
$data_store = \WC_Data_Store::load( 'admin-note' );
$note_ids = $data_store->get_notes_with_name( self::NOTE_NAME );
return ! empty( $note_ids );
}
}

View File

@@ -0,0 +1,224 @@
<?php
/**
* WooCommerce Admin WooCommerce Payments Note Provider.
*
* Adds a note to the merchant's inbox showing the benefits of the WooCommerce Payments.
*/
namespace Automattic\WooCommerce\Internal\Admin\Notes;
defined( 'ABSPATH' ) || exit;
use Automattic\WooCommerce\Admin\Notes\Note;
use Automattic\WooCommerce\Admin\Notes\Notes;
use Automattic\WooCommerce\Admin\Notes\NoteTraits;
/**
* WooCommerce_Payments
*/
class WooCommercePayments {
/**
* Note traits.
*/
use NoteTraits;
/**
* Name of the note for use in the database.
*/
const NOTE_NAME = 'wc-admin-woocommerce-payments';
/**
* Name of the note for use in the database.
*/
const PLUGIN_SLUG = 'woocommerce-payments';
/**
* Name of the note for use in the database.
*/
const PLUGIN_FILE = 'woocommerce-payments/woocommerce-payments.php';
/**
* Attach hooks.
*/
public function __construct() {
add_action( 'init', array( $this, 'install_on_action' ) );
add_action( 'wc-admin-woocommerce-payments_add_note', array( $this, 'add_note' ) );
}
/**
* Maybe add a note on WooCommerce Payments for US based sites older than a week without the plugin installed.
*/
public static function possibly_add_note() {
if ( ! self::is_wc_admin_active_in_date_range( 'week-1-4' ) || 'US' !== WC()->countries->get_base_country() ) {
return;
}
$data_store = Notes::load_data_store();
// We already have this note? Then mark the note as actioned.
$note_ids = $data_store->get_notes_with_name( self::NOTE_NAME );
if ( ! empty( $note_ids ) ) {
$note_id = array_pop( $note_ids );
$note = Notes::get_note( $note_id );
if ( false === $note ) {
return;
}
// If the WooCommerce Payments plugin was installed after the note was created, make sure it's marked as actioned.
if ( self::is_installed() && Note::E_WC_ADMIN_NOTE_ACTIONED !== $note->get_status() ) {
$note->set_status( Note::E_WC_ADMIN_NOTE_ACTIONED );
$note->save();
}
return;
}
$current_date = new \DateTime();
$publish_date = new \DateTime( '2020-04-14' );
if ( $current_date >= $publish_date ) {
$note = self::get_note();
if ( self::can_be_added() ) {
$note->save();
}
return;
} else {
$hook_name = sprintf( '%s_add_note', self::NOTE_NAME );
if ( ! WC()->queue()->get_next( $hook_name ) ) {
WC()->queue()->schedule_single( $publish_date->getTimestamp(), $hook_name );
}
}
}
/**
* Add a note about WooCommerce Payments.
*
* @return Note
*/
public static function get_note() {
$note = new Note();
$note->set_title( __( 'Try the new way to get paid', 'woocommerce' ) );
$note->set_content(
__( 'Securely accept credit and debit cards on your site. Manage transactions without leaving your WordPress dashboard. Only with <strong>WooCommerce Payments</strong>.', 'woocommerce' ) .
'<br><br>' .
sprintf(
/* translators: 1: opening link tag, 2: closing tag */
__( 'By clicking "Get started", you agree to our %1$sTerms of Service%2$s', 'woocommerce' ),
'<a href="https://wordpress.com/tos/" target="_blank">',
'</a>'
)
);
$note->set_content_data( (object) array() );
$note->set_type( Note::E_WC_ADMIN_NOTE_MARKETING );
$note->set_name( self::NOTE_NAME );
$note->set_source( 'woocommerce-admin' );
$note->add_action( 'learn-more', __( 'Learn more', 'woocommerce' ), 'https://woocommerce.com/payments/?utm_medium=product', Note::E_WC_ADMIN_NOTE_UNACTIONED );
$note->add_action( 'get-started', __( 'Get started', 'woocommerce' ), wc_admin_url( '&action=setup-woocommerce-payments' ), Note::E_WC_ADMIN_NOTE_ACTIONED, true );
$note->add_nonce_to_action( 'get-started', 'setup-woocommerce-payments', '' );
// Create the note as "actioned" if the plugin is already installed.
if ( self::is_installed() ) {
$note->set_status( Note::E_WC_ADMIN_NOTE_ACTIONED );
}
return $note;
}
/**
* Check if the WooCommerce Payments plugin is active or installed.
*/
protected static function is_installed() {
if ( defined( 'WC_Payments' ) ) {
return true;
}
include_once ABSPATH . '/wp-admin/includes/plugin.php';
return 0 === validate_plugin( self::PLUGIN_FILE );
}
/**
* Install and activate WooCommerce Payments.
*
* @return boolean Whether the plugin was successfully activated.
*/
private function install_and_activate_wcpay() {
$install_request = array( 'plugins' => self::PLUGIN_SLUG );
$installer = new \Automattic\WooCommerce\Admin\API\Plugins();
$result = $installer->install_plugins( $install_request );
if ( is_wp_error( $result ) ) {
return false;
}
wc_admin_record_tracks_event( 'woocommerce_payments_install', array( 'context' => 'inbox' ) );
$activate_request = array( 'plugins' => self::PLUGIN_SLUG );
$result = $installer->activate_plugins( $activate_request );
if ( is_wp_error( $result ) ) {
return false;
}
return true;
}
/**
* Install & activate WooCommerce Payments plugin, and redirect to setup.
*/
public function install_on_action() {
// TODO: Need to validate this request more strictly since we're taking install actions directly?
if (
! isset( $_GET['page'] ) ||
'wc-admin' !== $_GET['page'] ||
! isset( $_GET['action'] ) ||
'setup-woocommerce-payments' !== $_GET['action']
) {
return;
}
$data_store = Notes::load_data_store();
// We already have this note? Then mark the note as actioned.
$note_ids = $data_store->get_notes_with_name( self::NOTE_NAME );
if ( empty( $note_ids ) ) {
return;
}
$note_id = array_pop( $note_ids );
$note = Notes::get_note( $note_id );
if ( false === $note ) {
return;
}
$action = $note->get_action( 'get-started' );
if ( ! $action ||
( isset( $action->nonce_action ) &&
(
empty( $_GET['_wpnonce'] ) ||
! wp_verify_nonce( wp_unslash( $_GET['_wpnonce'] ), $action->nonce_action ) // phpcs:ignore WordPress.Security.ValidatedSanitizedInput
)
)
) {
return;
}
if ( ! current_user_can( 'install_plugins' ) ) {
return;
}
$this->install_and_activate_wcpay();
// WooCommerce Payments is installed at this point, so link straight into the onboarding flow.
$connect_url = add_query_arg(
array(
'wcpay-connect' => '1',
'_wpnonce' => wp_create_nonce( 'wcpay-connect' ),
),
admin_url()
);
wp_safe_redirect( $connect_url );
exit;
}
}

View File

@@ -0,0 +1,62 @@
<?php
/**
* WooCommerce Admin: WooCommerce Subscriptions.
*
* Adds a note to learn more about WooCommerce Subscriptions.
*/
namespace Automattic\WooCommerce\Internal\Admin\Notes;
defined( 'ABSPATH' ) || exit;
use Automattic\WooCommerce\Admin\Notes\Note;
use Automattic\WooCommerce\Admin\Notes\NoteTraits;
use Automattic\WooCommerce\Internal\Admin\Onboarding\OnboardingProfile;
/**
* WooCommerce_Subscriptions.
*/
class WooCommerceSubscriptions {
/**
* Note traits.
*/
use NoteTraits;
/**
* Name of the note for use in the database.
*/
const NOTE_NAME = 'wc-admin-woocommerce-subscriptions';
/**
* Get the note.
*
* @return Note|null
*/
public static function get_note() {
$onboarding_data = get_option( OnboardingProfile::DATA_OPTION, array() );
if ( ! isset( $onboarding_data['product_types'] ) || ! in_array( 'subscriptions', $onboarding_data['product_types'], true ) ) {
return;
}
if ( ! self::is_wc_admin_active_in_date_range( 'week-1', DAY_IN_SECONDS ) ) {
return;
}
$note = new Note();
$note->set_title( __( 'Do you need more info about WooCommerce Subscriptions?', 'woocommerce' ) );
$note->set_content( __( 'WooCommerce Subscriptions allows you to introduce a variety of subscriptions for physical or virtual products and services. Create product-of-the-month clubs, weekly service subscriptions or even yearly software billing packages. Add sign-up fees, offer free trials, or set expiration periods.', 'woocommerce' ) );
$note->set_type( Note::E_WC_ADMIN_NOTE_MARKETING );
$note->set_name( self::NOTE_NAME );
$note->set_content_data( (object) array() );
$note->set_source( 'woocommerce-admin' );
$note->add_action(
'learn-more',
__( 'Learn More', 'woocommerce' ),
'https://woocommerce.com/products/woocommerce-subscriptions/?utm_source=inbox&utm_medium=product',
Note::E_WC_ADMIN_NOTE_UNACTIONED,
true
);
return $note;
}
}

View File

@@ -0,0 +1,467 @@
<?php
/**
* WooCommerce Admin (Dashboard) WooCommerce.com Extension Subscriptions Note Provider.
*
* Adds notes to the merchant's inbox concerning WooCommerce.com extension subscriptions.
*/
namespace Automattic\WooCommerce\Internal\Admin\Notes;
defined( 'ABSPATH' ) || exit;
use Automattic\WooCommerce\Admin\Notes\Note;
use Automattic\WooCommerce\Admin\Notes\Notes;
/**
* Woo_Subscriptions_Notes
*/
class WooSubscriptionsNotes {
const LAST_REFRESH_OPTION_KEY = 'woocommerce_admin-wc-helper-last-refresh';
const NOTE_NAME = 'wc-admin-wc-helper-connection';
const CONNECTION_NOTE_NAME = 'wc-admin-wc-helper-connection';
const SUBSCRIPTION_NOTE_NAME = 'wc-admin-wc-helper-subscription';
const NOTIFY_WHEN_DAYS_LEFT = 60;
/**
* We want to bubble up expiration notices when they cross certain age
* thresholds. PHP 5.2 doesn't support constant arrays, so we do this.
*
* @return array
*/
private function get_bump_thresholds() {
return array( 60, 45, 20, 7, 1 ); // days.
}
/**
* Hook all the things.
*/
public function __construct() {
add_action( 'admin_init', array( $this, 'admin_init' ) );
add_action( 'update_option_woocommerce_helper_data', array( $this, 'update_option_woocommerce_helper_data' ), 10, 2 );
}
/**
* Reacts to changes in the helper option.
*
* @param array $old_value The previous value of the option.
* @param array $value The new value of the option.
*/
public function update_option_woocommerce_helper_data( $old_value, $value ) {
if ( ! is_array( $old_value ) ) {
$old_value = array();
}
if ( ! is_array( $value ) ) {
$value = array();
}
$old_auth = array_key_exists( 'auth', $old_value ) ? $old_value['auth'] : array();
$new_auth = array_key_exists( 'auth', $value ) ? $value['auth'] : array();
$old_token = array_key_exists( 'access_token', $old_auth ) ? $old_auth['access_token'] : '';
$new_token = array_key_exists( 'access_token', $new_auth ) ? $new_auth['access_token'] : '';
// The site just disconnected.
if ( ! empty( $old_token ) && empty( $new_token ) ) {
$this->remove_notes();
$this->add_no_connection_note();
return;
}
// The site is connected.
if ( $this->is_connected() ) {
$this->remove_notes();
$this->refresh_subscription_notes();
return;
}
}
/**
* Things to do on admin_init.
*/
public function admin_init() {
$this->check_connection();
if ( $this->is_connected() ) {
$refresh_notes = false;
// Did the user just do something on the helper page?.
if ( isset( $_GET['wc-helper-status'] ) ) { // @codingStandardsIgnoreLine.
$refresh_notes = true;
}
// Has it been more than a day since we last checked?
// Note: We do it this way and not wp_scheduled_task since WC_Helper_Options is not loaded for cron.
$time_now_gmt = current_time( 'timestamp', 0 );
$last_refresh = intval( get_option( self::LAST_REFRESH_OPTION_KEY, 0 ) );
if ( $last_refresh + DAY_IN_SECONDS <= $time_now_gmt ) {
update_option( self::LAST_REFRESH_OPTION_KEY, $time_now_gmt );
$refresh_notes = true;
}
if ( $refresh_notes ) {
$this->refresh_subscription_notes();
}
}
}
/**
* Checks the connection. Adds a note (as necessary) if there is no connection.
*/
public function check_connection() {
if ( ! $this->is_connected() ) {
$data_store = Notes::load_data_store();
$note_ids = $data_store->get_notes_with_name( self::CONNECTION_NOTE_NAME );
if ( ! empty( $note_ids ) ) {
// We already have a connection note. Exit early.
return;
}
$this->remove_notes();
$this->add_no_connection_note();
}
}
/**
* Whether or not we think the site is currently connected to WooCommerce.com.
*
* @return bool
*/
public function is_connected() {
$auth = \WC_Helper_Options::get( 'auth' );
return ( ! empty( $auth['access_token'] ) );
}
/**
* Returns the WooCommerce.com provided site ID for this site.
*
* @return int|false
*/
public function get_connected_site_id() {
if ( ! $this->is_connected() ) {
return false;
}
$auth = \WC_Helper_Options::get( 'auth' );
return absint( $auth['site_id'] );
}
/**
* Returns an array of product_ids whose subscriptions are active on this site.
*
* @return array
*/
public function get_subscription_active_product_ids() {
$site_id = $this->get_connected_site_id();
if ( ! $site_id ) {
return array();
}
$product_ids = array();
if ( $this->is_connected() ) {
$subscriptions = \WC_Helper::get_subscriptions();
foreach ( (array) $subscriptions as $subscription ) {
if ( in_array( $site_id, $subscription['connections'], true ) ) {
$product_ids[] = $subscription['product_id'];
}
}
}
return $product_ids;
}
/**
* Clears all connection or subscription notes.
*/
public function remove_notes() {
Notes::delete_notes_with_name( self::CONNECTION_NOTE_NAME );
Notes::delete_notes_with_name( self::SUBSCRIPTION_NOTE_NAME );
}
/**
* Adds a note prompting to connect to WooCommerce.com.
*/
public function add_no_connection_note() {
$note = self::get_note();
$note->save();
}
/**
* Get the WooCommerce.com connection note
*/
public static function get_note() {
$note = new Note();
$note->set_title( __( 'Connect to WooCommerce.com', 'woocommerce' ) );
$note->set_content( __( 'Connect to get important product notifications and updates.', 'woocommerce' ) );
$note->set_content_data( (object) array() );
$note->set_type( Note::E_WC_ADMIN_NOTE_INFORMATIONAL );
$note->set_name( self::CONNECTION_NOTE_NAME );
$note->set_source( 'woocommerce-admin' );
$note->add_action(
'connect',
__( 'Connect', 'woocommerce' ),
'?page=wc-addons&section=helper',
Note::E_WC_ADMIN_NOTE_UNACTIONED
);
return $note;
}
/**
* Gets the product_id (if any) associated with a note.
*
* @param Note $note The note object to interrogate.
* @return int|false
*/
public function get_product_id_from_subscription_note( &$note ) {
$content_data = $note->get_content_data();
if ( property_exists( $content_data, 'product_id' ) ) {
return intval( $content_data->product_id );
}
return false;
}
/**
* Removes notes for product_ids no longer active on this site.
*/
public function prune_inactive_subscription_notes() {
$active_product_ids = $this->get_subscription_active_product_ids();
$data_store = Notes::load_data_store();
$note_ids = $data_store->get_notes_with_name( self::SUBSCRIPTION_NOTE_NAME );
foreach ( (array) $note_ids as $note_id ) {
$note = Notes::get_note( $note_id );
$product_id = $this->get_product_id_from_subscription_note( $note );
if ( ! empty( $product_id ) ) {
if ( ! in_array( $product_id, $active_product_ids, true ) ) {
$note->delete();
}
}
}
}
/**
* Finds a note for a given product ID, if the note exists at all.
*
* @param int $product_id The product ID to search for.
* @return Note|false
*/
public function find_note_for_product_id( $product_id ) {
$product_id = intval( $product_id );
$data_store = Notes::load_data_store();
$note_ids = $data_store->get_notes_with_name( self::SUBSCRIPTION_NOTE_NAME );
foreach ( (array) $note_ids as $note_id ) {
$note = Notes::get_note( $note_id );
$found_product_id = $this->get_product_id_from_subscription_note( $note );
if ( $product_id === $found_product_id ) {
return $note;
}
}
return false;
}
/**
* Deletes a note for a given product ID, if the note exists at all.
*
* @param int $product_id The product ID to search for.
*/
public function delete_any_note_for_product_id( $product_id ) {
$product_id = intval( $product_id );
$note = $this->find_note_for_product_id( $product_id );
if ( $note ) {
$note->delete();
}
}
/**
* Adds or updates a note for an expiring subscription.
*
* @param array $subscription The subscription to work with.
*/
public function add_or_update_subscription_expiring( $subscription ) {
$product_id = $subscription['product_id'];
$product_name = $subscription['product_name'];
$expires = intval( $subscription['expires'] );
$time_now_gmt = current_time( 'timestamp', 0 );
$days_until_expiration = intval( ceil( ( $expires - $time_now_gmt ) / DAY_IN_SECONDS ) );
$note = $this->find_note_for_product_id( $product_id );
if ( $note ) {
$content_data = $note->get_content_data();
if ( property_exists( $content_data, 'days_until_expiration' ) ) {
// Note: There is no reason this property should not exist. This is just defensive programming.
$note_days_until_expiration = intval( $content_data->days_until_expiration );
if ( $days_until_expiration === $note_days_until_expiration ) {
// Note is already up to date. Bail.
return;
}
// If we have a note and we are at or have crossed a threshold, we should delete
// the old note and create a new one, thereby "bumping" the note to the top of the inbox.
$bump_thresholds = $this->get_bump_thresholds();
$crossing_threshold = false;
foreach ( (array) $bump_thresholds as $bump_threshold ) {
if ( ( $note_days_until_expiration > $bump_threshold ) && ( $days_until_expiration <= $bump_threshold ) ) {
$note->delete();
$note = false;
continue;
}
}
}
}
$note_title = sprintf(
/* translators: name of the extension subscription expiring soon */
__( '%s subscription expiring soon', 'woocommerce' ),
$product_name
);
$note_content = sprintf(
/* translators: number of days until the subscription expires */
__( 'Your subscription expires in %d days. Enable autorenew to avoid losing updates and access to support.', 'woocommerce' ),
$days_until_expiration
);
$note_content_data = (object) array(
'product_id' => $product_id,
'product_name' => $product_name,
'expired' => false,
'days_until_expiration' => $days_until_expiration,
);
if ( ! $note ) {
$note = new Note();
}
// Reset everything in case we are repurposing an expired note as an expiring note.
$note->set_title( $note_title );
$note->set_type( Note::E_WC_ADMIN_NOTE_WARNING );
$note->set_name( self::SUBSCRIPTION_NOTE_NAME );
$note->set_source( 'woocommerce-admin' );
$note->clear_actions();
$note->add_action(
'enable-autorenew',
__( 'Enable Autorenew', 'woocommerce' ),
'https://woocommerce.com/my-account/my-subscriptions/?utm_medium=product'
);
$note->set_content( $note_content );
$note->set_content_data( $note_content_data );
$note->save();
}
/**
* Adds a note for an expired subscription, or updates an expiring note to expired.
*
* @param array $subscription The subscription to work with.
*/
public function add_or_update_subscription_expired( $subscription ) {
$product_id = $subscription['product_id'];
$product_name = $subscription['product_name'];
$product_page = $subscription['product_url'];
$expires = intval( $subscription['expires'] );
$expires_date = gmdate( 'F jS', $expires );
$note = $this->find_note_for_product_id( $product_id );
if ( $note ) {
$note_content_data = $note->get_content_data();
if ( $note_content_data->expired ) {
// We've already got a full fledged expired note for this. Bail.
// Expired notes' content don't change with time.
return;
}
}
$note_title = sprintf(
/* translators: name of the extension subscription that expired */
__( '%s subscription expired', 'woocommerce' ),
$product_name
);
$note_content = sprintf(
/* translators: date the subscription expired, e.g. Jun 7th 2018 */
__( 'Your subscription expired on %s. Get a new subscription to continue receiving updates and access to support.', 'woocommerce' ),
$expires_date
);
$note_content_data = (object) array(
'product_id' => $product_id,
'product_name' => $product_name,
'expired' => true,
'expires' => $expires,
'expires_date' => $expires_date,
);
if ( ! $note ) {
$note = new Note();
}
$note->set_title( $note_title );
$note->set_content( $note_content );
$note->set_content_data( $note_content_data );
$note->set_type( Note::E_WC_ADMIN_NOTE_WARNING );
$note->set_name( self::SUBSCRIPTION_NOTE_NAME );
$note->set_source( 'woocommerce-admin' );
$note->clear_actions();
$note->add_action(
'renew-subscription',
__( 'Renew Subscription', 'woocommerce' ),
$product_page
);
$note->save();
}
/**
* For each active subscription on this site, checks the expiration date and creates/updates/deletes notes.
*/
public function refresh_subscription_notes() {
if ( ! $this->is_connected() ) {
return;
}
$this->prune_inactive_subscription_notes();
$subscriptions = \WC_Helper::get_subscriptions();
$active_product_ids = $this->get_subscription_active_product_ids();
foreach ( (array) $subscriptions as $subscription ) {
// Only concern ourselves with active products.
$product_id = $subscription['product_id'];
if ( ! in_array( $product_id, $active_product_ids, true ) ) {
continue;
}
// If the subscription will auto-renew, clean up and exit.
if ( $subscription['autorenew'] ) {
$this->delete_any_note_for_product_id( $product_id );
continue;
}
// If the subscription is not expiring by the first threshold, clean up and exit.
$bump_thresholds = $this->get_bump_thresholds();
$first_threshold = DAY_IN_SECONDS * $bump_thresholds[0];
$expires = intval( $subscription['expires'] );
$time_now_gmt = current_time( 'timestamp', 0 );
if ( $expires > $time_now_gmt + $first_threshold ) {
$this->delete_any_note_for_product_id( $product_id );
continue;
}
// Otherwise, if the subscription can still have auto-renew enabled, let them know that now.
if ( $expires > $time_now_gmt ) {
$this->add_or_update_subscription_expiring( $subscription );
continue;
}
// If we got this far, the subscription has completely expired, let them know.
$this->add_or_update_subscription_expired( $subscription );
}
}
}