* Poll and persist specs

* Process specs into admin notes

* Add send at time rule processor

* Fix style issues

* Clear actions before recreating them to avoid dupes

* Trigger the RINDS engine when a plugin is activated

* Unit test SendAtTimeRuleProcessor

* Implement plugins activated rule processor

* Don't throw exception for unrecognised rule type. Also unit test around this.

* add url_is_action_query to tell whether to wrap the URL in wc_admin_url() call or not

* Add NOT rule

* revert change to install.php

* Drop unimplemented resend after dismissal rule

* Add OR rule

* Explicitly make "fail" a type of rule that can be applied to a spec

* Add plugin version rule processor

* Tidy up, don't need to pass $spec everywhere

* Remove meta record for action state - not really needed

* Move spec runner into it's own class, add some checks around re-unactioning a note

* Replace if..else with switch

* Just update the option

* Check that the JSON coming in is an array

* Change OR rule to accept an array of operands

* Add Pass rule processor

* Fix specs that are initially not published

* Rename send at rule to publish after

* Add publish before rule

* Remove unused interface

* Can't use PHP7's anonymous classes

* New notification: How to draw attention to your online store

* Add feature flag for rule-base inbox notes

* rename everything to RemoteInboxNotifications from RINDS

* Fix merge fail

* fix test

* Change preunactioned to pending

* Move feature flag check into Events.php

* Refactor reading a data source

* Rename poll_data_sources function

* Refactor EvaluateAndGetStatus::evaluate to take the rule evaluator directly

* Check that the response body exists

* Add validation and defensive checks

* Add rule processor interface

* Update note created time on status change

* Move non-test dependencies into processor constructors

* Update to proposed live URL

* Remove setting icon as it's being deprecated

Co-authored-by: Rebecca Scott <me@becdetat.com>
This commit is contained in:
Bec Scott 2020-06-05 11:51:25 +10:00 committed by GitHub
parent b4c735f0de
commit a3ae3569c3
39 changed files with 2370 additions and 1 deletions

View File

@ -8,6 +8,7 @@
"marketing": true,
"navigation": false,
"onboarding": true,
"remote-inbox-notifications": false,
"shipping-label-banner": true,
"store-alerts": true,
"wcpay": true,

View File

@ -8,6 +8,7 @@
"marketing": true,
"navigation": true,
"onboarding": true,
"remote-inbox-notifications": true,
"shipping-label-banner": true,
"store-alerts": true,
"wcpay": true,

View File

@ -8,6 +8,7 @@
"marketing": true,
"navigation": false,
"onboarding": true,
"remote-inbox-notifications": false,
"shipping-label-banner": true,
"store-alerts": true,
"wcpay": true,

View File

@ -0,0 +1,28 @@
<?php
/**
* A provider for getting the current DateTime.
*
* @package WooCommerce Admin/Classes
*/
namespace Automattic\WooCommerce\Admin\DateTimeProvider;
defined( 'ABSPATH' ) || exit;
use Automattic\WooCommerce\Admin\DateTimeProvider\DateTimeProviderInterface;
/**
* Current DateTime Provider.
*
* Uses the current DateTime.
*/
class CurrentDateTimeProvider implements DateTimeProviderInterface {
/**
* Returns the current DateTime.
*
* @return DateTime
*/
public function get_now() {
return new \DateTime();
}
}

View File

@ -0,0 +1,23 @@
<?php
/**
* Interface for a provider for getting the current DateTime,
* designed to be mockable for unit tests.
*
* @package WooCommerce Admin/Classes
*/
namespace Automattic\WooCommerce\Admin\DateTimeProvider;
defined( 'ABSPATH' ) || exit;
/**
* DateTime Provider Interface.
*/
interface DateTimeProviderInterface {
/**
* Returns the current DateTime.
*
* @return DateTime
*/
public function get_now();
}

View File

@ -22,6 +22,9 @@ use \Automattic\WooCommerce\Admin\Notes\WC_Admin_Notes_WooCommerce_Payments;
use \Automattic\WooCommerce\Admin\Notes\WC_Admin_Notes_Marketing;
use \Automattic\WooCommerce\Admin\Notes\WC_Admin_Notes_WooCommerce_Subscriptions;
use \Automattic\WooCommerce\Admin\Notes\WC_Admin_Notes_Migrate_From_Shopify;
use \Automattic\WooCommerce\Admin\RemoteInboxNotifications\DataSourcePoller;
use \Automattic\WooCommerce\Admin\RemoteInboxNotifications\RemoteInboxNotificationsEngine;
use \Automattic\WooCommerce\Admin\Loader;
/**
* WC_Admin_Events Class.
@ -78,5 +81,10 @@ class Events {
WC_Admin_Notes_Giving_Feedback_Notes::possibly_add_note();
WC_Admin_Notes_WooCommerce_Subscriptions::possibly_add_note();
WC_Admin_Notes_Migrate_From_Shopify::possibly_add_note();
if ( Loader::is_feature_enabled( 'remote-inbox-notifications' ) ) {
DataSourcePoller::read_specs_from_data_sources();
RemoteInboxNotificationsEngine::run();
}
}
}

View File

@ -12,11 +12,13 @@ defined( 'ABSPATH' ) || exit;
use \Automattic\WooCommerce\Admin\Notes\WC_Admin_Notes;
use \Automattic\WooCommerce\Admin\Notes\WC_Admin_Notes_Historical_Data;
use \Automattic\WooCommerce\Admin\Notes\WC_Admin_Notes_Order_Milestones;
use \Automattic\WooCommerce\Admin\Notes\WC_Admin_Notes_Welcome_Message;
use \Automattic\WooCommerce\Admin\Notes\WC_Admin_Notes_Woo_Subscriptions_Notes;
use \Automattic\WooCommerce\Admin\Notes\WC_Admin_Notes_Tracking_Opt_In;
use \Automattic\WooCommerce\Admin\Notes\WC_Admin_Notes_WooCommerce_Payments;
use \Automattic\WooCommerce\Admin\Notes\WC_Admin_Notes_Install_JP_And_WCS_Plugins;
use \Automattic\WooCommerce\Admin\Notes\WC_Admin_Notes_Draw_Attention;
use \Automattic\WooCommerce\Admin\RemoteInboxNotifications\RemoteInboxNotificationsEngine;
/**
* Feature plugin main class.
@ -188,6 +190,9 @@ class FeaturePlugin {
new WC_Admin_Notes_WooCommerce_Payments();
new WC_Admin_Notes_Install_JP_And_WCS_Plugins();
new WC_Admin_Notes_Draw_Attention();
// Initialize RemoteInboxNotificationsEngine.
RemoteInboxNotificationsEngine::init();
}
/**

View File

@ -40,7 +40,7 @@ class Install {
'wc_admin_update_0251_remove_unsnooze_action',
'wc_admin_update_0251_db_version',
),
'1.1.0' => array(
'1.1.0' => array(
'wc_admin_update_110_remove_facebook_note',
'wc_admin_update_110_db_version',
),

View File

@ -24,6 +24,7 @@ class WC_Admin_Note extends \WC_Data {
const E_WC_ADMIN_NOTE_MARKETING = 'marketing'; // used for adding marketing messages.
// Note status codes.
const E_WC_ADMIN_NOTE_PENDING = 'pending'; // the note is not actioned but shouldn't be displayed.
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.
@ -137,6 +138,7 @@ class WC_Admin_Note extends \WC_Data {
*/
public static function get_allowed_statuses() {
$allowed_statuses = array(
self::E_WC_ADMIN_NOTE_PENDING,
self::E_WC_ADMIN_NOTE_ACTIONED,
self::E_WC_ADMIN_NOTE_UNACTIONED,
self::E_WC_ADMIN_NOTE_SNOOZED,

View File

@ -0,0 +1,76 @@
<?php
/**
* A provider for getting access to plugin queries.
*
* @package WooCommerce Admin/Classes
*/
namespace Automattic\WooCommerce\Admin\PluginsProvider;
defined( 'ABSPATH' ) || exit;
use Automattic\WooCommerce\Admin\PluginsProvider\PluginsProviderInterface;
use Automattic\WooCommerce\Admin\PluginsHelper;
/**
* Plugins Provider.
*
* Uses the live PluginsHelper.
*/
class PluginsProvider implements PluginsProviderInterface {
/**
* The deactivated plugin slug.
*
* @var string
*/
private static $deactivated_plugin_slug = '';
/**
* Get an array of active plugin slugs.
*
* @return array
*/
public function get_active_plugin_slugs() {
return array_filter(
PluginsHelper::get_active_plugin_slugs(),
function( $p ) {
return $p !== self::$deactivated_plugin_slug;
}
);
}
/**
* Set the deactivated plugin. This is needed because the deactivated_plugin
* hook happens before the option is updated which means that getting the
* active plugins includes the deactivated plugin.
*
* @param string $plugin_path The path to the plugin being deactivated.
*/
public static function set_deactivated_plugin( $plugin_path ) {
self::$deactivated_plugin_slug = explode( '/', $plugin_path )[0];
}
/**
* Get plugin data.
*
* @param string $plugin Path to the plugin file relative to the plugins directory or the plugin directory name.
*
* @return array|false
*/
public function get_plugin_data( $plugin ) {
return PluginsHelper::get_plugin_data( $plugin );
}
/**
* Get the path to the plugin file relative to the plugins directory from the plugin slug.
*
* E.g. 'woocommerce' returns 'woocommerce/woocommerce.php'
*
* @param string $slug Plugin slug to get path for.
*
* @return string|false
*/
public function get_plugin_path_from_slug( $slug ) {
return PluginsHelper::get_plugin_path_from_slug( $slug );
}
}

View File

@ -0,0 +1,43 @@
<?php
/**
* Interface for a provider for getting access to plugin queries,
* designed to be mockable for unit tests.
*
* @package WooCommerce Admin/Classes
*/
namespace Automattic\WooCommerce\Admin\PluginsProvider;
defined( 'ABSPATH' ) || exit;
/**
* Plugins Provider Interface
*/
interface PluginsProviderInterface {
/**
* Get an array of active plugin slugs.
*
* @return array
*/
public function get_active_plugin_slugs();
/**
* Get plugin data.
*
* @param string $plugin Path to the plugin file relative to the plugins directory or the plugin directory name.
*
* @return array|false
*/
public function get_plugin_data( $plugin );
/**
* Get the path to the plugin file relative to the plugins directory from the plugin slug.
*
* E.g. 'woocommerce' returns 'woocommerce/woocommerce.php'
*
* @param string $slug Plugin slug to get path for.
*
* @return string|false
*/
public function get_plugin_path_from_slug( $slug );
}

View File

@ -0,0 +1,167 @@
<?php
/**
* Handles polling and storage of specs
*
* @package WooCommerce Admin/Classes
*/
namespace Automattic\WooCommerce\Admin\RemoteInboxNotifications;
defined( 'ABSPATH' ) || exit;
/**
* Specs data source poller class.
* This handles polling specs from JSON endpoints, and
* stores the specs in to the database as an option.
*/
class DataSourcePoller {
const DATA_SOURCES = array(
'https://woocommerce.com/wp-json/wccom//inbox-notifications/1.0/notifications.json',
);
/**
* Reads the data sources for specs and persists those specs.
*/
public static function read_specs_from_data_sources() {
$specs = array();
// Note that this merges the specs from the data sources based on the
// slug - last one wins.
foreach ( self::DATA_SOURCES as $url ) {
$specs_from_data_source = self::read_data_source( $url, $specs );
self::merge_specs( $specs_from_data_source, $specs );
}
// Persist the specs as an option.
update_option( RemoteInboxNotificationsEngine::SPECS_OPTION_NAME, $specs );
}
/**
* Read a single data source and return the read specs
*
* @param string $url The URL to read the specs from.
*
* @return array The specs that have been read from the data source.
*/
private static function read_data_source( $url ) {
$response = wp_remote_get( $url );
if ( is_wp_error( $response ) || ! isset( $response['body'] ) ) {
return [];
}
$body = $response['body'];
$specs = json_decode( $body );
if ( null === $specs ) {
return [];
}
if ( ! is_array( $specs ) ) {
return [];
}
return $specs;
}
/**
* Merge the specs.
*
* @param Array $specs_to_merge_in The specs to merge in to $specs.
* @param Array $specs The master list of specs.
*/
private static function merge_specs( $specs_to_merge_in, &$specs ) {
foreach ( $specs_to_merge_in as $spec ) {
if ( ! self::validate_spec( $spec ) ) {
continue;
}
$slug = $spec->slug;
$specs[ $slug ] = $spec;
}
}
/**
* Validate the spec.
*
* @param object $spec The spec to validate.
*
* @return bool The result of the validation.
*/
private static function validate_spec( $spec ) {
if ( ! isset( $spec->slug ) ) {
return false;
}
if ( ! isset( $spec->status ) ) {
return false;
}
if ( ! isset( $spec->locales ) || ! is_array( $spec->locales ) ) {
return false;
}
if ( null === SpecRunner::get_locale( $spec->locales ) ) {
return false;
}
if ( ! isset( $spec->type ) ) {
return false;
}
if ( ! isset( $spec->slug ) ) {
return false;
}
if ( isset( $spec->actions ) && is_array( $spec->actions ) ) {
foreach ( $spec->actions as $action ) {
if ( ! self::validate_action( $action ) ) {
return false;
}
}
}
if ( isset( $spec->rules ) && is_array( $spec->rules ) ) {
foreach ( $spec->rules as $rule ) {
if ( ! isset( $rule->type ) ) {
return false;
}
$processor = GetRuleProcessor::get_processor( $rule->type );
if ( ! $processor->validate( $rule ) ) {
return false;
}
}
}
return true;
}
/**
* Validate the action.
*
* @param object $action The action to validate.
*
* @return bool The result of the validation.
*/
private static function validate_action( $action ) {
if ( ! isset( $action->locales ) || ! is_array( $action->locales ) ) {
return false;
}
if ( null === SpecRunner::get_action_locale( $action->locales ) ) {
return false;
}
if ( ! isset( $action->name ) ) {
return false;
}
if ( ! isset( $action->status ) ) {
return false;
}
return true;
}
}

View File

@ -0,0 +1,53 @@
<?php
/**
* Evaluates the spec and returns a status.
*
* @package WooCommerce Admin/Classes
*/
namespace Automattic\WooCommerce\Admin\RemoteInboxNotifications;
defined( 'ABSPATH' ) || exit;
use \Automattic\WooCommerce\Admin\Notes\WC_Admin_Note;
/**
* Evaluates the spec and returns a status.
*/
class EvaluateAndGetStatus {
/**
* Evaluates the spec and returns a status.
*
* @param array $spec The spec to evaluate.
* @param string $current_status The note's current status.
* @param object $rule_evaluator Evaluates rules into true/false.
*
* @return string The evaluated status.
*/
public static function evaluate( $spec, $current_status, $rule_evaluator ) {
// No rules should leave the note alone.
if ( ! isset( $spec->rules ) ) {
return $current_status;
}
$evaluated_result = $rule_evaluator->evaluate( $spec->rules );
// Pending notes should be the spec status if the spec passes,
// left alone otherwise.
if ( WC_Admin_Note::E_WC_ADMIN_NOTE_PENDING === $current_status ) {
return $evaluated_result
? $spec->status
: WC_Admin_Note::E_WC_ADMIN_NOTE_PENDING;
}
// When allow_redisplay isn't set, just leave the note alone.
if ( ! isset( $spec->allow_redisplay ) || ! $spec->allow_redisplay ) {
return $current_status;
}
// allow_redisplay is set, unaction the note if eval to true.
return $evaluated_result
? WC_Admin_Note::E_WC_ADMIN_NOTE_UNACTIONED
: $current_status;
}
}

View File

@ -0,0 +1,37 @@
<?php
/**
* Rule processor that fails.
*
* @package WooCommerce Admin/Classes;
*/
namespace Automattic\WooCommerce\Admin\RemoteInboxNotifications;
defined( 'ABSPATH' ) || exit;
/**
* Rule processor that fails.
*/
class FailRuleProcessor implements RuleProcessorInterface {
/**
* Fails the rule.
*
* @param object $rule The rule to process.
*
* @return bool Always false.
*/
public function process( $rule ) {
return false;
}
/**
* Validates the rule.
*
* @param object $rule The rule to validate.
*
* @return bool Pass/fail.
*/
public function validate( $rule ) {
return true;
}
}

View File

@ -0,0 +1,45 @@
<?php
/**
* Gets the processor for the specified rule type.
*
* @package WooCommerce Admin/Classes
*/
namespace Automattic\WooCommerce\Admin\RemoteInboxNotifications;
defined( 'ABSPATH' ) || exit;
/**
* Class encapsulating getting the processor for a given rule type.
*/
class GetRuleProcessor {
/**
* Get the processor for the specified rule type.
*
* @param string $rule_type The rule type.
*
* @return RuleProcessorInterface The matching processor for the specified rule type, or a FailRuleProcessor if no matching processor is found.
*/
public static function get_processor( $rule_type ) {
switch ( $rule_type ) {
case 'plugins_activated':
return new PluginsActivatedRuleProcessor();
case 'publish_after_time':
return new PublishAfterTimeRuleProcessor();
case 'publish_before_time':
return new PublishBeforeTimeRuleProcessor();
case 'not':
return new NotRuleProcessor();
case 'or':
return new OrRuleProcessor();
case 'fail':
return new FailRuleProcessor();
case 'pass':
return new PassRuleProcessor();
case 'plugin_version':
return new PluginVersionRuleProcessor();
}
return new FailRuleProcessor();
}
}

View File

@ -0,0 +1,54 @@
<?php
/**
* Rule processor that negates the rules in the rule's operand.
*
* @package WooCommerce Admin/Classes
*/
namespace Automattic\WooCommerce\Admin\RemoteInboxNotifications;
defined( 'ABSPATH' ) || exit;
/**
* Rule processor that negates the rules in the rule's operand.
*/
class NotRuleProcessor implements RuleProcessorInterface {
/**
* Constructor.
*
* @param RuleEvaluator $rule_evaluator The rule evaluator to use.
*/
public function __construct( $rule_evaluator = null ) {
$this->rule_evaluator = null === $rule_evaluator
? new RuleEvaluator()
: $rule_evaluator;
}
/**
* Evaluates the rules in the operand and negates the result.
*
* @param object $rule The specific rule being processed by this rule processor.
*
* @return bool The result of the operation.
*/
public function process( $rule ) {
$evaluated_operand = $this->rule_evaluator->evaluate( $rule->operand );
return ! $evaluated_operand;
}
/**
* Validates the rule.
*
* @param object $rule The rule to validate.
*
* @return bool Pass/fail.
*/
public function validate( $rule ) {
if ( ! isset( $rule->operand ) ) {
return false;
}
return true;
}
}

View File

@ -0,0 +1,62 @@
<?php
/**
* Rule processor that performs an OR operation on the rule's left and right
* operands.
*
* @package WooCommerce Admin/Classes
*/
namespace Automattic\WooCommerce\Admin\RemoteInboxNotifications;
defined( 'ABSPATH' ) || exit;
/**
* Rule processor that performs an OR operation on the rule's left and right
* operands.
*/
class OrRuleProcessor implements RuleProcessorInterface {
/**
* Constructor.
*
* @param RuleEvaluator $rule_evaluator The rule evaluator to use.
*/
public function __construct( $rule_evaluator = null ) {
$this->rule_evaluator = null === $rule_evaluator
? new RuleEvaluator()
: $rule_evaluator;
}
/**
* Performs an OR operation on the rule's left and right operands.
*
* @param object $rule The specific rule being processed by this rule processor.
*
* @return bool The result of the operation.
*/
public function process( $rule ) {
foreach ( $rule->operands as $operand ) {
$evaluated_operand = $this->rule_evaluator->evaluate( $operand );
if ( $evaluated_operand ) {
return true;
}
}
return false;
}
/**
* Validates the rule.
*
* @param object $rule The rule to validate.
*
* @return bool Pass/fail.
*/
public function validate( $rule ) {
if ( ! isset( $rule->operands ) || ! is_array( $rule->operands ) ) {
return false;
}
return true;
}
}

View File

@ -0,0 +1,38 @@
<?php
/**
* Rule processor that passes. This is required because an empty set of rules
* (or predicate) evaluates to false.
*
* @package WooCommerce Admin/Classes;
*/
namespace Automattic\WooCommerce\Admin\RemoteInboxNotifications;
defined( 'ABSPATH' ) || exit;
/**
* Rule processor that passes.
*/
class PassRuleProcessor implements RuleProcessorInterface {
/**
* Passes the rule.
*
* @param object $rule The specific rule being processed by this rule processor.
*
* @return bool Always true.
*/
public function process( $rule ) {
return true;
}
/**
* Validates the rule.
*
* @param object $rule The rule to validate.
*
* @return bool Pass/fail.
*/
public function validate( $rule ) {
return true;
}
}

View File

@ -0,0 +1,78 @@
<?php
/**
* Rule processor for sending when the provided plugin is activated and
* matches the specified version.
*
* @package WooCommerce Admin/Classes;
*/
namespace Automattic\WooCommerce\Admin\RemoteInboxNotifications;
defined( 'ABSPATH' ) || exit;
use \Automattic\WooCommerce\Admin\PluginsProvider\PluginsProvider;
/**
* Rule processor for sending when the provided plugin is activated and
* matches the specified version.
*/
class PluginVersionRuleProcessor implements RuleProcessorInterface {
/**
* Constructor.
*
* @param PluginsProviderInterface $plugins_provider The plugins provider.
*/
public function __construct( $plugins_provider = null ) {
$this->plugins_provider = null === $plugins_provider
? new PluginsProvider()
: $plugins_provider;
}
/**
* Process the rule.
*
* @param object $rule The specific rule being processed by this rule processor.
*
* @return bool Whether the rule passes or not.
*/
public function process( $rule ) {
$active_plugin_slugs = $this->plugins_provider->get_active_plugin_slugs();
if ( ! in_array( $rule->plugin, $active_plugin_slugs, true ) ) {
return false;
}
$plugin_data = $this->plugins_provider->get_plugin_data( $rule->plugin );
if ( ! $plugin_data ) {
return false;
}
$plugin_version = $plugin_data['Version'];
return version_compare( $plugin_version, $rule->version, $rule->operator );
}
/**
* Validates the rule.
*
* @param object $rule The rule to validate.
*
* @return bool Pass/fail.
*/
public function validate( $rule ) {
if ( ! isset( $rule->plugin ) ) {
return false;
}
if ( ! isset( $rule->version ) ) {
return false;
}
if ( ! isset( $rule->operator ) ) {
return false;
}
return true;
}
}

View File

@ -0,0 +1,66 @@
<?php
/**
* Rule processor for sending when the provided plugins are activated.
*
* @package WooCommerce Admin/Classes;
*/
namespace Automattic\WooCommerce\Admin\RemoteInboxNotifications;
defined( 'ABSPATH' ) || exit;
use \Automattic\WooCommerce\Admin\PluginsProvider\PluginsProvider;
/**
* Rule processor for sending when the provided plugins are activated.
*/
class PluginsActivatedRuleProcessor implements RuleProcessorInterface {
/**
* Constructor.
*
* @param PluginsProviderInterface $plugins_provider The plugins provider.
*/
public function __construct( $plugins_provider = null ) {
$this->plugins_provider = null === $plugins_provider
? new PluginsProvider()
: $plugins_provider;
}
/**
* Process the rule.
*
* @param object $rule The specific rule being processed by this rule processor.
*
* @return bool Whether the rule passes or not.
*/
public function process( $rule ) {
if ( 0 === count( $rule->plugins ) ) {
return false;
}
$active_plugin_slugs = $this->plugins_provider->get_active_plugin_slugs();
foreach ( $rule->plugins as $plugin_slug ) {
if ( ! in_array( $plugin_slug, $active_plugin_slugs, true ) ) {
return false;
}
}
return true;
}
/**
* Validates the rule.
*
* @param object $rule The rule to validate.
*
* @return bool Pass/fail.
*/
public function validate( $rule ) {
if ( ! isset( $rule->plugins ) || ! is_array( $rule->plugins ) ) {
return false;
}
return true;
}
}

View File

@ -0,0 +1,54 @@
<?php
/**
* Rule processor for sending after a specified date/time.
*
* @package WooCommerce Admin/Classes;
*/
namespace Automattic\WooCommerce\Admin\RemoteInboxNotifications;
defined( 'ABSPATH' ) || exit;
use \Automattic\WooCommerce\Admin\DateTimeProvider\CurrentDateTimeProvider;
/**
* Rule processor for sending after a specified date/time.
*/
class PublishAfterTimeRuleProcessor implements RuleProcessorInterface {
/**
* Constructor.
*
* @param DateTimeProviderInterface $date_time_provider The DateTime provider.
*/
public function __construct( $date_time_provider = null ) {
$this->date_time_provider = null === $date_time_provider
? new CurrentDateTimeProvider()
: $date_time_provider;
}
/**
* Process the rule.
*
* @param object $rule The specific rule being processed by this rule processor.
*
* @return bool Whether the rule passes or not.
*/
public function process( $rule ) {
return $this->date_time_provider->get_now() >= new \DateTime( $rule->publish_after );
}
/**
* Validates the rule.
*
* @param object $rule The rule to validate.
*
* @return bool Pass/fail.
*/
public function validate( $rule ) {
if ( ! isset( $rule->publish_after ) ) {
return false;
}
return true;
}
}

View File

@ -0,0 +1,54 @@
<?php
/**
* Rule processor for sending before a specified date/time.
*
* @package WooCommerce Admin/Classes;
*/
namespace Automattic\WooCommerce\Admin\RemoteInboxNotifications;
defined( 'ABSPATH' ) || exit;
use \Automattic\WooCommerce\Admin\DateTimeProvider\CurrentDateTimeProvider;
/**
* Rule processor for sending before a specified date/time.
*/
class PublishBeforeTimeRuleProcessor implements RuleProcessorInterface {
/**
* Constructor.
*
* @param DateTimeProviderInterface $date_time_provider The DateTime provider.
*/
public function __construct( $date_time_provider = null ) {
$this->date_time_provider = null === $date_time_provider
? new CurrentDateTimeProvider()
: $date_time_provider;
}
/**
* Process the rule.
*
* @param object $rule The specific rule being processed by this rule processor.
*
* @return bool Whether the rule passes or not.
*/
public function process( $rule ) {
return $this->date_time_provider->get_now() <= new \DateTime( $rule->publish_before );
}
/**
* Validates the rule.
*
* @param object $rule The rule to validate.
*
* @return bool Pass/fail.
*/
public function validate( $rule ) {
if ( ! isset( $rule->publish_before ) ) {
return false;
}
return true;
}
}

View File

@ -0,0 +1,59 @@
<?php
/**
* Handles running specs
*
* @package WooCommerce Admin/Classes
*/
namespace Automattic\WooCommerce\Admin\RemoteInboxNotifications;
defined( 'ABSPATH' ) || exit;
use \Automattic\WooCommerce\Admin\Notes\WC_Admin_Note;
use \Automattic\WooCommerce\Admin\PluginsProvider\PluginsProvider;
/**
* RemoteInboxNotifications engine.
* This goes through the specs and runs (creates admin notes) for those
* specs that are able to be triggered.
*/
class RemoteInboxNotificationsEngine {
const SPECS_OPTION_NAME = 'wc_remote_inbox_notifications_specs';
/**
* Initialize the engine.
*/
public static function init() {
add_action( 'activated_plugin', array( __CLASS__, 'run' ) );
add_action( 'deactivated_plugin', array( __CLASS__, 'run_on_deactivated_plugin' ), 10, 1 );
}
/**
* Go through the specs and run them.
*/
public static function run() {
$specs = get_option( self::SPECS_OPTION_NAME );
if ( false === $specs ) {
// We are running too early, need to poll data sources first.
return;
}
foreach ( $specs as $spec ) {
SpecRunner::run_spec( $spec );
}
}
/**
* The deactivated_plugin hook happens before the option is updated
* (https://github.com/WordPress/WordPress/blob/master/wp-admin/includes/plugin.php#L826)
* so this captures the deactivated plugin path and pushes it into the
* PluginsProvider.
*
* @param string $plugin Path to the plugin file relative to the plugins directory.
*/
public static function run_on_deactivated_plugin( $plugin ) {
PluginsProvider::set_deactivated_plugin( $plugin );
self::run();
}
}

View File

@ -0,0 +1,53 @@
<?php
/**
* Evaluate the given rules as an AND operation - return false early if a
* rule evaluates to false.
*
* @package WooCommerce Admin/Classes
*/
namespace Automattic\WooCommerce\Admin\RemoteInboxNotifications;
defined( 'ABSPATH' ) || exit;
/**
* Evaluate the given rules as an AND operation - return false early if a
* rule evaluates to false.
*/
class RuleEvaluator {
/**
* Constructor.
*
* @param GetRuleProcessor $get_rule_processor The GetRuleProcessor to use.
*/
public function __construct( $get_rule_processor = null ) {
$this->get_rule_processor = null === $get_rule_processor
? new GetRuleProcessor()
: $get_rule_processor;
}
/**
* Evaluate the given rules as an AND operation - return false early if a
* rule evaluates to false.
*
* @param array $rules The rules being processed.
*
* @return bool The result of the operation.
*/
public function evaluate( $rules ) {
if ( 0 === count( $rules ) ) {
return false;
}
foreach ( $rules as $rule ) {
$processor = $this->get_rule_processor->get_processor( $rule->type );
$processor_result = $processor->process( $rule );
if ( ! $processor_result ) {
return false;
}
}
return true;
}
}

View File

@ -0,0 +1,33 @@
<?php
/**
* Interface for a rule processor.
*
* @package WooCommerce Admin/Classes
*/
namespace Automattic\WooCommerce\Admin\RemoteInboxNotifications;
defined( 'ABSPATH' ) || exit;
/**
* Rule processor interface
*/
interface RuleProcessorInterface {
/**
* Processes a rule, returning the boolean result of the processing.
*
* @param object $rule The rule to process.
*
* @return bool The result of the processing.
*/
public function process( $rule );
/**
* Validates the rule.
*
* @param object $rule The rule to validate.
*
* @return bool Pass/fail.
*/
public function validate( $rule );
}

View File

@ -0,0 +1,190 @@
<?php
/**
* Runs a single spec.
*
* @package WooCommerce Admin/Classes
*/
namespace Automattic\WooCommerce\Admin\RemoteInboxNotifications;
defined( 'ABSPATH' ) || exit;
use \Automattic\WooCommerce\Admin\Notes\WC_Admin_Note;
/**
* Runs a single spec.
*/
class SpecRunner {
/**
* Run the spec.
*
* @param object $spec The spec to run.
*/
public static function run_spec( $spec ) {
$data_store = \WC_Data_Store::load( 'admin-note' );
// Create or update the note.
$existing_note_ids = $data_store->get_notes_with_name( $spec->slug );
if ( 0 === count( $existing_note_ids ) ) {
$note = new WC_Admin_Note();
$note->set_status( WC_Admin_Note::E_WC_ADMIN_NOTE_PENDING );
} else {
$note = new WC_Admin_Note( $existing_note_ids[0] );
}
// Evaluate the spec and get the new note status.
$rule_evaluator = new RuleEvaluator(
new GetRuleProcessor()
);
$previous_status = $note->get_status();
$status = EvaluateAndGetStatus::evaluate(
$spec,
$previous_status,
$rule_evaluator
);
// If the status is changing, update the created date to now.
if ( $previous_status !== $status ) {
$note->set_date_created( time() );
}
// Get the matching locale or fall back to en-US.
$locale = self::get_locale( $spec->locales );
if ( null === $locale ) {
return;
}
// Set up the note.
$note->set_title( $locale->title );
$note->set_content( $locale->content );
$note->set_content_data( (object) array() );
$note->set_status( $status );
$note->set_type( $spec->type );
$note->set_name( $spec->slug );
if ( isset( $spec->source ) ) {
$note->set_source( $spec->source );
}
// Clear then create actions.
$note->clear_actions();
$actions = isset( $spec->actions ) ? $spec->actions : array();
foreach ( $actions as $action ) {
$action_locale = self::get_action_locale( $action->locales );
$url = self::get_url( $action );
$note->add_action(
$action->name,
( null === $action_locale || ! isset( $action_locale->label ) )
? ''
: $action_locale->label,
$url,
$action->status,
isset( $action->is_primary ) ? $action->is_primary : false
);
}
$note->save();
}
/**
* Get the URL for an action.
*
* @param object $action The action.
*
* @return string The URL for the action.
*/
private static function get_url( $action ) {
if ( ! isset( $action->url ) ) {
return '';
}
if ( isset( $action->url_is_admin_query ) && $action->url_is_admin_query ) {
return wc_admin_url( $action->url );
}
return $action->url;
}
/**
* Get the locale for the WordPress locale, or fall back to the en_US
* locale.
*
* @param Array $locales The locales to search through.
*
* @returns object The locale that was found, or null if no matching locale was found.
*/
public static function get_locale( $locales ) {
$wp_locale = get_locale();
$matching_wp_locales = array_values(
array_filter(
$locales,
function( $l ) use ( $wp_locale ) {
return $wp_locale === $l->locale;
}
)
);
if ( 0 !== count( $matching_wp_locales ) ) {
return $matching_wp_locales[0];
}
// Fall back to en_US locale.
$en_us_locales = array_values(
array_filter(
$locales,
function( $l ) {
return 'en_US' === $l->locale;
}
)
);
if ( 0 !== count( $en_us_locales ) ) {
return $en_us_locales[0];
}
return null;
}
/**
* Get the action locale that matches the note locale, or fall back to the
* en_US locale.
*
* @param Array $action_locales The locales from the spec's action.
*
* @return object The matching locale, or the en_US fallback locale, or null if neither was found.
*/
public static function get_action_locale( $action_locales ) {
$wp_locale = get_locale();
$matching_wp_locales = array_values(
array_filter(
$action_locales,
function ( $l ) use ( $wp_locale ) {
return $wp_locale === $l->locale;
}
)
);
if ( 0 !== count( $matching_wp_locales ) ) {
return $matching_wp_locales[0];
}
// Fall back to en_US locale.
$en_us_locales = array_values(
array_filter(
$action_locales,
function( $l ) {
return 'en_US' === $l->locale;
}
)
);
if ( 0 !== count( $en_us_locales ) ) {
return $en_us_locales[0];
}
return null;
}
}

View File

@ -0,0 +1,205 @@
<?php
/**
* Evaluate and get status tests.
*
* @package WooCommerce\Tests\RemoteInboxNotifications
*/
use Automattic\WooCommerce\Admin\RemoteInboxNotifications\EvaluateAndGetStatus;
/**
* class WC_Tests_RemoteInboxNotifications_EvaluateAndGetStatus
*/
class WC_Tests_RemoteInboxNotifications_EvaluateAndGetStatus extends WC_Unit_Test_Case {
/**
* Build up a spec given the supplied parameters.
*
* @param bool $allow_redisplay Allow note redisplay after it has been actioned.
*
* @return object The spec object.
*/
private function get_spec( $allow_redisplay ) {
return json_decode(
'{
"status": "unactioned",
"rules": [],
"allow_redisplay": ' . ( $allow_redisplay ? 'true' : 'false' ) . '
}'
);
}
/**
* Get a spec with no rules property.
*
* @return object The spec object.
*/
private function get_no_rules_spec() {
return json_decode(
'{
"status": "unactioned",
"allow_redisplay": false
}'
);
}
/**
* Tests that for a pending note evaling to true, status is changed
* to the spec status.
*
* @group fast
*/
public function test_pending_note_eval_to_true() {
$spec = $this->get_spec( false );
$result = EvaluateAndGetStatus::evaluate(
$spec,
'unactioned',
new PassingRuleEvaluator()
);
$this->assertEquals( 'unactioned', $result );
}
/**
* Tests that for a pending note evaluating to false, status is
* left at pending.
*
* @group fast
*/
public function test_pending_note_eval_to_false() {
$spec = $this->get_spec( false );
$result = EvaluateAndGetStatus::evaluate(
$spec,
'pending',
new FailingRuleEvaluator()
);
$this->assertEquals( 'pending', $result );
}
/**
* Tests that for a snoozed note evaluating to true without allow_redisplay
* set, status is left as snoozed.
*
* @group fast
*/
public function test_snoozed_note_eval_to_true_without_allow_redisplay() {
$spec = $this->get_spec( false );
$result = EvaluateAndGetStatus::evaluate(
$spec,
'snoozed',
new PassingRuleEvaluator()
);
$this->assertEquals( 'snoozed', $result );
}
/**
* Tests that for a snoozed note evaluating to false without
* allow_redisplay set, status is left as snoozed
*
* @group fast
*/
public function test_snoozed_note_eval_to_false_without_allow_redisplay() {
$spec = $this->get_spec( false );
$result = EvaluateAndGetStatus::evaluate(
$spec,
'snoozed',
new FailingRuleEvaluator()
);
$this->assertEquals( 'snoozed', $result );
}
/**
* Tests that for an actioned note eval to true with allow_redisplay set,
* status is changed to unactioned.
*
* @group fast
*/
public function test_actioned_note_eval_to_true_with_allow_redisplay_set() {
$spec = $this->get_spec( true );
$result = EvaluateAndGetStatus::evaluate(
$spec,
'actioned',
new PassingRuleEvaluator()
);
$this->assertEquals( 'unactioned', $result );
}
/**
* Tests that for an actioned note eval to false with allow_redirect set,
* status is left at actioned.
*
* @group fast
*/
public function test_actioned_note_eval_to_false_with_allow_redisplay_set() {
$spec = $this->get_spec( true );
$result = EvaluateAndGetStatus::evaluate(
$spec,
'actioned',
new FailingRuleEvaluator()
);
$this->assertEquals( 'actioned', $result );
}
/**
* Tests that for a pending note eval to true with allow_redirect
* set, status is changed to unactioned.
*
* @group fast
*/
public function test_pending_note_eval_to_true_with_allow_redirect_set() {
$spec = $this->get_spec( true );
$result = EvaluateAndGetStatus::evaluate(
$spec,
'pending',
new PassingRuleEvaluator()
);
$this->assertEquals( 'unactioned', $result );
}
/**
* Tests that for a pending note eval to false with allow_redirect
* set, status is left as pending.
*
* @group fast
*/
public function test_pending_note_eval_to_false_with_allow_redirect_set() {
$spec = $this->get_spec( true );
$result = EvaluateAndGetStatus::evaluate(
$spec,
'pending',
new FailingRuleEvaluator()
);
$this->assertEquals( 'pending', $result );
}
/**
* Tests that for a spec with no rules the current status is returned.
*
* @group fast
*/
public function test_spec_with_no_rules_returns_current_status() {
$spec = $this->get_no_rules_spec();
$result = EvaluateAndGetStatus::evaluate(
$spec,
'unactioned',
new FailingRuleEvaluator()
);
$this->assertEquals( 'unactioned', $result );
}
}

View File

@ -0,0 +1,22 @@
<?php
/**
* FailingRuleEvaluator
*
* @package WooCommerce\Tests\RemoteInboxNotifications
*/
/**
* class FailingRuleEvaluator
*/
class FailingRuleEvaluator {
/**
* Evaluate to false.
*
* @param array $rules The rules to evaluate.
*
* @return bool The evaluated result.
*/
public function evaluate( $rules ) {
return false;
}
}

View File

@ -0,0 +1,26 @@
<?php
/**
* Get rule processor tests.
*
* @package WooCommerce\Tests\RemoteInboxNotifications
*/
use Automattic\WooCommerce\Admin\RemoteInboxNotifications\GetRuleProcessor;
/**
* class WC_Tests_RemoteInboxNotifications_GetRuleProcessor
*/
class WC_Tests_RemoteInboxNotifications_GetRuleProcessor extends WC_Unit_Test_Case {
/**
* Tests that an unknown rule processor returns a FailRuleProcessor
*
* @group fast
*/
public function test_unknown_rule_processor_returns_fail_rule_processor() {
$get_rule_processor = new GetRuleProcessor();
$result = $get_rule_processor->get_processor( 'unknown rule type' );
$this->assertEquals( 'Automattic\\WooCommerce\\Admin\\RemoteInboxNotifications\\FailRuleProcessor', get_class( $result ) );
}
}

View File

@ -0,0 +1,32 @@
<?php
/**
* Mock DateTime Provider.
*
* @package WooCommerce\Tests\RemoteInboxNotifications
*/
use Automattic\WooCommerce\Admin\DateTimeProvider\DateTimeProviderInterface;
/**
* Mock DateTime Provider.
*/
class MockDateTimeProvider implements DateTimeProviderInterface {
/**
* Construct the mock DateTime provider using the specified value for now.
*
* @param DateTime $now The value to use for now.
*/
public function __construct( $now ) {
$this->now = $now;
}
/**
* Returns the specified DateTime.
*
* @return DateTime
*/
public function get_now() {
return $this->now;
}
}

View File

@ -0,0 +1,32 @@
<?php
/**
* MockGetRuleProcessor.
*
* @package WooCommerce\Tests\RemoteInboxNotifications
*/
use Automattic\WooCommerce\Admin\RemoteInboxNotifications\PublishAfterTimeRuleProcessor;
use Automattic\WooCommerce\Admin\RemoteInboxNotifications\FailRuleProcessor;
/**
* MockGetRuleProcessor.
*/
class MockGetRuleProcessor {
/**
* Get the processor for the specified rule type.
*
* @param string $rule_type The rule type.
*
* @return object The matching processor for the specified rule type, or a FailRuleProcessor if no matching processor is found.
*/
public static function get_processor( $rule_type ) {
if ( 'publish_after_time' === $rule_type ) {
return new PublishAfterTimeRuleProcessor(
new MockDateTimeProvider( new \DateTime( '2020-04-24 10:00:00' ) )
);
}
return new FailRuleProcessor();
}
}

View File

@ -0,0 +1,80 @@
<?php
/**
* Mock plugins Provider.
*
* @package WooCommerce\Tests\RemoteInboxNotifications
*/
use Automattic\WooCommerce\Admin\PluginsProvider\PluginsProviderInterface;
/**
* Mock plugins Provider.
*
* Returns the provided plugins instead of the current plugins. Needed for
* unit tests.
*/
class MockPluginsProvider implements PluginsProviderInterface {
/**
* Construct the mock plugins provider using the specified value for the
* active plugins slugs.
*
* @param array $active_plugin_slugs The value to use for the active plugin slugs.
* @param array $get_plugins_data The data to be used instead of get_plugins() calls.
*/
public function __construct(
$active_plugin_slugs,
$get_plugins_data = array()
) {
$this->active_plugin_slugs = $active_plugin_slugs;
$this->get_plugins_data = $get_plugins_data;
}
/**
* Get an array of provided plugin slugs.
*
* @return array
*/
public function get_active_plugin_slugs() {
return $this->active_plugin_slugs;
}
/**
* Get plugin data.
*
* @param string $plugin Path to the plugin file relative to the plugins directory or the plugin directory name.
*
* @return array|false
*/
public function get_plugin_data( $plugin ) {
$plugin_path = $this->get_plugin_path_from_slug( $plugin );
$plugins = $this->get_plugins_data;
return isset( $plugins[ $plugin_path ] ) ? $plugins[ $plugin_path ] : false;
}
/**
* Get the path to the plugin file relative to the plugins directory from the plugin slug.
*
* E.g. 'woocommerce' returns 'woocommerce/woocommerce.php'
*
* @param string $slug Plugin slug to get path for.
*
* @return string|false
*/
public function get_plugin_path_from_slug( $slug ) {
$plugins = $this->get_plugins_data;
if ( strstr( $slug, '/' ) ) {
return $slug;
}
foreach ( $plugins as $plugin_path => $data ) {
$path_parts = explode( '/', $plugin_path );
if ( $path_parts[0] === $slug ) {
return $plugin_path;
}
}
return false;
}
}

View File

@ -0,0 +1,97 @@
<?php
/**
* Not rule processor tests.
*
* @package WooCommerce\Tests\RemoteInboxNotifications
*/
use Automattic\WooCommerce\Admin\RemoteInboxNotifications\NotRuleProcessor;
use Automattic\WooCommerce\Admin\RemoteInboxNotifications\RuleEvaluator;
/**
* class WC_Tests_RemoteInboxNotifications_NotRuleProcessor
*/
class WC_Tests_RemoteInboxNotifications_NotRuleProcessor extends WC_Unit_Test_Case {
/**
* An empty operand evaluates to false, so negating that should
* evaluate to true.
*
* @group fast
*/
public function test_spec_passes_for_empty_operand() {
$get_rule_processor = new MockGetRuleProcessor();
$processor = new NotRuleProcessor(
new RuleEvaluator(
$get_rule_processor
)
);
$rule = json_decode(
'{
"type": "not",
"operand": []
}'
);
$result = $processor->process( $rule );
$this->assertEquals( true, $result );
}
/**
* Operand that evaluates to true negated to false.
*
* @group fast
*/
public function test_spec_fails_for_passing_operand() {
$get_rule_processor = new MockGetRuleProcessor();
$processor = new NotRuleProcessor(
new RuleEvaluator(
$get_rule_processor
)
);
$rule = json_decode(
'{
"type": "not",
"operand": [
{
"type": "publish_after_time",
"publish_after": "2020-04-24 09:00:00"
}
]
}'
);
$result = $processor->process( $rule );
$this->assertEquals( false, $result );
}
/**
* Operand that evaluates to false negated to true.
*
* @group fast
*/
public function test_spec_passes_for_failing_operand() {
$get_rule_processor = new MockGetRuleProcessor();
$processor = new NotRuleProcessor(
new RuleEvaluator(
$get_rule_processor
)
);
$rule = json_decode(
'{
"type": "not",
"operand": [
{
"type": "publish_after_time",
"publish_after": "2020-04-24 11:00:00"
}
]
}'
);
$result = $processor->process( $rule );
$this->assertEquals( true, $result );
}
}

View File

@ -0,0 +1,162 @@
<?php
/**
* Or rule processor tests.
*
* @package WooCommerce\Tests\RemoteInboxNotifications
*/
use Automattic\WooCommerce\Admin\RemoteInboxNotifications\OrRuleProcessor;
use Automattic\WooCommerce\Admin\RemoteInboxNotifications\RuleEvaluator;
/**
* class WC_Tests_RemoteInboxNotifications_OrRuleProcessor
*/
class WC_Tests_RemoteInboxNotifications_OrRuleProcessor extends WC_Unit_Test_Case {
/**
* Both operands evaluating to false and ORed together evaluates to false.
*
* @group fast
*/
public function test_spec_fails_for_both_operands_false() {
$get_rule_processor = new MockGetRuleProcessor();
$processor = new OrRuleProcessor(
new RuleEvaluator(
$get_rule_processor
)
);
$rule = json_decode(
'{
"type": "or",
"operands": [
[
{
"type": "publish_after_time",
"publish_after": "2020-04-24 11:00:00"
}
],
[
{
"type": "publish_after_time",
"publish_after": "2020-04-24 11:00:00"
}
]
]
}'
);
$result = $processor->process( $rule );
$this->assertEquals( false, $result );
}
/**
* First operand evaluating to true and ORed together evaluates to true.
*
* @group fast
*/
public function test_spec_passes_for_first_operand_true() {
$get_rule_processor = new MockGetRuleProcessor();
$processor = new OrRuleProcessor(
new RuleEvaluator(
$get_rule_processor
)
);
$rule = json_decode(
'{
"type": "or",
"operands": [
[
{
"type": "publish_after_time",
"publish_after": "2020-04-24 09:00:00"
}
],
[
{
"type": "publish_after_time",
"publish_after": "2020-04-24 11:00:00"
}
]
]
}'
);
$result = $processor->process( $rule );
$this->assertEquals( true, $result );
}
/**
* Second operand evaluating to true and ORed together evaluates to true.
*
* @group fast
*/
public function test_spec_passes_for_second_operand_true() {
$get_rule_processor = new MockGetRuleProcessor();
$processor = new OrRuleProcessor(
new RuleEvaluator(
$get_rule_processor
)
);
$rule = json_decode(
'{
"type": "or",
"operands": [
[
{
"type": "publish_after_time",
"publish_after": "2020-04-24 11:00:00"
}
],
[
{
"type": "publish_after_time",
"publish_after": "2020-04-24 09:00:00"
}
]
]
}'
);
$result = $processor->process( $rule );
$this->assertEquals( true, $result );
}
/**
* Both operands evaluating to true and ORed together evaluates to true.
*
* @group fast
*/
public function test_spec_passes_for_both_operands_true() {
$get_rule_processor = new MockGetRuleProcessor();
$processor = new OrRuleProcessor(
new RuleEvaluator(
$get_rule_processor
)
);
$rule = json_decode(
'{
"type": "or",
"operands": [
[
{
"type": "publish_after_time",
"publish_after": "2020-04-24 09:00:00"
}
],
[
{
"type": "publish_after_time",
"publish_after": "2020-04-24 09:00:00"
}
]
]
}'
);
$result = $processor->process( $rule );
$this->assertEquals( true, $result );
}
}

View File

@ -0,0 +1,22 @@
<?php
/**
* PassingRuleEvaluator
*
* @package WooCommerce\Tests\RemoteInboxNotifications
*/
/**
* class PassingRuleEvaluator
*/
class PassingRuleEvaluator {
/**
* Evaluate to true.
*
* @param array $rules The rules to evaluate.
*
* @return bool The evaluated result.
*/
public function evaluate( $rules ) {
return true;
}
}

View File

@ -0,0 +1,158 @@
<?php
/**
* Plugin version rule processor tests.
*
* @package WooCommerce\Tests\RemoteInboxNotifications
*/
use Automattic\WooCommerce\Admin\RemoteInboxNotifications\PluginVersionRuleProcessor;
/**
* class WC_Tests_RemoteInboxNotifications_PluginVersionRuleProcessor
*/
class WC_Tests_RemoteInboxNotifications_PluginVersionRuleProcessor extends WC_Unit_Test_Case {
/**
* Test that the processor does not pass if the plugin is not activated.
*
* @group fast
*/
public function test_spec_does_not_pass_if_plugin_not_activated() {
$mock_plugins_provider = new MockPluginsProvider( array(), array() );
$processor = new PluginVersionRuleProcessor( $mock_plugins_provider );
$rule = json_decode(
'{
"type": "plugin_version",
"plugin": "jetpack",
"version": "1.2.3",
"operator": "<"
}'
);
$result = $processor->process( $rule );
$this->assertEquals( false, $result );
}
/**
* Test that the processor does not pass if the plugin is not activated.
*
* @group fast
*/
public function test_spec_does_not_pass_if_plugin_not_in_data() {
$mock_plugins_provider = new MockPluginsProvider(
array(
'jetpack',
),
array()
);
$processor = new PluginVersionRuleProcessor( $mock_plugins_provider );
$rule = json_decode(
'{
"type": "plugin_version",
"plugin": "jetpack",
"version": "1.2.3",
"operator": "<"
}'
);
$result = $processor->process( $rule );
$this->assertEquals( false, $result );
}
/**
* Test that the processor does not pass if the installed version is less
* than the required version.
*
* @group fast
*/
public function test_spec_does_not_pass_if_installed_version_less_than_required_version() {
$mock_plugins_provider = new MockPluginsProvider(
array(
'jetpack',
),
array(
'jetpack/jetpack.php' => array(
'Version' => '1.2.4',
),
)
);
$processor = new PluginVersionRuleProcessor( $mock_plugins_provider );
$rule = json_decode(
'{
"type": "plugin_version",
"plugin": "jetpack",
"version": "1.2.3",
"operator": "<"
}'
);
$result = $processor->process( $rule );
$this->assertEquals( false, $result );
}
/**
* Test that the processor passes if the installed version is equal
* to the required version.
*
* @group fast
*/
public function test_spec_passes_if_installed_version_equals_required_version() {
$mock_plugins_provider = new MockPluginsProvider(
array(
'jetpack',
),
array(
'jetpack/jetpack.php' => array(
'Version' => '1.2.3',
),
)
);
$processor = new PluginVersionRuleProcessor( $mock_plugins_provider );
$rule = json_decode(
'{
"type": "plugin_version",
"plugin": "jetpack",
"version": "1.2.3",
"operator": "="
}'
);
$result = $processor->process( $rule );
$this->assertEquals( true, $result );
}
/**
* Test that the processor passes if the installed version is later than
* the required version.
*
* @group fast
*/
public function test_spec_passes_if_installed_version_is_later_than_required_version() {
$mock_plugins_provider = new MockPluginsProvider(
array(
'jetpack',
),
array(
'jetpack/jetpack.php' => array(
'Version' => '1.2.4',
),
)
);
$processor = new PluginVersionRuleProcessor( $mock_plugins_provider );
$rule = json_decode(
'{
"type": "plugin_version",
"plugin": "jetpack",
"version": "1.2.3",
"operator": ">"
}'
);
$result = $processor->process( $rule );
$this->assertEquals( true, $result );
}
}

View File

@ -0,0 +1,144 @@
<?php
/**
* Plugins activated rule processor tests.
*
* @package WooCommerce\Tests\RemoteInboxNotifications
*/
use Automattic\WooCommerce\Admin\RemoteInboxNotifications\PluginsActivatedRuleProcessor;
use Automattic\WooCommerce\Admin\PluginsProvider\PluginsProviderInterface;
/**
* class WC_Tests_RemoteInboxNotifications_PluginsActivatedRuleProcessor
*/
class WC_Tests_RemoteInboxNotifications_PluginsActivatedRuleProcessor extends WC_Unit_Test_Case {
/**
* Tests that the processor does not pass a plugins_activated rule with
* no plugins to verify.
*
* @group fast
*/
public function test_spec_does_not_pass_with_no_plugins_to_verify() {
$mock_plugins_provider = new MockPluginsProvider( array() );
$processor = new PluginsActivatedRuleProcessor( $mock_plugins_provider );
$rule = json_decode(
'{
"type": "plugins_activated",
"plugins": [
]
}'
);
$result = $processor->process( $rule );
$this->assertEquals( false, $result );
}
/**
* Tests that the processor does not pass a plugins_activated rule with
* no active plugins.
*
* @group fast
*/
public function test_spec_does_not_pass_with_no_active_plugins() {
$mock_plugins_provider = new MockPluginsProvider( array() );
$processor = new PluginsActivatedRuleProcessor( $mock_plugins_provider );
$rule = json_decode(
'{
"type": "plugins_activated",
"plugins": [
"plugin-slug-1"
]
}'
);
$result = $processor->process( $rule );
$this->assertEquals( false, $result );
}
/**
* Tests that the processor does not pass a plugins_activated rule with
* no matching active plugins.
*
* @group fast
*/
public function test_spec_does_not_pass_with_no_matching_plugins() {
$mock_plugins_provider = new MockPluginsProvider(
array(
'non-matching-slug',
)
);
$processor = new PluginsActivatedRuleProcessor( $mock_plugins_provider );
$rule = json_decode(
'{
"type": "plugins_activated",
"plugins": [
"plugin-slug-1"
]
}'
);
$result = $processor->process( $rule );
$this->assertEquals( false, $result );
}
/**
* Tests that the processor does not pass a plugins_activated rule with
* only one matching plugin.
*
* @group fast
*/
public function test_spec_does_not_pass_with_only_one_matching_plugin() {
$mock_plugins_provider = new MockPluginsProvider(
array(
'plugin-slug-1',
'plugin-slug-2',
)
);
$processor = new PluginsActivatedRuleProcessor( $mock_plugins_provider );
$rule = json_decode(
'{
"type": "plugins_activated",
"plugins": [
"plugin-slug-1",
"plugin-slug-3"
]
}'
);
$result = $processor->process( $rule );
$this->assertEquals( false, $result );
}
/**
* Tests that the processor passes a plugins_activated rule with both
* matching plugins.
*
* @group fast
*/
public function test_spec_does_pass_with_both_matching_plugins() {
$mock_plugins_provider = new MockPluginsProvider(
array(
'plugin-slug-1',
'plugin-slug-2',
)
);
$processor = new PluginsActivatedRuleProcessor( $mock_plugins_provider );
$rule = json_decode(
'{
"type": "plugins_activated",
"plugins": [
"plugin-slug-1",
"plugin-slug-2"
]
}'
);
$result = $processor->process( $rule );
$this->assertEquals( true, $result );
}
}

View File

@ -0,0 +1,79 @@
<?php
/**
* Publish after time rule processor tests.
*
* @package WooCommerce\Tests\RemoteInboxNotifications
*/
use Automattic\WooCommerce\Admin\RemoteInboxNotifications\PublishAfterTimeRuleProcessor;
use Automattic\WooCommerce\Admin\DateTimeProvider\DateTimeProviderInterface;
/**
* class WC_Tests_RemoteInboxNotifications_PublishAfterTimeRuleProcessor
*/
class WC_Tests_RemoteInboxNotifications_PublishAfterTimeRuleProcessor extends WC_Unit_Test_Case {
/**
* Get the publish_after rule.
*
* @return object The rule.
*/
private function get_rule() {
return json_decode(
'{
"type": "publish_after_time",
"publish_after": "2020-04-22 12:00:00"
}'
);
}
/**
* Tests that the processor passes a publish_after_time rule with a
* publish_after time in the past.
*
* @group fast
*/
public function test_spec_passes_for_time_in_the_past() {
$mock_date_time_provider = new MockDateTimeProvider(
new \DateTime( '2020-04-22 14:00:00' )
);
$processor = new PublishAfterTimeRuleProcessor( $mock_date_time_provider );
$result = $processor->process( $this->get_rule() );
$this->assertEquals( true, $result );
}
/**
* Tests that the processor passes a publish_after_time rule with a
* publish_after time right now.
*
* @group fast
*/
public function test_spec_passes_for_time_now() {
$mock_date_time_provider = new MockDateTimeProvider(
new \DateTime( '2020-04-22 12:00:00' )
);
$processor = new PublishAfterTimeRuleProcessor( $mock_date_time_provider );
$result = $processor->process( $this->get_rule() );
$this->assertEquals( true, $result );
}
/**
* Tests that the processor does not pass a publish_after_time rule with a
* publish_after time in the future.
*
* @group fast
*/
public function test_spec_does_not_pass_for_time_in_future() {
$mock_date_time_provider = new MockDateTimeProvider(
new \DateTime( '2020-04-22 09:00:00' )
);
$processor = new PublishAfterTimeRuleProcessor( $mock_date_time_provider );
$result = $processor->process( $this->get_rule() );
$this->assertEquals( false, $result );
}
}

View File

@ -0,0 +1,79 @@
<?php
/**
* Publish before time rule processor tests.
*
* @package WooCommerce\Tests\RemoteInboxNotifications
*/
use Automattic\WooCommerce\Admin\RemoteInboxNotifications\PublishBeforeTimeRuleProcessor;
use Automattic\WooCommerce\Admin\DateTimeProvider\DateTimeProviderInterface;
/**
* class WC_Tests_RemoteInboxNotifications_PublishBeforeTimeRuleProcessor
*/
class WC_Tests_RemoteInboxNotifications_PublishBeforeTimeRuleProcessor extends WC_Unit_Test_Case {
/**
* Get the publish_before rule.
*
* @return object The rule.
*/
private function get_rule() {
return json_decode(
'{
"type": "publish_before_time",
"publish_before": "2020-04-22 12:00:00"
}'
);
}
/**
* Tests that the processor passes a publish_before_time rule with a
* publish_before time in the future.
*
* @group fast
*/
public function test_spec_passes_for_time_in_the_future() {
$mock_date_time_provider = new MockDateTimeProvider(
new \DateTime( '2020-04-22 08:00:00' )
);
$processor = new PublishBeforeTimeRuleProcessor( $mock_date_time_provider );
$result = $processor->process( $this->get_rule() );
$this->assertEquals( true, $result );
}
/**
* Tests that the processor passes a publish_before_time rule with a
* publish_before time right now.
*
* @group fast
*/
public function test_spec_passes_for_time_now() {
$mock_date_time_provider = new MockDateTimeProvider(
new \DateTime( '2020-04-22 12:00:00' )
);
$processor = new PublishBeforeTimeRuleProcessor( $mock_date_time_provider );
$result = $processor->process( $this->get_rule() );
$this->assertEquals( true, $result );
}
/**
* Tests that the processor does not pass a publish_before_time rule with a
* publish_before time in the past.
*
* @group fast
*/
public function test_spec_does_not_pass_for_time_in_the_future() {
$mock_date_time_provider = new MockDateTimeProvider(
new \DateTime( '2020-04-22 14:00:00' )
);
$processor = new PublishBeforeTimeRuleProcessor( $mock_date_time_provider );
$result = $processor->process( $this->get_rule() );
$this->assertEquals( false, $result );
}
}