From 513173a9d96f65d34e11f1e9daca2eb64e683d6c Mon Sep 17 00:00:00 2001 From: Fernando Date: Tue, 12 Jan 2021 21:09:22 -0300 Subject: [PATCH] Add merchant email notifications (https://github.com/woocommerce/woocommerce-admin/pull/5922) * Added MerchantEmailNotifications class * Added new type and Events refactor # Conflicts: # src/Events.php * Added templates * Refactored MerchantEmailNotifications and NotificationEmail * Templates refactored * Added email opening tracking endpoint * Added templates handling * Moved folder `MerchantEmailNotifications` * Fixed template extensibility * Fixed note `heading` check * Added default type in `get_template_filename` * Added tests * Removed bypass * Modified URL * Added required noteTypes * Added flag for functionallity * Fixed plain link * Fixed comment * Turned email notifications on by default This commit adds the code to turn email notifications on by default * Fixed email styles * Fixed typo * Renamed method "possible_send" as "run" * Removed unnecessary control * Fixed another typo * Renamed method as "get_notification_email_addresses" * Refactored method "send_merchant_notification" * Renamed plain-merchant-notification * Fixed tests * Merchant email notifications - Action triggering (https://github.com/woocommerce/woocommerce-admin/pull/5976) * Added templates # Conflicts: # includes/emails/plain-mechant-notification.php # Conflicts: # includes/emails/html-merchant-notification.php * Added note action triggering This commit adds the note actions triggering # Conflicts: # includes/emails/html-merchant-notification.php * Fixed error handling * Refactored "trigger_note_action" method * Fixed actions url * Fixed note URL * Added external redirect * Added image and html handling for email * Fixed tests * Fixed buttons style Co-authored-by: Fernando Marichal * Add your first product: email notification (https://github.com/woocommerce/woocommerce-admin/pull/6024) * Added AddFirstProduct note # Conflicts: # src/Events.php * Added "AddFirstProduct" email note This commit adds the email note "AddFirstProduct" * Fixed image This commit removes the image img-product-light.svg to use a png instead. Otherwise, the gmail proxy would break the image * Fixed readme.txt Co-authored-by: Fernando Marichal * Added readme.txt Co-authored-by: Fernando Marichal --- .../images/admin_notes/img-product-light.png | Bin 0 -> 2319 bytes .../emails/html-merchant-notification.php | 69 ++++++ .../emails/plain-merchant-notification.php | 23 ++ .../packages/data/src/constants.js | 1 + plugins/woocommerce-admin/readme.txt | 2 + .../woocommerce-admin/src/API/NoteActions.php | 84 +------- plugins/woocommerce-admin/src/API/Notes.php | 27 +++ plugins/woocommerce-admin/src/Events.php | 39 +++- .../woocommerce-admin/src/FeaturePlugin.php | 4 + .../src/Notes/AddFirstProduct.php | 76 +++++++ .../src/Notes/DeprecatedNotes.php | 1 + .../MerchantEmailNotifications.php | 122 +++++++++++ .../NotificationEmail.php | 197 ++++++++++++++++++ plugins/woocommerce-admin/src/Notes/Note.php | 4 + plugins/woocommerce-admin/src/Notes/Notes.php | 100 +++++++++ .../helpers/class-wc-helper-admin-notes.php | 32 ++- .../notes/class-wc-tests-email-notes.php | 129 ++++++++++++ 17 files changed, 823 insertions(+), 87 deletions(-) create mode 100644 plugins/woocommerce-admin/images/admin_notes/img-product-light.png create mode 100644 plugins/woocommerce-admin/includes/emails/html-merchant-notification.php create mode 100644 plugins/woocommerce-admin/includes/emails/plain-merchant-notification.php create mode 100644 plugins/woocommerce-admin/src/Notes/AddFirstProduct.php create mode 100644 plugins/woocommerce-admin/src/Notes/MerchantEmailNotifications/MerchantEmailNotifications.php create mode 100644 plugins/woocommerce-admin/src/Notes/MerchantEmailNotifications/NotificationEmail.php create mode 100644 plugins/woocommerce-admin/tests/notes/class-wc-tests-email-notes.php diff --git a/plugins/woocommerce-admin/images/admin_notes/img-product-light.png b/plugins/woocommerce-admin/images/admin_notes/img-product-light.png new file mode 100644 index 0000000000000000000000000000000000000000..3ecc1c2341e83efe71f5380384929be0b4967b99 GIT binary patch literal 2319 zcmV+q3GnubP)YtpB?t)# z0TLB&Y!4L`5|N79ERL7)c&mUSBzOo2QK0ZZX=t;@o*8e6W2dyGr8I3=y3s9(hjXr- z&^Vrn9gpn^@kmFGTHBiW?mz!I_uMQpR?AF9uPKe)Fv(OKVJ$Ng?Wm!J7iE&4Eu&(gmj4PsK z2Y^4u+GVypu=lRu{K@SATv3VNA-?5&!y7n%;_(LCp(_mwxS_Fq8ReEAriIYklo0t0 zR2=g4}K>j3DO2Jl=l4lxLK!>6o2xqjW&k=n*a<&ug(UfbMv z8z+QcVEBl@3XvXwcCJ9kxq}ZSSXTre8&bR^pwJ|3BQ4eR~E zrHl~X2#N86LE(>@Z39%!QqY}T2{m`Xn6MK^ExzIBv`Z`Z;DfXf4H*=CigAkWRd4mA z>t{t-f~6cN{2!yLoAM`a%C98kM=obQ(aoUxaYMrAcnJ5%*rep0BWJ$;lt0RJb!d%~wV&)_lEYXrp?pl+=M zW$_HY3sd^@WdeMHaRv_~U^4=4+NIQQ-l-tyi-T(F?4xY`%B+|<5J$jzPL)gD0NkSM zB#HJ!?`PbDLEQoK;z6pd?})jjbDRLqf~yv)(A5WG5`^;y>lr$%|3D<$OV%($)w8I2 zA$qPjz+LAH0bDe6wSnNJi6RH$$h^I~@agqPEhEGN>XRDzIPDDXGuwI(=>fPT&{YP4 za!M0eo`Sdzz(Aa=h`O~@&v@b+Sy$|L+9Ce}3O-1;cAcr;qCeoWL01_F#wnkHO70L0 z#IKxp>S7iDdDNuyMx?`YlQ8Bl6{jZ>#ZG8&m5Wybhbb{vP6tpL- zGkPiISqvDu7n(R>^;-`H!83#v1Ud2g=8gpdeSiR_96GErB(7!-F1#=h zoraT|GZL@g$zWZ3;$eB$2!z#E5du_JL8ewMU^d zimn7;^^o?$>tluWT41d&v*cvN4j!3+nXKKQ?q2X`*lIsKhjPI@O(SHZK;qN!k?*8z&_rScwI;8RDkdqH1=`!mKA=hiw4dM1z}Y(#&eUnNoP}2^~CD~#n=S-JWkN4*6x!uA9$!@yLId8>Xw>Yc8|>f zgZ*ciL6U9I&}q!iyXRIneqYvSQZvB7jMUtukLodT)7Yzl$ya(nFZ$li9mnvxD+ax` zwsu)I^qdOC-Ul?cP@UYx1A*R68eZC;ol8XpJ4{%o~-R7OM z%85?qH6Z~XpC91e#Lr0{XbWT(Incu7(TYF%G7Vj$D~L9McA}LmF#+SC_Zb~}PFSAwvK=a#G_SBSfodlobX`S9X}4hzHY!IV4(&*S{VmbYX)S3cMxv>c?v&=viT zb&Ai|80nxIzrBj%#TQ}!-WSkA-v|56&o4yiWE-4QjI$1L8|9JK)-1Gxvx_U?A~rEj z_&phSbPP5UrHln#g1)>s29SSd9Q_;CG?r=qN|}!(v^DSy>y+VV0z)y_FLSEU1&j>z zv-5;}1{TC)3s%K?9rfD=Z=rbcO?V{oJ6Z_;#<_!Liiv&3O@$F pmT?Fkk^F{(e}}!6S1pul`4_n<=$F7sRzUy&002ovPDHLkV1m9Qbff?P literal 0 HcmV?d00001 diff --git a/plugins/woocommerce-admin/includes/emails/html-merchant-notification.php b/plugins/woocommerce-admin/includes/emails/html-merchant-notification.php new file mode 100644 index 00000000000..9509cbcc901 --- /dev/null +++ b/plugins/woocommerce-admin/includes/emails/html-merchant-notification.php @@ -0,0 +1,69 @@ + + + +
+ +
+ + + array( + 'href' => array(), + 'title' => array(), + ), + 'br' => array(), + 'em' => array(), + 'strong' => array(), + ) +); + +$base_color = get_option( 'woocommerce_email_base_color' ); +$base_text = wc_light_or_dark( $base_color, '#202020', '#ffffff' ); +$container_styles = 'margin-top: 25px;'; +$buttons_styles = " + font-style: normal; + font-weight: normal; + font-size: 13px; + line-height: 18px; + text-align: center; + color: {$base_text}; + margin-right: 15px; + text-decoration: none; + background: {$base_color}; + border: 1px solid {$base_color}; + border-radius: 3px; + padding: 5px;"; +?> + +
+ +
+label, $trigger_note_action_url . $an_action->id ) ); +} +echo "\n\n----------------------------------------\n\n"; + +echo wp_kses_post( apply_filters( 'woocommerce_email_footer_text', get_option( 'woocommerce_email_footer_text' ) ) ); diff --git a/plugins/woocommerce-admin/packages/data/src/constants.js b/plugins/woocommerce-admin/packages/data/src/constants.js index 4839bc0dd56..82764e56bac 100644 --- a/plugins/woocommerce-admin/packages/data/src/constants.js +++ b/plugins/woocommerce-admin/packages/data/src/constants.js @@ -24,4 +24,5 @@ export const QUERY_DEFAULTS = { pageSize: 25, period: 'month', compare: 'previous_year', + noteTypes: [ 'info', 'marketing', 'survey', 'warning' ], }; diff --git a/plugins/woocommerce-admin/readme.txt b/plugins/woocommerce-admin/readme.txt index e24c035aec4..395cdf1b4c5 100644 --- a/plugins/woocommerce-admin/readme.txt +++ b/plugins/woocommerce-admin/readme.txt @@ -89,6 +89,8 @@ Release and roadmap notes are available on the [WooCommerce Developers Blog](htt - Add: Welcome modal when coming from Calypso #6004 - Enhancement: Add an a/b experiment for installing free business features #5786 - Dev: Add `onChangeCallback` feature to the wc-admin
component #5786 +- Dev: Add merchant email notifications #5922 +- Add: Email note to add first product. #6024 - Add: Note for users coming from Calypso. #6030 - Enhancement: Add an "unread" indicator to inbox messages. #6047 diff --git a/plugins/woocommerce-admin/src/API/NoteActions.php b/plugins/woocommerce-admin/src/API/NoteActions.php index 5851d2f40fa..9f0f6b7ae97 100644 --- a/plugins/woocommerce-admin/src/API/NoteActions.php +++ b/plugins/woocommerce-admin/src/API/NoteActions.php @@ -65,16 +65,7 @@ class NoteActions extends Notes { ); } - // Find note action by ID. - $action_id = $request->get_param( 'action_id' ); - $actions = $note->get_actions( 'edit' ); - $triggered_action = false; - - foreach ( $actions as $action ) { - if ( $action->id === $action_id ) { - $triggered_action = $action; - } - } + $triggered_action = NotesFactory::get_action_by_id( $note, $request->get_param( 'action_id' ) ); if ( ! $triggered_action ) { return new \WP_Error( @@ -84,81 +75,12 @@ class NoteActions extends Notes { ); } - /** - * Fires when an admin note action is taken. - * - * @param string $name The triggered action name. - * @param Note $note The corresponding Note. - */ - do_action( 'woocommerce_note_action', $triggered_action->name, $note ); + $triggered_note = NotesFactory::trigger_note_action( $note, $triggered_action ); - /** - * Fires when an admin note action is taken. - * For more specific targeting of note actions. - * - * @param Note $note The corresponding Note. - */ - do_action( 'woocommerce_note_action_' . $triggered_action->name, $note ); - - // Update the note with the status for this action. - if ( ! empty( $triggered_action->status ) ) { - $note->set_status( $triggered_action->status ); - } - - $note->save(); - - if ( in_array( $note->get_type(), array( 'error', 'update' ) ) ) { - $tracks_event = 'wcadmin_store_alert_action'; - } else { - $tracks_event = 'wcadmin_inbox_action_click'; - } - - wc_admin_record_tracks_event( - $tracks_event, - array( - 'note_name' => $note->get_name(), - 'note_type' => $note->get_type(), - 'note_title' => $note->get_title(), - 'note_content' => $note->get_content(), - 'action_name' => $triggered_action->name, - 'action_label' => $triggered_action->label, - 'screen' => $this->get_screen_name(), - ) - ); - - $data = $note->get_data(); + $data = $triggered_note->get_data(); $data = $this->prepare_item_for_response( $data, $request ); $data = $this->prepare_response_for_collection( $data ); return rest_ensure_response( $data ); } - - /** - * Get screen name. - * - * @return string The screen name. - */ - public function get_screen_name() { - $screen_name = ''; - - if ( isset( $_SERVER['HTTP_REFERER'] ) ) { - parse_str( wp_parse_url( $_SERVER['HTTP_REFERER'], PHP_URL_QUERY ), $queries ); // phpcs:ignore sanitization ok. - } - if ( isset( $queries ) ) { - $page = isset( $queries['page'] ) ? $queries['page'] : null; - $path = isset( $queries['path'] ) ? $queries['path'] : null; - $post_type = isset( $queries['post_type'] ) ? $queries['post_type'] : null; - $post = isset( $queries['post'] ) ? get_post_type( $queries['post'] ) : null; - } - - if ( isset( $page ) ) { - $current_page = 'wc-admin' === $page ? 'home_screen' : $page; - $screen_name = isset( $path ) ? substr( str_replace( '/', '_', $path ), 1 ) : $current_page; - } elseif ( isset( $post_type ) ) { - $screen_name = $post_type; - } elseif ( isset( $post ) ) { - $screen_name = $post; - } - return $screen_name; - } } diff --git a/plugins/woocommerce-admin/src/API/Notes.php b/plugins/woocommerce-admin/src/API/Notes.php index 35a865b247c..52b278ae3f1 100644 --- a/plugins/woocommerce-admin/src/API/Notes.php +++ b/plugins/woocommerce-admin/src/API/Notes.php @@ -101,6 +101,19 @@ class Notes extends \WC_REST_CRUD_Controller { ) ); + register_rest_route( + $this->namespace, + '/' . $this->rest_base . '/tracker/(?P[\d-]+)', + array( + array( + 'methods' => \WP_REST_Server::READABLE, + 'callback' => array( $this, 'track_opened_email' ), + 'permission_callback' => '__return_true', + ), + 'schema' => array( $this, 'get_public_item_schema' ), + ) + ); + register_rest_route( $this->namespace, '/' . $this->rest_base . '/update', @@ -451,6 +464,20 @@ class Notes extends \WC_REST_CRUD_Controller { return apply_filters( 'woocommerce_rest_prepare_note', $response, $data, $request ); } + + /** + * Track opened emails. + * + * @param WP_REST_Request $request Request object. + */ + public function track_opened_email( $request ) { + $note = NotesRepository::get_note( $request->get_param( 'note_id' ) ); + if ( ! $note ) { + return; + } + wc_admin_record_tracks_event( 'wcadmin_email_note_opened', array( 'note_name' => $note->get_name() ) ); + } + /** * Get the query params for collections. * diff --git a/plugins/woocommerce-admin/src/Events.php b/plugins/woocommerce-admin/src/Events.php index ae0887c84c1..5a671b0326a 100644 --- a/plugins/woocommerce-admin/src/Events.php +++ b/plugins/woocommerce-admin/src/Events.php @@ -29,6 +29,7 @@ use \Automattic\WooCommerce\Admin\Notes\LaunchChecklist; use \Automattic\WooCommerce\Admin\Notes\RealTimeOrderAlerts; use \Automattic\WooCommerce\Admin\RemoteInboxNotifications\DataSourcePoller; use \Automattic\WooCommerce\Admin\RemoteInboxNotifications\RemoteInboxNotificationsEngine; +use \Automattic\WooCommerce\Admin\Notes\MerchantEmailNotifications\MerchantEmailNotifications; use \Automattic\WooCommerce\Admin\Loader; use \Automattic\WooCommerce\Admin\Notes\InsightFirstSale; use \Automattic\WooCommerce\Admin\Notes\NeedSomeInspiration; @@ -43,6 +44,7 @@ use \Automattic\WooCommerce\Admin\Notes\ManageOrdersOnTheGo; use \Automattic\WooCommerce\Admin\Notes\NavigationFeedback; use \Automattic\WooCommerce\Admin\Notes\NavigationFeedbackFollowUp; use \Automattic\WooCommerce\Admin\Notes\FilterByProductVariationsInReports; +use \Automattic\WooCommerce\Admin\Notes\AddFirstProduct; use \Automattic\WooCommerce\Admin\Notes\DrawAttention; /** @@ -88,6 +90,22 @@ class Events { * Note: Order_Milestones::other_milestones is hooked to this as well. */ public function do_wc_admin_daily() { + $this->possibly_add_notes(); + + if ( $this->is_remote_inbox_notifications_enabled() ) { + DataSourcePoller::read_specs_from_data_sources(); + RemoteInboxNotificationsEngine::run(); + } + + if ( $this->is_merchant_email_notifications_enabled() ) { + MerchantEmailNotifications::run(); + } + } + + /** + * Adds notes that should be added. + */ + protected function possibly_add_notes() { NewSalesRecord::possibly_add_note(); MobileApp::possibly_add_note(); TrackingOptIn::possibly_add_note(); @@ -120,12 +138,8 @@ class Events { DrawAttention::possibly_add_note(); ChoosingTheme::possibly_add_note(); InsightFirstProductAndPayment::possibly_add_note(); + AddFirstProduct::possibly_add_note(); AddingAndManangingProducts::possibly_add_note(); - - if ( $this->is_remote_inbox_notifications_enabled() ) { - DataSourcePoller::read_specs_from_data_sources(); - RemoteInboxNotificationsEngine::run(); - } } /** @@ -147,4 +161,19 @@ class Events { // All checks have passed. return true; } + + /** + * Checks if merchant email notifications are enabled. + * + * @return bool Whether merchant email notifications are enabled. + */ + protected function is_merchant_email_notifications_enabled() { + // Check if the feature flag is disabled. + if ( 'yes' !== get_option( 'woocommerce_merchant_email_notifications', 'yes' ) ) { + return false; + } + + // All checks have passed. + return true; + } } diff --git a/plugins/woocommerce-admin/src/FeaturePlugin.php b/plugins/woocommerce-admin/src/FeaturePlugin.php index 02eb6b64639..7ef996e19db 100644 --- a/plugins/woocommerce-admin/src/FeaturePlugin.php +++ b/plugins/woocommerce-admin/src/FeaturePlugin.php @@ -21,6 +21,7 @@ use \Automattic\WooCommerce\Admin\RemoteInboxNotifications\RemoteInboxNotificati use \Automattic\WooCommerce\Admin\Notes\SetUpAdditionalPaymentTypes; use \Automattic\WooCommerce\Admin\Notes\TestCheckout; use \Automattic\WooCommerce\Admin\Notes\SellingOnlineCourses; +use \Automattic\WooCommerce\Admin\Notes\MerchantEmailNotifications\MerchantEmailNotifications; use \Automattic\WooCommerce\Admin\Notes\WelcomeToWooCommerceForStoreUsers; /** @@ -196,6 +197,9 @@ class FeaturePlugin { // Initialize RemoteInboxNotificationsEngine. RemoteInboxNotificationsEngine::init(); + + // Initialize MerchantEmailNotifications. + MerchantEmailNotifications::init(); } /** diff --git a/plugins/woocommerce-admin/src/Notes/AddFirstProduct.php b/plugins/woocommerce-admin/src/Notes/AddFirstProduct.php new file mode 100644 index 00000000000..18db173769f --- /dev/null +++ b/plugins/woocommerce-admin/src/Notes/AddFirstProduct.php @@ -0,0 +1,76 @@ + 1, + 'return' => 'ids', + 'status' => array( 'publish' ), + ) + ); + $products = $query->get_products(); + if ( 0 !== count( $products ) ) { + return; + } + + $content_lines = array( + __( 'Nice one, you’ve created a WooCommerce store! Now it’s time to add your first product.

', 'woocommerce-admin' ), + __( 'There are three ways to add your products: you can create products manually, import them at once via CSV file, or migrate them from another service.

', 'woocommerce-admin' ), + __( 'Explore our docs for more information, or just get started!', 'woocommerce-admin' ), + ); + + $additional_data = array( + 'role' => 'administrator', + ); + + $note = new Note(); + $note->set_title( __( 'Store setup', 'woocommerce-admin' ) ); + $note->set_content( implode( '', $content_lines ) ); + $note->set_content_data( (object) $additional_data ); + $note->set_image( + plugins_url( + '/images/admin_notes/img-product-light.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' ), admin_url( 'admin.php?page=wc-admin&task=products' ) ); + return $note; + } +} diff --git a/plugins/woocommerce-admin/src/Notes/DeprecatedNotes.php b/plugins/woocommerce-admin/src/Notes/DeprecatedNotes.php index 9979ffe9ee7..d3290a357cc 100644 --- a/plugins/woocommerce-admin/src/Notes/DeprecatedNotes.php +++ b/plugins/woocommerce-admin/src/Notes/DeprecatedNotes.php @@ -29,6 +29,7 @@ class WC_Admin_Note extends DeprecatedClassFacade { const E_WC_ADMIN_NOTE_UNACTIONED = Note::E_WC_ADMIN_NOTE_UNACTIONED; const E_WC_ADMIN_NOTE_ACTIONED = Note::E_WC_ADMIN_NOTE_ACTIONED; const E_WC_ADMIN_NOTE_SNOOZED = Note::E_WC_ADMIN_NOTE_SNOOZED; + const E_WC_ADMIN_NOTE_EMAIL = Note::E_WC_ADMIN_NOTE_EMAIL; /** * The name of the non-deprecated class that this facade covers. diff --git a/plugins/woocommerce-admin/src/Notes/MerchantEmailNotifications/MerchantEmailNotifications.php b/plugins/woocommerce-admin/src/Notes/MerchantEmailNotifications/MerchantEmailNotifications.php new file mode 100644 index 00000000000..5c5a043f1f7 --- /dev/null +++ b/plugins/woocommerce-admin/src/Notes/MerchantEmailNotifications/MerchantEmailNotifications.php @@ -0,0 +1,122 @@ +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 = \WC_Data_Store::load( 'admin-note' ); + $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(); + wc_admin_record_tracks_event( 'wcadmin_email_note_sent', array( 'note_name' => $note->get_name() ) ); + } + } + } + + /** + * Send the notification to the merchant. + * + * @param object $note The note to send. + */ + public static function send_merchant_notification( $note ) { + \WC_Emails::instance(); + $users_emails = self::get_notification_email_addresses( $note ); + $email = new NotificationEmail( $note ); + foreach ( $users_emails as $user_email ) { + if ( is_email( $user_email ) ) { + $email->trigger( $user_email ); + } + } + } + + /** + * Get email addresses by role to notify. + * + * @param object $note The note to send. + * @return array Emails to notify + */ + public static function get_notification_email_addresses( $note ) { + $content_data = $note->get_content_data(); + $role = 'administrator'; + if ( isset( $content_data->role ) ) { + $role = $content_data->role; + } + $args = array( 'role' => $role ); + $users = get_users( $args ); + return array_column( $users, 'user_email' ); + } +} diff --git a/plugins/woocommerce-admin/src/Notes/MerchantEmailNotifications/NotificationEmail.php b/plugins/woocommerce-admin/src/Notes/MerchantEmailNotifications/NotificationEmail.php new file mode 100644 index 00000000000..502a78fcece --- /dev/null +++ b/plugins/woocommerce-admin/src/Notes/MerchantEmailNotifications/NotificationEmail.php @@ -0,0 +1,197 @@ +note = $note; + $this->id = 'merchant_notification'; + $this->template_base = WC_ADMIN_ABSPATH . 'includes/emails/'; + + // 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 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->get_note_content(), + 'email_heading' => $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->get_heading(), + 'email_content' => $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 $email Email to send the note. + */ + public function trigger( $email ) { + $this->recipient = $email; + $this->opened_tracking_url = sprintf( + '%1$s/wp-json/wc-analytics/admin/notes/tracker/%2$d', + site_url(), + $this->note->get_id() + ); + $this->trigger_note_action_url = sprintf( + '%1$s&external_redirect=1¬e=%2$d&action=', + wc_admin_url(), + $this->note->get_id() + ); + $this->send( + $this->get_recipient(), + $this->get_subject(), + $this->get_content(), + $this->get_headers(), + $this->get_attachments() + ); + } +} diff --git a/plugins/woocommerce-admin/src/Notes/Note.php b/plugins/woocommerce-admin/src/Notes/Note.php index f0ef9075564..6e375144b84 100644 --- a/plugins/woocommerce-admin/src/Notes/Note.php +++ b/plugins/woocommerce-admin/src/Notes/Note.php @@ -21,12 +21,14 @@ class Note extends \WC_Data { const E_WC_ADMIN_NOTE_INFORMATIONAL = 'info'; // used for presenting informational messages. const E_WC_ADMIN_NOTE_MARKETING = 'marketing'; // used for adding marketing messages. const E_WC_ADMIN_NOTE_SURVEY = 'survey'; // used for adding survey messages. + const E_WC_ADMIN_NOTE_EMAIL = 'email'; // used for adding notes that will be sent by email. // Note status codes. const E_WC_ADMIN_NOTE_PENDING = 'pending'; // the note is pending - hidden but not actioned. const E_WC_ADMIN_NOTE_UNACTIONED = 'unactioned'; // the note has not yet been actioned by a user. const E_WC_ADMIN_NOTE_ACTIONED = 'actioned'; // the note has had its action completed by a user. const E_WC_ADMIN_NOTE_SNOOZED = 'snoozed'; // the note has been snoozed by a user. + const E_WC_ADMIN_NOTE_SENT = 'sent'; // the note has been sent by email to the user. /** * This is the name of this object type. @@ -127,6 +129,7 @@ class Note extends \WC_Data { self::E_WC_ADMIN_NOTE_INFORMATIONAL, self::E_WC_ADMIN_NOTE_MARKETING, self::E_WC_ADMIN_NOTE_SURVEY, + self::E_WC_ADMIN_NOTE_EMAIL, ); return apply_filters( 'woocommerce_note_types', $allowed_types ); @@ -143,6 +146,7 @@ class Note extends \WC_Data { self::E_WC_ADMIN_NOTE_ACTIONED, self::E_WC_ADMIN_NOTE_UNACTIONED, self::E_WC_ADMIN_NOTE_SNOOZED, + self::E_WC_ADMIN_NOTE_SENT, ); return apply_filters( 'woocommerce_note_statuses', $allowed_statuses ); diff --git a/plugins/woocommerce-admin/src/Notes/Notes.php b/plugins/woocommerce-admin/src/Notes/Notes.php index 68d9246ab5b..b0b889becbf 100644 --- a/plugins/woocommerce-admin/src/Notes/Notes.php +++ b/plugins/woocommerce-admin/src/Notes/Notes.php @@ -301,4 +301,104 @@ class Notes { return $note->get_status(); } + + /** + * Get action by id. + * + * @param Note $note The note that has of the action. + * @param int $action_id Action ID. + * @return object|bool The found action. + */ + public static function get_action_by_id( $note, $action_id ) { + $actions = $note->get_actions( 'edit' ); + $found_action = false; + + foreach ( $actions as $action ) { + if ( $action->id === $action_id ) { + $found_action = $action; + } + } + return $found_action; + } + + /** + * Trigger note action. + * + * @param Note $note The note that has the triggered action. + * @param object $triggered_action The triggered action. + * @return Note|bool + */ + public static function trigger_note_action( $note, $triggered_action ) { + /** + * Fires when an admin note action is taken. + * + * @param string $name The triggered action name. + * @param Note $note The corresponding Note. + */ + do_action( 'woocommerce_note_action', $triggered_action->name, $note ); + + /** + * Fires when an admin note action is taken. + * For more specific targeting of note actions. + * + * @param Note $note The corresponding Note. + */ + do_action( 'woocommerce_note_action_' . $triggered_action->name, $note ); + + // Update the note with the status for this action. + if ( ! empty( $triggered_action->status ) ) { + $note->set_status( $triggered_action->status ); + } + + $note->save(); + + if ( in_array( $note->get_type(), array( 'error', 'update' ), true ) ) { + $tracks_event = 'wcadmin_store_alert_action'; + } else { + $tracks_event = 'wcadmin_inbox_action_click'; + } + + wc_admin_record_tracks_event( + $tracks_event, + array( + 'note_name' => $note->get_name(), + 'note_type' => $note->get_type(), + 'note_title' => $note->get_title(), + 'note_content' => $note->get_content(), + 'action_name' => $triggered_action->name, + 'action_label' => $triggered_action->label, + 'screen' => self::get_screen_name(), + ) + ); + return $note; + } + + /** + * Get screen name. + * + * @return string The screen name. + */ + public static function get_screen_name() { + $screen_name = ''; + + if ( isset( $_SERVER['HTTP_REFERER'] ) ) { + parse_str( wp_parse_url( $_SERVER['HTTP_REFERER'], PHP_URL_QUERY ), $queries ); // phpcs:ignore sanitization ok. + } + if ( isset( $queries ) ) { + $page = isset( $queries['page'] ) ? $queries['page'] : null; + $path = isset( $queries['path'] ) ? $queries['path'] : null; + $post_type = isset( $queries['post_type'] ) ? $queries['post_type'] : null; + $post = isset( $queries['post'] ) ? get_post_type( $queries['post'] ) : null; + } + + if ( isset( $page ) ) { + $current_page = 'wc-admin' === $page ? 'home_screen' : $page; + $screen_name = isset( $path ) ? substr( str_replace( '/', '_', $path ), 1 ) : $current_page; + } elseif ( isset( $post_type ) ) { + $screen_name = $post_type; + } elseif ( isset( $post ) ) { + $screen_name = $post; + } + return $screen_name; + } } diff --git a/plugins/woocommerce-admin/tests/framework/helpers/class-wc-helper-admin-notes.php b/plugins/woocommerce-admin/tests/framework/helpers/class-wc-helper-admin-notes.php index 43a56d80adc..bce9e3d750f 100644 --- a/plugins/woocommerce-admin/tests/framework/helpers/class-wc-helper-admin-notes.php +++ b/plugins/woocommerce-admin/tests/framework/helpers/class-wc-helper-admin-notes.php @@ -24,7 +24,7 @@ class WC_Helper_Admin_Notes { } /** - * Create two notes that we can use for notes REST API tests + * Create four notes that we can use for notes REST API tests */ public static function add_notes_for_tests() { $data_store = WC_Data_Store::load( 'admin-note' ); @@ -102,4 +102,34 @@ class WC_Helper_Admin_Notes { $note_4->save(); } + + /** + * Create a note that we can use for email notes tests + */ + public static function add_email_notes_for_test() { + $data_store = WC_Data_Store::load( 'admin-note' ); + + $note_5 = new Note(); + $note_5->set_title( 'PHPUNIT_TEST_NOTE_5_TITLE' ); + $note_5->set_content( 'PHPUNIT_TEST_NOTE_5_CONTENT' ); + $additional_data = array( + 'heading' => 'PHPUNIT_TEST_EMAIL_HEADING', + 'role' => 'administrator', + 'template_html' => 'PHPUNIT_TEST_EMAIL_HTML_TEMPLATE', + 'template_plain' => 'PHPUNIT_TEST_EMAIL_HTML_PLAIN', + ); + $note_5->set_content_data( (object) $additional_data ); + $note_5->set_type( Note::E_WC_ADMIN_NOTE_EMAIL ); + $note_5->set_name( 'PHPUNIT_TEST_NOTE_NAME' ); + $note_5->set_source( 'PHPUNIT_TEST' ); + $note_5->set_is_snoozable( false ); + $note_5->set_layout( 'plain' ); + $note_5->set_image( '' ); + $note_5->add_action( + 'PHPUNIT_TEST_NOTE_5_ACTION_SLUG', + 'PHPUNIT_TEST_NOTE_5_ACTION_LABEL', + '?s=PHPUNIT_TEST_NOTE_5_ACTION_URL' + ); + $note_5->save(); + } } diff --git a/plugins/woocommerce-admin/tests/notes/class-wc-tests-email-notes.php b/plugins/woocommerce-admin/tests/notes/class-wc-tests-email-notes.php new file mode 100644 index 00000000000..25e971aca3d --- /dev/null +++ b/plugins/woocommerce-admin/tests/notes/class-wc-tests-email-notes.php @@ -0,0 +1,129 @@ +user = $this->factory->user->create( + array( + 'role' => 'administrator', + ) + ); + + WC_Helper_Admin_Notes::reset_notes_dbs(); + WC_Helper_Admin_Notes::add_notes_for_tests(); + WC_Helper_Admin_Notes::add_email_notes_for_test(); + } + + /** + * Tests NotificationEmail default values. + */ + public function test_default_values_create_notification_email() { + $note = new Note(); + $note->set_title( 'PHPUNIT_TEST_NOTE_EMAIL_TITLE' ); + $note->set_content( 'PHPUNIT_TEST_NOTE_EMAIL_CONTENT' ); + $note->set_type( Note::E_WC_ADMIN_NOTE_EMAIL ); + $note->set_name( 'PHPUNIT_TEST_NOTE_EMAIL_NAME' ); + $note->set_source( 'PHPUNIT_TEST' ); + $note->set_is_snoozable( false ); + $note->set_layout( 'plain' ); + $note->set_image( '' ); + $content_data = array( + 'role' => 'administrator', + ); + $note->set_content_data( (object) $content_data ); + $note->add_action( + 'PHPUNIT_TEST_EMAIL_ACTION_SLUG', + 'PHPUNIT_TEST_EMAIL_ACTION_LABEL', + '?s=PHPUNIT_TEST_EMAIL_ACTION_URL' + ); + $note->set_is_deleted( false ); + $notification_email = new NotificationEmail( $note ); + + $this->assertEquals( $notification_email->id, 'merchant_notification' ); + $this->assertEquals( $notification_email->get_default_heading(), $note->get_title() ); + $this->assertEquals( $notification_email->get_template_filename(), 'html-merchant-notification.php' ); + $this->assertEquals( $notification_email->get_template_filename( 'html' ), 'html-merchant-notification.php' ); + $this->assertEquals( $notification_email->get_template_filename( 'plain' ), 'plain-merchant-notification.php' ); + } + + /** + * Tests NotificationEmail is created correctly. + */ + public function test_create_notification_email() { + $data_store = WC_Data_Store::load( 'admin-note' ); + $note_data = $data_store->get_notes( + array( + 'type' => array( Note::E_WC_ADMIN_NOTE_EMAIL ), + 'status' => array( 'unactioned' ), + ) + ); + $note = Notes::get_note( $note_data[0]->note_id ); + $content_data = array( + 'heading' => 'PHPUNIT_TEST_EMAIL_HEADING', + 'role' => 'administrator', + ); + $note->set_content_data( (object) $content_data ); + $note->save(); + $notification_email = new NotificationEmail( $note ); + $notification_email->opened_tracking_url = 'PHPUNIT_TEST_NOTE_EMAIL_TRACKING_URL'; + $notification_email->trigger_note_action_url = 'PHPUNIT_TEST_NOTE_EMAIL_TRIGGER_ACTION_URL'; + $content_html = $notification_email->get_content_html(); + $content_plain = $notification_email->get_content_plain(); + + $this->assertEquals( $notification_email->get_default_heading(), $content_data['heading'] ); + $this->assertEquals( $notification_email->get_default_subject(), $note->get_title() ); + $this->assertEquals( $notification_email->get_note_content(), $note->get_content() ); + $this->assertEquals( $notification_email->get_note_content(), $note->get_content() ); + $this->assertTrue( strpos( $content_html, 'PHPUNIT_TEST_NOTE_5_ACTION_URL' ) >= 0 ); + $this->assertTrue( strpos( $content_html, 'PHPUNIT_TEST_NOTE_5_ACTION_LABEL' ) >= 0 ); + $this->assertTrue( strpos( $content_html, 'PHPUNIT_TEST_NOTE_5_CONTENT' ) >= 0 ); + $this->assertTrue( strpos( $content_html, 'PHPUNIT_TEST_NOTE_EMAIL_TRACKING_URL' ) >= 0 ); + $this->assertTrue( strpos( $content_html, 'PHPUNIT_TEST_NOTE_EMAIL_TRIGGER_ACTION_URL' ) >= 0 ); + $this->assertTrue( strpos( $content_plain, 'PHPUNIT_TEST_NOTE_5_ACTION_URL' ) >= 0 ); + $this->assertTrue( strpos( $content_plain, 'PHPUNIT_TEST_NOTE_5_ACTION_LABEL' ) >= 0 ); + $this->assertTrue( strpos( $content_plain, 'PHPUNIT_TEST_NOTE_5_CONTENT' ) >= 0 ); + $this->assertTrue( strpos( $content_plain, 'PHPUNIT_TEST_EMAIL_HEADING' ) >= 0 ); + } + + /** + * Tests NotificationEmail validations. + */ + public function test_create_invalid_notification_email() { + $data_store = WC_Data_Store::load( 'admin-note' ); + $note_data = $data_store->get_notes( + array( + 'type' => array( Note::E_WC_ADMIN_NOTE_EMAIL ), + 'status' => array( 'unactioned' ), + ) + ); + $note = Notes::get_note( $note_data[0]->note_id ); + $content_data = array( + 'role' => 'invalid_role', + ); + $note->set_content_data( (object) $content_data ); + $notification_email = new NotificationEmail( $note ); + + $this->assertEmpty( MerchantEmailNotifications::get_notification_email_addresses( $note ) ); + $this->assertEmpty( $notification_email->get_template_filename( 'wrong_type' ) ); + } +}