initial commit

This commit is contained in:
2021-12-10 12:03:04 +00:00
commit c46c7ddbf0
3643 changed files with 582794 additions and 0 deletions

View File

@ -0,0 +1,55 @@
<?php
/**
* Rule processor that performs a comparison operation against the base
* location - country.
*/
namespace Automattic\WooCommerce\Admin\RemoteInboxNotifications;
defined( 'ABSPATH' ) || exit;
/**
* Rule processor that performs a comparison operation against the base
* location - country.
*/
class BaseLocationCountryRuleProcessor implements RuleProcessorInterface {
/**
* Performs a comparison operation against the base location - country.
*
* @param object $rule The specific rule being processed by this rule processor.
* @param object $stored_state Stored state.
*
* @return bool The result of the operation.
*/
public function process( $rule, $stored_state ) {
$base_location = wc_get_base_location();
if ( ! $base_location ) {
return false;
}
return ComparisonOperation::compare(
$base_location['country'],
$rule->value,
$rule->operation
);
}
/**
* Validates the rule.
*
* @param object $rule The rule to validate.
*
* @return bool Pass/fail.
*/
public function validate( $rule ) {
if ( ! isset( $rule->value ) ) {
return false;
}
if ( ! isset( $rule->operation ) ) {
return false;
}
return true;
}
}

View File

@ -0,0 +1,55 @@
<?php
/**
* Rule processor that performs a comparison operation against the base
* location - state.
*/
namespace Automattic\WooCommerce\Admin\RemoteInboxNotifications;
defined( 'ABSPATH' ) || exit;
/**
* Rule processor that performs a comparison operation against the base
* location - state.
*/
class BaseLocationStateRuleProcessor implements RuleProcessorInterface {
/**
* Performs a comparison operation against the base location - state.
*
* @param object $rule The specific rule being processed by this rule processor.
* @param object $stored_state Stored state.
*
* @return bool The result of the operation.
*/
public function process( $rule, $stored_state ) {
$base_location = wc_get_base_location();
if ( ! $base_location ) {
return false;
}
return ComparisonOperation::compare(
$base_location['state'],
$rule->value,
$rule->operation
);
}
/**
* Validates the rule.
*
* @param object $rule The rule to validate.
*
* @return bool Pass/fail.
*/
public function validate( $rule ) {
if ( ! isset( $rule->value ) ) {
return false;
}
if ( ! isset( $rule->operation ) ) {
return false;
}
return true;
}
}

View File

@ -0,0 +1,43 @@
<?php
/**
* Compare two operands using the specified operation.
*/
namespace Automattic\WooCommerce\Admin\RemoteInboxNotifications;
defined( 'ABSPATH' ) || exit;
/**
* Compare two operands using the specified operation.
*/
class ComparisonOperation {
/**
* Compare two operands using the specified operation.
*
* @param object $left_operand The left hand operand.
* @param object $right_operand The right hand operand.
* @param string $operation The operation used to compare the operands.
*/
public static function compare( $left_operand, $right_operand, $operation ) {
switch ( $operation ) {
case '=':
return $left_operand === $right_operand;
case '<':
return $left_operand < $right_operand;
case '<=':
return $left_operand <= $right_operand;
case '>':
return $left_operand > $right_operand;
case '>=':
return $left_operand >= $right_operand;
case '!=':
return $left_operand !== $right_operand;
case 'contains':
return in_array( $right_operand, $left_operand, true );
case '!contains':
return ! in_array( $right_operand, $left_operand, true );
}
return false;
}
}

View File

@ -0,0 +1,306 @@
<?php
/**
* Handles polling and storage of specs
*/
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',
);
/**
* The logger instance.
*
* @var WC_Logger|null
*/
protected static $logger = null;
/**
* Get the logger instance.
*
* @return WC_Logger
*/
private static function get_logger() {
if ( is_null( self::$logger ) ) {
self::$logger = wc_get_logger();
}
return self::$logger;
}
/**
* Reads the data sources for specs and persists those specs.
*
* @return bool Whether any specs were read.
*/
public static function read_specs_from_data_sources() {
$specs = array();
$data_sources = apply_filters( 'woocommerce_admin_remote_inbox_data_sources', self::DATA_SOURCES );
// Note that this merges the specs from the data sources based on the
// slug - last one wins.
foreach ( $data_sources as $url ) {
$specs_from_data_source = self::read_data_source( $url, $specs );
self::merge_specs( $specs_from_data_source, $specs, $url );
}
// Persist the specs as an option.
update_option(
RemoteInboxNotificationsEngine::SPECS_OPTION_NAME,
$specs,
false
);
return 0 !== count( $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 ) {
$logger_context = array( 'source' => $url );
$logger = self::get_logger();
$response = wp_remote_get( $url );
if ( is_wp_error( $response ) || ! isset( $response['body'] ) ) {
$logger->error(
'Error getting remote inbox notification data feed',
$logger_context
);
// phpcs:ignore
$logger->error( print_r( $response, true ), $logger_context );
return [];
}
$body = $response['body'];
$specs = json_decode( $body );
if ( null === $specs ) {
$logger->error(
'Empty response in remote inbox notification data feed',
$logger_context
);
return [];
}
if ( ! is_array( $specs ) ) {
$logger->error(
'Remote inbox notification data feed is not an array',
$logger_context
);
return [];
}
return $specs;
}
/**
* Merge the specs.
*
* @param Array $specs_to_merge_in The specs to merge in to $specs.
* @param Array $specs The list of specs being merged into.
* @param string $url The url of the feed being merged in (for error reporting).
*/
private static function merge_specs( $specs_to_merge_in, &$specs, $url ) {
foreach ( $specs_to_merge_in as $spec ) {
if ( ! self::validate_spec( $spec, $url ) ) {
continue;
}
$slug = $spec->slug;
$specs[ $slug ] = $spec;
}
}
/**
* Validate the spec.
*
* @param object $spec The spec to validate.
* @param string $url The url of the feed that provided the spec.
*
* @return bool The result of the validation.
*/
private static function validate_spec( $spec, $url ) {
$logger = self::get_logger();
$logger_context = array( 'source' => $url );
if ( ! isset( $spec->slug ) ) {
$logger->error(
'Spec is invalid because the slug is missing in feed',
$logger_context
);
// phpcs:ignore
$logger->error( print_r( $spec, true ), $logger_context );
return false;
}
if ( ! isset( $spec->status ) ) {
$logger->error(
'Spec is invalid because the status is missing in feed',
$logger_context
);
// phpcs:ignore
$logger->error( print_r( $spec, true ), $logger_context );
return false;
}
if ( ! isset( $spec->locales ) || ! is_array( $spec->locales ) ) {
$logger->error(
'Spec is invalid because the status is missing or empty in feed',
$logger_context
);
// phpcs:ignore
$logger->error( print_r( $spec, true ), $logger_context );
return false;
}
if ( null === SpecRunner::get_locale( $spec->locales ) ) {
$logger->error(
'Spec is invalid because the locale could not be retrieved in feed',
$logger_context
);
// phpcs:ignore
$logger->error( print_r( $spec, true ), $logger_context );
return false;
}
if ( ! isset( $spec->type ) ) {
$logger->error(
'Spec is invalid because the type is missing in feed',
$logger_context
);
// phpcs:ignore
$logger->error( print_r( $spec, true ), $logger_context );
return false;
}
if ( isset( $spec->actions ) && is_array( $spec->actions ) ) {
foreach ( $spec->actions as $action ) {
if ( ! self::validate_action( $action, $url ) ) {
$logger->error(
'Spec is invalid because an action is invalid in feed',
$logger_context
);
// phpcs:ignore
$logger->error( print_r( $spec, true ), $logger_context );
return false;
}
}
}
if ( isset( $spec->rules ) && is_array( $spec->rules ) ) {
foreach ( $spec->rules as $rule ) {
if ( ! isset( $rule->type ) ) {
$logger->error(
'Spec is invalid because a rule type is empty in feed',
$logger_context
);
// phpcs:ignore
$logger->error( print_r( $rule, true ), $logger_context );
// phpcs:ignore
$logger->error( print_r( $spec, true ), $logger_context );
return false;
}
$processor = GetRuleProcessor::get_processor( $rule->type );
if ( ! $processor->validate( $rule ) ) {
$logger->error(
'Spec is invalid because a rule is invalid in feed',
$logger_context
);
// phpcs:ignore
$logger->error( print_r( $rule, true ), $logger_context );
// phpcs:ignore
$logger->error( print_r( $spec, true ), $logger_context );
return false;
}
}
}
return true;
}
/**
* Validate the action.
*
* @param object $action The action to validate.
* @param string $url The url of the feed containing the action (for error reporting).
*
* @return bool The result of the validation.
*/
private static function validate_action( $action, $url ) {
$logger = self::get_logger();
$logger_context = array( 'source' => $url );
if ( ! isset( $action->locales ) || ! is_array( $action->locales ) ) {
$logger->error(
'Action is invalid because it has empty or missing locales in feed',
$logger_context
);
// phpcs:ignore
$logger->error( print_r( $action, true ), $logger_context );
return false;
}
if ( null === SpecRunner::get_action_locale( $action->locales ) ) {
$logger->error(
'Action is invalid because the locale could not be retrieved in feed',
$logger_context
);
// phpcs:ignore
$logger->error( print_r( $action, true ), $logger_context );
return false;
}
if ( ! isset( $action->name ) ) {
$logger->error(
'Action is invalid because the name is missing in feed',
$logger_context
);
// phpcs:ignore
$logger->error( print_r( $action, true ), $logger_context );
return false;
}
if ( ! isset( $action->status ) ) {
$logger->error(
'Action is invalid because the status is missing in feed',
$logger_context
);
// phpcs:ignore
$logger->error( print_r( $action, true ), $logger_context );
return false;
}
return true;
}
}

View File

@ -0,0 +1,59 @@
<?php
/**
* Evaluates the spec and returns a status.
*/
namespace Automattic\WooCommerce\Admin\RemoteInboxNotifications;
defined( 'ABSPATH' ) || exit;
use \Automattic\WooCommerce\Admin\Notes\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 $stored_state Stored state.
* @param object $rule_evaluator Evaluates rules into true/false.
*
* @return string The evaluated status.
*/
public static function evaluate( $spec, $current_status, $stored_state, $rule_evaluator ) {
// No rules should leave the note alone.
if ( ! isset( $spec->rules ) ) {
return $current_status;
}
$evaluated_result = $rule_evaluator->evaluate(
$spec->rules,
$stored_state,
array(
'slug' => $spec->slug,
'source' => 'remote-inbox-notifications',
)
);
// Pending notes should be the spec status if the spec passes,
// left alone otherwise.
if ( Note::E_WC_ADMIN_NOTE_PENDING === $current_status ) {
return $evaluated_result
? $spec->status
: 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
? Note::E_WC_ADMIN_NOTE_UNACTIONED
: $current_status;
}
}

View File

@ -0,0 +1,90 @@
<?php
namespace Automattic\WooCommerce\Admin\RemoteInboxNotifications;
/**
* Class EvaluationLogger
*
* @package Automattic\WooCommerce\Admin\RemoteInboxNotifications
*/
class EvaluationLogger {
/**
* Slug of the spec.
*
* @var string
*/
private $slug;
/**
* Results of rules in the given spec.
*
* @var array
*/
private $results = array();
/**
* Logger class to use.
*
* @var WC_Logger_Interface|null
*/
private $logger;
/**
* Logger source.
*
* @var string logger source.
*/
private $source = '';
/**
* EvaluationLogger constructor.
*
* @param string $slug Slug of a spec that is being evaluated.
* @param null $source Logger source.
* @param \WC_Logger_Interface $logger Logger class to use.
*/
public function __construct( $slug, $source = null, \WC_Logger_Interface $logger = null ) {
$this->slug = $slug;
if ( null === $logger ) {
$logger = wc_get_logger();
}
if ( $source ) {
$this->source = $source;
}
$this->logger = $logger;
}
/**
* Add evaluation result of a rule.
*
* @param string $rule_type name of the rule being tested.
* @param boolean $result result of a given rule.
*/
public function add_result( $rule_type, $result ) {
array_push(
$this->results,
array(
'rule' => $rule_type,
'result' => $result ? 'passed' : 'failed',
)
);
}
/**
* Log the results.
*/
public function log() {
if ( false === defined( 'WC_ADMIN_DEBUG_RULE_EVALUATOR' ) || true !== constant( 'WC_ADMIN_DEBUG_RULE_EVALUATOR' ) ) {
return;
}
foreach ( $this->results as $result ) {
$this->logger->debug(
"[{$this->slug}] {$result['rule']}: {$result['result']}",
array( 'source' => $this->source )
);
}
}
}

View File

@ -0,0 +1,36 @@
<?php
/**
* Rule processor that fails.
*/
namespace Automattic\WooCommerce\Admin\RemoteInboxNotifications;
defined( 'ABSPATH' ) || exit;
/**
* Rule processor that fails.
*/
class FailRuleProcessor implements RuleProcessorInterface {
/**
* Fails the rule.
*
* @param object $rule The specific rule being processed by this rule processor.
* @param object $stored_state Stored state.
*
* @return bool Always false.
*/
public function process( $rule, $stored_state ) {
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,65 @@
<?php
/**
* Gets the processor for the specified rule type.
*/
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();
case 'stored_state':
return new StoredStateRuleProcessor();
case 'order_count':
return new OrderCountRuleProcessor();
case 'wcadmin_active_for':
return new WCAdminActiveForRuleProcessor();
case 'product_count':
return new ProductCountRuleProcessor();
case 'onboarding_profile':
return new OnboardingProfileRuleProcessor();
case 'is_ecommerce':
return new IsEcommerceRuleProcessor();
case 'base_location_country':
return new BaseLocationCountryRuleProcessor();
case 'base_location_state':
return new BaseLocationStateRuleProcessor();
case 'note_status':
return new NoteStatusRuleProcessor();
case 'option':
return new OptionRuleProcessor();
case 'wca_updated':
return new WooCommerceAdminUpdatedRuleProcessor();
}
return new FailRuleProcessor();
}
}

View File

@ -0,0 +1,49 @@
<?php
/**
* Rule processor that passes (or fails) when the site is on the eCommerce
* plan.
*
* @package WooCommerce\Admin\Classes
*/
namespace Automattic\WooCommerce\Admin\RemoteInboxNotifications;
defined( 'ABSPATH' ) || exit;
/**
* Rule processor that passes (or fails) when the site is on the eCommerce
* plan.
*/
class IsEcommerceRuleProcessor implements RuleProcessorInterface {
/**
* Passes (or fails) based on whether the site is on the eCommerce plan or
* not.
*
* @param object $rule The rule being processed by this rule processor.
* @param object $stored_state Stored state.
*
* @return bool The result of the operation.
*/
public function process( $rule, $stored_state ) {
if ( ! function_exists( 'wc_calypso_bridge_is_ecommerce_plan' ) ) {
return false === $rule->value;
}
return (bool) wc_calypso_bridge_is_ecommerce_plan() === $rule->value;
}
/**
* Validate the rule.
*
* @param object $rule The rule to validate.
*
* @return bool Pass/fail.
*/
public function validate( $rule ) {
if ( ! isset( $rule->value ) ) {
return false;
}
return true;
}
}

View File

@ -0,0 +1,56 @@
<?php
/**
* Rule processor that negates the rules in the rule's operand.
*/
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.
* @param object $stored_state Stored state.
*
* @return bool The result of the operation.
*/
public function process( $rule, $stored_state ) {
$evaluated_operand = $this->rule_evaluator->evaluate(
$rule->operand,
$stored_state
);
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,61 @@
<?php
/**
* Rule processor that compares against the status of another note. For
* example, this could be used to conditionally create a note only if another
* note has not been actioned.
*/
namespace Automattic\WooCommerce\Admin\RemoteInboxNotifications;
defined( 'ABSPATH' ) || exit;
use Automattic\WooCommerce\Admin\Notes\Notes;
/**
* Rule processor that compares against the status of another note.
*/
class NoteStatusRuleProcessor implements RuleProcessorInterface {
/**
* Compare against the status of another note.
*
* @param object $rule The rule being processed by this rule processor.
* @param object $stored_state Stored state.
*
* @return bool The result of the operation.
*/
public function process( $rule, $stored_state ) {
$status = Notes::get_note_status( $rule->note_name );
if ( ! $status ) {
return false;
}
return ComparisonOperation::compare(
$status,
$rule->status,
$rule->operation
);
}
/**
* Validates the rule.
*
* @param object $rule The rule to validate.
*
* @return bool Pass/fail.
*/
public function validate( $rule ) {
if ( ! isset( $rule->note_name ) ) {
return false;
}
if ( ! isset( $rule->status ) ) {
return false;
}
if ( ! isset( $rule->operation ) ) {
return false;
}
return true;
}
}

View File

@ -0,0 +1,65 @@
<?php
/**
* Rule processor that performs a comparison operation against a value in the
* onboarding profile.
*/
namespace Automattic\WooCommerce\Admin\RemoteInboxNotifications;
defined( 'ABSPATH' ) || exit;
/**
* Rule processor that performs a comparison operation against a value in the
* onboarding profile.
*/
class OnboardingProfileRuleProcessor implements RuleProcessorInterface {
/**
* Performs a comparison operation against a value in the onboarding
* profile.
*
* @param object $rule The rule being processed by this rule processor.
* @param object $stored_state Stored state.
*
* @return bool The result of the operation.
*/
public function process( $rule, $stored_state ) {
$onboarding_profile = get_option( 'woocommerce_onboarding_profile' );
if ( empty( $onboarding_profile ) ) {
return false;
}
if ( ! isset( $onboarding_profile[ $rule->index ] ) ) {
return false;
}
return ComparisonOperation::compare(
$onboarding_profile[ $rule->index ],
$rule->value,
$rule->operation
);
}
/**
* Validates the rule.
*
* @param object $rule The rule to validate.
*
* @return bool Pass/fail.
*/
public function validate( $rule ) {
if ( ! isset( $rule->index ) ) {
return false;
}
if ( ! isset( $rule->value ) ) {
return false;
}
if ( ! isset( $rule->operation ) ) {
return false;
}
return true;
}
}

View File

@ -0,0 +1,86 @@
<?php
/**
* Rule processor that performs a comparison operation against an option value.
*/
namespace Automattic\WooCommerce\Admin\RemoteInboxNotifications;
defined( 'ABSPATH' ) || exit;
/**
* Rule processor that performs a comparison operation against an option value.
*/
class OptionRuleProcessor implements RuleProcessorInterface {
/**
* Performs a comparison operation against the option value.
*
* @param object $rule The specific rule being processed by this rule processor.
* @param object $stored_state Stored state.
*
* @return bool The result of the operation.
*/
public function process( $rule, $stored_state ) {
$is_contains = $rule->operation && strpos( $rule->operation, 'contains' ) !== false;
$default_value = $is_contains ? array() : false;
$default = isset( $rule->default ) ? $rule->default : $default_value;
$option_value = get_option( $rule->option_name, $default );
if ( $is_contains && ! is_array( $option_value ) ) {
$logger = wc_get_logger();
$logger->warning(
sprintf(
'ComparisonOperation "%s" option value "%s" is not an array, defaulting to empty array.',
$rule->operation,
$rule->option_name
),
array(
'option_value' => $option_value,
'rule' => $rule,
)
);
$option_value = array();
}
if ( isset( $rule->transformers ) && is_array( $rule->transformers ) ) {
$option_value = TransformerService::apply( $option_value, $rule->transformers, $rule->default );
}
return ComparisonOperation::compare(
$option_value,
$rule->value,
$rule->operation
);
}
/**
* Validates the rule.
*
* @param object $rule The rule to validate.
*
* @return bool Pass/fail.
*/
public function validate( $rule ) {
if ( ! isset( $rule->option_name ) ) {
return false;
}
if ( ! isset( $rule->value ) ) {
return false;
}
if ( ! isset( $rule->operation ) ) {
return false;
}
if ( isset( $rule->transformers ) && is_array( $rule->transformers ) ) {
foreach ( $rule->transformers as $transform_args ) {
$transformer = TransformerService::create_transformer( $transform_args->use );
if ( ! $transformer->validate( $transform_args->arguments ) ) {
return false;
}
}
}
return true;
}
}

View File

@ -0,0 +1,64 @@
<?php
/**
* Rule processor that performs an OR operation on the rule's left and right
* operands.
*/
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.
* @param object $stored_state Stored state.
*
* @return bool The result of the operation.
*/
public function process( $rule, $stored_state ) {
foreach ( $rule->operands as $operand ) {
$evaluated_operand = $this->rule_evaluator->evaluate(
$operand,
$stored_state
);
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,61 @@
<?php
/**
* Rule processor for publishing based on the number of orders.
*/
namespace Automattic\WooCommerce\Admin\RemoteInboxNotifications;
defined( 'ABSPATH' ) || exit;
/**
* Rule processor for publishing based on the number of orders.
*/
class OrderCountRuleProcessor implements RuleProcessorInterface {
/**
* Constructor.
*
* @param object $orders_provider The orders provider.
*/
public function __construct( $orders_provider = null ) {
$this->orders_provider = null === $orders_provider
? new OrdersProvider()
: $orders_provider;
}
/**
* Process the rule.
*
* @param object $rule The rule to process.
* @param object $stored_state Stored state.
*
* @return bool Whether the rule passes or not.
*/
public function process( $rule, $stored_state ) {
$count = $this->orders_provider->get_order_count();
return ComparisonOperation::compare(
$count,
$rule->value,
$rule->operation
);
}
/**
* Validates the rule.
*
* @param object $rule The rule to validate.
*
* @return bool Pass/fail.
*/
public function validate( $rule ) {
if ( ! isset( $rule->value ) ) {
return false;
}
if ( ! isset( $rule->operation ) ) {
return false;
}
return true;
}
}

View File

@ -0,0 +1,36 @@
<?php
/**
* Provider for order-related queries and operations.
*/
namespace Automattic\WooCommerce\Admin\RemoteInboxNotifications;
defined( 'ABSPATH' ) || exit;
/**
* Provider for order-related queries and operations.
*/
class OrdersProvider {
/**
* Allowed order statuses for calculating milestones.
*
* @var array
*/
protected $allowed_statuses = array(
'pending',
'processing',
'completed',
);
/**
* Returns the number of orders.
*
* @return integer The number of orders.
*/
public function get_order_count() {
$status_counts = array_map( 'wc_orders_count', $this->allowed_statuses );
$orders_count = array_sum( $status_counts );
return $orders_count;
}
}

View File

@ -0,0 +1,37 @@
<?php
/**
* Rule processor that passes. This is required because an empty set of rules
* (or predicate) evaluates to false.
*/
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.
* @param object $stored_state Stored state.
*
* @return bool Always true.
*/
public function process( $rule, $stored_state ) {
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,77 @@
<?php
/**
* Rule processor for sending when the provided plugin is activated and
* matches the specified version.
*/
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.
* @param object $stored_state Stored state.
*
* @return bool Whether the rule passes or not.
*/
public function process( $rule, $stored_state ) {
$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,65 @@
<?php
/**
* Rule processor for sending when the provided plugins are activated.
*/
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.
* @param object $stored_state Stored state.
*
* @return bool Whether the rule passes or not.
*/
public function process( $rule, $stored_state ) {
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,70 @@
<?php
/**
* Rule processor that performs a comparison operation against the number of
* products.
*/
namespace Automattic\WooCommerce\Admin\RemoteInboxNotifications;
defined( 'ABSPATH' ) || exit;
/**
* Rule processor that performs a comparison operation against the number of
* products.
*/
class ProductCountRuleProcessor implements RuleProcessorInterface {
/**
* Constructor.
*
* @param object $product_query The product query.
*/
public function __construct( $product_query = null ) {
$this->product_query = null === $product_query
? new \WC_Product_Query(
array(
'limit' => 1,
'paginate' => true,
'return' => 'ids',
'status' => array( 'publish' ),
)
)
: $product_query;
}
/**
* Performs a comparison operation against the number of products.
*
* @param object $rule The specific rule being processed by this rule processor.
* @param object $stored_state Stored state.
*
* @return bool The result of the operation.
*/
public function process( $rule, $stored_state ) {
$products = $this->product_query->get_products();
return ComparisonOperation::compare(
$products->total,
$rule->value,
$rule->operation
);
}
/**
* Validates the rule.
*
* @param object $rule The rule to validate.
*
* @return bool Pass/fail.
*/
public function validate( $rule ) {
if ( ! isset( $rule->value ) ) {
return false;
}
if ( ! isset( $rule->operation ) ) {
return false;
}
return true;
}
}

View File

@ -0,0 +1,53 @@
<?php
/**
* Rule processor for sending after a specified date/time.
*/
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.
* @param object $stored_state Stored state.
*
* @return bool Whether the rule passes or not.
*/
public function process( $rule, $stored_state ) {
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,53 @@
<?php
/**
* Rule processor for sending before a specified date/time.
*/
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.
* @param object $stored_state Stored state.
*
* @return bool Whether the rule passes or not.
*/
public function process( $rule, $stored_state ) {
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,172 @@
<?php
/**
* Handles running specs
*/
namespace Automattic\WooCommerce\Admin\RemoteInboxNotifications;
defined( 'ABSPATH' ) || exit;
use \Automattic\WooCommerce\Admin\PluginsProvider\PluginsProvider;
use \Automattic\WooCommerce\Admin\Features\Onboarding;
/**
* Remote Inbox Notifications 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';
const STORED_STATE_OPTION_NAME = 'wc_remote_inbox_notifications_stored_state';
const WCA_UPDATED_OPTION_NAME = 'wc_remote_inbox_notifications_wca_updated';
/**
* Initialize the engine.
*/
public static function init() {
// Init things that need to happen before admin_init.
add_action( 'init', array( __CLASS__, 'on_init' ), 0, 0 );
// Continue init via admin_init.
add_action( 'admin_init', array( __CLASS__, 'on_admin_init' ) );
// Trigger when the profile data option is updated (during onboarding).
add_action(
'update_option_' . Onboarding::PROFILE_DATA_OPTION,
array( __CLASS__, 'update_profile_option' ),
10,
2
);
// Hook into WCA updated. This is hooked up here rather than in
// on_admin_init because that runs too late to hook into the action.
add_action( 'woocommerce_admin_updated', array( __CLASS__, 'run_on_woocommerce_admin_updated' ) );
}
/**
* This is triggered when the profile option is updated and if the
* profiler is being completed, triggers a run of the engine.
*
* @param mixed $old_value Old value.
* @param mixed $new_value New value.
*/
public static function update_profile_option( $old_value, $new_value ) {
// Return early if we're not completing the profiler.
if (
( isset( $old_value['completed'] ) && $old_value['completed'] ) ||
! isset( $new_value['completed'] ) ||
! $new_value['completed']
) {
return;
}
self::run();
}
/**
* Init is continued via admin_init so that WC is loaded when the product
* query is used, otherwise the query generates a "0 = 1" in the WHERE
* condition and thus doesn't return any results.
*/
public static function on_admin_init() {
add_action( 'activated_plugin', array( __CLASS__, 'run' ) );
add_action( 'deactivated_plugin', array( __CLASS__, 'run_on_deactivated_plugin' ), 10, 1 );
StoredStateSetupForProducts::admin_init();
// Pre-fetch stored state so it has the correct initial values.
self::get_stored_state();
}
/**
* An init hook is used here so that StoredStateSetupForProducts can set
* up a hook that gets triggered by action-scheduler - this is needed
* because the admin_init hook doesn't get triggered by WP Cron.
*/
public static function on_init() {
StoredStateSetupForProducts::init();
}
/**
* Go through the specs and run them.
*/
public static function run() {
$specs = get_option( self::SPECS_OPTION_NAME );
if ( false === $specs || 0 === count( $specs ) ) {
// We are running too early, need to poll data sources first.
if ( DataSourcePoller::read_specs_from_data_sources() ) {
self::run();
}
return;
}
$stored_state = self::get_stored_state();
foreach ( $specs as $spec ) {
SpecRunner::run_spec( $spec, $stored_state );
}
}
/**
* Set an option indicating that WooCommerce Admin has just been updated,
* run the specs, then clear that option. This lets the
* WooCommerceAdminUpdatedRuleProcessor trigger on WCA update.
*/
public static function run_on_woocommerce_admin_updated() {
update_option( self::WCA_UPDATED_OPTION_NAME, true, false );
self::run();
update_option( self::WCA_UPDATED_OPTION_NAME, false, false );
}
/**
* Gets the stored state option, and does the initial set up if it doesn't
* already exist.
*
* @return object The stored state option.
*/
public static function get_stored_state() {
$stored_state = get_option( self::STORED_STATE_OPTION_NAME );
if ( false === $stored_state ) {
$stored_state = new \stdClass();
$stored_state = StoredStateSetupForProducts::init_stored_state(
$stored_state
);
add_option(
self::STORED_STATE_OPTION_NAME,
$stored_state,
'',
false
);
}
return $stored_state;
}
/**
* 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();
}
/**
* Update the stored state option.
*
* @param object $stored_state The stored state.
*/
public static function update_stored_state( $stored_state ) {
update_option( self::STORED_STATE_OPTION_NAME, $stored_state, false );
}
}

View File

@ -0,0 +1,75 @@
<?php
/**
* Evaluate the given rules as an AND operation - return false early if a
* rule evaluates to false.
*/
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|object $rules The rule or rules being processed.
* @param object|null $stored_state Stored state.
* @param array $logger_args Arguments for the event logger. `slug` is required.
*
* @throws \InvalidArgumentException Thrown when $logger_args is missing slug.
*
* @return bool The result of the operation.
*/
public function evaluate( $rules, $stored_state = null, $logger_args = array() ) {
if ( ! is_array( $rules ) ) {
$rules = array( $rules );
}
if ( 0 === count( $rules ) ) {
return false;
}
$evaluation_logger = null;
if ( count( $logger_args ) ) {
if ( ! array_key_exists( 'slug', $logger_args ) ) {
throw new \InvalidArgumentException( 'Missing required field: slug in $logger_args.' );
}
array_key_exists( 'source', $logger_args ) ? $source = $logger_args['source'] : $source = null;
$evaluation_logger = new EvaluationLogger( $logger_args['slug'], $source );
}
foreach ( $rules as $rule ) {
$processor = $this->get_rule_processor->get_processor( $rule->type );
$processor_result = $processor->process( $rule, $stored_state );
$evaluation_logger && $evaluation_logger->add_result( $rule->type, $processor_result );
if ( ! $processor_result ) {
$evaluation_logger && $evaluation_logger->log();
return false;
}
}
$evaluation_logger && $evaluation_logger->log();
return true;
}
}

View File

@ -0,0 +1,32 @@
<?php
/**
* Interface for a rule processor.
*/
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.
* @param object $stored_state Stored state.
*
* @return bool The result of the processing.
*/
public function process( $rule, $stored_state );
/**
* 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.
*/
namespace Automattic\WooCommerce\Admin\RemoteInboxNotifications;
defined( 'ABSPATH' ) || exit;
use \Automattic\WooCommerce\Admin\Notes\Note;
use \Automattic\WooCommerce\Admin\Notes\Notes;
/**
* Runs a single spec.
*/
class SpecRunner {
/**
* Run the spec.
*
* @param object $spec The spec to run.
* @param object $stored_state Stored state.
*/
public static function run_spec( $spec, $stored_state ) {
$data_store = Notes::load_data_store();
// Create or update the note.
$existing_note_ids = $data_store->get_notes_with_name( $spec->slug );
if ( 0 === count( $existing_note_ids ) ) {
$note = new Note();
$note->set_status( Note::E_WC_ADMIN_NOTE_PENDING );
} else {
$note = Notes::get_note( $existing_note_ids[0] );
if ( false === $note ) {
return;
}
}
// Evaluate the spec and get the new note status.
$previous_status = $note->get_status();
$status = EvaluateAndGetStatus::evaluate(
$spec,
$previous_status,
$stored_state,
new RuleEvaluator()
);
// 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,58 @@
<?php
/**
* Rule processor that performs a comparison operation against a value in the
* stored state object.
*/
namespace Automattic\WooCommerce\Admin\RemoteInboxNotifications;
defined( 'ABSPATH' ) || exit;
/**
* Rule processor that performs a comparison operation against a value in the
* stored state object.
*/
class StoredStateRuleProcessor implements RuleProcessorInterface {
/**
* Performs a comparison operation against a value in the stored state object.
*
* @param object $rule The rule being processed by this rule processor.
* @param object $stored_state Stored state.
*
* @return bool The result of the operation.
*/
public function process( $rule, $stored_state ) {
if ( ! isset( $stored_state->{$rule->index} ) ) {
return false;
}
return ComparisonOperation::compare(
$stored_state->{$rule->index},
$rule->value,
$rule->operation
);
}
/**
* Validates the rule.
*
* @param object $rule The rule to validate.
*
* @return bool Pass/fail.
*/
public function validate( $rule ) {
if ( ! isset( $rule->index ) ) {
return false;
}
if ( ! isset( $rule->value ) ) {
return false;
}
if ( ! isset( $rule->operation ) ) {
return false;
}
return true;
}
}

View File

@ -0,0 +1,130 @@
<?php
/**
* Handles stored state setup for products.
*/
namespace Automattic\WooCommerce\Admin\RemoteInboxNotifications;
defined( 'ABSPATH' ) || exit;
use \Automattic\WooCommerce\Admin\PluginsProvider\PluginsProvider;
use \Automattic\WooCommerce\Admin\RemoteInboxNotifications\SpecRunner;
/**
* Handles stored state setup for products.
*/
class StoredStateSetupForProducts {
const ASYNC_RUN_REMOTE_NOTIFICATIONS_ACTION_NAME =
'woocommerce_admin/stored_state_setup_for_products/async/run_remote_notifications';
/**
* Initialize the class via the admin_init hook.
*/
public static function admin_init() {
add_action( 'product_page_product_importer', array( __CLASS__, 'run_on_product_importer' ) );
add_action( 'transition_post_status', array( __CLASS__, 'run_on_transition_post_status' ), 10, 3 );
}
/**
* Initialize the class via the init hook.
*/
public static function init() {
add_action( self::ASYNC_RUN_REMOTE_NOTIFICATIONS_ACTION_NAME, array( __CLASS__, 'run_remote_notifications' ) );
}
/**
* Run the remote notifications engine. This is triggered by
* action-scheduler after a product is added. It also cleans up from
* setting the product count increment.
*/
public static function run_remote_notifications() {
RemoteInboxNotificationsEngine::run();
}
/**
* Set initial stored state values.
*
* @param object $stored_state The stored state.
*
* @return object The stored state.
*/
public static function init_stored_state( $stored_state ) {
$stored_state->there_were_no_products = ! self::are_there_products();
$stored_state->there_are_now_products = ! $stored_state->there_were_no_products;
return $stored_state;
}
/**
* Are there products query.
*
* @return bool
*/
private static function are_there_products() {
$query = new \WC_Product_Query(
array(
'limit' => 1,
'paginate' => true,
'return' => 'ids',
'status' => array( 'publish' ),
)
);
$products = $query->get_products();
$count = $products->total;
return $count > 0;
}
/**
* Runs on product importer steps.
*/
public static function run_on_product_importer() {
// We're only interested in when the importer completes.
// phpcs:disable WordPress.Security.NonceVerification.Recommended
if ( ! isset( $_REQUEST['step'] ) ) {
return;
}
if ( 'done' !== $_REQUEST['step'] ) {
return;
}
// phpcs:enable
$stored_state = RemoteInboxNotificationsEngine::get_stored_state();
$stored_state->there_are_now_products = true;
RemoteInboxNotificationsEngine::update_stored_state( $stored_state );
self::enqueue_async_run_remote_notifications();
}
/**
* Runs when a post status transitions, but we're only interested if it is
* a product being published.
*
* @param string $new_status The new status.
* @param string $old_status The old status.
* @param Post $post The post.
*/
public static function run_on_transition_post_status( $new_status, $old_status, $post ) {
if (
'product' !== $post->post_type ||
'publish' !== $new_status
) {
return;
}
$stored_state = RemoteInboxNotificationsEngine::get_stored_state();
$stored_state->there_are_now_products = true;
RemoteInboxNotificationsEngine::update_stored_state( $stored_state );
self::enqueue_async_run_remote_notifications();
}
/**
* Enqueues an async action (using action-scheduler) to run remote
* notifications.
*/
private static function enqueue_async_run_remote_notifications() {
as_enqueue_async_action( self::ASYNC_RUN_REMOTE_NOTIFICATIONS_ACTION_NAME );
}
}

View File

@ -0,0 +1,34 @@
<?php
namespace Automattic\WooCommerce\Admin\RemoteInboxNotifications;
use stdClass;
/**
* An interface to define a transformer.
*
* Interface TransformerInterface
*
* @package Automattic\WooCommerce\Admin\RemoteInboxNotifications
*/
interface TransformerInterface {
/**
* Transform given value to a different value.
*
* @param mixed $value a value to transform.
* @param stdClass|null $arguments arguments.
* @param string|null $default default value.
*
* @return mixed|null
*/
public function transform( $value, stdClass $arguments = null, $default = null);
/**
* Validate Transformer arguments.
*
* @param stdClass|null $arguments arguments to validate.
*
* @return mixed
*/
public function validate( stdClass $arguments = null );
}

View File

@ -0,0 +1,70 @@
<?php
namespace Automattic\WooCommerce\Admin\RemoteInboxNotifications;
use InvalidArgumentException;
use stdClass;
/**
* A simple service class for the Transformer classes.
*
* Class TransformerService
*
* @package Automattic\WooCommerce\Admin\RemoteInboxNotifications
*/
class TransformerService {
/**
* Create a transformer object by name.
*
* @param string $name name of the transformer.
*
* @return TransformerInterface|null
*/
public static function create_transformer( $name ) {
$camel_cased = str_replace( ' ', '', ucwords( str_replace( '_', ' ', $name ) ) );
$classname = __NAMESPACE__ . '\\Transformers\\' . $camel_cased;
if ( ! class_exists( $classname ) ) {
return null;
}
return new $classname();
}
/**
* Apply transformers to the given value.
*
* @param mixed $target_value a value to transform.
* @param array $transformer_configs transform configuration.
* @param string $default default value.
*
* @throws InvalidArgumentException Throws when one of the requried arguments is missing.
* @return mixed|null
*/
public static function apply( $target_value, array $transformer_configs, $default ) {
foreach ( $transformer_configs as $transformer_config ) {
if ( ! isset( $transformer_config->use ) ) {
throw new InvalidArgumentException( 'Missing required config value: use' );
}
if ( ! isset( $transformer_config->arguments ) ) {
$transformer_config->arguments = null;
}
$transformer = self::create_transformer( $transformer_config->use );
if ( null === $transformer ) {
throw new InvalidArgumentException( "Unable to find a transformer by name: {$transformer_config->use}" );
}
$transformed_value = $transformer->transform( $target_value, $transformer_config->arguments, $default );
// if the transformer returns null, then return the previously transformed value.
if ( null === $transformed_value ) {
return $target_value;
}
$target_value = $transformed_value;
}
return $target_value;
}
}

View File

@ -0,0 +1,44 @@
<?php
namespace Automattic\WooCommerce\Admin\RemoteInboxNotifications\Transformers;
use Automattic\WooCommerce\Admin\RemoteInboxNotifications\TransformerInterface;
use InvalidArgumentException;
use stdClass;
/**
* Search array value by one of its key.
*
* @package Automattic\WooCommerce\Admin\RemoteInboxNotifications\Transformers
*/
class ArrayColumn implements TransformerInterface {
/**
* Search array value by one of its key.
*
* @param mixed $value a value to transform.
* @param stdClass|null $arguments required arguments 'key'.
* @param string|null $default default value.
*
* @throws InvalidArgumentException Throws when the required argument 'key' is missing.
*
* @return mixed
*/
public function transform( $value, stdClass $arguments = null, $default = null ) {
return array_column( $value, $arguments->key );
}
/**
* Validate Transformer arguments.
*
* @param stdClass|null $arguments arguments to validate.
*
* @return mixed
*/
public function validate( stdClass $arguments = null ) {
if ( ! isset( $arguments->key ) ) {
return false;
}
return true;
}
}

View File

@ -0,0 +1,45 @@
<?php
namespace Automattic\WooCommerce\Admin\RemoteInboxNotifications\Transformers;
use Automattic\WooCommerce\Admin\RemoteInboxNotifications\TransformerInterface;
use stdClass;
/**
* Flatten nested array.
*
* @package Automattic\WooCommerce\Admin\RemoteInboxNotifications\Transformers
*/
class ArrayFlatten implements TransformerInterface {
/**
* Search a given value in the array.
*
* @param mixed $value a value to transform.
* @param stdClass|null $arguments arguments.
* @param string|null $default default value.
*
* @return mixed|null
*/
public function transform( $value, stdClass $arguments = null, $default = null ) {
$return = array();
array_walk_recursive(
$value,
function( $item ) use ( &$return ) {
$return[] = $item;
}
);
return $return;
}
/**
* Validate Transformer arguments.
*
* @param stdClass|null $arguments arguments to validate.
*
* @return mixed
*/
public function validate( stdClass $arguments = null ) {
return true;
}
}

View File

@ -0,0 +1,37 @@
<?php
namespace Automattic\WooCommerce\Admin\RemoteInboxNotifications\Transformers;
use Automattic\WooCommerce\Admin\RemoteInboxNotifications\TransformerInterface;
use stdClass;
/**
* Search array value by one of its key.
*
* @package Automattic\WooCommerce\Admin\RemoteInboxNotifications\Transformers
*/
class ArrayKeys implements TransformerInterface {
/**
* Search array value by one of its key.
*
* @param mixed $value a value to transform.
* @param stdClass|null $arguments arguments.
* @param string|null $default default value.
*
* @return mixed
*/
public function transform( $value, stdClass $arguments = null, $default = null ) {
return array_keys( $value );
}
/**
* Validate Transformer arguments.
*
* @param stdClass|null $arguments arguments to validate.
*
* @return mixed
*/
public function validate( stdClass $arguments = null ) {
return true;
}
}

View File

@ -0,0 +1,49 @@
<?php
namespace Automattic\WooCommerce\Admin\RemoteInboxNotifications\Transformers;
use Automattic\WooCommerce\Admin\RemoteInboxNotifications\TransformerInterface;
use InvalidArgumentException;
use stdClass;
/**
* Searches a given a given value in the array.
*
* @package Automattic\WooCommerce\Admin\RemoteInboxNotifications\Transformers
*/
class ArraySearch implements TransformerInterface {
/**
* Search a given value in the array.
*
* @param mixed $value a value to transform.
* @param stdClass|null $arguments required argument 'value'.
* @param string|null $default default value.
*
* @throws InvalidArgumentException Throws when the required 'value' is missing.
*
* @return mixed|null
*/
public function transform( $value, stdClass $arguments = null, $default = null ) {
$key = array_search( $arguments->value, $value, true );
if ( false !== $key ) {
return $value[ $key ];
}
return null;
}
/**
* Validate Transformer arguments.
*
* @param stdClass|null $arguments arguments to validate.
*
* @return mixed
*/
public function validate( stdClass $arguments = null ) {
if ( ! isset( $arguments->value ) ) {
return false;
}
return true;
}
}

View File

@ -0,0 +1,37 @@
<?php
namespace Automattic\WooCommerce\Admin\RemoteInboxNotifications\Transformers;
use Automattic\WooCommerce\Admin\RemoteInboxNotifications\TransformerInterface;
use stdClass;
/**
* Search array value by one of its key.
*
* @package Automattic\WooCommerce\Admin\RemoteInboxNotifications\Transformers
*/
class ArrayValues implements TransformerInterface {
/**
* Search array value by one of its key.
*
* @param mixed $value a value to transform.
* @param stdClass|null $arguments arguments.
* @param string|null $default default value.
*
* @return mixed
*/
public function transform( $value, stdClass $arguments = null, $default = null ) {
return array_values( $value );
}
/**
* Validate Transformer arguments.
*
* @param stdClass|null $arguments arguments to validate.
*
* @return mixed
*/
public function validate( stdClass $arguments = null ) {
return true;
}
}

View File

@ -0,0 +1,37 @@
<?php
namespace Automattic\WooCommerce\Admin\RemoteInboxNotifications\Transformers;
use Automattic\WooCommerce\Admin\RemoteInboxNotifications\TransformerInterface;
use stdClass;
/**
* Count elements in Array.
*
* @package Automattic\WooCommerce\Admin\RemoteInboxNotifications\Transformers
*/
class Count implements TransformerInterface {
/**
* Count elements in Array.
*
* @param array $value an array to count.
* @param stdClass|null $arguments arguments.
* @param string|null $default default value.
*
* @return number
*/
public function transform( $value, stdClass $arguments = null, $default = null ) {
return count( $value );
}
/**
* Validate Transformer arguments.
*
* @param stdClass|null $arguments arguments to validate.
*
* @return mixed
*/
public function validate( stdClass $arguments = null ) {
return true;
}
}

View File

@ -0,0 +1,75 @@
<?php
namespace Automattic\WooCommerce\Admin\RemoteInboxNotifications\Transformers;
use Automattic\WooCommerce\Admin\RemoteInboxNotifications\TransformerInterface;
use InvalidArgumentException;
use stdClass;
/**
* Find an array value by dot notation.
*
* @package Automattic\WooCommerce\Admin\RemoteInboxNotifications\Transformers
*/
class DotNotation implements TransformerInterface {
/**
* Find given path from the given value.
*
* @param mixed $value a value to transform.
* @param stdClass|null $arguments required argument 'path'.
* @param string|null $default default value.
*
* @throws InvalidArgumentException Throws when the required 'path' is missing.
*
* @return mixed
*/
public function transform( $value, stdclass $arguments = null, $default = null ) {
if ( is_object( $value ) ) {
// if the value is an object, convert it to an array.
$value = json_decode( wp_json_encode( $value ), true );
}
return $this->get( $value, $arguments->path, $default );
}
/**
* Find the given $path in $array by dot notation.
*
* @param array $array an array to search in.
* @param string $path a path in the given array.
* @param null $default default value to return if $path was not found.
*
* @return mixed|null
*/
public function get( $array, $path, $default = null ) {
if ( isset( $array[ $path ] ) ) {
return $array[ $path ];
}
foreach ( explode( '.', $path ) as $segment ) {
if ( ! is_array( $array ) || ! array_key_exists( $segment, $array ) ) {
return $default;
}
$array = $array[ $segment ];
}
return $array;
}
/**
* Validate Transformer arguments.
*
* @param stdClass|null $arguments arguments to validate.
*
* @return mixed
*/
public function validate( stdClass $arguments = null ) {
if ( ! isset( $arguments->path ) ) {
return false;
}
return true;
}
}

View File

@ -0,0 +1,24 @@
<?php
/**
* WCAdmin active for provider.
*/
namespace Automattic\WooCommerce\Admin\RemoteInboxNotifications;
use Automattic\WooCommerce\Admin\WCAdminHelper;
defined( 'ABSPATH' ) || exit;
/**
* WCAdminActiveForProvider class
*/
class WCAdminActiveForProvider {
/**
* Get the number of seconds that the store has been active.
*
* @return number Number of seconds.
*/
public function get_wcadmin_active_for_in_seconds() {
return WCAdminHelper::get_wcadmin_active_for_in_seconds();
}
}

View File

@ -0,0 +1,65 @@
<?php
/**
* Rule processor for publishing if wc-admin has been active for at least the
* given number of seconds.
*/
namespace Automattic\WooCommerce\Admin\RemoteInboxNotifications;
defined( 'ABSPATH' ) || exit;
/**
* Rule processor for publishing if wc-admin has been active for at least the
* given number of seconds.
*/
class WCAdminActiveForRuleProcessor implements RuleProcessorInterface {
/**
* Constructor
*
* @param object $wcadmin_active_for_provider Provides the amount of time wcadmin has been active for.
*/
public function __construct( $wcadmin_active_for_provider = null ) {
$this->wcadmin_active_for_provider = null === $wcadmin_active_for_provider
? new WCAdminActiveForProvider()
: $wcadmin_active_for_provider;
}
/**
* Performs a comparison operation against the amount of time wc-admin has
* been active for in days.
*
* @param object $rule The rule being processed.
* @param object $stored_state Stored state.
*
* @return bool The result of the operation.
*/
public function process( $rule, $stored_state ) {
$active_for_seconds = $this->wcadmin_active_for_provider->get_wcadmin_active_for_in_seconds();
$rule_seconds = $rule->days * DAY_IN_SECONDS;
return ComparisonOperation::compare(
$active_for_seconds,
$rule_seconds,
$rule->operation
);
}
/**
* Validates the rule.
*
* @param object $rule The rule to validate.
*
* @return bool Pass/fail.
*/
public function validate( $rule ) {
if ( ! isset( $rule->days ) ) {
return false;
}
if ( ! isset( $rule->operation ) ) {
return false;
}
return true;
}
}

View File

@ -0,0 +1,36 @@
<?php
/**
* Rule processor for sending when WooCommerce Admin has been updated.
*/
namespace Automattic\WooCommerce\Admin\RemoteInboxNotifications;
defined( 'ABSPATH' ) || exit;
/**
* Rule processor for sending when WooCommerce Admin has been updated.
*/
class WooCommerceAdminUpdatedRuleProcessor implements RuleProcessorInterface {
/**
* Process the rule.
*
* @param object $rule The specific rule being processed by this rule processor.
* @param object $stored_state Stored state.
*
* @return bool Whether the rule passes or not.
*/
public function process( $rule, $stored_state ) {
return get_option( RemoteInboxNotificationsEngine::WCA_UPDATED_OPTION_NAME, false );
}
/**
* Validates the rule.
*
* @param object $rule The rule to validate.
*
* @return bool Pass/fail.
*/
public function validate( $rule ) {
return true;
}
}