first commit

This commit is contained in:
2026-03-05 13:07:40 +01:00
commit 64ba0721ee
25709 changed files with 4691006 additions and 0 deletions

View File

@@ -0,0 +1,344 @@
<?php
/**
* Abstract class for actions.
*
* @since 4.4.1
* @version 1.0
* @package Email Subscribers
*/
/**
* Abstract Class ES_Workflow_Action
*
* All workflow actions extend this class.
*
* @since 4.4.1
*/
abstract class ES_Workflow_Action {
/**
* The action's unique name/slug.
*
* @since 4.4.1
* @var string
*/
public $name;
/**
* The action's title.
*
* @since 4.4.1
* @var string
*/
public $title;
/**
* The action's description.
*
* @since 4.4.1
* @var string
*/
public $description;
/**
* The action's group.
*
* @since 4.4.1
* @var string
*/
public $group;
/**
* The action's fields objects.
*
* @since 4.4.1
* @var Field[]
*/
public $fields;
/**
* Array containing the action's option values.
*
* @since 4.4.1
* @var array
*/
public $options;
/**
* The workflow the action belongs to.
*
* This prop may not be set depending on the context.
*
* @since 4.4.1
* @var Workflow
*/
public $workflow;
/**
* The workflow trigger for which action is being added.
*
* This prop may not be set depending on the context.
*
* @since 4.4.3
*
* @var ES_Workflow_Trigger
*/
public $trigger;
/**
* Knows if admin details have been loaded.
*
* @since 4.4.1
* @var bool
*/
protected $has_loaded_admin_details = false;
/**
* Called when an action should be run.
*
* @since 4.4.1
* @throws Exception When an error occurs.
*/
abstract public function run();
/**
* Action constructor.
*
* @since 4.4.1
*/
public function __construct() {
$this->init();
}
/**
* This method no longer has an explicit purpose and is deprecated.
*
* @since 4.4.1
*
* @deprecated
*/
public function init() {}
/**
* Method to load the action's fields.
*
* @since 4.4.1
*
* @modified 4.5.3 Removed dependency to modify child action file to load extra fields.
*/
public function load_fields() {}
/**
* Method to set the action's admin props.
*
* Admin props include: title, group and description.
*
* @since 4.4.1
*/
protected function load_admin_details() {}
/**
* Loads the action's admin props.
*
* @since 4.4.1
*/
protected function maybe_load_admin_details() {
if ( ! $this->has_loaded_admin_details ) {
$this->load_admin_details();
$this->has_loaded_admin_details = true;
}
}
/**
* Get the action's title.
*
* @since 4.4.1
* @param bool $prepend_group Action group.
* @return string $title Action group title
*/
public function get_title( $prepend_group = false ) {
$this->maybe_load_admin_details();
$group = $this->get_group();
$title = $this->title ? $this->title : '';
if ( $prepend_group && __( 'Other', 'email-subscribers' ) !== $group ) {
return $group . ' - ' . $title;
}
return $title;
}
/**
* Get the action's group.
*
* @since 4.4.1
* @return string
*/
public function get_group() {
$this->maybe_load_admin_details();
return $this->group ? $this->group : __( 'Other', 'email-subscribers' );
}
/**
* Get the action's description.
*
* @since 4.4.1
* @return string
*/
public function get_description() {
$this->maybe_load_admin_details();
return $this->description ? $this->description : '';
}
/**
* Get the action's name.
*
* @since 4.4.1
* @return string
*/
public function get_name() {
return $this->name ? $this->name : '';
}
/**
* Set the action's name.
*
* @since 4.4.1
*
* @param string $name Action name.
*/
public function set_name( $name ) {
$this->name = $name;
}
/**
* Get the action's description HTML.
*
* @since 4.4.1
* @return string
*/
public function get_description_html() {
if ( ! $this->get_description() ) {
return '';
}
return '<p class="ig-es-field-description">' . $this->get_description() . '</p>';
}
/**
* Adds a field to the action.
*
* Should only be called in the load_fields() method.
*
* @since 4.4.1
* @param Field $field Action field object.
*/
public function add_field( $field ) {
$field->set_name_base( 'ig_es_workflow_data[actions]' );
$this->fields[ $field->get_name() ] = $field;
}
/**
* Gets specific field belonging to the action.
*
* @since 4.4.1
* @param string $name field name.
*
* @return ES_Field|false
*/
public function get_field( $name ) {
$this->get_fields();
if ( ! isset( $this->fields[ $name ] ) ) {
return false;
}
return $this->fields[ $name ];
}
/**
* Gets the action's fields.
*
* @return ES_Field[]
*
* @since 4.4.1
*
* @modified 4.4.3 Removed isset condition to allow latest list of fields every time when called.
*
* @modified 4.5.3 Added new action action_name_load_extra_fileds to allow loading of extra fields for action.
*/
public function get_fields() {
$this->fields = array();
$this->load_fields();
// Load extra fields for workflow action.
do_action( $this->name . '_load_extra_fields', $this );
return $this->fields;
}
/**
* Set the action's options.
*
* @since 4.4.1
* @param array $options Options array.
*/
public function set_options( $options ) {
$this->options = $options;
}
/**
* Returns an option for use when running the action.
*
* Option value will already have been sanitized by it's field ::sanitize_value() method.
*
* @since 4.4.1
* @param string $field_name Field name.
*
* @return mixed Will vary depending on the field type specified in the action's fields.
*/
public function get_option( $field_name, $process_variables = false, $allow_html = false ) {
$value = $this->get_option_raw( $field_name );
// Process the option value only if it's a string
// The value will almost always be a string but it could be a bool if the field is checkbox
if ( $value && is_string( $value ) ) {
if ( $process_variables ) {
$value = $this->workflow->variable_processor()->process_field( $value, $allow_html );
}
}
return apply_filters( 'ig_es_action_option', $value, $field_name, $this );
}
/**
* Get an option for use when editing the action.
*
* The value will be already sanitized by the field object.
* This is used to displaying an option value for editing.
*
* @since 4.4.1
*
* @param string $field_name Field name.
*
* @return mixed
*/
public function get_option_raw( $field_name ) {
if ( isset( $this->options[ $field_name ] ) ) {
return $this->options[ $field_name ];
}
return false;
}
/**
* Load preview for the action
*
* @return null
*/
public function load_preview() {
return null;
}
}

View File

@@ -0,0 +1,78 @@
<?php
/**
* Workflow Data Types.
*
* @since 4.4.1
* @version 1.0
* @package Email Subscribers
*/
/**
* Abstract Class ES_Workflow_Data_Type
*
* @class ES_Workflow_Data_Type
* @since 4.4.1
*/
abstract class ES_Workflow_Data_Type {
/**
* Data type id
*
* @var string
*/
public $id;
/**
* Returns id of data type
*
* @since 4.4.1
* @return string
*/
public function get_id() {
return $this->id;
}
/**
* Sets id of data type
*
* @since 4.4.1
* @param string $id ID.
*/
public function set_id( $id ) {
$this->id = $id;
}
/**
* Validate given data item
*
* @since 4.4.1
* @param mixed $item Data item object.
* @return bool
*/
abstract public function validate( $item );
/**
* Returns id of given data item object. Only validated $items should be passed to this method
*
* @since 4.4.1
* @param mixed $item Data item object.
* @return mixed
*/
abstract public function compress( $item );
/**
* Return data item object from given id.
*
* @since 4.4.1
* @param string $compressed_item Data item object ID.
* @param array $compressed_data_layer Data layer.
* @return mixed
*/
abstract public function decompress( $compressed_item, $compressed_data_layer );
}

View File

@@ -0,0 +1,169 @@
<?php
/**
* Abstract class for workflow actions, triggers and data type related functions.
*
* @since 4.4.1
* @version 1.0
* @package Email Subscribers
*/
/**
* Abstract class for workflow actions, triggers and data type related functions.
*
* @class ES_Registry
* @since 4.4.1
*/
abstract class ES_Workflow_Registry {
/**
* Registered include classes
*
* @since 4.4.1
* @var array
*/
protected static $includes;
/**
* Loaded registered class objects
*
* @var array
*/
protected static $loaded = array();
/**
*
* Implement this method in sub classes
*
* @since 4.4.1
* @return array
*/
public static function load_includes() {
return array();
}
/**
* Optional method to implement
*
* @since 4.4.1
* @param string $name Class name.
* @param mixed $object Class object.
*/
public static function after_loaded( $name, $object ) {}
/**
* Initiate registered include classes.
*
* @since 4.4.1
* @return array
*/
public static function get_includes() {
if ( ! isset( static::$includes ) ) {
static::$includes = static::load_includes();
}
return static::$includes;
}
/**
* Get objects of all registered classes.
*
* @since 4.4.1
* @return mixed
*/
public static function get_all() {
foreach ( static::get_includes() as $name => $path ) {
static::load( $name );
}
return static::$loaded;
}
/**
* Get object of specific registered class.
*
* @since 4.4.1
* @param string $name Registered class name.
* @return bool|object
*/
public static function get( $name ) {
if ( static::load( $name ) ) {
return static::$loaded[ $name ];
}
return false;
}
/**
* Returns if class has been initiated or not
*
* @since 4.4.1
* @param string $name Registered class name.
* @return bool
*/
public static function is_loaded( $name ) {
return isset( static::$loaded[ $name ] );
}
/**
* Load an object by name.
*
* Returns true if the object has been loaded.
*
* @since 4.4.1
* @param string $name Registered class name.
*
* @return bool
*/
public static function load( $name ) {
if ( self::is_loaded( $name ) ) {
return true;
}
$includes = static::get_includes();
$object = false;
if ( empty( $includes[ $name ] ) ) {
return false;
}
$include = $includes[ $name ];
// Check if include is a file path or a class name
// NOTE: the file include method should NOT be used! It is kept for compatibility.
if ( strstr( $include, '.php' ) ) {
if ( file_exists( $include ) ) {
$object = include_once $include;
}
} else {
// If include is not a file path, assume it's a class name.
if ( class_exists( $include ) ) {
$object = new $include();
}
}
if ( ! is_object( $object ) ) {
return false;
}
static::after_loaded( $name, $object );
static::$loaded[ $name ] = $object;
return true;
}
/**
* Clear all registry cached data.
*
* @since 4.4.1
*/
public static function reset() {
static::$includes = null;
static::$loaded = array();
}
}

View File

@@ -0,0 +1,537 @@
<?php
/**
* Abstract rule class.
*
* @since 5.5.0
* @version 1.0
* @package Email Subscribers
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
if ( ! class_exists( 'Es_Workflow_Rule' ) ) {
/**
* Abstract class for workflow rules
*
* @class Es_Rule_Abstract
*
* @since 5.5.0
*/
abstract class Es_Workflow_Rule {
/**
* Name of the rule
*
* @var string
*/
public $name;
/**
* Title of the rule
*
* @var string
*/
public $title;
/**
* Group that rules belongs to
*
* @var string
*/
public $group;
/**
* Type of the rule
*
* @var string string|number|object|select
*/
public $type;
/**
* Define the data type used by the rule.
*
* @var string
*/
public $data_item;
/**
* Comparison type that the rule has used
*
* @var array
*/
public $compare_types = [];
/**
* Workflow that the rule belongs to
*
* @var ES_Workflow
*/
private $workflow;
/**
* Is that rule has multiple input value fields?
*
* @var bool - e.g meta rules have 2 value fields so their value data is an stored as an array
*/
public $has_multiple_value_fields = false;
/**
* Some triggers excluded for particular rule
*
* @var array
*/
public $excluded_triggers = array();
/**
* Constructor
*/
public function __construct() {
$this->init();
$this->determine_rule_group();
}
/**
* Init the rule.
*/
abstract public function init();
/**
* Validates that a given workflow data item passed the rule validation
* based on the supplied $compare_type and $value.
*
* @param mixed $data_item A valid workflow data item e.g. an instance of `\WC_Order` for an order based rule.
* @param string $compare_type The user selected compare type for the rule.
* @param mixed $value The user entered value for the rule. This value is validated by the validate_value() method beforehand.
*
* @return bool
*/
abstract public function validate( $data_item, $compare_type, $value );
/**
* Validate the rule's user entered value.
*
* @param mixed $value
*
* @throws UnexpectedValueException When the value is not valid.
*/
public function validate_value( $value ) {
// Override this method in child classes.
}
/**
* Get rule group
*
* @return string
*/
public function get_group() {
return $this->group;
}
/**
* Get rule name
*
* @return string
*/
public function get_name() {
return $this->name;
}
/**
* Get rule title
*
* @return string
*/
public function get_title() {
return $this->title;
}
/**
* Set workflow
*
* @param $workflow
*/
public function set_workflow( $workflow ) {
$this->workflow = $workflow;
}
/**
* Determine the rule group based on it's title.
*
* If the group prop is already set that will be used.
*
* @return void
*/
public function determine_rule_group() {
if ( isset( $this->group ) ) {
return;
}
// extract the hyphenated part of the title and use as group
if ( isset( $this->title ) && strstr( $this->title, '-' ) ) {
list( $this->group ) = explode( ' - ', $this->title, 2 );
}
if ( empty( $this->group ) ) {
$this->group = __( 'Other', 'email-subscribers' );
}
}
/**
* Get is/is not compare types.
*
* @return array
*/
public function get_is_or_not_compare_types() {
return [
'is' => __( 'is', 'email-subscribers' ),
'is_not' => __( 'is not', 'email-subscribers' ),
];
}
/**
* Get the string comparison types
*
* @return array
*/
public function get_string_compare_types() {
return [
'contains' => __( 'contains', 'email-subscribers' ),
'not_contains' => __( 'does not contain', 'email-subscribers' ),
'is' => __( 'is', 'email-subscribers' ),
'is_not' => __( 'is not', 'email-subscribers' ),
'starts_with' => __( 'starts with', 'email-subscribers' ),
'ends_with' => __( 'ends with', 'email-subscribers' ),
'blank' => __( 'is blank', 'email-subscribers' ),
'not_blank' => __( 'is not blank', 'email-subscribers' ),
'regex' => __( 'matches regex', 'email-subscribers' ),
];
}
/**
* Get the multiple string comparison types
*
* @return array
*/
public function get_multi_string_compare_types() {
return [
'contains' => __( 'any contains', 'email-subscribers' ),
'is' => __( 'any matches exactly', 'email-subscribers' ),
'starts_with' => __( 'any starts with', 'email-subscribers' ),
'ends_with' => __( 'any ends with', 'email-subscribers' ),
];
}
/**
* Get the Float value comparison types
*
* @return array
*/
public function get_float_compare_types() {
return $this->get_is_or_not_compare_types() + [
'greater_than' => __( 'is greater than', 'email-subscribers' ),
'less_than' => __( 'is less than', 'email-subscribers' ),
];
}
/**
* Get the integer comparison types
*
* @return array
*/
public function get_integer_compare_types() {
return $this->get_float_compare_types() + [
'multiple_of' => __( 'is a multiple of', 'email-subscribers' ),
'not_multiple_of' => __( 'is not a multiple of', 'email-subscribers' )
];
}
/**
* Get multi-select match compare types.
*
* @return array
*/
public function get_multi_select_compare_types() {
return [
'matches_all' => __( 'matches all', 'email-subscribers' ),
'matches_any' => __( 'matches any', 'email-subscribers' ),
'matches_none' => __( 'matches none', 'email-subscribers' ),
];
}
/**
* Get numeric compare types.
*/
public function get_numeric_select_compare_types() {
return [
'exactly_equal_to' => __( 'exactly equal to', 'email-subscribers' ),
'less_than_or_equal_to' => __( 'less than or equal to', 'email-subscribers' ),
'more_than_or_equal_to' => __( 'more than or equal to', 'email-subscribers' ),
];
}
/**
* Get includes or not includes compare types.
*
* @return array
*/
public function get_includes_or_not_compare_types() {
return [
'includes' => __( 'includes', 'email-subscribers' ),
'not_includes' => __( 'does not include', 'email-subscribers' ),
];
}
/**
* Check the comparison type
*
* @param $compare_type
*
* @return bool
*/
public function is_string_compare_type( $compare_type ) {
return array_key_exists( $compare_type, $this->get_string_compare_types() );
}
/**
* Check the comparison type
*
* @param $compare_type
*
* @return bool
*/
public function is_integer_compare_type( $compare_type ) {
return array_key_exists( $compare_type, $this->get_integer_compare_types() );
}
/**
* Check the comparison type
*
* @param $compare_type
*
* @return bool
*/
public function is_float_compare_type( $compare_type ) {
return array_key_exists( $compare_type, $this->get_float_compare_types() );
}
/**
* Get the is/is not comparison type
*
* @param $compare_type
*
* @return bool
*/
public function is_is_or_is_not_compare_type( $compare_type ) {
return array_key_exists( $compare_type, $this->get_is_or_not_compare_types() );
}
/**
* Validate a string based rule value.
*
* @param string $actual_value
* @param string $compare_type
* @param string $expected_value
*
* @return bool
*/
public function validate_string( $actual_value, $compare_type, $expected_value ) {
$actual_value = (string) $actual_value;
$expected_value = (string) $expected_value;
// most comparisons are case in-sensitive
$actual_value_lowercase = strtolower( $actual_value );
$expected_value_lowercase = strtolower( $expected_value );
switch ( $compare_type ) {
case 'is':
return $actual_value_lowercase == $expected_value_lowercase;
case 'is_not':
return $actual_value_lowercase != $expected_value_lowercase;
case 'contains':
return strstr( $actual_value_lowercase, $expected_value_lowercase ) !== false;
case 'not_contains':
return strstr( $actual_value_lowercase, $expected_value_lowercase ) === false;
case 'starts_with':
return str_starts_with( $actual_value_lowercase, $expected_value_lowercase );
case 'ends_with':
return str_ends_with( $actual_value_lowercase, $expected_value_lowercase );
case 'blank':
return empty( $actual_value );
case 'not_blank':
return ! empty( $actual_value );
case 'regex':
// Regex validation must not use case insensitive values
return $this->validate_string_regex( $actual_value, $expected_value );
}
return false;
}
/**
* Remove the global regex modifier as it is not supported by PHP.
*
* @param string $regex
*
* @return string
*/
protected function remove_global_regex_modifier( $regex ) {
return preg_replace_callback( '/(\/[a-z]+)$/', function ( $modifiers ) {
return str_replace( 'g', '', $modifiers[0] );
}, $regex );
}
/**
* Validates string regex rule.
*
* @param string $string
* @param string $regex
*
* @return bool
*/
protected function validate_string_regex( $string, $regex ) {
$regex = $this->remove_global_regex_modifier( trim( $regex ) );
// Add '/' delimiters if none are provided in the regex.
if ( ! preg_match( '#^/(.+)/[gi]*$#', $regex ) ) {
// Escape any unescaped delimiters in the regex first.
if ( preg_match( '#[^\\\\]/#', $regex ) ) {
$regex = str_replace( '/', '\\/', $regex );
}
$regex = '/' . $regex . '/';
}
return (bool) @preg_match( $regex, $string );
}
/**
* Only supports 'contains', 'is', 'starts_with', 'ends_with'
*
* @param array $actual_values
* @param string $compare_type
* @param string $expected_value
*
* @return bool
*/
public function validate_string_multi( $actual_values, $compare_type, $expected_value ) {
if ( empty( $expected_value ) ) {
return false;
}
// look for at least one item that validates the text match
foreach ( $actual_values as $coupon_code ) {
if ( $this->validate_string( $coupon_code, $compare_type, $expected_value ) ) {
return true;
}
}
return false;
}
/**
* Check the given two numbers against the operator
*
* @param $actual_value
* @param $compare_type
* @param $expected_value
*
* @return bool
*/
public function validate_number( $actual_value, $compare_type, $expected_value ) {
$actual_value = (float) $actual_value;
$expected_value = (float) $expected_value;
switch ( $compare_type ) {
case 'is':
return $actual_value == $expected_value;
break;
case 'is_not':
return $actual_value != $expected_value;
break;
case 'greater_than':
return $actual_value > $expected_value;
break;
case 'less_than':
return $actual_value < $expected_value;
break;
}
// validate 'multiple of' compares, only accept integers
if ( ! $this->is_whole_number( $actual_value ) || ! $this->is_whole_number( $expected_value ) ) {
return false;
}
$actual_value = (int) $actual_value;
$expected_value = (int) $expected_value;
switch ( $compare_type ) {
case 'multiple_of':
return 0 == $actual_value % $expected_value;
case 'not_multiple_of':
return 0!= $actual_value % $expected_value;
}
return false;
}
/**
* Check the given input is whole number or not
*
* @param $number
*
* @return bool
*/
public function is_whole_number( $number ) {
$number = (float) $number;
return floor( $number ) == $number;
}
/**
* Format the given value
*
* @param $value
*
* @return mixed
*/
public function format_value( $value ) {
return $value;
}
}
}

View File

@@ -0,0 +1,414 @@
<?php
/**
* Abstract class for triggers.
*
* @since 4.4.1
* @version 1.0
* @package Email Subscribers
*/
/**
* Triggers abstract class. All workflow triggers should extend this class.
*
* @class ES_Workflow_Trigger
*
* @since 4.4.1
*/
abstract class ES_Workflow_Trigger {
/**
* Trigger title
*
* @since 4.4.1
* @var string
*/
public $title;
/**
* Trigger name
*
* @since 4.4.1
* @var string
*/
public $name;
/**
* Trigger description
*
* @since 4.4.1
* @var string
*/
public $description;
/**
* Trigger group
*
* @since 4.4.1
* @var string
*/
public $group;
/**
* Supplied data by the trigger class
*
* @since 4.4.1
* @var array
*/
public $supplied_data_items = array();
/**
* Trigger fields
*
* @since 4.4.1
* @var array
*/
public $fields = array();
/**
* Trigger options
*
* @since 4.4.1
* @var array
*/
public $options;
/**
* Trigger rules
*
* @since 4.4.1
* @var array
*/
protected $rules;
/**
* Flag for trigger fields
*
* @since 4.4.1
* @var boolean
*/
protected $has_loaded_fields = false;
/**
* Flag for trigger admin fields
*
* @since 4.4.1
* @var boolean
*/
public $has_loaded_admin_details = false;
/**
* Method to register event(user registerd, comment added) to trigger class
*/
abstract public function register_hooks();
/**
* Construct
*
* @since 4.4.1
*/
public function __construct() {
$this->init();
$this->supplied_data_items = array_unique( $this->supplied_data_items );
add_action( 'ig_es_init_workflow_triggers', array( $this, 'register_hooks' ) );
}
/**
* Init
*
* @since 4.4.1
*/
public function init() {}
/**
* Method to set title, group, description and other admin props
*
* @since 4.4.1
*/
public function load_admin_details() {}
/**
* Registers any fields used on for a trigger
*
* @since 4.4.1
*/
public function load_fields() {}
/**
* Admin info loader
*
* @since 4.4.1
*/
public function maybe_load_admin_details() {
if ( ! $this->has_loaded_admin_details ) {
$this->load_admin_details();
$this->has_loaded_admin_details = true;
}
}
/**
* Field loader
*
* @since 4.4.1
*
* @modified 4.5.3 Added new action trigger_name_load_extra_fields to allow loading of extra fields for given trigger.
*/
public function maybe_load_fields() {
if ( ! $this->has_loaded_fields ) {
// Load fields defined in trigger.
$this->load_fields();
// Load extra fields for given trigger.
do_action( $this->name . '_load_extra_fields', $this );
$this->has_loaded_fields = true;
}
}
/**
* Validate a workflow against trigger
*
* @since 4.4.1
* @param ES_Workflow $workflow workflow object.
* @return bool
*/
public function validate_workflow( $workflow ) {
return true;
}
/**
* Add trigger option field
*
* @param object $option Option object.
*
* @since 4.4.6
*/
public function add_field( $option ) {
$option->set_name_base( 'ig_es_workflow_data[trigger_options]' );
$this->fields[ $option->get_name() ] = $option;
}
/**
* Get supplied data item from trigger
*
* @since 4.4.1
* @return array
*/
public function get_supplied_data_items() {
return $this->supplied_data_items;
}
/**
* Method to get trigger option field
*
* @param string $name Field name.
*
* @return ES_Field|false
*
* @since 4.4.6
*/
public function get_field( $name ) {
$this->maybe_load_fields();
if ( ! isset( $this->fields[ $name ] ) ) {
return false;
}
return $this->fields[ $name ];
}
/**
* Method to get trigger option fields
*
* @return ES_Field[]
*/
public function get_fields() {
$this->maybe_load_fields();
return $this->fields;
}
/**
* Check if there are active workflow for this trigger
*
* @return bool
*
* @since 4.6.10
*/
public function has_workflows() {
$workflow_query = new ES_Workflow_Query();
$workflow_query->set_triggers( $this->get_name() );
$workflows = $workflow_query->get_results();
if ( ! empty( $workflows ) ) {
return true;
}
return false;
}
/**
* Get workflow ids registered to use this trigger
*
* @since 4.4.1
* @return array
*/
public function get_workflow_ids() {
// Get all workflows that using this trigger.
$query = new ES_Workflow_Query();
$query->set_return( 'ids' );
$query->set_trigger( $this->get_name() );
$workflows = $query->get_results();
$workflow_ids = array();
if ( ! empty( $workflows ) ) {
foreach ( $workflows as $workflow ) {
$workflow_ids[] = $workflow['id'];
}
}
return $workflow_ids;
}
/**
* Get workflow registered to use this trigger
*
* @since 4.4.1
* @return ES_Workflow[]
*/
public function get_workflows() {
$workflows = array();
foreach ( $this->get_workflow_ids() as $workflow_id ) {
$workflow = ES_Workflow_Factory::get( $workflow_id );
if ( $workflow ) {
$workflows[] = $workflow;
}
}
return apply_filters( 'ig_es_trigger_workflows', $workflows, $this );
}
/**
* Every data item registered with the trigger should be supplied to this method in its object form.
* E.g. a 'user' should be passed as a WP_User object, and an 'order' should be passed as a WC_Order object
*
* @since 4.4.1
* @param ES_Workflow_Data_Layer|array $data_layer Workflow data layer.
*/
public function maybe_run( $data_layer = array() ) {
// Get all workflows that are registered to use this trigger.
$workflows = $this->get_workflows();
if ( ! $workflows ) {
return;
}
// Flag to check if we should start the workflow processing immediately.
$process_immediately = false;
foreach ( $workflows as $workflow ) {
// First we need to schedule all the workflows.
$workflow->schedule( $data_layer );
$timing_type = $workflow->get_timing_type();
// Check if there are any workflows which needs to run immediately.
if ( 'immediately' === $timing_type ) {
$process_immediately = true;
}
}
if ( $process_immediately ) {
$request_args = array(
'action' => 'ig_es_trigger_workflow_queue_processing',
);
IG_ES_Background_Process_Helper::send_async_ajax_request( $request_args );
}
}
/**
* Get trigger name
*
* @since 4.4.1
* @return string
*/
public function get_name() {
return $this->name;
}
/**
* Set trigger name
*
* @since 4.4.1
* @param string $name trigger name.
*/
public function set_name( $name ) {
$this->name = $name;
}
/**
* Get trigger title
*
* @since 4.4.1
* @return string
*/
public function get_title() {
$this->maybe_load_admin_details();
return $this->title;
}
/**
* Get trigger group
*
* @since 4.4.1
* @return string
*/
public function get_group() {
$this->maybe_load_admin_details();
return $this->group ? $this->group : __( 'Other', 'email-subscribers' );
}
/**
* Get trigger description
*
* @since 4.4.1
* @return string|null
*/
public function get_description() {
$this->maybe_load_admin_details();
return $this->description;
}
/**
* Get trigger description html
*
* @since 4.4.1
* @return string
*/
public function get_description_html() {
if ( ! $this->get_description() ) {
return '';
}
return '<p class="ig-es-field-description">' . $this->get_description() . '</p>';
}
}

View File

@@ -0,0 +1,235 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Base class for workflow placeholder variables
*/
abstract class IG_ES_Workflow_Variable {
/**
* Variable name
* wc_order.id, cart.link, user.first_name etc
*
* @var string
*/
protected $name;
/**
* Variable description
*
* @var string
*/
protected $description;
/**
* Stores parameter field objects.
*
* @var ES_Field[]
*/
protected $parameter_fields = array();
/**
* Variable data type
* e.g. wc_order in wc_order.id, cart in cart.link, user in user.first_name etc
*
* @var string
*/
protected $data_type;
/**
* Variable data field
* e.g. id in wc_order.id, link in cart.link, first_name in user.first_name etc
*
* @var string
*/
protected $data_field;
/**
* Does variable support fallback value in case no value is found
*
* @var bool
*/
public $use_fallback = true;
/**
* Knows if admin details have been loaded.
*
* @var bool
*/
public $has_loaded_admin_details = false;
/**
* Optional method
*/
public function init() {}
/**
* Method to set description and other admin props
*/
public function load_admin_details() {}
public function maybe_load_admin_details() {
if ( ! $this->has_loaded_admin_details ) {
$this->load_admin_details();
$this->has_loaded_admin_details = true;
}
}
/**
* Constructor
*/
public function __construct() {
$this->init();
}
/**
* Sets the name, data_type and data_field props
*
* @param $name
*/
public function setup( $name ) {
$this->name = $name;
list( $this->data_type, $this->data_field ) = explode( '.', $this->name );
}
/**
* Get variable description
*
* @return string
*/
public function get_description() {
$this->maybe_load_admin_details();
return $this->description;
}
/**
* Get the parameter fields for the variable.
*
* @return ES_Field[]
*/
public function get_parameter_fields() {
$this->maybe_load_admin_details();
return $this->parameter_fields;
}
/**
* Get variable name
*
* @return string
*/
public function get_name() {
return $this->name;
}
/**
* Get variable data type
*
* @return string
*/
public function get_data_type() {
return $this->data_type;
}
/**
* Get variable data field
*
* @return string
*/
public function get_data_field() {
return $this->data_field;
}
/**
* Add a parameter field to the variable.
*
* @param ES_Field $field
*/
protected function add_parameter_field( ES_Field $field ) {
$this->parameter_fields[ $field->get_name() ] = $field;
}
/**
* Add a text parameter field to the variable.
*
* @param string $name
* @param string $description
* @param bool $required
* @param string $placeholder
* @param array $extra
*/
protected function add_parameter_text_field( $name, $description, $required = false, $placeholder = '', $extra = array() ) {
$field = new ES_Text();
$field->set_name( $name );
$field->set_description( $description );
$field->set_required( $required );
$field->set_placeholder( $placeholder );
$field->meta = $extra;
if ( $required ) {
$field->add_extra_attr( 'data-required', 'yes' );
}
$this->add_parameter_field( $field );
}
/**
* Add a select parameter field to the variable.
*
* @param string $name
* @param string $description
* @param array $options
* @param bool $required
* @param array $extra
*/
protected function add_parameter_select_field( $name, $description, $options = array(), $required = false, $extra = array() ) {
$field = new ES_Select( false );
$field->set_name( $name );
$field->set_description( $description );
$field->set_required( $required );
$field->set_options( $options );
$field->meta = $extra;
if ( $required ) {
$field->add_extra_attr( 'data-required', 'yes' );
}
$this->add_parameter_field( $field );
}
/**
* Add a number parameter field to the variable.
*
* @param string $name
* @param string $description
* @param bool $required
* @param string $placeholder
* @param array $extra
*
* @since 5.3.4
*/
public function add_parameter_number_field( $name, $description, $required = false, $placeholder = '', $extra = array()) {
$field = new ES_Number();
$field->set_name( $name );
$field->set_description( $description );
$field->set_required( $required );
$field->set_placeholder( $placeholder );
$field->meta = $extra;
if ( $required ) {
$field->add_extra_attr( 'data-required', 'yes' );
}
$this->add_parameter_field( $field );
}
}

View File

@@ -0,0 +1,53 @@
<?php
/**
* Abstract action class to send an email to provided email address.
*
* @since 4.5.3
* @version 1.0
* @package Email Subscribers
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Abstract class for send email workflow action
*
* @class ES_Action_Send_Email_Abstract
*
* @since 4.5.3
*/
abstract class ES_Action_Send_Email_Abstract extends ES_Workflow_Action {
/**
* Load admin props.
*
* @since 4.5.3
*/
public function load_admin_details() {
$this->group = __( 'Email', 'email-subscribers' );
}
/**
* Load fields.
*
* @since 4.5.3
*/
public function load_fields() {
$send_to = new ES_Text();
$send_to->set_name( 'ig-es-send-to' );
$send_to->set_title( __( 'Send to', 'email-subscribers' ) );
$send_to->set_description( __( 'Enter emails here or use variable such as {{EMAIL}}. Multiple emails can be separated by commas.', 'email-subscribers' ) );
$send_to->set_placeholder( __( 'E.g. {{EMAIL}}, admin@example.com', 'email-subscribers' ) );
$send_to->set_required();
$subject = new ES_Text();
$subject->set_name( 'ig-es-email-subject' );
$subject->set_title( __( 'Email subject', 'email-subscribers' ) );
$subject->set_required();
$this->add_field( $send_to );
$this->add_field( $subject );
}
}

View File

@@ -0,0 +1,209 @@
<?php
/**
* Action to add contact to the selected list
*
* @since 4.4.1
* @version 1.0
* @package Email Subscribers
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
if ( ! class_exists( 'ES_Action_Add_To_List' ) ) {
/**
* Class to add contact to the selected list
*
* @class ES_Action_Add_To_List
*
* @since 4.4.1
*/
class ES_Action_Add_To_List extends ES_Workflow_Action {
/**
* Load action admin details.
*
* @since 4.4.1
*/
public function load_admin_details() {
$this->title = __( 'Add to list', 'email-subscribers' );
$this->group = __( 'List', 'email-subscribers' );
}
/**
* Load action fields
*
* @since 4.4.1
*/
public function load_fields() {
$lists = ES()->lists_db->get_list_id_name_map();
$list_field = new ES_Select();
$list_field->set_name( 'ig-es-list' );
$list_field->set_title( __( 'Select List', 'email-subscribers' ) );
$list_field->set_options( $lists );
$list_field->set_required();
$this->add_field( $list_field );
}
/**
* Called when an action should be run
*
* @since 4.4.1
*/
public function run() {
$list_id = $this->get_option( 'ig-es-list' );
$user_list_status = $this->get_option( 'ig-es-user-list-status' );
$user_list_status = empty( $user_list_status ) ? 'subscribed' : $user_list_status;
if ( ! $list_id ) {
return;
}
$raw_data = $this->workflow->data_layer()->get_raw_data();
if ( ! empty( $raw_data ) ) {
foreach ( $raw_data as $data_type_id => $data_item ) {
$data_type = ES_Workflow_Data_Types::get( $data_type_id );
if ( ! $data_type || ! $data_type->validate( $data_item ) ) {
continue;
}
$data = array();
if ( is_callable( array( $data_type, 'get_data' ) ) ) {
$data = $data_type->get_data( $data_item );
}
if ( ! empty( $data['email'] ) ) {
$this->add_contact( $list_id, $data, $user_list_status );
}
}
}
}
/**
* Add contact data to given list
*
* @param int $list_id List id to add the contact's data.
* @param array $data Contact's data.
*
* @modify 5.6.7
*/
public function add_contact( $list_id = 0, $data = array(), $user_list_status = '' ) {
// Don't know where to add contact? please find it first.
if ( empty( $list_id ) ) {
return;
}
// Email not found? Say good bye.
if ( empty( $data['email'] ) || ! filter_var( $data['email'], FILTER_VALIDATE_EMAIL ) ) {
return;
}
$is_domain_blocked = ES_Common::is_domain_blocked( $data['email'] );
// Store it blocked emails
if ( $is_domain_blocked ) {
$data = array(
'email' => $data['email'],
'ip' => ig_es_get_ip(),
);
ES()->blocked_emails_db->insert( $data );
return;
}
// Source not set? Say bye.
if ( empty( $data['source'] ) ) {
return;
}
$email = trim( $data['email'] );
$source = trim( $data['source'] );
$status = ! empty( $data['status'] ) ? trim( $data['status'] ) : 'verified';
$wp_user_id = ! empty( $data['wp_user_id'] ) ? trim( $data['wp_user_id'] ) : 0;
// If first name is set, get the first name and last name from $data.
// Else prepare the first name and last name from $data['name'] field or $data['email'] field.
if ( ! empty( $data['first_name'] ) ) {
$first_name = $data['first_name'];
$last_name = ! empty( $data['last_name'] ) ? $data['last_name'] : '';
} else {
$name = ! empty( $data['name'] ) ? trim( $data['name'] ) : '';
$first_name = '';
$last_name = '';
if ( ! empty( $name ) ) {
$name_parts = ES_Common::prepare_first_name_last_name( $name );
$first_name = $name_parts['first_name'];
$last_name = $name_parts['last_name'];
}
}
$guid = ES_Common::generate_guid();
$contact_data = array(
'first_name' => $first_name,
'last_name' => $last_name,
'email' => $email,
'source' => $source,
'status' => $status,
'user_list_status' => $user_list_status,
'hash' => $guid,
'created_at' => ig_get_current_date_time(),
'wp_user_id' => $wp_user_id,
);
do_action( 'ig_es_add_contact', $contact_data, $list_id );
}
/**
* Create contact list from product
*
* @param WC_Product $product Product object.
*
* @return int $list_id List ID.
*
* @since 4.4.3
*/
public function create_list_from_product( $product ) {
$list_id = 0;
if ( ! ( $product instanceof WC_Product ) ) {
return $list_id;
}
$product_name = $product->get_name();
$product_sku = $product->get_sku();
$list_name = $product_name;
if ( empty( $product_sku ) ) {
$list_slug = $product_name;
} else {
$list_slug = $product_sku;
}
$list = ES()->lists_db->get_list_by_slug( $list_slug );
if ( ! empty( $list ) ) {
$list_id = $list['id'];
} else {
$list_id = ES()->lists_db->add_list( $list_name, $list_slug );
}
return $list_id;
}
}
}

View File

@@ -0,0 +1,71 @@
<?php
/**
* Action to add contact to the selected list
*
* @since 4.4.1
* @version 1.0
* @package Email Subscribers
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
if ( ! class_exists( 'ES_Action_Delete_Contact' ) ) {
/**
* Class to add contact to the selected list
*
* @class ES_Action_Delete_Contact
*
* @since 4.4.1
*/
class ES_Action_Delete_Contact extends ES_Workflow_Action {
/**
* Load action admin details.
*
* @since 4.4.1
*/
public function load_admin_details() {
$this->title = __( 'Delete Contact', 'email-subscribers' );
$this->group = __( 'Contact', 'email-subscribers' );
}
/**
* Called when an action should be run
*
* @since 4.4.1
*/
public function run() {
global $wpdb;
$raw_data = $this->workflow->data_layer()->get_raw_data();
if ( ! empty( $raw_data ) ) {
foreach ( $raw_data as $data_type_id => $data_item ) {
$data_type = ES_Workflow_Data_Types::get( $data_type_id );
if ( ! $data_type || ! $data_type->validate( $data_item ) ) {
continue;
}
$data = array();
if ( is_callable( array( $data_type, 'get_data' ) ) ) {
$data = $data_type->get_data( $data_item );
}
$email = ! empty( $data['email'] ) ? $data['email'] : '';
if ( ! empty( $email ) ) {
$where = $wpdb->prepare( 'email = %s', $email );
$contact_id = ES()->contacts_db->get_column_by_condition( 'id', $where );
if ( $contact_id ) {
ES()->contacts_db->delete_contacts_by_ids( $contact_id );
}
}
}
}
}
}
}

View File

@@ -0,0 +1,251 @@
<?php
/**
* Action to send an email to provided email address.
*
* @since 4.5.3
* @version 1.0
* @package Email Subscribers
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Class to handle send email action
*
* @class ES_Action_Send_Email
*
* @since 4.5.3
*/
if ( ! class_exists( 'ES_Action_Send_Email' ) ) {
class ES_Action_Send_Email extends ES_Action_Send_Email_Abstract {
/**
* Load action admin details.
*
* @since 4.5.3
*/
public function load_admin_details() {
$this->group = __( 'Email', 'email-subscribers' );
$this->title = __( 'Send Email', 'email-subscribers' );
}
/**
* Load action fields
*
* @since 4.5.3
*/
public function load_fields() {
global $ig_es_tracker;
parent::load_fields();
$is_woocommerce_active = $ig_es_tracker::is_plugin_activated( 'woocommerce/woocommerce.php' );
if ( $is_woocommerce_active ) {
$email_template_field = new ES_Select( false );
$email_template_field->set_name( 'ig-es-email-template' );
$email_template_field->set_title( __( 'Email styling', 'email-subscribers' ) );
$email_template_field->set_description( __( 'Select which style to use when formatting the email.', 'email-subscribers' ) );
$email_template_field_options = array(
'none' => __( 'None', 'email-subscribers' ),
'woocommerce' => 'WooCommerce email styling',
);
$email_template_field->set_options( $email_template_field_options );
$email_template_field->set_required();
$this->add_field( $email_template_field );
$email_heading_field = new ES_Text();
$email_heading_field->set_name( 'ig-es-email-heading' );
$email_heading_field->set_title( __( 'Email heading', 'email-subscribers' ) );
$email_heading_field->set_description( __( 'Enter text to be shown in email header area.', 'email-subscribers' ) );
$email_template = $this->get_option( 'ig-es-email-template', false );
$is_wocoomerce_template = 'woocommerce' === $email_template;
if ( ! $is_wocoomerce_template ) {
$email_heading_field->add_container_classes( 'hidden' );
}
$this->add_field( $email_heading_field );
}
$tracking_campaign_id = $this->get_option( 'ig-es-tracking-campaign-id', false );
if ( empty( $tracking_campaign_id ) ) {
$tracking_campaign_id = uniqid();
}
$email_content = new ES_WP_Editor();
$email_content->set_id( 'ig-es-workflow-email-content-' . $tracking_campaign_id );
$email_content->set_name( 'ig-es-email-content' );
$email_content->set_title( __( 'Email content', 'email-subscribers' ) );
$email_content->set_required();
$this->add_field( $email_content );
$tracking_field = new ES_Checkbox();
$tracking_field->set_name( 'ig-es-email-tracking-enabled' );
$tracking_field->set_title( __( 'Track opens and clicks', 'email-subscribers' ) );
$tracking_field->add_classes( 'form-checkbox text-indigo-600' );
$tracking_field->default_to_checked = false;
$this->add_field( $tracking_field );
$tracking_campaign_id = new ES_Hidden_Field();
$tracking_campaign_id->set_name( 'ig-es-tracking-campaign-id' );
$this->add_field( $tracking_campaign_id );
}
/**
* Create content for showing preview
*
* @return mixed|null
*/
public function load_preview() {
$email_content = $this->get_option( 'ig-es-email-content', true, true );
$email_template = $this->get_option( 'ig-es-email-template', false );
$email_heading = $this->get_option( 'ig-es-email-heading', false );
$email_content = wpautop( $email_content );
$email_content = $this->add_template_styling( $email_content, $email_heading, $email_template );
$current_user = wp_get_current_user();
$email_content = ES_Common::replace_keywords_with_fallback( $email_content, array(
'subscriber.first_name' => $current_user->first_name,
'subscriber.name' => $current_user->display_name,
'subscriber.last_name' => $current_user->last_name,
'subscriber.email' => $current_user->user_email
) );
return ES_Common::replace_keywords_with_fallback( $email_content, array(
'EMAIL' => $current_user->user_email,
'NAME' => $current_user->display_name,
'FIRSTNAME' => $current_user->first_name,
'LASTNAME' => $current_user->last_name,
) );
}
/**
* Called when an action should be run
*
* @since 4.5.3
*/
public function run() {
$recipients = $this->get_option( 'ig-es-send-to', true );
$subject = $this->get_option( 'ig-es-email-subject', true );
$email_template = $this->get_option( 'ig-es-email-template', false );
$email_heading = $this->get_option( 'ig-es-email-heading', false );
$email_content = $this->get_option( 'ig-es-email-content', true, true );
$tracking_enabled = $this->get_option( 'ig-es-email-tracking-enabled', false );
$tracking_campaign_id = $this->get_option( 'ig-es-tracking-campaign-id', false );
$recipients = explode(',', $recipients );
$recipients = array_map( 'trim', $recipients );
// Check if we have all required data to send the email.
if ( empty( $recipients ) || empty( $email_content ) || empty( $subject ) ) {
return;
}
// Replace line breaks with paragraphs in email body.
$email_content = wpautop( $email_content );
$raw_data = $this->workflow->data_layer()->get_raw_data();
if ( ! empty( $raw_data ) ) {
foreach ( $raw_data as $data_type_id => $data_item ) {
$data_type = ES_Workflow_Data_Types::get( $data_type_id );
if ( ! $data_type || ! $data_type->validate( $data_item ) ) {
continue;
}
$data = array();
if ( method_exists( $data_type, 'get_data' ) ) {
$data = $data_type->get_data( $data_item );
if ( ! empty( $data['email'] ) ) {
foreach ( $recipients as $index => $recipient_email ) {
// Replace placeholder tags with the got data from the triggerred event.
$recipients[$index] = str_replace( '{{EMAIL}}', $data['email'], $recipient_email );
}
// If source is 'es, it means it is from ES subscriber form, replace {{EMAIL}}, {{NAME}} placeholders with subscriber's email, name
// If we don't replace it here then for workflow configured to be sent to admins, {{EMAIL}}, {{NAME}} gets replaced with admin email and names which is not desired for subscriber based workflows.
if ( 'es' === $data['source'] ) {
$subject = str_replace( '{{EMAIL}}', $data['email'], $subject );
$subject = str_replace( '{{NAME}}', $data['name'], $subject );
$email_content = str_replace( '{{EMAIL}}', $data['email'], $email_content );
$email_content = str_replace( '{{NAME}}', $data['name'], $email_content );
$email_content = str_replace( '{{LIST}}', $data['list_name'], $email_content );
}
}
if ( 'campaign' === $data_type_id && ! empty( $data['notification_guid'] ) ) {
$notification = ES_DB_Mailing_Queue::get_notification_by_hash( $data['notification_guid'] );
$subject = str_replace( '{{SUBJECT}}', $notification['subject'], $subject );
$email_count = $notification['count'];
$campaign_subject = $notification['subject'];
$cron_date = gmdate( 'Y-m-d H:i:s' );
$cron_local_date = get_date_from_gmt( $cron_date ); // Convert from GMT to local date/time based on WordPress time zone setting.
$cron_date = ES_Common::convert_date_to_wp_date( $cron_local_date ); // Get formatted date from WordPress date/time settings.
$email_content = str_replace( '{{DATE}}', $cron_date, $email_content );
$email_content = str_replace( '{{COUNT}}', $email_count, $email_content );
$email_content = str_replace( '{{SUBJECT}}', $campaign_subject, $email_content );
}
}
}
$email_content = $this->add_template_styling( $email_content, $email_heading, $email_template );
$es_mailer = ES()->mailer;
if ( ! empty( $tracking_campaign_id ) ) {
$data['campaign_id'] = $tracking_campaign_id;
}
if ( $tracking_enabled ) {
$es_mailer->can_track_open_clicks = true;
} else {
$es_mailer->can_track_open_clicks = false;
}
$es_mailer->add_unsubscribe_link = false;
$es_mailer->send( $subject, $email_content, $recipients, $data );
}
}
/**
* Add template styling to email content.
*/
public function add_template_styling( $email_content, $email_heading = '', $email_template = 'none' ) {
if ( 'woocommerce' === $email_template ) {
// Make sure WC function exisists before calling it.
if ( function_exists( 'WC' ) ) {
$email_content = WC()->mailer()->wrap_message( $email_heading, $email_content );
$wc_email = new WC_Email();
$email_content = $wc_email->style_inline( $email_content );
// When inlining CSS, curly braces {{UNSUBSCRIBE-LINK}} gets converted to following characters. We are reverting them back
$email_content = str_replace( array( '%5C%7B', '%5C%7D' ), array( '{', '}' ), $email_content );
$email_content = str_replace( array( '%7B', '%7D' ), array( '{', '}' ), $email_content );
}
}
return $email_content;
}
}
}

View File

@@ -0,0 +1,117 @@
<?php
/**
* Action to add contact to the selected list
*
* @since 4.4.1
* @version 1.0
* @package Email Subscribers
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
if ( ! class_exists( 'ES_Action_Update_Contact' ) ) {
/**
* Class to add contact to the selected list
*
* @class ES_Action_Update_Contact
*
* @since 4.4.1
*/
class ES_Action_Update_Contact extends ES_Workflow_Action {
/**
* Load action admin details.
*
* @since 4.4.1
*/
public function load_admin_details() {
$this->title = __( 'Update Contact', 'email-subscribers' );
$this->group = __( 'Contact', 'email-subscribers' );
}
/**
* Called when an action should be run
*
* @since 4.4.1
*/
public function run() {
$raw_data = $this->workflow->data_layer()->get_raw_data();
if ( ! empty( $raw_data ) ) {
foreach ( $raw_data as $data_type_id => $data_item ) {
$data_type = ES_Workflow_Data_Types::get( $data_type_id );
if ( ! $data_type || ! $data_type->validate( $data_item ) ) {
continue;
}
$data = array();
if ( is_callable( array( $data_type, 'get_data' ) ) ) {
$data = $data_type->get_data( $data_item );
}
$user_id = ! empty( $data['wp_user_id'] ) ? $data['wp_user_id'] : 0;
if ( ! empty( $user_id ) ) {
$user = get_user_by( 'ID', $user_id );
if ( $user instanceof WP_User ) {
// Check if user exist with this email.
$es_contact_id = ES()->contacts_db->get_contact_id_by_wp_user_id( $user_id );
if ( ! $es_contact_id ) {
$es_contact_id = ES()->contacts_db->get_contact_id_by_email( $user->user_email );
}
if ( ! empty( $es_contact_id ) ) {
$first_name = get_user_meta( $user_id, 'first_name', true );
$last_name = get_user_meta( $user_id, 'last_name', true );
if ( empty( $first_name ) && empty( $last_name ) ) {
$first_name = $user->display_name;
}
$contact = array(
'email' => $user->user_email,
'first_name' => $first_name,
'last_name' => $last_name,
'wp_user_id' => $user->ID,
);
ES()->contacts_db->update_contact( $es_contact_id, $contact );
}
}
} else {
$email = ! empty( $data['email'] ) ? $data['email'] : '';
$es_contact_id = ES()->contacts_db->get_contact_id_by_email( $email );
if ( ! empty( $es_contact_id ) ) {
$first_name = ! empty( $data['first_name'] ) ? $data['first_name'] : '';
$last_name = ! empty( $data['last_name'] ) ? $data['last_name'] : '';
// Check if we are getting the name field.
if ( empty( $first_name ) && empty( $last_name ) && ! empty( $data['name'] ) ) {
$name = explode( ' ', $data['name'] );
$first_name = $name[0];
if ( isset( $name[1] ) ) {
$last_name = $name[1];
}
}
$contact = array(
'email' => $email,
'first_name' => $first_name,
'last_name' => $last_name,
);
ES()->contacts_db->update_contact( $es_contact_id, $contact );
}
}
}
}
}
}
}

View File

@@ -0,0 +1,234 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Handles workflow admin ajax functionality
*
* @since 4.4.1
* @version 1.0
* @package Email Subscribers
*/
/**
* Class to handle workflow admin ajax functionality
*
* @class ES_Workflow_Admin_Ajax
*
* @since 4.4.1
*/
class ES_Workflow_Admin_Ajax {
/**
* Hook in methods
*
* @since 4.4.1
*
* @since 4.6.9 Added modal_variable_info ajax event.
*/
public static function init() {
$ajax_events = array(
'fill_trigger_fields',
'fill_action_fields',
'toggle_workflow_status',
'modal_variable_info',
'create_workflow_from_gallery_item',
);
foreach ( $ajax_events as $ajax_event ) {
add_action( 'wp_ajax_ig_es_' . $ajax_event, array( __CLASS__, $ajax_event ) );
}
}
/**
* Get trigger fields
*/
public static function fill_trigger_fields() {
check_ajax_referer( 'ig-es-admin-ajax-nonce', 'security' );
$trigger_name = ig_es_get_request_data( 'trigger_name' );
$workflow_id = ig_es_get_request_data( 'workflow_id' );
$trigger = ES_Workflow_Triggers::get( $trigger_name );
if ( ! $trigger ) {
die;
}
$workflow = null;
if ( ! empty( $workflow_id ) ) {
$workflow = new ES_Workflow( $workflow_id );
}
ob_start();
ES_Workflow_Admin::get_view(
'trigger-fields',
array(
'trigger' => $trigger,
'workflow' => $workflow,
)
);
$fields = ob_get_clean();
wp_send_json_success(
array(
'fields' => $fields,
'trigger' => ES_Workflow_Admin_Edit::get_trigger_data( $trigger ),
)
);
}
/**
* Method to get workflow action related fields in ajax request
*/
public static function fill_action_fields() {
check_ajax_referer( 'ig-es-admin-ajax-nonce', 'security' );
$action_name = ig_es_get_request_data( 'action_name' );
$action_number = ig_es_get_request_data( 'action_number' );
$trigger_name = ig_es_get_request_data( 'trigger_name' );
$action = ES_Workflow_Actions::get( $action_name );
$trigger = ES_Workflow_Triggers::get( $trigger_name );
if ( ! empty( $trigger ) ) {
$action->trigger = $trigger;
}
ob_start();
ES_Workflow_Admin::get_view(
'action-fields',
array(
'workflow_action' => $action,
'action_number' => $action_number,
)
);
$fields = ob_get_clean();
wp_send_json_success(
array(
'fields' => $fields,
'title' => ( $action instanceof ES_Workflow_Action ) ? $action->get_title( true ) : '',
'description' => ( $action instanceof ES_Workflow_Action ) ? $action->get_description_html() : '',
)
);
}
/**
* Method to toggle workflow status
*/
public static function toggle_workflow_status() {
check_ajax_referer( 'ig-es-admin-ajax-nonce', 'security' );
$workflow = ES_Workflow_Factory::get( ig_es_get_request_data( 'workflow_id' ) );
$new_state = ig_es_get_request_data( 'new_state' );
if ( ! $workflow || ! $new_state ) {
die;
}
$status_updated = $workflow->update_status( $new_state );
if ( $status_updated ) {
wp_send_json_success();
} else {
wp_send_json_error();
}
}
/**
* Show vairable info modal
*
* @since 4.6.9
*/
public static function modal_variable_info() {
check_ajax_referer( 'ig-es-admin-ajax-nonce', 'security' );
$variable = IG_ES_Variables::get_variable( ES_Clean::string( ig_es_get_request_data( 'variable' ) ) );
if ( $variable ) {
ES_Workflow_Admin::get_view(
'modal-variable-info',
array(
'variable' => $variable,
)
);
die;
}
wp_die( esc_html__( 'Variable not found.', 'email-subscribers' ) );
}
/**
* Method to toggle workflow status
*/
public static function create_workflow_from_gallery_item() {
check_ajax_referer( 'ig-es-admin-ajax-nonce', 'security' );
$item_name = ig_es_get_request_data( 'item_name' );
if ( ! $item_name ) {
die;
}
$workflow_id = 0;
$workflow_gallery = ES_Workflow_Gallery::get_workflow_gallery_items();
if ( ! empty( $workflow_gallery[ $item_name ] ) ) {
$item_data = $workflow_gallery[ $item_name ];
$workflow_title = isset( $item_data['title'] ) ? ig_es_clean( $item_data['title'] ) : '';
$workflow_name = ! empty( $workflow_title ) ? sanitize_title( ES_Clean::string( $workflow_title ) ) : '';
$trigger_name = isset( $item_data['trigger_name'] ) ? ig_es_clean( $item_data['trigger_name'] ) : '';
$trigger_options = isset( $item_data['trigger_options'] ) ? ig_es_clean( $item_data['trigger_options'] ) : array();
$rules = isset( $item_data['rules'] ) ? ig_es_clean( $item_data['rules'] ) : array();
$actions = isset( $item_data['actions'] ) ? $item_data['actions'] : array(); // We can't sanitize actions data since some actions like Send email allows html in its field.
$status = isset( $item_data['status'] ) ? ig_es_clean( $item_data['status'] ) : 0;
$type = isset( $item_data['type'] ) ? ig_es_clean( $item_data['type'] ) : 0;
$priority = isset( $item_data['priority'] ) ? ig_es_clean( $item_data['priority'] ) : 0;
$meta = isset( $item_data['meta'] ) ? ig_es_clean( $item_data['meta'] ) : 0;
$workflow_data = array(
'name' => $workflow_name,
'title' => $workflow_title,
'trigger_name' => $trigger_name,
'trigger_options' => maybe_serialize( $trigger_options ),
'rules' => maybe_serialize( $rules ),
'actions' => maybe_serialize( $actions ),
'meta' => maybe_serialize( $meta ),
'status' => 0,
'type' => 0,
'priority' => 0,
);
$workflow_id = ES()->workflows_db->insert_workflow( $workflow_data );
}
if ( $workflow_id ) {
$redirect_url = ES_Workflow_Admin_Edit::get_admin_edit_url( $workflow_id );
wp_send_json_success(
array(
'workflow_id' => $workflow_id,
'redirect_url' => $redirect_url,
)
);
} else {
wp_send_json_error(
array(
'error_message' => __( 'Workflow could not be created. Please try again.', 'email-subscribers' ),
)
);
}
}
}

View File

@@ -0,0 +1,57 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Workflow admin
*
* @since 4.4.1
* @version 1.0
* @package Email Subscribers
*/
/**
* Helper class for workflow admin functionality
*
* @class ES_Workflow_Admin
*
* @since 4.4.1
*/
class ES_Workflow_Admin {
/**
* Workflow admin init
*
* @since 4.4.1
*/
public static function init() {
ES_Workflow_Admin_Ajax::init();
ES_Workflow_Admin_Edit::init();
ES_Workflow_Gallery::init();
}
/**
* Method to load workflow admin views
*
* @since 4.4.1
*
* @param string $view View name.
* @param array $imported_variables Passed variables.
* @param mixed $path Path to view file.
*/
public static function get_view( $view, $imported_variables = array(), $path = false ) {
if ( $imported_variables && is_array( $imported_variables ) ) {
extract( $imported_variables ); // phpcs:ignore
}
if ( ! $path ) {
$path = ES_PLUGIN_DIR . 'lite/includes/workflows/admin/views/';
}
include $path . $view . '.php';
}
}

View File

@@ -0,0 +1,81 @@
<?php
/**
* Workflow single action fields
*
* @since 4.4.1
* @version 1.0
* @package Email Subscribers
*/
/**
* View args:
*
* @var ES_action $workflow_action
* @var ES_Workflow $workflow
* @var $fill_fields (optional)
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
if ( ! $workflow_action || ! $action_number ) {
return;
}
// Default to false.
if ( ! isset( $fill_fields ) ) {
$fill_fields = false;
}
if ( $fill_fields ) {
$workflow_action = $workflow->get_action( $action_number );
}
$fields = $workflow_action->get_fields();
?>
<?php
foreach ( $fields as $field ) :
// add action number to name base.
$field->set_name_base( "ig_es_workflow_data[actions][$action_number]" );
if ( $fill_fields ) {
$value = $workflow_action->get_option_raw( $field->get_name() );
} else {
$value = '';
}
?>
<tr class="ig-es-table__row <?php echo esc_attr( $field->get_container_classes() ); ?>"
data-name="<?php echo esc_attr( $field->get_name() ); ?>"
data-type="<?php echo esc_attr( $field->get_type() ); ?>"
data-required="<?php echo (int) $field->get_required(); ?> ">
<td class="ig-es-table__col ig-es-table__col--label">
<?php
if ( 'checkbox' !== $field->get_type() ) :
?>
<label><?php echo esc_html( $field->get_title() ); ?>
<?php if ( $field->get_required() ) : ?>
<span class="required">*</span>
<?php endif; ?>
</label>
<?php
endif;
?>
</td>
<td class="ig-es-table__col ig-es-table__col--field ig-es-field-wrap">
<?php $field->render( $value ); ?>
<?php if ( $field->get_description() ) : ?>
<p class="ig-es-field-description">
<?php echo wp_kses_post( $field->get_description() ); ?>
</p>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>

View File

@@ -0,0 +1,83 @@
<?php
/**
* Workflow single action
*
* @since 4.4.1
* @version 1.0
* @package Email Subscribers
*/
defined( 'ABSPATH' ) || exit;
/**
* View args:
*
* @var int $action_number
* @var ES_Workflow_Action $action
* @var array $action_select_box_values
* @var ES_Workflow $workflow
*/
?>
<div class="ig-es-action"
data-action-number="<?php echo $action ? esc_attr( $action_number ) : ''; ?>">
<div class="ig-es-action__header">
<div class="row-options">
<button class="js-preview-action text-indigo-600 hidden" title="<?php esc_attr_e( 'Preview', 'email-subscribers' ); ?>"><span class="dashicons dashicons-welcome-view-site"></span></button>
<a class="js-edit-action text-indigo-600" href="#" title="<?php esc_attr_e( 'Edit', 'email-subscribers' ); ?>"><span class="dashicons dashicons-edit"></span></a>
<a class="js-delete-action text-indigo-600" href="#" title="<?php esc_attr_e( 'Delete', 'email-subscribers' ); ?>"><span class="dashicons dashicons-trash"></span></a>
</div>
<h4 class="action-title"><?php echo esc_html( $action ? $action->get_title( true ) : __( 'New Action', 'email-subscribers' ) ); ?></h4>
</div>
<div class="ig-es-action__fields">
<table class="ig-es-table">
<tr class="ig-es-table__row" data-name="action_name" data-type="select" data-required="1">
<td class="ig-es-table__col ig-es-table__col--label">
<label><?php esc_attr_e( 'Action', 'email-subscribers' ); ?> <span class="required">*</span></label>
</td>
<td class="ig-es-table__col ig-es-table__col--field">
<?php
$action_field = new ES_Select();
if ( $action ) {
$action_field->set_name_base( "ig_es_workflow_data[actions][{$action_number}]" );
$action_field->set_name( 'action_name' );
} else {
$action_field->set_name( '' );
}
$action_field->set_options( $action_select_box_values );
$action_field->add_classes( 'ig-es-field js-action-select' );
$action_field->render( $action ? $action->get_name() : false );
?>
<?php if ( $action && $action->get_description() ) : ?>
<div class="js-action-description"><?php echo wp_kses_post( $action->get_description_html() ); ?></div>
<?php else : ?>
<div class="js-action-description"></div>
<?php endif; ?>
</td>
</tr>
<?php
if ( $action ) {
ES_Workflow_Admin::get_view(
'action-fields',
array(
'workflow_action' => $action,
'action_number' => $action_number,
'workflow' => $workflow,
'fill_fields' => true,
)
);
}
?>
</table>
</div>
</div>

View File

@@ -0,0 +1,109 @@
<?php
/**
* Admin workflow actions metabox
*
* @since 4.4.1
* @version 1.0
* @package Email Subscribers
*/
/**
* Worfklow object
*
* @var ES_Workflow $workflow
*
* Workflow Action objects
* @var ES_Workflow_Action[] $workflow_actions
*
* Action select box value
* @var array $action_select_box_values
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
?>
<div class="ig-es-actions-container">
<?php if ( is_array( $workflow_actions ) ) : ?>
<?php $n = 1; ?>
<?php
foreach ( $workflow_actions as $workflow_action ) :
ES_Workflow_Admin::get_view(
'action',
array(
'workflow' => $workflow,
'action' => $workflow_action,
'action_number' => $n,
'action_select_box_values' => $action_select_box_values,
)
);
?>
<?php $n++; ?>
<?php endforeach; ?>
<?php endif; ?>
<div class="ig-es-action-template">
<?php
// Render blank action template.
ES_Workflow_Admin::get_view(
'action',
array(
'workflow' => $workflow,
'action' => false,
'action_number' => false,
'action_select_box_values' => $action_select_box_values,
)
);
?>
</div>
<?php if ( empty( $workflow_actions ) ) : ?>
<div class="js-ig-es-no-actions-message">
<p>
<?php
/* translators: 1: Starting strong tag 2: Closing strong tag */
echo sprintf( esc_html__( 'No actions found. Click the %1$s+ Add action%2$s to create an action.', 'email-subscribers' ), '<strong>', '</strong>' );
?>
</p>
</div>
<?php endif; ?>
</div>
<div class="ig-es-metabox-footer">
<button type="button" class="js-ig-es-add-action inline-flex justify-center rounded-md border border-transparent px-4 py-1.5 bg-white text-sm leading-5 font-medium text-white bg-indigo-600 hover:bg-indigo-500 focus:outline-none focus:shadow-outline-blue transition ease-in-out duration-150"><?php echo esc_html__( '+ Add action', 'email-subscribers' ); ?></button>
</div>
<!--Email Action Preview-->
<div class="hidden" id="workflow-email-preview-popup">
<div class="fixed top-0 left-0 flex items-center justify-center w-full h-full" style="background-color: rgba(0,0,0,.5); z-index: 999;">
<div id="campaign-preview-main-container" class="absolute h-auto pt-2 ml-16 mr-4 text-left bg-white rounded shadow-xl z-80 w-1/2 md:max-w-5xl lg:max-w-7xl md:pt-3 lg:pt-2">
<div class="py-2 px-4">
<div class="flex">
<button id="close-workflow-email-preview-popup" class="text-sm font-medium tracking-wide text-gray-700 select-none no-outline focus:outline-none focus:shadow-outline-red hover:border-red-400 active:shadow-lg">
<svg class="h-5 w-5 inline" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
</div>
<div id="workflow-email-preview-container" class="hidden">
<div id="campaign-browser-preview-container">
<?php
do_action( 'ig_es_campaign_preview_options_content', array( 'type' => 'workflow' ) );
?>
<div id="workflow-preview-iframe-container" class="py-4 list-decimal popup-preview">
</div>
</div>
</div>
<div id="workflow-email-preview-loader" class="p-13 text-center" style="min-width: 600px">
<img src="<?php echo esc_attr( ES_PLUGIN_URL . 'lite/admin/images/spinner-2x.gif' ); ?>" class="inline-block es-loader pl-2 h-5 w-7"
alt="<?php echo esc_attr__( 'Loading...', 'email-subscribers' ); ?>"/>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,31 @@
<?php
/**
* Admin workflow options metabox
*
* @since 4.4.1
* @version 1.0
* @package Email Subscribers
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Passed worfklow object
*
* @var ES_Workflow $workflow
*/
?>
<table class="ig-es-table">
<tr class="ig-es-table__row">
<td class="ig-es-table__col">
<label class="ig-es-label"><?php echo esc_html__( 'Workflow priority', 'email-subscribers' ); ?></label>
<?php
$field = new ES_Number();
$field->set_name( 'ig_es_workflow_data[priority]' );
$field->render( $workflow ? $workflow->priority : '' );
?>
</td>
</tr>
</table>

View File

@@ -0,0 +1,126 @@
<?php
/**
* Admin workflow rules metabox
*
* @since 5.5.0
* @version 1.0
* @package Email Subscribers
*/
/**
* Worfklow object
*
* @var ES_Workflow $workflow
*
* All available rules
* @var array $all_rules
*
* Workflow rules
* @var array $workflow_rules
*
* Workflow trigger
* @var ES_Workflow_Trigger | false $selected_trigger
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
$supplied_data_items = array();
if ( is_callable( array( $selected_trigger, 'get_supplied_data_items' ) ) ) {
$supplied_data_items = $selected_trigger->get_supplied_data_items();
}
$trigger_name = array();
if ( is_callable( array( $selected_trigger, 'get_name' ) ) ) {
$trigger_name = $selected_trigger->get_name();
}
?>
<div id="ig-es-rules-container"></div>
<script>
let igEsWorkflowRules =
<?php
echo wp_json_encode( array(
'all_rules' => $all_rules,
'workflow_rules' => $workflow_rules,
'supplied_data_items' => $supplied_data_items,
'trigger_name' => $trigger_name
) );
?>
;
</script>
<div class="ig-es-metabox-footer rules-metabox-footer hidden">
<button type="button"
class="ig-es-add-rule-group inline-flex justify-center rounded-md border border-transparent px-4 py-1.5 bg-white text-sm leading-5 font-medium text-white bg-indigo-600 hover:bg-indigo-500 focus:outline-none focus:shadow-outline-blue transition ease-in-out duration-150"><?php echo esc_html__( '+ Add rule group', 'email-subscribers' ); ?>
</button>
</div>
<div class="hidden" id="rule-template-container">
<p class="ig-es-rules-empty-message px-4 py-1.5">
<?php
printf(
/* translators: 1: HTML Tag 2: HTML Tag */
esc_attr__( 'Rules can be used to add conditional logic to workflows. Click the %1$s+ Add rule group%2$s button to create a rule.', 'email-subscribers' ), '<strong>', '</strong>'
);
?>
</p>
<div class="ig-es-no-rules-message px-4 py-3">
<span class="inline-block">
<?php
esc_attr_e( 'Selected trigger does not have any rules.', 'email-subscribers' );
?>
</span>
<?php
if ( ! ES()->is_pro() ) {
$utm_args = array(
'utm_medium' => 'workflow_rules',
);
$pricing_url = ES_Common::get_utm_tracking_url( $utm_args );
?>
<span class="workflow-rules-upgrade-to-max-message">
<?php
/* translators: 1: HTML Tag 2: HTML Tag */
printf( esc_attr__( 'Upgrade to %1$sMAX%2$s to get finer control with additional rules.', 'email-subscribers' ), '<a href="' . esc_url( $pricing_url ) . '" target="_blank"><strong>', '</strong></a>' );
?>
</span>
<?php
}
?>
</div>
<input type="text" disabled class="ig-es-field rule-value-text-field border-gray-400">
<select class="ig-es-field rule-value-object-field" data-placeholder="" data-action=""></select>
<select class="ig-es-field rule-value-select-field ig-es-field--type-select"></select>
<div class="ig-es-rule-group px-4 py-1.5">
</div>
<div class="ig-es-rule-container inline-flex mt-3 mb-3">
<div class="ig-es-rule__fields inline-flex">
<div class="ig-es-rule-select-container ig-es-rule__field-container pr-3">
<select name="" class="rule-select-field ig-es-field" disabled>
<option value=""><?php esc_attr_e( '[Select Rule]', 'email-subscribers' ); ?></option>
</select>
</div>
<div class="ig-es-rule-field-compare ig-es-rule__field-container pr-3">
<select name="" class="ig-es-field rule-compare-field" disabled>
</select>
</div>
<div class="ig-es-rule-field-value ig-es-rule__field-container pr-3">
<input type="text" disabled class="ig-es-field rule-value-field border-gray-400">
<input type="number" disabled class="ig-es-field rule-value-number-field border-gray-400">
</div>
</div>
<div class="ig-es-rule__buttons inline-flex">
<button type="button"
class="add-rule ig-es-rule__add button h-5"><?php esc_html_e( 'and', 'email-subscribers' ); ?></button>
<button type="button"
class="remove-rule ig-es-rule__remove text-red-600 mx-3 h-5 py-1"><span class="dashicons dashicons-remove"></span></button>
</div>
</div>
</div>

View File

@@ -0,0 +1,72 @@
<?php
/**
* Admin workflow save metabox
*
* @since 4.4.1
* @version 1.0
* @package Email Subscribers
*/
?>
<div class="submitbox" id="submitpost">
<table class="ig-es-table">
<tr class="ig-es-table__row">
<td class="ig-es-table__col">
<div class="ig-es-input-group__input">
<?php
if ( $workflow ) {
$workflow_status = $workflow->is_active() ? 1 : 0;
} else {
$workflow_status = 1;
}
$workflow_status_field = new ES_Select( false );
$workflow_status_field->set_name( 'ig_es_workflow_data[status]' );
$workflow_status_field->set_options(
array(
0 => __( 'Inactive', 'email-subscribers' ),
1 => __( 'Active', 'email-subscribers' ),
)
);
$workflow_status_field->render( $workflow_status );
?>
</div>
</td>
</tr>
</table>
<div id="major-publishing-actions">
<?php
if ( $workflow ) :
$workflow_id = $workflow->get_id();
$nonce = wp_create_nonce( 'es_post_workflow' );
?>
<div id="delete-action">
<?php
echo sprintf( '<a class="submitdelete deletion" href="?page=%s&action=%s&id=%s&_wpnonce=%s" onclick="return checkDelete()">%s</a>', esc_attr( 'es_workflows' ), 'delete', esc_attr( $workflow_id ), esc_attr( $nonce ), esc_html__( 'Delete', 'email-subscribers' ) );
?>
</div>
<?php
endif;
?>
<div id="publishing-action">
<?php
$is_runnable = false;
if ( $workflow ) :
$is_runnable = $workflow->is_runnable();
endif;
$tooltip_html = ES_Common::get_tooltip_html( __( 'Performs add to list action on existing orders that match trigger conditions.', 'email-subscribers' ) );
$allowed_tags = ig_es_allowed_html_tags_in_esc();
?>
<label id="run-workflow-checkbox-wrapper" class="<?php echo esc_attr( ! $is_runnable ? 'hidden' : '' ); ?>">
<input type="checkbox" class="form-checkbox " name="run_workflow" value="yes">
<span class="pr-3 text-gray-500 text-sm font-normal text-left">
<?php echo esc_html__( 'Run now', 'email-subscribers' ); ?>
<?php echo wp_kses( $tooltip_html, $allowed_tags ); ?>
</span>
</label>
<button type="submit" id="publish" name="save_workflow" value="save" class="inline-flex justify-center rounded-md border border-transparent px-4 py-1 bg-white text-sm leading-5 font-medium text-white bg-indigo-600 hover:bg-indigo-500 focus:outline-none focus:shadow-outline-blue transition ease-in-out duration-150"><?php echo esc_html__( 'Save', 'email-subscribers' ); ?></button>
</div>
<div class="clear"></div>
</div>
</div>

View File

@@ -0,0 +1,79 @@
<?php
/**
* Admin workflow timing metabox
*
* @since 4.4.1
* @version 1.0
* @package Email Subscribers
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Passed worfklow object
*
* @var ES_Workflow $workflow
*/
$option_base = 'ig_es_workflow_data[workflow_options]';
?>
<table class="ig-es-table">
<tr class="ig-es-table__row">
<td class="ig-es-table__col">
<?php
$field = new ES_Select( false );
$field
->set_name_base( $option_base )
->set_name( 'when_to_run' )
->set_options(
array(
'immediately' => __( 'Run immediately', 'email-subscribers' ),
'delayed' => __( 'Delayed', 'email-subscribers' ),
)
)
->add_data_attr( 'ig-es-bind', 'timing' )
->render( $workflow ? $workflow->get_timing_type() : '' );
?>
</td>
</tr>
<tr class="ig-es-table__row" data-ig-es-show="timing=delayed">
<td class="ig-es-table__col">
<div class="field-cols">
<label class="ig-es-label"><?php esc_html_e( 'Length of the delay', 'email-subscribers' ); ?>
</label>
<div class="col-1">
<?php
$run_delay_value = new ES_Number();
$run_delay_value
->set_name_base( $option_base )
->set_name( 'run_delay_value' )
->set_min( '0' )
->add_extra_attr( 'step', 'any' )
->render( $workflow ? $workflow->get_option( 'run_delay_value' ) : '' );
?>
</div>
<div class="col-2">
<?php
$run_delay_unit = new ES_Select();
$run_delay_unit
->set_name_base( $option_base )
->set_name( 'run_delay_unit' )
->set_options(
[
'h' => __( 'Hours', 'email-subscribers' ),
'm' => __( 'Minutes', 'email-subscribers' ),
'd' => __( 'Days', 'email-subscribers' ),
'w' => __( 'Weeks', 'email-subscribers' ),
]
)
->render( $workflow ? $workflow->get_option( 'run_delay_unit' ) : '' );
?>
</div>
</div>
</td>
</tr>
</table>

View File

@@ -0,0 +1,143 @@
<?php
/**
* Admin trigger metabox
*
* @since 4.4.1
* @version 1.0
* @package Email Subscribers
*/
// Group triggers.
$trigger_list = array();
foreach ( ES_Workflow_Triggers::get_all() as $trigger ) {
if ( $trigger instanceof ES_Workflow_Trigger ) {
$trigger_list[ $trigger->get_group() ][ $trigger->get_name() ] = $trigger;
}
}
if ( ! ES()->is_starter() ) {
$starter_trigger_list = array(
'Comment' => array(
'ig_es_comment_added' => __( 'Comment Added', 'email-subscribers' ),
),
'Form' => array(
'ig_es_cf7_submitted' => __( 'Contact Form 7 Submitted', 'email-subscribers' ),
'ig_es_wpforms_submitted' => __( 'WP Form Submitted', 'email-subscribers' ),
'ig_es_ninja_forms_submitted' => __( 'Ninja Form Submitted', 'email-subscribers' ),
'ig_es_gravity_forms_submitted' => __( 'Gravity Form Submitted', 'email-subscribers' ),
'ig_es_forminator_forms_submitted' => __( 'Forminator Form Submitted', 'email-subscribers' ),
'ig_es_formidable_forms_submitted' => __( 'Formidable Form Submitted', 'email-subscribers' ),
),
'Order' => array(
'ig_es_wc_order_completed' => __( 'WooCommerce Order Completed', 'email-subscribers' ),
'ig_es_edd_complete_purchase' => __( 'EDD Purchase Completed', 'email-subscribers' ),
'ig_es_give_donation_made' => __( 'Give Donation Added', 'email-subscribers' ),
),
);
$trigger_list = array_merge_recursive( $trigger_list, $starter_trigger_list );
}
if ( ! ES()->is_pro() ) {
$pro_trigger_list = array(
'Comment' => array(
'ig_es_wc_product_review_approved' => __( 'New Product Review Posted', 'email-subscribers' ),
),
'Order' => array(
'ig_es_wc_order_refunded' => __( 'WooCommerce Order Refunded', 'email-subscribers' ),
),
'Wishlists' => array(
'ig_es_yith_wc_wishlist' => __( 'Wishlist Item On Sale (YITH Wishlists)', 'email-subscribers' ),
),
'Carts' => array(
'ig_es_wc_cart_abandoned' => __( 'Cart Abandoned', 'email-subscribers' ),
'ig_es_wc_cart_abandoned_registered_users' => __( 'Cart Abandoned - Registered Users Only', 'email-subscribers' ),
'ig_es_wc_cart_abandoned_guests_users' => __( 'Cart Abandoned - Guests Only', 'email-subscribers' ),
),
'User' => array(
'ig_es_user_role_changed' => __( 'User Role Changed', 'email-subscribers' ),
),
'LearnDash' => array(
'ig_es_ld_user_enrolled' => __( 'User enrolled in course', 'email-subscribers' ),
'ig_es_ld_user_removed' => __( 'User removed from a course', 'email-subscribers' ),
),
'Ultimate Member' => array(
'ig_es_um_membership_approved' => __( 'Membership Approved', 'email-subscribers' ),
'ig_es_um_membership_deactivated' => __( 'Membership Deactivated', 'email-subscribers' ),
),
'Paid Memberships Pro' => array(
'ig_es_pmp_membership_purchased' => __( 'Membership Purchased', 'email-subscribers' ),
'ig_es_pmp_membership_expired' => __( 'Membership Expired', 'email-subscribers' ),
'ig_es_pmp_membership_canceled' => __( 'Membership Canceled', 'email-subscribers' ),
),
'MemberPress' => array(
'ig_es_mp_one_time_product_purchased' => __( 'Product Purchased - One Time', 'email-subscribers' ),
'ig_es_mp_recurring_product_purchased' => __( 'Product Purchased - Recurring', 'email-subscribers' ),
'ig_es_mp_membership_expired' => __( 'Membership Expired', 'email-subscribers' ),
'ig_es_mp_membership_canceled' => __( 'Membership Canceled', 'email-subscribers' ),
),
'WooCommerce Memberships' => array(
'ig_es_wcm_membership_created' => __( 'Membership Created', 'email-subscribers' ),
'ig_es_wcm_membership_expired' => __( 'Membership Expired', 'email-subscribers' ),
'ig_es_wcm_membership_canceled' => __( 'Membership Canceled', 'email-subscribers' ),
),
'BuddyBoss' => array(
'es_trigger_buddyboss_user_account_activated' => __( 'Buddyboss user activated', 'email-subscribers' ),
'es_trigger_buddyboss_user_account_suspended' => __( 'Buddyboss user suspended', 'email-subscribers' ),
'es_trigger_buddyboss_user_account_unsuspended' => __( 'Buddyboss user unsuspended', 'email-subscribers' ),
),
);
$trigger_list = array_merge_recursive( $trigger_list, $pro_trigger_list );
}
?>
<table class="ig-es-table">
<tr class="ig-es-table__row" data-name="trigger_name" data-type="select"
data-required="1">
<td class="ig-es-table__col ig-es-table__col--label">
<label><?php esc_html_e( 'Trigger', 'email-subscribers' ); ?> <span class="required">*</span></label>
</td>
<td class="ig-es-table__col ig-es-table__col--field">
<select name="ig_es_workflow_data[trigger_name]" class="ig-es-field js-trigger-select" required>
<option value=""><?php esc_html_e( '[Select]', 'email-subscribers' ); ?></option>
<?php foreach ( $trigger_list as $trigger_group => $triggers ) : ?>
<optgroup label="<?php echo esc_attr( $trigger_group ); ?>">
<?php
foreach ( $triggers as $trigger_name => $_trigger ) :
if ( $_trigger instanceof ES_Workflow_Trigger ) :
?>
<option value="<?php echo esc_attr( $_trigger->get_name() ); ?>" <?php echo esc_attr( $current_trigger && $current_trigger->get_name() === $trigger_name ? 'selected="selected"' : '' ); ?>><?php echo esc_html( $_trigger->get_title() ); ?></option>
<?php
elseif ( is_string( $_trigger ) ) :
?>
<option value="<?php echo esc_attr( $trigger_name ); ?>" disabled><?php echo esc_html( $_trigger ); ?></option>
<?php
endif;
endforeach;
?>
</optgroup>
<?php endforeach; ?>
</select>
<div class="js-trigger-description">
<?php if ( $current_trigger && $current_trigger->get_description() ) : ?>
<?php echo wp_kses_post( $current_trigger->get_description_html() ); ?>
<?php endif; ?>
</div>
</td>
</tr>
<?php
if ( $workflow ) {
ES_Workflow_Admin::get_view(
'trigger-fields',
array(
'trigger' => $current_trigger,
'workflow' => $workflow,
'fill_fields' => true,
)
);
}
?>
</table>

View File

@@ -0,0 +1,54 @@
<?php
/**
* ES_Workflow object
*
* @var ES_Workflow $workflow
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
$active_page = ( isset( $_GET['page'] ) ) ? sanitize_text_field( $_GET['page'] ) : '';
$workflow_action = ( isset( $_GET['action'] ) ) ? sanitize_text_field( $_GET['action'] ) : '';
$show_variables_list = ( 'es_workflows' === $active_page && 'new' !== $workflow_action );
?>
<div id="ig-es-variable-info-popup" style="display:none">
<div class="fixed flex inset-0 overflow-x-hidden overflow-y-auto z-50 flex justify-center w-full h-full" style="background-color: rgba(0,0,0,.5);">
<section class="absolute flex justify-center mx-auto md:mx-auto lg:mx-auto my-12 sm:my-12 lg:my-24">
<div
class="inline-block overflow-hidden text-left align-bottom transition-all transform bg-white rounded-lg shadow-xl sm:my-8 sm:align-middle sm:max-w-lg sm:w-full"
role="dialog"
aria-modal="true"
aria-labelledby="modal-headline"
>
<span class="ig-es-close-variable-info-popup cursor-pointer"><svg class="mt-1 w-6 h-6 text-red-500" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg></span>
<div id="ig-es-workflow-variable-info-body" class="px-4 pt-5 pb-4 bg-white sm:p-6 sm:pb-4 ig-es-loading" data-loader="<?php echo esc_url( ES_PLUGIN_URL ); ?>lite/admin/images/spinner-2x.gif">
</div>
</div>
</section>
</div>
</div>
<table class="ig-es-table">
<tr class="ig-es-table__row">
<td class="ig-es-table__col">
<div id="ig-es-workflow-variables-container" class="ig-es-workflow-variables-container">
<?php foreach ( IG_ES_Variables::get_list() as $data_type => $vars ) : ?>
<div class="ig-es-variables-group py-1 <?php echo esc_attr( $show_variables_list ? '' : 'hidden' ); ?>" data-ig-es-variable-group="<?php echo esc_attr( $data_type ); ?>">
<?php foreach ( $vars as $variable => $file_path ) : ?>
<span class="ig-es-workflow-variable-outer inline-block items-center justify-center px-2 py-2 mr-2 mb-2 text-xs font-bold leading-none bg-gray-100 hover:bg-gray-300 rounded-full">
<span class="ig-es-workflow-variable cursor-pointer" data-ig-es-variable-slug="<?php echo esc_attr( $data_type . '.' . $variable ); ?>">
<?php echo esc_html( $data_type . '.' . $variable ); ?>
</span>
</span>
<?php endforeach; ?>
<hr>
</div>
<?php endforeach; ?>
<p class="js-ig-es-no-variables-message" style="display:<?php echo esc_attr( $show_variables_list ? 'none' : 'block' ); ?>;">
<?php echo esc_html__( 'Sorry, no placeholder tags are available for this trigger', 'email-subscribers' ); ?>
</p>
</div>
</td>
</tr>
</table>

View File

@@ -0,0 +1,79 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* IG_ES_Variable object
*
* @var IG_ES_Variable $variable
*/
?>
<div class="ig-es-modal__header text-center">
<h1><?php echo esc_html( $variable->get_name() ); ?></h1>
</div>
<div class="ig-es-modal__body">
<div class="ig-es-modal__body-inner pt-2">
<?php if ( $variable->get_description() ) : ?>
<p class="mb-2"><?php echo esc_html( $variable->get_description() ); ?></p>
<?php endif; ?>
<table class="ig-es-table ig-es-table--bordered ig-es-workflow-variable-parameters-table">
<?php foreach ( $variable->get_parameter_fields() as $field ) : ?>
<tr class="ig-es-table__row ig-es-workflow-variables-parameter-row"
data-parameter-name="<?php echo esc_attr( $field->get_name() ); ?>"
<?php
if ( isset( $field->meta['show'] ) ) :
?>
data-parameter-show="<?php echo esc_attr( $field->meta['show'] ); ?>"<?php endif; ?>
<?php echo ( $field->get_required() ? 'data-is-required="true"' : '' ); ?>
>
<td class="ig-es-table__col ig-es-table__col--label">
<strong><?php echo esc_html( ucfirst( $field->get_name() ) ); ?></strong>
<?php
if ( $field->get_required() ) :
?>
<span class="required">*</span><?php endif; ?>
</td>
<td class="ig-es-table__col ig-es-table__col--field">
<?php $field->add_classes( 'ig-es-workflow-variable-parameter' ); ?>
<?php $field->render( '' ); ?>
<p class="field-desciption mb-2 text-xs italic font-normal leading-snug text-gray-500 helper">
<?php echo $field->get_description() ? esc_html( $field->get_description() ) : ''; ?>
</p>
</td>
</tr>
<?php endforeach; ?>
<?php if ( $variable->use_fallback ) : ?>
<tr class="ig-es-table__row">
<td class="ig-es-table__col ig-es-table__col--label">
<strong><?php echo esc_html__( 'Fallback', 'email-subscribers' ); ?></strong>
</td>
<td class="ig-es-table__col ig-es-table__col--field">
<input type="text" name="fallback" class="ig-es-field ig-es-field--type-text ig-es-workflow-variable-parameter">
<p class="field-desciption mb-2 text-xs italic font-normal leading-snug text-gray-500 helper">
<?php echo esc_html__( 'Entered text is displayed when there is no value found.', 'email-subscribers' ); ?>
</p>
</td>
</tr>
<?php endif; ?>
</table>
<div class="ig-es-workflow-variable-clipboard-form">
<div id="ig_es_workflow_variable_preview_field" class="ig-es-workflow-variable-preview-field w-full p-3 text-center mt-2 bg-gray-100 ig-es-workflow-variable-preview-field hidden" data-variable="<?php echo esc_attr( $variable->get_name() ); ?>">
</div>
<button type="button" class="mt-2 ig-es-clipboard-btn w-full inline-flex justify-center rounded-md border border-transparent px-4 py-1 bg-white text-sm leading-5 font-medium text-white bg-indigo-600 hover:bg-indigo-500 focus:outline-none focus:shadow-outline-blue transition ease-in-out duration-150"><?php echo esc_html__( 'Copy to clipboard', 'email-subscribers' ); ?></button>
</div>
</div>
</div>

View File

@@ -0,0 +1,72 @@
<?php
// phpcs:ignoreFile
/**
* Can be loaded by ajax
*
* @var $workflow Workflow
* @var $trigger Trigger
* @var $fill_fields (optional)
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
// default to false
if ( ! isset( $fill_fields ) ) {
$fill_fields = false;
}
if ( ! $trigger ) {
return;
}
// if we're populating field values, get the trigger object from the workflow
// Otherwise just use the unattached trigger object
if ( $fill_fields ) {
$trigger = $workflow->get_trigger();
}
$fields = $trigger->get_fields();
?>
<?php
foreach ( $fields as $field ) :
if ( $fill_fields ) {
$value = $workflow->get_trigger_option( $field->get_name() );
} else {
$value = null;
}
?>
<tr class="ig-es-table__row ig-es-trigger-option"
data-name="name"
data-type="<?php echo esc_html( $field->get_type() ); ?>"
data-required="<?php echo (int) $field->get_required(); ?> ">
<td class="ig-es-table__col ig-es-table__col--label">
<?php echo esc_html( $field->get_title() ); ?>
<?php if ( $field->get_required() ) : ?>
<span class="required">*</span>
<?php endif; ?>
</td>
<td class="ig-es-table__col ig-es-table__col--field">
<?php $field->render( $value ); ?>
<?php if ( $field->get_description() ) : ?>
<p class="ig-es-field-description">
<?php echo esc_html( $field->get_description() ); ?>
</p>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>

View File

@@ -0,0 +1,145 @@
<?php
/**
* Container class for sanitizer functions.
*
* @since 4.4.1
* @version 1.0
* @package Email Subscribers
*/
/**
* Sanitizer class
*
* @class ES_Clean
* @since 4.4.1
*/
class ES_Clean {
/**
* Sanitizes a text string.
*
* @since 4.4.1
*
* @param string $string input string.
*
* @return string
*/
public static function string( $string ) {
return sanitize_text_field( $string );
}
/**
* Sanitizes a email string.
*
* @since 4.4.1
*
* @param string $email input email.
*
* @return string
*/
public static function email( $email ) {
return strtolower( sanitize_email( $email ) );
}
/**
* Sanitize a multi-line string. Will strip HTML tags.
*
* @since 4.4.1
*
* @param string $text input text string.
*
* @return string
*/
public static function textarea( $text ) {
return implode( "\n", array_map( 'sanitize_text_field', explode( "\n", $text ) ) );
}
/**
* Sanitize array of
*
* @since 4.4.1
*
* @param array $var input array.
*
* @return array
*/
public static function ids( $var ) {
if ( is_array( $var ) ) {
return array_filter( array_map( 'absint', $var ) );
} elseif ( is_numeric( $var ) ) {
return array( absint( $var ) );
}
return array();
}
/**
* Sanitize a numeric string into an integer
*
* @since 4.4.1
*
* @param string|int $id ID.
*
* @return int
*/
public static function id( $id ) {
return absint( $id );
}
/**
* Sanitize an array of input text
*
* @since 4.4.1
*
* @param mixed $var input array/text.
*
* @return array|string
*/
public static function recursive( $var ) {
if ( is_array( $var ) ) {
return array_map( array( 'ES_Clean', 'recursive' ), $var );
} else {
return is_scalar( $var ) ? self::string( $var ) : $var;
}
}
/**
* HTML encodes emoji's in string or array.
*
* @since 4.4.1
*
* @param string|array $data input.
*
* @return string|array
*/
public static function encode_emoji( $data ) {
if ( is_array( $data ) ) {
foreach ( $data as &$field ) {
if ( is_array( $field ) || is_string( $field ) ) {
$field = self::encode_emoji( $field );
}
}
} elseif ( is_string( $data ) ) {
$data = wp_encode_emoji( $data );
}
return $data;
}
/**
* Performs a basic sanitize for editor content permitting all HTML.
*
* @param string $content
*
* @return string $content
*
* @since 4.5.3
*/
public static function editor_content( $content ) {
$content = wp_check_invalid_utf8( stripslashes( (string) $content ) );
return $content;
}
}

View File

@@ -0,0 +1,215 @@
<?php
/**
* Class to format data according to workflows
*
* @class ES_Format
* @since 4.4.0
*/
class ES_Format {
const MYSQL = 'Y-m-d H:i:s';
/**
* Get date/time in human readable format from passed date time
*
* @param int|string|DateTime| $date
* @param bool|int $max_diff Set to 0 to disable diff format
* @param bool $convert_from_gmt If its gmt convert it to local time
* @param bool $shorten_month
*
* @since 4.4.1 $shorten_month param added
*
* @return string|false
*/
public static function datetime( $date, $max_diff = false, $convert_from_gmt = true, $shorten_month = false ) {
$timestamp = self::mixed_date_to_timestamp( $date );
if ( ! $timestamp ) {
return false; // convert to timestamp ensures WC_DateTime objects are in UTC
}
if ( $convert_from_gmt ) {
$timestamp = strtotime( get_date_from_gmt( gmdate( self::MYSQL, $timestamp ), self::MYSQL ) );
}
$now = current_time( 'timestamp' );
if ( false === $max_diff ) {
$max_diff = DAY_IN_SECONDS; // set default
}
$diff = $timestamp - $now;
if ( abs( $diff ) >= $max_diff ) {
$time_format = get_option( 'time_format' );
$date_to_display = date_i18n( self::get_date_format( $shorten_month ) . ' ' . $time_format, $timestamp );
return $date_to_display;
}
return self::human_time_diff( $timestamp );
}
/**
* Get human readable date from passed date
*
* @param int|string|DateTime| $date
* @param bool|int $max_diff
* @param bool $convert_from_gmt If its gmt convert it to local time
* @param bool $shorten_month
*
* @since 4.1.1 $shorten_month param added
*
* @return string|false
*/
public static function date( $date, $max_diff = false, $convert_from_gmt = true, $shorten_month = false ) {
$timestamp = self::mixed_date_to_timestamp( $date );
if ( ! $timestamp ) {
return false; // convert to timestamp ensures WC_DateTime objects are in UTC
}
if ( $convert_from_gmt ) {
$timestamp = strtotime( get_date_from_gmt( gmdate( self::MYSQL, $timestamp ), self::MYSQL ) );
}
$now = current_time( 'timestamp' );
if ( false === $max_diff ) {
$max_diff = WEEK_IN_SECONDS; // set default
}
$diff = $timestamp - $now;
if ( abs( $diff ) >= $max_diff ) {
$date_to_display = date_i18n( self::get_date_format( $shorten_month ), $timestamp );
return $date_to_display;
}
return self::human_time_diff( $timestamp );
}
/**
* Get date format from site settings
*
* @since 4.4.1
* @param bool $shorten_month
* @return string
*/
public static function get_date_format( $shorten_month = false ) {
$format = get_option( 'date_format' );
if ( $shorten_month ) {
$format = str_replace( 'F', 'M', $format );
}
return $format;
}
/**
* Get human readable date from passed timestamp
*
* @param integer $timestamp
* @return string
*/
private static function human_time_diff( $timestamp ) {
$now = current_time( 'timestamp' );
$diff = $timestamp - $now;
if ( $diff < 55 && $diff > -55 ) {
/* translators: %d: time difference in second %d: time difference in seconds */
$diff_string = sprintf( _n( '%d second', '%d seconds', abs( $diff ), 'email-subscribers' ), abs( $diff ) );
} else {
$diff_string = human_time_diff( $now, $timestamp );
}
if ( $diff > 0 ) {
/* translators: %s: time difference */
return sprintf( __( '%s from now', 'email-subscribers' ), $diff_string );
} else {
/* translators: %s: time difference */
return sprintf( __( '%s ago', 'email-subscribers' ), $diff_string );
}
}
/**
* Convert date object/string to timestamp
*
* @param int|string|DateTime $date
* @return int|bool
*/
public static function mixed_date_to_timestamp( $date ) {
if ( ! $date ) {
return false;
}
$timestamp = 0;
if ( is_numeric( $date ) ) {
$timestamp = $date;
} else {
if ( is_a( $date, 'DateTime' ) ) { // maintain support for \DateTime
$timestamp = $date->getTimestamp();
} elseif ( is_string( $date ) ) {
$timestamp = strtotime( $date );
}
}
if ( $timestamp < 0 ) {
return false;
}
return $timestamp;
}
/**
* Get weekday number from day.
*
* @param integer $day - 1 (for Monday) through 7 (for Sunday)
* @return string|false
*/
public static function weekday( $day ) {
global $wp_locale;
$days = array(
1 => $wp_locale->get_weekday( 1 ),
2 => $wp_locale->get_weekday( 2 ),
3 => $wp_locale->get_weekday( 3 ),
4 => $wp_locale->get_weekday( 4 ),
5 => $wp_locale->get_weekday( 5 ),
6 => $wp_locale->get_weekday( 6 ),
7 => $wp_locale->get_weekday( 0 ),
);
if ( ! isset( $days[ $day ] ) ) {
return false;
}
return $days[ $day ];
}
/**
* Format a price decimal value.
*
* Does NOT localize the decimal.
*
* @param float|string $number
* @param int $places
* @param bool $trim_zeros
*
* @return string
*/
public static function decimal( $number, $places = null, $trim_zeros = false ) {
if ( null === $places ) {
$places = wc_get_price_decimals();
}
return wc_format_decimal( $number, $places, $trim_zeros );
}
}

View File

@@ -0,0 +1,233 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
class ES_Workflow_Action_Preview {
/**
* Get email preview
*
* @param $trigger_name
* @param $action
*
* @return string|null
*/
public static function get_preview( $trigger_name, $action ) {
try {
$workflow = self::load_preview_workflow( $trigger_name, $action );
$trigger = ES_Workflow_Triggers::get( $trigger_name );
$required_items = $trigger->get_supplied_data_items();
$workflow->set_data_layer( self::get_preview_data_layer( $required_items ), false );
$workflow_action = $workflow->get_action( 1 );
$workflow_action->workflow = $workflow;
return $workflow_action->load_preview();
} catch ( Exception $exception ) {
return $exception->getMessage();
}
}
/**
* Initialize Workflow dynamically for preview purpose
*
* @param $trigger_name
* @param $action
*
* @return ES_Workflow
*/
public static function load_preview_workflow( $trigger_name, $action ) {
$workflow_object = new stdClass();
$workflow_object->id = 0;
$workflow_object->name = 'action-preview';
$workflow_object->title = __( 'Action Preview', 'email-subscribers' );
$workflow_object->trigger_name = $trigger_name;
$workflow_object->trigger_options = array();
$workflow_object->rules = array();
$workflow_object->actions = array( $action );
$workflow_object->meta = array();
$workflow_object->status = 'draft';
$workflow_object->priority = 0;
$workflow_object->created_at = current_time( 'mysql', true );
$workflow_object->updated_at = current_time( 'mysql', true );
$workflow = new ES_Workflow( $workflow_object );
$workflow->preview_mode = true;
return $workflow;
}
/**
* Get data layer for preview
*
* @param array $required_items
* @return array $data_layer
*
* @throws Exception
*/
public static function get_preview_data_layer( $required_items = [] ) {
$data_layer = [];
if ( in_array( 'user', $required_items ) ) {
$data_layer['user'] = wp_get_current_user();
}
if ( in_array( 'customer', $required_items ) ) {
$data_layer['customer'] = IG_ES_Customer_Factory::get_by_user_id( get_current_user_id() );
}
/**
* Order and order item
*/
if ( in_array( 'wc_order', $required_items ) || in_array( 'order_item', $required_items ) ) {
$order = self::get_preview_order();
$order_items = $order->get_items();
if ( empty( $order_items ) ) {
throw new Exception( __( 'A valid "Order items" must exist to generate the preview.', 'email-subscribers' ) );
}
$data_layer['wc_order'] = $order;
$data_layer['order_item'] = current( $order_items );
}
/**
* Product
*/
if ( in_array( 'product', $required_items ) ) {
$product_ids = self::get_preview_product_ids();
$data_layer['product'] = wc_get_product( $product_ids[0] );
}
/**
* Cart
*/
if ( in_array( 'cart', $required_items ) ) {
$cart = new IG_ES_Cart();
$cart->set_id( 1 );
$cart->set_total( 100 );
$cart->set_user_id( get_current_user_id() );
$cart->set_token();
$cart->set_date_last_modified( new DateTime() );
$items = [];
foreach ( self::get_preview_product_ids() as $product_id ) {
$product = wc_get_product( $product_id );
// Reject products that can't be purchased
if ( ! $product->is_purchasable() ) {
continue;
}
$variation_id = 0;
$variation = [];
if ( $product->is_type( 'variable' ) ) {
$variations = $product->get_available_variations();
if ( $variations ) {
$variation_id = $variations[0]['variation_id'];
$variation = $variations[0]['attributes'];
}
}
$items[ uniqid() ] = [
'product_id' => $product_id,
'variation_id' => $variation_id,
'variation' => $variation,
'quantity' => 1,
'line_subtotal' => (float) $product->get_price(),
'line_subtotal_tax' => (float) wc_get_price_including_tax( $product ) - (float) $product->get_price(),
];
}
$cart->set_items( $items );
$cart->set_coupons( [
'10off' => [
'discount_incl_tax' => '10',
'discount_excl_tax' => '9',
'discount_tax' => '1'
]
] );
$data_layer['cart'] = $cart;
}
/**
* Guest
*/
if ( in_array( 'guest', $required_items ) ) {
$guest = new IG_ES_Guest();
$guest->set_email( 'guest@example.com' );
$data_layer['guest'] = $guest;
}
return apply_filters( 'ig_es_get_preview_data_layer', $data_layer, $required_items );
}
/**
* Get preview products.
*
* @return array
*
* @throws Exception
*/
protected static function get_preview_product_ids() {
// Cache for request since this may be called multiple times
static $products = null;
if ( null === $products ) {
$product_query = new \WP_Query(
[
'post_type' => 'product',
'posts_per_page' => 4,
'fields' => 'ids'
]
);
$products = $product_query->posts;
if ( empty( $products ) ) {
throw new Exception( __( 'A valid "Product" must exist to generate the preview.', 'email-subscribers' ) );
}
}
return $products;
}
/**
* Get an order for preview.
*
* @param int $offset used to do multiple attempts to get a valid order
*
* @return WC_Order
* @throws Exception
*/
protected static function get_preview_order( $offset = 0 ) {
$orders = wc_get_orders(
[
'type' => 'shop_order',
'limit' => 1,
'offset' => $offset,
'return' => 'ids',
]
);
if ( ! $orders ) {
throw new Exception( __( 'A valid "Order" must exist to generate the preview.', 'email-subscribers' ) );
}
$order = wc_get_order( $orders[0] );
// if the order has a blank email, it will cause issues
if ( $order && $order->get_billing_email() ) {
return $order;
}
return self::get_preview_order( $offset + 1 );
}
}

View File

@@ -0,0 +1,120 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Class to load workflow actions
*
* @class ES_Workflow_Actions
* @since 4.4.1
*/
class ES_Workflow_Actions extends ES_Workflow_Registry {
/**
* Registered include classes
*
* @since 4.4.1
* @var array
*/
public static $includes;
/**
* Loaded registered class objects
*
* @var array
*/
public static $loaded = array();
/**
*
* Implement this method in sub classes
*
* @since 4.4.1
* @return array
*/
public static function load_includes() {
$includes = array(
'ig_es_add_to_list' => 'ES_Action_Add_To_List',
'ig_es_delete_contact' => 'ES_Action_Delete_Contact',
'ig_es_update_contact' => 'ES_Action_Update_Contact',
'ig_es_send_email' => 'ES_Action_Send_Email',
'ig_es_update_contact_custom_field' => 'ES_Action_Update_Contact_Custom_Field',
);
return apply_filters( 'ig_es_workflow_actions', $includes );
}
/**
* Get object of specific action class.
*
* @param $action_name string
*
* @return ES_Workflow_Action|false
*
* @since 4.4.1
*/
public static function get( $action_name ) {
static::load( $action_name );
if ( ! isset( static::$loaded[ $action_name ] ) ) {
return false;
}
return static::$loaded[ $action_name ];
}
/**
* Get all registered workflow actions
*
* @return ES_Workflow_Action[]
*
* @since 4.4.1
*/
public static function get_all() {
foreach ( static::get_includes() as $name => $path ) {
static::load( $name );
}
return static::$loaded;
}
/**
* Load action class object by action name
*
* @param $action_name
*
* @since 4.4.1
*/
public static function load( $action_name ) {
if ( static::is_loaded( $action_name ) ) {
return;
}
$action = false;
$includes = static::get_includes();
if ( ! empty( $includes[ $action_name ] ) ) {
/**
* Registered include classes
*
* @since 4.4.1
* @var ES_Workflow_Action $action
*/
$action_class = $includes[ $action_name ];
if ( class_exists( $action_class ) ) {
$action = new $action_class();
$action->set_name( $action_name );
}
}
static::$loaded[ $action_name ] = $action;
}
}

View File

@@ -0,0 +1,324 @@
<?php
/**
* Class to handle data absctraction from worflow trigger
*
* @class ES_Workflow_Data_Layer
*/
class ES_Workflow_Data_Layer {
private $data = array();
/**
* Constructor
*
* @param array $data
*/
public function __construct( $data = array() ) {
if ( is_array( $data ) ) {
$this->data = $data;
}
$this->init();
}
/**
* Initiate the data layer
*/
public function init() {
do_action( 'ig_es_data_layer_init' );
}
public function clear() {
$this->data = array();
}
/**
* Returns unvalidated data layer
*
* @return array
*/
public function get_raw_data() {
return $this->data;
}
/**
* Set data item
*
* @param $type
* @param $item
*/
public function set_item( $type, $item ) {
$this->data[ $type ] = $item;
}
/**
* Get data item
*
* @param string $type
* @return mixed
*/
public function get_item( $type ) {
if ( ! isset( $this->data[ $type ] ) ) {
return false;
}
return ig_es_validate_data_item( $type, $this->data[ $type ] );
}
/**
* Is the data layer missing data?
*
* Data can be missing if it has been deleted e.g. if an order has been trashed.
*
* @since 4.6
*
* @return bool
*/
public function is_missing_data() {
$is_missing = false;
foreach ( $this->get_raw_data() as $data_item ) {
if ( ! $data_item ) {
$is_missing = true;
}
}
return $is_missing;
}
/**
* Get customer object from data layer
*
* @return IG_ES_Customer|false
*/
public function get_customer() {
return $this->get_item( 'customer' );
}
/**
* Gets the customer email based on the data layer.
*
* @return string
*/
public function get_customer_email() {
$customer_email = '';
$customer = $this->get_customer();
if ( $customer ) {
// If the customer has an account always use the account email over a order billing email
// The reason for this is that a customer could change their account email and their
// orders will not be updated.
$customer_email = $customer->get_email();
}
return $customer_email;
}
/**
* Gets the customer billing address 1.
*
* @return string
*/
public function get_customer_address_1() {
$prop = '';
$customer = $this->get_customer();
if ( $customer ) {
$prop = $customer->get_billing_address_1();
}
return $prop;
}
/**
* Gets the customer billing address 2.
*
* @return string
*/
public function get_customer_address_2() {
$prop = '';
$customer = $this->get_customer();
if ( $customer ) {
$prop = $customer->get_billing_address_2();
}
return $prop;
}
/**
* Gets the customer first name based on the data layer.
*
* @return string
*/
public function get_customer_first_name() {
$prop = '';
$customer = $this->get_customer();
if ( $customer ) {
$prop = $customer->get_first_name();
}
return $prop;
}
/**
* Gets the customer last name based on the data layer.
*
* @return string
*/
public function get_customer_last_name() {
$prop = '';
$customer = $this->get_customer();
if ( $customer ) {
$prop = $customer->get_last_name();
}
return $prop;
}
/**
* Gets the customer full name based on the data layer.
*
* @return string
*/
public function get_customer_full_name() {
/* translators: 1. Customer first name 2. Customer last name */
return trim( sprintf( _x( '%1$s %2$s', 'full name', 'email-subscribers' ), $this->get_customer_first_name(), $this->get_customer_last_name() ) );
}
/**
* Gets the customer billing phone.
* Doesn't parse or format.
*
* @return string
*/
public function get_customer_phone() {
$prop = '';
$customer = $this->get_customer();
if ( $customer ) {
$prop = $customer->get_billing_phone();
}
return $prop;
}
/**
* Gets the customer billing company.
*
* @return string
*/
public function get_customer_company() {
$prop = '';
$customer = $this->get_customer();
if ( $customer ) {
$prop = $customer->get_billing_company();
}
return $prop;
}
/**
* Gets the customer billing country code.
*
* @return string
*/
public function get_customer_country() {
$prop = '';
$customer = $this->get_customer();
if ( $customer ) {
$prop = $customer->get_billing_country();
}
return $prop;
}
/**
* Gets the customer billing state.
*
* @return string
*/
public function get_customer_state() {
$prop = '';
$customer = $this->get_customer();
if ( $customer ) {
$prop = $customer->get_billing_state();
}
return $prop;
}
/**
* Gets the customer billing city.
*
* @return string
*/
public function get_customer_city() {
$prop = '';
$customer = $this->get_customer();
if ( $customer ) {
$prop = $customer->get_billing_city();
}
return $prop;
}
/**
* Gets the customer billing postcode.
*
* @return string
*/
public function get_customer_postcode() {
$prop = '';
$customer = $this->get_customer();
if ( $customer ) {
$prop = $customer->get_billing_postcode();
}
return $prop;
}
/**
* Get cart object from data layer
*
* @return IG_ES_Cart|false
*/
public function get_cart() {
return $this->get_item( 'cart' );
}
/**
* Get cart object from data layer
*
* @return IG_ES_Guest|false
*/
public function get_guest() {
return $this->get_item( 'guest' );
}
}

View File

@@ -0,0 +1,76 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Class to load required data types for workflows
*
* @class ES_Data_Types
* @since 4.4.1
*/
class ES_Workflow_Data_Types extends ES_Workflow_Registry {
/**
* Registered include classes
*
* @since 4.4.1
* @var array
*/
public static $includes;
/**
* Loaded registered class objects
*
* @var array
*/
public static $loaded = array();
/**
*
* Get registered data type
*
* @since 4.4.1
* @return array
*/
public static function load_includes() {
return apply_filters(
'ig_es_data_types_includes',
array(
'user' => 'ES_Data_Type_User',
'subscriber' => 'ES_Data_Type_Subscriber',
'campaign' => 'ES_Data_Type_Campaign',
)
);
}
/**
* Get data item from data type
*
* @param $data_type_id
* @return Data_Type|false
*/
public static function get( $data_type_id ) {
return parent::get( $data_type_id );
}
/**
* Set data type in data item
*
* @param string $data_type_id
* @param Data_Type $data_type
*/
public static function after_loaded( $data_type_id, $data_type ) {
$data_type->set_id( $data_type_id );
}
/**
* Get non supported data types
*
* @return array
*/
public static function get_non_stored_data_types() {
return array( 'shop', 'coupon' );
}
}

View File

@@ -0,0 +1,99 @@
<?php
/**
* Helper class for Workflow date time options
*
* @since 4.4.1
* @version 1.0
* @package Email Subscribers
*/
defined( 'ABSPATH' ) || exit;
/**
* Class for Workflow date time options
*
* @class ES_Workflow_DateTime
*
* @since 4.4.1
*/
class ES_Workflow_DateTime extends DateTime {
/**
* Same as parent but forces UTC timezone if no timezone is supplied instead of using the PHP default.
*
* @param string $time time-based string.
* @param \DateTimeZone|string $timezone DateTimeZone object
*
* @throws \Exception Emits Exception in case of an error.
*/
public function __construct( $time = 'now', $timezone = null ) {
if ( ! $timezone ) {
$timezone = new DateTimeZone( 'UTC' );
}
parent::__construct( $time, $timezone instanceof DateTimeZone ? $timezone : null );
}
/**
* Convert ES_Workflow_DateTime from site timezone to UTC.
*
* Note this doesn't actually set the timezone property, it directly modifies the date.
*
* @return $this
*/
public function convert_to_utc_time() {
ES_Workflow_Time_Helper::convert_to_gmt( $this );
return $this;
}
/**
* Convert ES_Workflow_DateTime from UTC to the site timezone.
*
* Note this doesn't actually set the timezone property, it directly modifies the date.
*
* @return $this
*/
public function convert_to_site_time() {
ES_Workflow_Time_Helper::convert_from_gmt( $this );
return $this;
}
/**
* Convert to mysql date time string
*
* @since 4.4.0
*
* @return string
*/
public function to_mysql_string() {
return $this->format( 'Y-m-d H:i:s' );
}
/**
* Set time to the day end in the current timezone.
*
* @return $this
*
* @since 4.4.0
*/
public function set_time_to_day_start() {
$this->setTime( 0, 0, 0 );
return $this;
}
/**
* Set time to the day start in the current timezone.
*
* @return $this
*
* @since 4.4.0
*/
public function set_time_to_day_end() {
$this->setTime( 23, 59, 59 );
return $this;
}
}

View File

@@ -0,0 +1,44 @@
<?php
/**
* Load workflows based on ID
*
* @since 4.4.1
* @version 1.0
* @package Email Subscribers
*/
/**
* Class to load workflows based on ID
*
* @class ES_Workflow_Factory
*
* @since 4.4.1
*/
class ES_Workflow_Factory {
/**
* Method to get workflow from workflow ID
*
* @param int $id Workflow ID.
*
* @return ES_Workflow|false
*
* @since 4.4.1
*/
public static function get( $id ) {
$id = ES_Clean::id( $id );
if ( ! $id ) {
return false;
}
$workflow = new ES_Workflow( $id );
if ( ! $workflow->exists ) {
return false;
}
return $workflow;
}
}

View File

@@ -0,0 +1,122 @@
<?php
// Exit if accessed directly
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
if ( ! class_exists( 'ES_Workflow_Gallery' ) ) {
/**
* The admin-specific functionality of the plugin.
*
* Admin Settings
*
* @package Email_Subscribers
* @subpackage Email_Subscribers/admin
*/
class ES_Workflow_Gallery {
// class instance
public static $instance;
// class constructor
public function __construct() {
self::init();
}
public static function get_instance() {
if ( ! isset( self::$instance ) ) {
self::$instance = new self();
}
return self::$instance;
}
public static function init() {
self::register_hooks();
}
public static function register_hooks() {
add_filter( 'ig_es_workflow_gallery', array( __CLASS__, 'add_workflow_gallery' ) );
add_filter( 'ig_es_workflow_gallery', array( __CLASS__, 'filter_workflow_gallery_items' ), 99 );
}
public static function add_workflow_gallery( $gallery = array() ) {
$item_files_path = untrailingslashit( ES_PLUGIN_DIR . 'lite/includes/workflows/gallery-items/*.php' );
$lite_gallery = self::get_gallery_from_folder( $item_files_path );
if ( ! empty( $lite_gallery ) ) {
$gallery = array_merge( $gallery, $lite_gallery );
}
return $gallery;
}
public static function get_gallery_from_folder( $item_path = '' ) {
global $ig_es_tracker;
$gallery = array();
if ( ! empty( $item_path ) ) {
$item_files = glob( $item_path );
if ( is_array( $item_files ) && count( $item_files ) ) {
foreach ( $item_files as $file ) {
if ( is_file( $file ) && is_admin() ) {
$file_name = basename( $file, '.php' );
$item_data = include $file;
if ( ! empty( $item_data['required_plugins'] )) {
$required_plugins = $item_data['required_plugins'];
$required_plugins_active = true;
foreach ( $required_plugins as $required_plugin ) {
if ( ! $ig_es_tracker::is_plugin_activated( $required_plugin ) ) {
$required_plugins_active = false;
break;
}
}
if ( ! $required_plugins_active ) {
continue;
}
$gallery[ $file_name ] = $item_data;
} else {
$gallery[ $file_name ] = $item_data;
}
}
}
}
}
return $gallery;
}
public static function get_workflow_gallery_items() {
$workflow_gallery = apply_filters( 'ig_es_workflow_gallery', array() );
return $workflow_gallery;
}
public static function filter_workflow_gallery_items( $gallery_items = array() ) {
$integration_plugins = ig_es_get_request_data( 'integration-plugins');
if ( ! empty( $integration_plugins ) && ! empty( $gallery_items ) ) {
$integration_plugins = explode( ',', $integration_plugins );
foreach ( $gallery_items as $item_index => $gallery_item ) {
$required_plugins = ! empty( $gallery_item['required_plugins'] ) ? $gallery_item['required_plugins'] : array();
$is_integration_plugin_active = ! empty( array_intersect( $required_plugins, $integration_plugins ) );
if ( ! $is_integration_plugin_active ) {
unset( $gallery_items[ $item_index ] );
}
}
}
return $gallery_items;
}
}
}

View File

@@ -0,0 +1,67 @@
<?php
/**
* Loads functionalities required for the workflows.
*
* @since 4.4.1
* @version 1.0
* @package Email Subscribers
*/
defined( 'ABSPATH' ) || exit;
/**
*
* Class to loads functionalities required for the workflows.
*
* @class ES_Workflow_Loader
*/
class ES_Workflow_Loader {
/**
* Instance of singleton.
*
* @var ES_Workflow_Loader
*/
private static $instance = null;
/**
* Constructor
*/
private function __construct() {
add_action( 'plugins_loaded', array( &$this, 'load' ), 20 );
}
/**
* Load workflows
*/
public function load() {
// Init all triggers.
// Actions don't load until required by admin interface or when a workflow runs.
ES_Workflow_Triggers::init();
if ( is_admin() ) {
$this->admin = new ES_Workflow_Admin();
ES_Workflow_Admin::init();
if ( ES()->is_pro() ) {
ES_Pro_Workflow_Admin::init();
}
}
}
/**
* Return the singleton instance.
*
* @return ES_Workflow_Loader
*/
public static function instance() {
if ( is_null( self::$instance ) ) {
self::$instance = new self();
}
return self::$instance;
}
}
ES_Workflow_Loader::instance();

View File

@@ -0,0 +1,162 @@
<?php
/**
* Query workflows based on auguements.
*
* @since 4.4.1
* @version 1.0
* @package Email Subscribers
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Class to query workflows based on arguements.
*
* @class Workflow_Query
*
* @since 4.4.1
*/
class ES_Workflow_Query {
/**
* Trigger name
*
* @var string|ES_Workflow_Trigger
*/
public $trigger;
/**
* Trigger names
*
* @var array|ES_Workflow_Trigger
*/
public $triggers;
/**
* Query arguements
*
* @var array
*/
public $args;
/**
* Return result type
*
* @var string
*/
public $return = 'objects';
/**
* Construct
*/
public function __construct() {
$this->args = array(
'status' => IG_ES_WORKFLOW_STATUS_ACTIVE, // Fetch only active workflows.
'type' => array(
IG_ES_WORKFLOW_TYPE_USER, // Fetch user defined workflows.
IG_ES_WORKFLOW_TYPE_SYSTEM, // Fetch system defined workflows.
),
'order' => 'ASC',
'order_by' => 'priority',
);
}
/**
* Set trigger name or array of names to query.
*
* @param string|ES_Workflow_Trigger $trigger Workflow trigger object|name.
*/
public function set_trigger( $trigger ) {
if ( $trigger instanceof ES_Workflow_Trigger ) {
$this->trigger = $trigger->get_name();
} else {
$this->trigger = $trigger;
}
}
/**
* Set trigger name or array of names to query.
*
* @param string|ES_Workflow_Trigger[] $trigger Workflow trigger object|name.
*/
public function set_triggers( $triggers ) {
if ( ! empty( $triggers ) ) {
if ( is_array( $triggers ) ) {
foreach ( $triggers as $trigger ) {
if ( $trigger instanceof ES_Workflow_Trigger ) {
$this->triggers[] = $trigger->get_name();
} else {
$this->triggers[] = $trigger;
}
}
} elseif ( is_string( $triggers ) ) {
$this->triggers[] = $triggers;
}
}
}
/**
* Get workflows by status
*
* @since 4.6.5
* @param int $status
* @return $this
*/
public function where_status( $status ) {
$this->args['status'] = $status;
}
/**
* Set return object
*
* @param objects|ids $return Result format ids or objects.
*/
public function set_return( $return ) {
$this->return = $return;
}
/**
* Get workflows based on query arguements
*
* @return ES_Workflow[] $workflows Workflow object
*/
public function get_results() {
if ( $this->trigger ) {
$this->args['trigger_name'] = $this->trigger;
}
if ( $this->triggers ) {
$this->args['trigger_names'] = $this->triggers;
}
$this->args['fields'] = array();
if ( 'ids' === $this->return ) {
$this->args['fields'][] = 'id';
}
$results = ES()->workflows_db->get_workflows( $this->args );
if ( ! $results ) {
return array();
}
$workflows = array();
foreach ( $results as $post ) {
if ( 'ids' === $this->return ) {
$workflows[] = $post;
} else {
$workflow = new ES_Workflow( $post );
$workflows[] = $workflow;
}
}
return $workflows;
}
}

View File

@@ -0,0 +1,103 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Class to load workflow rules
*
* @class ES_Workflow_Rules
* @since 5.5.0
*/
class ES_Workflow_Rules extends ES_Workflow_Registry {
/**
* Registered include classes
*
* @var array
*/
public static $includes;
/**
* Loaded registered class objects
*
* @var array
*/
public static $loaded = array();
/**
*
* Implement this method in sub classes
*
* @return array
*/
public static function load_includes() {
$includes = array();
return apply_filters( 'ig_es_workflow_rules', $includes );
}
/**
* Get object of specific rule class.
*
* @param $name string
*
* @return ES_Workflow_Rule|false
*
*/
public static function get( $name ) {
static::load( $name );
if ( ! isset( static::$loaded[ $name ] ) ) {
return false;
}
return static::$loaded[ $name ];
}
/**
* Load rule class object by rule name
*
* @param $name
*/
public static function load( $name ) {
if ( static::is_loaded( $name ) ) {
return;
}
$rule = false;
$includes = static::get_includes();
if ( ! empty( $includes[ $name ] ) ) {
/**
* Registered include classes
*
* @var ES_Workflow_Rule $rule
*/
$rule_class = $includes[ $name ];
if ( class_exists( $rule_class ) ) {
$rule = new $rule_class();
}
}
static::$loaded[ $name ] = $rule;
}
/**
* Get all registered workflow rules
*
* @return ES_Workflow_Rule[]
*
*/
public static function get_all() {
foreach ( static::get_includes() as $name => $path ) {
static::load( $name );
}
return static::$loaded;
}
}

View File

@@ -0,0 +1,76 @@
<?php
/**
* Helper class for getting/setting workflow time options
*
* @since 4.4.1
* @version 1.0
* @package Email Subscribers
*/
/**
* Class for workflow timing options.
*
* @class ES_Workflow_Time_Helper
*
* @since 4.4.1
*/
class ES_Workflow_Time_Helper {
/**
* Method to calculate total seconds from start of the day till time given.
*
* @param string|DateTime $time - string must be in format 00:00.
*
* @return int
*/
public static function calculate_seconds_from_day_start( $time ) {
if ( is_a( $time, 'DateTime' ) ) {
$time = $time->format( 'G:i' );
}
$parts = explode( ':', $time );
if ( count( $parts ) !== 2 ) {
return 0;
}
return ( absint( $parts[0] ) * HOUR_IN_SECONDS + absint( $parts[1] ) * MINUTE_IN_SECONDS );
}
/**
* Convert local time to GMT time.
*
* @param \DateTime|ES_Workflow_DateTime $datetime DateTime object.
*/
public static function convert_to_gmt( $datetime ) {
$datetime->modify( '-' . self::get_timezone_offset() * HOUR_IN_SECONDS . ' seconds' );
}
/**
* Convert GMT time to local time
*
* @param \DateTime|DateTime $datetime DateTime object.
*/
public static function convert_from_gmt( $datetime ) {
$datetime->modify( '+' . self::get_timezone_offset() * HOUR_IN_SECONDS . ' seconds' );
}
/**
* Get timezone offset
*
* @return float|int
*/
public static function get_timezone_offset() {
$timezone = get_option( 'timezone_string' );
if ( $timezone ) {
$timezone_object = new DateTimeZone( $timezone );
return $timezone_object->getOffset( new DateTime( 'now', new DateTimeZone( 'UTC' ) ) ) / HOUR_IN_SECONDS;
} else {
return floatval( get_option( 'gmt_offset', 0 ) );
}
}
}

View File

@@ -0,0 +1,131 @@
<?php
/**
* Includes all workflows triggers.
*
* @since 4.4.1
* @version 1.0
* @package Email Subscribers
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Class to include registered workflow triggers
*
* @class ES_Workflow_Triggers
*
* @since 4.4.1
*/
class ES_Workflow_Triggers extends ES_Workflow_Registry {
/**
* Registered workflow trigger list
*
* @var array
*/
public static $includes;
/**
* Loaded workflow triggers
*
* @var array
*/
public static $loaded = array();
/**
* Get workflow trigger list.
*
* @return array
*/
public static function load_includes() {
$includes = array(
'ig_es_user_registered' => 'ES_Trigger_User_Registered',
'ig_es_user_deleted' => 'ES_Trigger_User_Deleted',
'ig_es_user_updated' => 'ES_Trigger_User_Updated',
'ig_es_user_subscribed' => 'ES_Trigger_User_Subscribed',
'ig_es_user_unconfirmed' => 'ES_Trigger_User_Unconfirmed',
'ig_es_user_unsubscribed' => 'ES_Trigger_User_Unsubscribed',
'ig_es_campaign_sent' => 'ES_Trigger_Campaign_Sent',
'ig_es_campaign_failed' => 'ES_Trigger_Campaign_Failed',
);
return apply_filters( 'ig_es_workflow_triggers', $includes );
}
/**
* Get trigger object based on its name
*
* @param string $trigger_name Trigger name.
*
* @return ES_Workflow_Trigger|false
*/
public static function get( $trigger_name ) {
static::init();
if ( ! isset( static::$loaded[ $trigger_name ] ) ) {
return false;
}
return static::$loaded[ $trigger_name ];
}
/**
* Get all available triggers.
*
* @return ES_Workflow_Trigger[]
*/
public static function get_all() {
static::init();
return static::$loaded;
}
/**
* Load and init all triggers
*/
public static function init() {
foreach ( static::get_includes() as $name => $path ) {
static::load( $name );
}
if ( ! did_action( 'ig_es_init_workflow_triggers' ) ) {
do_action( 'ig_es_init_workflow_triggers' );
}
}
/**
* Load trigger class based on its name.
*
* @param string $trigger_name Trigger name.
*/
public static function load( $trigger_name ) {
if ( static::is_loaded( $trigger_name ) ) {
return;
}
$trigger = false;
$includes = static::get_includes();
if ( ! empty( $includes[ $trigger_name ] ) ) {
/**
* Workflow Trigger object
*
* @var ES_Workflow_Trigger
*/
$trigger_class = $includes[ $trigger_name ];
if ( class_exists( $trigger_class ) ) {
$trigger = new $trigger_class();
$trigger->set_name( $trigger_name );
}
}
static::$loaded[ $trigger_name ] = $trigger;
}
}

View File

@@ -0,0 +1,616 @@
<?php
/**
* Show workflows list in admin dashboard.
*
* @since 4.4.1
* @version 1.0
* @package Email Subscribers
*/
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
if ( ! class_exists( 'WP_List_Table' ) ) {
require_once ABSPATH . 'wp-admin/includes/class-wp-list-table.php';
}
/**
* Class to show workflows list in admin dashboard.
*
* @class ES_Workflows_Table
*
* @since 4.4.1
*/
class ES_Workflows_Table extends ES_List_Table {
/**
* Number of workflows to show at once.
*
* @since 4.4.1
* @var string
*/
public static $option_per_page = 'es_workflows_per_page';
/**
* ES_DB_Workflows object
*
* @since 4.4.1
* @var $db
*/
protected $db;
/**
* ES_Workflows_Table constructor.
*
* @since 4.4.1
*/
public function __construct() {
parent::__construct(
array(
'singular' => __( 'Workflow', 'email-subscribers' ), // Singular name of the listed records.
'plural' => __( 'Workflows', 'email-subscribers' ), // Plural name of the listed records.
'ajax' => false, // Does this table support ajax?
'screen' => 'es_workflows',
)
);
$this->db = new ES_DB_Workflows();
$this->init();
}
public function init() {
$this->register_hooks();
}
public function register_hooks() {
add_action( 'ig_es_show_workflows', array( $this, 'show_workflows' ) );
add_action( 'ig_es_show_workflow_gallery', array( $this, 'show_workflow_gallery' ) );
}
/**
* Show existing workflows
*
* @since 5.3.8
*/
public function show_workflows() {
?>
<div id="poststuff" class="es-items-lists">
<div id="post-body" class="metabox-holder column-1">
<div id="post-body-content">
<div class="meta-box-sortables ui-sortable">
<form method="get">
<input type="hidden" name="page" value="es_workflows" />
<?php
// Display search field and other available filter fields.
$this->prepare_items();
?>
</form>
<form method="post">
<?php
// Display bulk action fields, pagination and list items.
$this->display();
?>
</form>
</div>
</div>
</div>
<br class="clear">
</div>
<?php
}
/**
* Show workflow gallery tab
*
* @since 5.3.8
*/
public function show_workflow_gallery() {
$workflow_gallery_items = ES_Workflow_Gallery::get_workflow_gallery_items();
?>
<div class="ig-es-workflow-gallery-tab-description">
<p class="pb-2 text-sm font-normal text-gray-500">
<?php
/* translators: 1. Opening strong tag(<strong>) 2: Closing strong tag(</strong>) */
echo sprintf( esc_html__( 'Here\'s a collection of some useful workflows for you. Simply click on %1$sUse workflow%2$s button to begin.', 'email-subscribers' ), '<strong>', '</strong>' );
?>
</p>
</div>
<div class="ig-es-workflow-gallery-tab-content bg-white rounded-lg shadow-md">
<div class="w-full overflow-auto py-4 px-6 mt-2">
<?php
if ( ! empty( $workflow_gallery_items ) ) {
?>
<div class="ig-es-workflow-gallery-list">
<?php
foreach ( $workflow_gallery_items as $item_name => $item ) {
?>
<div class="ig-es-workflow-gallery-item flex mt-3 mb-2 pb-3 border-b border-gray">
<div class="ig-es-workflow-gallery-item-detail">
<h2 class="ig-es-workflow-gallery-item-title font-medium text-gray-600 tracking-wide text-lg">
<?php echo esc_html( $item['title'] ); ?>
</h2>
<p class="ig-es-workflow-gallery-item-description text-gray-600 text-sm pt-1">
<?php echo esc_html( $item['description'] ); ?>
</p>
</div>
<div class="ig-es-workflow-gallery-item-actions">
<button data-item-name=<?php echo esc_attr( $item_name ); ?> type="button" class="ig-es-create-workflow-from-gallery-item ig-es-inline-loader inline-flex justify-center w-full py-1.5 text-sm font-medium leading-5 text-white transition duration-150 ease-in-out bg-indigo-600 border border-indigo-500 rounded-md cursor-pointer select-none focus:outline-none focus:shadow-outline-indigo focus:shadow-lg hover:bg-indigo-500 hover:text-white hover:shadow-md md:px-2 lg:px-3 xl:px-4">
<span>
<?php echo esc_html__( 'Use workflow', 'email-subscribers' ); ?>
</span>
<svg class="es-btn-loader animate-spin h-4 w-4 text-indigo" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
</button>
</div>
</div>
<?php
}
?>
</div>
<?php
} else {
?>
<h2 class="text-base font-medium text-gray-600 tracking-wide text-lg text-xl">
<?php echo esc_html__( 'No items found in workflow gallery.', 'email-subscribers' ); ?>
</h2>
<?php
}
?>
</div>
</div>
<?php
}
/**
* Add Screen Option
*
* @since 4.4.1
*/
public static function screen_options() {
$action = ig_es_get_request_data( 'action' );
if ( ! in_array( $action, array( 'new', 'edit' ), true ) ) {
// Admin screen options for workflow list table.
$option = 'per_page';
$args = array(
'label' => __( 'Number of workflows per page', 'email-subscribers' ),
'default' => 20,
'option' => self::$option_per_page,
);
add_screen_option( $option, $args );
}
}
/**
* Render Workflows list | Save/Edit Workflow page
*
* @since 4.4.1
*/
public function render() {
$action = ig_es_get_request_data( 'action' );
$workflow_id = ig_es_get_request_data( 'id' );
// After successfully adding/updating a workflow, ES redirects user to workflow list page with the status of the performed action in 'action_status' URL query parameter.
$action_status = ig_es_get_request_data( 'action_status' );
if ( ! empty( $action_status ) ) {
if ( ! empty( $workflow_id ) ) {
$workflow = new ES_Workflow( $workflow_id );
if ( $workflow->exists ) {
$run_workflow = get_transient( 'ig_es_run_workflow' );
if ( $workflow->is_runnable() && 'yes' === $run_workflow ) {
?>
<script>
jQuery(document).ready(function(){
let workflow_id = <?php echo esc_js( $workflow_id ); ?>;
ig_es_run_workflow( workflow_id );
});
</script>
<?php
} else {
// Show workflow added/updated notice only if there is not workflow to run to avoid notice cluster on the page.
$workflow_edit_url = $workflow->get_edit_url();
if ( 'added' === $action_status ) {
/* translators: 1. Workflow edit URL anchor tag 2: Anchor close tag */
$message = sprintf( __( 'Workflow added. %1$sEdit workflow%2$s.', 'email-subscribers' ), '<a href="' . esc_url( $workflow_edit_url ) . '" class="text-indigo-600">', '</a>' );
$status = 'success';
} elseif ( 'updated' === $action_status ) {
/* translators: 1. Workflow edit URL anchor tag 2: Anchor close tag */
$message = sprintf( __( 'Workflow updated. %1$sEdit workflow%2$s', 'email-subscribers' ), '<a href="' . esc_url( $workflow_edit_url ) . '" class="text-indigo-600">', '</a>' );
$status = 'success';
} elseif ( 'not_saved' === $action_status ) {
$message = __( 'Unable to save workflow. Please try again later.', 'email-subscribers' );
$status = 'error';
} elseif ( 'not_allowed' === $action_status ) {
$message = __( 'You are not allowed to add/edit workflows.', 'email-subscribers' );
$status = 'error';
} else {
$message = __( 'An error has occured. Please try again later', 'email-subscribers' );
$status = 'error';
}
}
}
}
}
if ( ! empty( $message ) && ! empty( $status ) ) {
ES_Common::show_message( $message, $status );
}
?>
<div class="wrap pt-4 font-sans">
<?php
if ( 'new' === $action ) {
ES_Workflow_Admin_Edit::load_workflow();
} elseif ( 'edit' === $action && ! empty( $workflow_id ) ) {
ES_Workflow_Admin_Edit::load_workflow( $workflow_id );
} else {
$this->load_workflow_list();
}
?>
</div>
<?php
}
/**
* Render Workflows list
*
* @since 4.4.1
*
* @modified 4.4.4 Added wp-heading-inline class to heading tag.
*/
public function load_workflow_list() {
$tab = ig_es_get_request_data( 'tab' );
?>
<div class="flex">
<div>
<h2 class="wp-heading-inline text-3xl pb-1 font-bold text-gray-700 sm:leading-9 sm:truncate pr-4">
<?php esc_html_e( 'Workflows', 'email-subscribers' ); ?>
</h2>
</div>
<div class="mt-1">
<a href="admin.php?page=es_workflows&action=new" class="px-3 py-1 ml-2 leading-5 align-middle ig-es-title-button">
<?php esc_html_e( 'Add New', 'email-subscribers' ); ?></a>
<?php do_action( 'ig_es_after_workflow_type_buttons' ); ?>
</div>
</div>
<div><hr class="wp-header-end"></div>
<div class="mt-2">
<ul class="ig-es-tabs overflow-hidden">
<li class="ig-es-tab-heading relative float-left px-1 pb-2 text-center list-none cursor-pointer <?php echo '' === $tab ? esc_attr( 'active' ) : ''; ?>">
<a href="admin.php?page=es_workflows">
<span class="mt-1 text-base font-medium tracking-wide text-gray-400">
<?php echo esc_html__( 'Workflows', 'email-subscribers' ); ?>
</span>
</a>
</li>
<li class="ig-es-tab-heading relative float-left px-1 pb-2 ml-5 text-center list-none cursor-pointer hover:border-2 <?php echo 'gallery' === $tab ? esc_attr( 'active' ) : ''; ?>">
<a href="admin.php?page=es_workflows&tab=gallery">
<span class="mt-1 text-base font-medium tracking-wide text-gray-400">
<?php echo esc_html__( 'Workflow gallery', 'email-subscribers' ); ?>
</span>
</a>
</li>
</ul>
</div>
<?php
if ( 'gallery' === $tab ) {
do_action( 'ig_es_show_workflow_gallery' );
} else {
do_action( 'ig_es_show_workflows' );
}
}
/**
* Retrieve lists data from the database
*
* @param int $per_page Workflows to show per page.
* @param int $page_number Page number to show.
* @param int $do_count_only Flag to fetch only count.
*
* @return mixed
*/
public function get_lists( $per_page = 5, $page_number = 1, $do_count_only = false ) {
$order_by = sanitize_sql_orderby( ig_es_get_request_data( 'orderby' ) );
$order = ig_es_get_request_data( 'order' );
$search = ig_es_get_request_data( 's' );
$type = ig_es_get_request_data( 'type' );
$args = array(
's' => $search,
'order' => $order,
'order_by' => $order_by,
'per_page' => $per_page,
'page_number' => $page_number,
'type' => $type,
);
if ( '' !== $type ) {
if ( 'system' === $type ) {
$type = IG_ES_WORKFLOW_TYPE_SYSTEM;
} elseif ( 'user' === $type ) {
$type = IG_ES_WORKFLOW_TYPE_USER;
}
$args['type'] = $type;
}
$result = ES()->workflows_db->get_workflows( $args, ARRAY_A, $do_count_only );
return $result;
}
/**
* Text Display when no items available
*
* @since 4.4.1
*/
public function no_items() {
echo esc_html__( 'No Workflows Found.', 'email-subscribers' );
}
/**
* Render a column when no column specific method exist.
*
* @param array $item Workflow item.
* @param string $column_name Column name.
*
* @return mixed
*/
public function column_default( $item, $column_name ) {
$output = '';
switch ( $column_name ) {
case 'last_ran_at':
$workflow_id = $item['id'];
$item['meta'] = maybe_unserialize( $item['meta'] );
$output .= '<span class="last_ran_at_date_time" data-workflow-id="' . $workflow_id . '">';
if ( isset( $item['meta']['last_ran_at'] ) ) {
$output .= ig_es_format_date_time( $item['meta']['last_ran_at'] );
} else {
$output .= '-';
}
$output .= '</span>';
$workflow = new ES_Workflow( $workflow_id );
if ( $workflow->exists && $workflow->is_runnable() ) {
/* translators: 1. Run workflow button start tag 2: Button close tag */
$output .= sprintf( __( ' %1$sRun%2$s', 'email-subscribers' ), '<button type="button" class="inline-flex justify-center rounded-md border border-transparent px-2 py-0.5 bg-white text-sm leading-5 font-medium text-white bg-indigo-600 hover:bg-indigo-500 focus:outline-none focus:shadow-outline-blue transition ease-in-out duration-150 ig-es-run-workflow-btn" data-workflow-id="' . $workflow_id . '">', '</button>' );
}
break;
default:
$output = $item[ $column_name ];
}
return $output;
}
/**
* Render the bulk edit checkbox
*
* @param array $item Workflow item.
*
* @return string
*/
public function column_cb( $item ) {
return sprintf(
'<input type="checkbox" name="workflows[]" value="%s" />',
$item['id']
);
}
/**
* Method for title column
*
* @param array $item an array of DB data.
*
* @return string
*/
public function column_title( $item ) {
$nonce = wp_create_nonce( 'es_post_workflow' );
$title = $item['title'];
$actions ['edit'] = sprintf( '<a href="?page=%s&action=%s&id=%s" class="text-indigo-600">%s</a>', $this->screen->id, 'edit', absint( $item['id'] ), __( 'Edit', 'email-subscribers' ) );
$is_system_workflow = ( IG_ES_WORKFLOW_TYPE_SYSTEM === (int) $item['type'] ) ? true : false;
if ( ! $is_system_workflow ) {
$actions['delete'] = sprintf( '<a href="?page=%s&action=%s&id=%s&_wpnonce=%s" onclick="return checkDelete()">%s</a>', esc_attr( 'es_workflows' ), 'delete', absint( $item['id'] ), $nonce, __( 'Delete', 'email-subscribers' ) );
}
$title .= $this->row_actions( $actions );
return $title;
}
/**
* Method for status column
*
* @param array $item an array of DB data.
*
* @return string
*/
public function column_status( $item ) {
return '<button type="button" class="ig-es-switch js-toggle-workflow-status" '
. 'data-workflow-id="' . $item['id'] . '" '
. 'data-ig-es-switch="' . ( '1' === $item['status'] ? 'active' : 'inactive' ) . '">'
. __( 'Toggle Status', 'email-subscribers' ) . '</button>';
}
/**
* Associative array of columns
*
* @return array
*/
public function get_columns() {
$columns = array(
'cb' => '<input type = "checkbox" />',
'title' => __( 'Title', 'email-subscribers' ),
'last_ran_at' => __( 'Last ran at', 'email-subscribers' ),
'status' => __( 'Status', 'email-subscribers' ),
);
return $columns;
}
/**
* Columns to make sortable.
*
* @return array
*/
public function get_sortable_columns() {
$sortable_columns = array(
'title' => array( 'title', true ),
);
return $sortable_columns;
}
/**
* Returns an associative array containing the bulk action
*
* @return array
*/
public function get_bulk_actions() {
$actions = array(
'bulk_activate' => esc_html__( 'Activate', 'email-subscribers' ),
'bulk_deactivate' => esc_html__( 'Deactivate', 'email-subscribers' ),
'bulk_delete' => esc_html__( 'Delete', 'email-subscribers' ),
);
return $actions;
}
/**
* Prepare search box
*
* @param string $text Search text.
* @param string $input_id Input field id.
*
* @since 4.4.1
*/
public function search_box( $text = '', $input_id = '' ) {
?>
<p class="search-box">
<label class="screen-reader-text"
for="<?php echo esc_attr( $input_id ); ?>"><?php echo esc_attr( $text ); ?>:</label>
<input type="search" id="<?php echo esc_attr( $input_id ); ?>" name="s" value="<?php _admin_search_query(); ?>" />
<?php submit_button( __( 'Search Workflows', 'email-subscribers' ), 'button', false, false, array( 'id' => 'search-submit' ) ); ?>
</p>
<?php
}
/**
* Handles data query and filter, sorting, and pagination.
*/
public function prepare_items() {
$this->_column_headers = $this->get_column_info();
/** Process bulk action */
$this->process_bulk_action();
// Note: Disable Search box for now.
$search = ig_es_get_request_data( 's' );
$this->search_box( $search, 'workflow-search-input' );
$per_page = $this->get_items_per_page( self::$option_per_page, 25 );
$current_page = $this->get_pagenum();
$total_items = $this->get_lists( 0, 0, true );
$this->set_pagination_args(
array(
'total_items' => $total_items, // We have to calculate the total number of items.
'per_page' => $per_page, // We have to determine how many items to show on a page.
)
);
$this->items = $this->get_lists( $per_page, $current_page );
}
/**
* Method to process bulk actions on workflows
*/
public function process_bulk_action() {
if ( 'delete' === $this->current_action() ) {
// In our file that handles the request, verify the nonce.
$nonce = ig_es_get_request_data( '_wpnonce' );
if ( ! wp_verify_nonce( $nonce, 'es_post_workflow' ) ) {
$message = __( 'You are not allowed to delete workflow.', 'email-subscribers' );
$status = 'error';
} else {
$workflow_id = ig_es_get_request_data( 'id' );
$this->db->delete_workflows( $workflow_id );
$this->db->delete_workflows_campaign( $workflow_id );
$message = __( 'Workflow deleted successfully!', 'email-subscribers' );
$status = 'success';
}
ES_Common::show_message( $message, $status );
}
$action = ig_es_get_request_data( 'action' );
// If the delete bulk action is triggered.
if ( 'bulk_delete' === $action ) {
$ids = ig_es_get_request_data( 'workflows' );
if ( is_array( $ids ) && count( $ids ) > 0 ) {
// Delete multiple Workflows.
$this->db->delete_workflows( $ids );
$this->db->delete_workflows_campaign( $ids );
$message = __( 'Workflow(s) deleted successfully!', 'email-subscribers' );
ES_Common::show_message( $message );
} else {
$message = __( 'Please select workflow(s) to delete.', 'email-subscribers' );
ES_Common::show_message( $message, 'error' );
}
} elseif ( 'bulk_activate' === $action || 'bulk_deactivate' === $action ) {
$ids = ig_es_get_request_data( 'workflows' );
if ( is_array( $ids ) && count( $ids ) > 0 ) {
$new_status = ( 'bulk_activate' === $action ) ? 1 : 0;
// Update multiple Workflows.
$this->db->update_status( $ids, $new_status );
$workflow_action = 'bulk_activate' === $action ? __( 'activated', 'email-subscribers' ) : __( 'deactivated', 'email-subscribers' );
/* translators: %s: Workflow action */
$message = sprintf( __( 'Workflow(s) %s successfully!', 'email-subscribers' ), $workflow_action );
ES_Common::show_message( $message );
} else {
$workflow_action = 'bulk_activate' === $action ? __( 'activate', 'email-subscribers' ) : __( 'deactivate', 'email-subscribers' );
/* translators: %s: Workflow action */
$message = sprintf( __( 'Please select workflow(s) to %s.', 'email-subscribers' ), $workflow_action );
ES_Common::show_message( $message, 'error' );
}
}
}
}

View File

@@ -0,0 +1,102 @@
<?php
/**
* Class to extract placeholder tags from given string
*
* @class IG_ES_Replace_Helper
*/
class IG_ES_Replace_Helper {
/**
* Search patterns
*
* @var array
*/
public $patterns = array(
'variables' => array(
'match' => 1,
'expression' => '/{{(.*?)}}/'
)
);
/**
* Pattern to search
*
* @var string
*/
public $selected_pattern;
/**
* Pattern string
*
* @var string
*/
public $string;
/**
* Callback for preg_replace_callback function
*
* @var callable */
public $callback;
/**
* The keywords that can be processable
*
* @var array
*/
public $parsable_keywords;
/**
* Constructor
*
* @param $string
* @param callable $callback
* @param string $pattern_name
* @param string $parsable_keywords
*/
public function __construct( $string, $callback, $pattern_name = '', $parsable_keywords = array() ) {
$this->string = $string;
$this->callback = $callback;
$this->parsable_keywords = $parsable_keywords;
if ( $pattern_name && isset( $this->patterns[$pattern_name] ) ) {
$this->selected_pattern = $this->patterns[$pattern_name];
}
}
/**
* Process passed string against selected regular expression
*
* @return mixed
*/
public function process() {
if ( ! $this->selected_pattern ) {
return false;
}
return preg_replace_callback( $this->selected_pattern['expression'], array( $this, 'callback' ) , $this->string );
}
/**
* Pre process match before using the actual callback
*
* @param $match
* @return string
*/
public function callback( $match ) {
if ( is_array( $match ) ) {
$match = $match[ $this->selected_pattern['match'] ];
}
if ( ! empty( $this->parsable_keywords ) ) {
return call_user_func( $this->callback, $match, $this->parsable_keywords );
}
return call_user_func( $this->callback, $match );
}
}

View File

@@ -0,0 +1,200 @@
<?php
/**
* Process variables into values. Is used on workflows and action options.
*
* @class Variable_Processor
*/
class IG_ES_Variables_Processor {
/**
* Workflow object
*
* @var Workflow
*/
public $workflow;
/**
* Class constructor
*
* @param $workflow
*/
public function __construct( $workflow ) {
$this->workflow = $workflow;
}
/**
* Process field's value for placeholder tags
*
* @param $text string
* @param bool $allow_html
* @return string
*/
public function process_field( $text, $allow_html = false ) {
$replacer = new IG_ES_Replace_Helper( $text, array( $this, 'callback_process_field' ), 'variables' );
$value = $replacer->process();
if ( ! $allow_html ) {
$value = html_entity_decode( wp_strip_all_tags( $value ) );
}
return $value;
}
/**
* Callback public function to process a variable string.
*
* @param string $string
*
* @return string
*/
public function callback_process_field( $string ) {
$string = $this->sanitize( $string );
if ( self::is_excluded( $string ) ) {
return '{{' . $string . '}}';
}
$variable = self::parse_variable( $string );
if ( ! $variable ) {
return '';
}
$parameters = $variable->parameters;
$value = $this->get_variable_value( $variable->type, $variable->field, $parameters );
$value = (string) apply_filters( 'ig_es_variables_after_get_value', $value, $variable->type, $variable->field, $parameters, $this->workflow );
if ( '' === $value ) {
// backwards compatibility
if ( isset( $parameters['default'] ) ) {
$parameters['fallback'] = $parameters['default'];
}
// show default if set and no real value
if ( isset( $parameters['fallback'] ) ) {
$value = $parameters['fallback'];
}
}
return $value;
}
/**
* Get related variable which handles parsing of placeholder string
*
* @param $string
* @return IG_ES_Workflow_Variable_Parser|bool
*/
public static function parse_variable( $string ) {
$variable = new IG_ES_Workflow_Variable_Parser();
if ( $variable->parse( $string ) ) {
return $variable;
}
return false;
}
/**
* Get the value of a variable.
*
* @param string $data_type
* @param string $data_field
* @param array $parameters
*
* @return string
*/
public function get_variable_value( $data_type, $data_field, $parameters = array() ) {
// Short circuit filter for the variable value
$short_circuit = (string) apply_filters( 'ig_es_text_variable_value', false, $data_type, $data_field );
if ( $short_circuit ) {
return $short_circuit;
}
$variable_name = "$data_type.$data_field";
$variable = IG_ES_Variables::get_variable( $variable_name );
$value = '';
if ( $variable instanceof IG_ES_Workflow_Variable && method_exists( $variable, 'get_value' ) ) {
if ( in_array( $data_type, ES_Workflow_Data_Types::get_non_stored_data_types(), true ) ) {
$value = $variable->get_value( $parameters, $this->workflow );
} else {
$data_item = $this->workflow->get_data_item( $variable->get_data_type() );
if ( $data_item ) {
$value = $variable->get_value( $data_item, $parameters, $this->workflow );
}
}
}
return (string) apply_filters( 'ig_es_get_variable_value', (string) $value, $this, $variable );
}
/**
* Based on sanitize_title()
*
* @param $string
* @return mixed|string
*/
public static function sanitize( $string ) {
// remove style and script tags
$string = wp_strip_all_tags( $string, true );
$string = remove_accents( $string );
// remove unicode white spaces
$string = preg_replace( "#\x{00a0}#siu", ' ', $string );
$string = trim($string);
return $string;
}
/**
* Certain variables can be excluded from processing.
*
* @param string $variable
* @return bool
*/
public static function is_excluded( $variable ) {
$is_site_tag = false !== strpos( $variable, 'site.' );
if ( $is_site_tag ) {
return true;
}
$excluded = apply_filters('ig_es_variables_processor_excluded', array(
'EMAIL',
'NAME',
'FIRSTNAME',
'LASTNAME',
'LINK',
'SUBSCRIBE-LINK',
'UNSUBSCRIBE-LINK',
'TOTAL-CONTACTS',
'GROUP',
'LIST',
'SITENAME',
'SITEURL',
'SUBJECT',
'COUNT',
'DATE',
));
return in_array( $variable, $excluded, true );
}
}

View File

@@ -0,0 +1,129 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Class to include registered workflow variables
*
* @class IG_ES_Variables
*/
class IG_ES_Variables {
/**
* List of already loaded variables in variable name => variable class object format
* This helps in removing the need of recreating the class object
*
* @var array
*/
private static $loaded_variables = array();
/**
* List of registered workflow variables in [variable_data_type][variable_field] => path to variable file format
*
* @var array
*/
private static $variables_list;
/**
* List of registered workflow variables in [variable_data_type] => [variable_field] format
*
* @var array
*/
private static $included_variables = array('subscriber'=>array('email','name','first_name','last_name'));
/**
* Get registered workflow variables list in [variable_data_type][variable_field] => path to variable file format
*
* @return array
*/
public static function get_list() {
// cache the list after first generation
if ( isset( self::$variables_list ) ) {
return self::$variables_list;
}
$variables = array();
$included_variables = self::$included_variables;
if ( ES()->is_pro() ) {
$included_variables['subscriber'][] = 'unsubscribe_reason';
}
if ( ! empty( $included_variables ) ) {
// generate paths to included variables
foreach ( $included_variables as $data_type => $fields ) {
foreach ( $fields as $field ) {
$filename = str_replace( '_', '-', $data_type ) . '-' . str_replace( '_', '-', $field ) . '.php';
$variables[$data_type][$field] = ES_PLUGIN_DIR . 'lite/includes/workflows/variables/' . $data_type . '/' . $filename;
}
}
}
self::$variables_list = apply_filters( 'ig_es_workflow_variables', $variables );
return self::$variables_list;
}
/**
* Get path to file which handles variable
*
* @param $data_type
* @param $data_field
* @return false|string
*/
public static function get_path_to_variable( $data_type, $data_field ) {
$list = self::get_list();
if ( isset( $list[$data_type][$data_field] ) ) {
return $list[$data_type][$data_field];
}
return false;
}
/**
* Get variable object based on variable name
* e.g. returns IG_ES_Variable_WC_Order_ID class object if variable name is wc_order.id
*
* @param $variable_name string
* @return IG_ES_Variable|false
*/
public static function get_variable( $variable_name ) {
if ( isset( self::$loaded_variables[$variable_name] ) ) {
return self::$loaded_variables[$variable_name];
}
list( $data_type, $data_field ) = explode( '.', $variable_name );
$path = self::get_path_to_variable( $data_type, $data_field );
if ( ! file_exists( $path ) ) {
if ( ! file_exists( $path ) ) {
return false;
}
}
/**
* Variable class object
*
* @var IG_ES_Variable $variable_object
*/
$variable_object = require_once $path;
if ( ! $variable_object ) {
return false;
}
$variable_object->setup( $variable_name );
self::$loaded_variables[$variable_name] = $variable_object;
return $variable_object;
}
}

View File

@@ -0,0 +1,135 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Class to parse a variable string into separate usable parts
*
* @class IG_ES_Workflow_Variable_Parser
*/
class IG_ES_Workflow_Variable_Parser {
/**
* Variable name
* e.g. wc_order.id, cart.link, user.first_name etc
*
* @var string
*/
public $name;
/**
* Variable data type
* e.g. wc_order, cart, user etc
*
* @var string
*/
public $type;
/**
* Variable field name
* e.g. id in wc_order.id, link in cart.link, first_name in user.first_name etc
*
* @var string
*/
public $field;
/**
* Extra parameters attributes passed in placeholder string
* * e.g. array( 'fallback' => 'test' ) in {{ comment.id | fallback: 'test' }}
*
* @var array
*/
public $parameters;
/**
* Actual paramter string
* e.g. fallback: 'test' in {{ comment.id | fallback: 'test' }}
*
* @var string */
public $parameter_string;
/**
* Returns true on successful parsing
*
* @param $variable_string
* @return bool
*/
public function parse( $variable_string ) {
$matches = array();
// extract the variable name (first part) of the variable string, e.g. 'customer.email'
preg_match('/([a-z._0-9])+/', $variable_string, $matches, PREG_OFFSET_CAPTURE );
if ( ! $matches ) {
return false;
}
$name = $matches[0][0];
// the name must contain a period
if ( ! strstr( $name, '.' ) ) {
return false;
}
list( $type, $field ) = explode( '.', $name, 2 );
$parameter_string = trim( substr( $variable_string, $matches[1][1] + 1 ) );
$parameters = $this->parse_parameters_from_string( $parameter_string );
$this->name = $name;
$this->type = $type;
$this->field = $field;
$this->parameters = $parameters;
$this->parameter_string = $parameter_string;
return true;
}
/**
* Extract the parameters from the keyword
*
* @param $parameter_string
*
* @return array
*
* @since 5.3.5
*/
public function parse_parameters_from_string( $parameter_string ) {
$parameters = array();
$parameter_string = trim( ig_es_str_replace_first_match( $parameter_string, '|' ) ); // remove pipe
$parameters_split = preg_split( '/(,)(?=(?:[^\']|\'[^\']*\')*$)/', $parameter_string );
foreach ( $parameters_split as $parameter ) {
if ( strstr( $parameter, ':' ) ) {
list( $key, $value ) = explode( ':', $parameter, 2 );
} else if ( strstr( $parameter, '=' ) ) {
list( $key, $value ) = explode( '=', $parameter, 2 );
} else {
continue;
}
$key = ES_Clean::string( $key );
$value = ES_Clean::string( $this->unquote( $value ) );
$parameters[ $key ] = $value;
}
return $parameters;
}
/**
* Remove single quotes from start and end of a string
*
* @param $string
* @return string
*/
private function unquote( $string ) {
return trim( trim( $string ), "'" );
}
}

View File

@@ -0,0 +1,68 @@
<?php
/**
* Workflow data type form data
*
* @since 4.4.6
* @version 1.0
* @package Email Subscribers
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Abstract class to handle form data
*
* @class ES_Data_Type_Form_Data
*
* @since 4.4.6
*/
abstract class ES_Data_Type_Form_Data extends ES_Workflow_Data_Type {
/**
* Validate given data item
*
* @since 4.4.6
*
* @param array $item Data item to validate.
*
* @return bool
*/
public function validate( $item ) {
// Check if we have an array with email field not being empty.
if ( ! is_array( $item ) || empty( $item['email'] ) ) {
return false;
}
return true;
}
/**
* Returns passed form data. Only validated $items should be passed to this method
*
* @param array $item Passed data item.
*
* @return mixed
*/
public function compress( $item ) {
// Return the same $item as submitted contact form aren't saved in DB for later user.
return $item;
}
/**
* Return data item object from given data.
*
* @param array $compressed_item Data item.
* @param array $compressed_data_layer Data layer.
*
* @return Array|false
*/
public function decompress( $compressed_item, $compressed_data_layer ) {
if ( ! $compressed_item ) {
return false;
}
return $compressed_item;
}
}

View File

@@ -0,0 +1,93 @@
<?php
/**
* Workflow data type campaign
*
* @since 5.0.1
* @version 1.0
* @package Email Subscribers
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Class to handle campaign data item
*
* @class ES_Data_Type_Campaign
*
* @since 5.0.1
*/
class ES_Data_Type_Campaign extends ES_Workflow_Data_Type {
/**
* Validate given data item
*
* @since 5.0.1
*
* @param WP_User $item Data item object.
*
* @return bool
*/
public function validate( $item ) {
if ( empty( $item['notification_guid'] ) ) {
return false;
}
return true;
}
/**
* Returns id of given data item object. Only validated $items should be passed to this method
*
* @since 5.0.1
*
* @param WP_User $item Data item object.
*
* @return mixed
*/
public function compress( $item ) {
return $item;
}
/**
* Return data item object from given id.
*
* @since 5.0.1
*
* @param string $compressed_item Data item object ID.
* @param array $compressed_data_layer Data layer.
*
* @return mixed
*/
public function decompress( $compressed_item, $compressed_data_layer ) {
if ( ! $compressed_item ) {
return false;
}
return $compressed_item;
}
/**
* Abstract required data from data item object
*
* @since 5.0.1
*
* @param array $item Data item object.
* @return array
*/
public function get_data( $item ) {
$data = array();
if ( ! empty( $item ) ) {
$data = $item;
}
return $data;
}
}

View File

@@ -0,0 +1,96 @@
<?php
/**
* Workflow data type subscriber
*
* @since 4.7.2
* @version 1.0
* @package Email Subscribers
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Class to handle subscriber data item
*
* @class ES_Data_Type_Subscriber
*
* @since 4.7.2
*/
class ES_Data_Type_Subscriber extends ES_Workflow_Data_Type {
/**
* Validate given data item
*
* @since 4.7.2
*
* @param WP_User $item Data item object.
*
* @return bool
*/
public function validate( $item ) {
if ( empty( $item ) || ! is_email( $item['email'] ) ) {
return false;
}
return true;
}
/**
* Returns id of given data item object. Only validated $items should be passed to this method
*
* @since 4.7.2
*
* @param WP_User $item Data item object.
*
* @return mixed
*/
public function compress( $item ) {
return $item;
}
/**
* Return data item object from given id.
*
* @since 4.7.2
*
* @param string $compressed_item Data item object ID.
* @param array $compressed_data_layer Data layer.
*
* @return mixed
*/
public function decompress( $compressed_item, $compressed_data_layer ) {
if ( ! $compressed_item ) {
return false;
}
return $compressed_item;
}
/**
* Abstract required data from data item object
*
* @since 4.7.2
*
* @param array $item Data item object.
* @return array
*/
public function get_data( $item ) {
$data = array();
if ( ! empty( $item['email'] ) ) {
$data = $item;
$data['source'] = 'es';
}
return $data;
}
}

View File

@@ -0,0 +1,106 @@
<?php
/**
* Workflow data type user
*
* @since 4.4.1
* @version 1.0
* @package Email Subscribers
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Class to handle WP_User data item
*
* @class Data_Type_User
*
* @since 4.4.1
*/
class ES_Data_Type_User extends ES_Workflow_Data_Type {
/**
* Validate given data item
*
* @since 4.4.1
*
* @param WP_User $item Data item object.
*
* @return bool
*/
public function validate( $item ) {
if ( ! ( $item instanceof WP_User ) ) {
return false;
}
return true;
}
/**
* Returns id of given data item object. Only validated $items should be passed to this method
*
* @since 4.4.1
*
* @param WP_User $item Data item object.
*
* @return mixed
*/
public function compress( $item ) {
return $item->ID;
}
/**
* Return data item object from given id.
*
* @since 4.4.1
*
* @param string $compressed_item Data item object ID.
* @param array $compressed_data_layer Data layer.
*
* @return mixed
*/
public function decompress( $compressed_item, $compressed_data_layer ) {
if ( $compressed_item ) {
return get_user_by( 'id', absint( $compressed_item ) );
}
return false;
}
/**
* Abstract required data from data item object
*
* @since 4.4.1
*
* @param WP_User $item Data item object.
* @return array
*/
public function get_data( $item ) {
$data = array();
if ( $item instanceof WP_User ) {
$name = $item->display_name;
$email = $item->user_email;
// prepare data.
$data = array(
'name' => $name,
'email' => $email,
'source' => 'wp',
'status' => 'verified',
'hash' => ES_Common::generate_guid(),
'created_at' => ig_get_current_date_time(),
'wp_user_id' => $item->ID,
);
}
return $data;
}
}

View File

@@ -0,0 +1,140 @@
<?php
/**
* Workflow Queue DB
*
* @since 4.4.1
* @version 1.0
* @package Email Subscribers
*/
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* ES_DB_Workflows_Queue class
*
* @since 4.4.1
*/
class ES_DB_Workflows_Queue extends ES_DB {
/**
* Workflow queue table name
*
* @since 4.4.1
* @var $table_name
*/
public $table_name;
/**
* Workflow queue table version
*
* @since 4.4.1
* @var $version
*/
public $version;
/**
* Workflow queue table primary key
*
* @since 4.4.1
* @var $primary_key
*/
public $primary_key;
/**
* ES_DB_Workflows_Queue constructor.
*
* @since 4.4.1
*/
public function __construct() {
global $wpdb;
parent::__construct();
$this->table_name = $wpdb->prefix . 'ig_workflows_queue';
$this->primary_key = 'id';
$this->version = '1.0';
}
/**
* Returns workflow queue table's columns
*
* @since 4.4.1
*
* @return array workflow queue table columns
*/
public function get_columns() {
return array(
'id' => '%d',
'workflow_id' => '%d',
'scheduled_at' => '%s',
'created_at' => '%s',
'meta' => '%s',
'failed' => '%d',
'failure_code' => '%d',
);
}
/**
* Returns default values for workflow columns
*
* @since 4.4.1
*
* @return array default values for workflow columns
*/
public function get_column_defaults() {
return array(
'workflow_id' => 0,
'scheduled_at' => '',
'created_at' => '',
'meta' => '',
'failed' => 0,
'failure_code' => 0,
);
}
/**
* Update meta value
*
* @since 4.4.1
*
* @param int $queue_id Queue ID.
* @param array $meta_data Meta data to update.
*
* @return bool|false|int
*/
public function update_meta( $queue_id = 0, $meta_data = array() ) {
$update = false;
if ( ! empty( $queue_id ) && ! empty( $meta_data ) ) {
$queue = $this->get( $queue_id );
if ( ! empty( $queue ) ) {
if ( isset( $queue['meta'] ) ) {
$meta = maybe_unserialize( $queue['meta'] );
// If $meta is an empty or isn't an array, then convert it to an array before adding data to it.
if ( empty( $meta ) || ! is_array( $meta ) ) {
$meta = array();
}
foreach ( $meta_data as $meta_key => $meta_value ) {
$meta[ $meta_key ] = $meta_value;
}
$queue['meta'] = maybe_serialize( $meta );
$update = $this->update( $queue_id, $queue );
}
}
}
return $update;
}
}

View File

@@ -0,0 +1,979 @@
<?php
/**
* Workflow DB
*
* @since 4.4.1
* @version 1.0
* @package Email Subscribers
*/
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* ES_DB_Workflows class
*
* @since 4.4.1
*/
class ES_DB_Workflows extends ES_DB {
/**
* Workflow table name
*
* @since 4.4.1
* @var string $table_name
*/
public $table_name;
/**
* Workflow table version
*
* @since 4.4.1
* @var string $version
*/
public $version;
/**
* Workflow table primary key
*
* @since 4.4.1
* @var string
*/
public $primary_key;
/**
* ES_DB_Workflows constructor.
*
* @since 4.4.1
*/
public function __construct() {
global $wpdb;
parent::__construct();
$this->table_name = $wpdb->prefix . 'ig_workflows';
$this->primary_key = 'id';
$this->version = '1.0';
}
/**
* Get columns and formats
*
* @since 4.4.1
*/
public function get_columns() {
return array(
'id' => '%d',
'name' => '%s',
'title' => '%s',
'trigger_name' => '%s',
'trigger_options' => '%s',
'rules' => '%s',
'actions' => '%s',
'meta' => '%s',
'status' => '%d',
'type' => '%d',
'priority' => '%d',
'created_at' => '%s',
'updated_at' => '%s',
);
}
/**
* Get default column values
*
* @since 4.4.1
*/
public function get_column_defaults() {
return array(
'name' => null,
'title' => null,
'trigger_name' => null,
'trigger_options' => '',
'rules' => '',
'actions' => '',
'meta' => '',
'status' => 1,
'type' => 0,
'priority' => 0,
'created_at' => ig_get_current_date_time(),
'updated_at' => '',
);
}
/**
* Get workflows based on arguements
*
* @param array $query_args Query arguements.
* @param string $output Output format.
* @param boolean $do_count_only Count only flag.
*
* @return mixed $result Query result
*
* @since 4.4.1
*/
public function get_workflows( $query_args = array(), $output = ARRAY_A, $do_count_only = false ) {
global $wpdb, $wpbd;
if ( $do_count_only ) {
$sql = 'SELECT count(*) as total FROM ' . IG_WORKFLOWS_TABLE;
} else {
$sql = 'SELECT ';
if ( ! empty( $query_args['fields'] ) && is_array( $query_args['fields'] ) ) {
$sql .= implode( ' ,', $query_args['fields'] );
} else {
$sql .= '*';
}
$sql .= ' FROM ' . IG_WORKFLOWS_TABLE;
}
$args = array();
$query = array();
if ( ! empty( $query_args['ids'] ) ) {
$ids_count = count( $query_args['ids'] );
$ids_placeholders = array_fill( 0, $ids_count, '%d' );
$query[] = ' id IN( ' . implode( ',', $ids_placeholders ) . ' )';
$args = array_merge( $args, $query_args['ids'] );
}
if ( ! empty( $query_args['s'] ) ) {
$query[] = ' title LIKE %s ';
$args[] = '%' . $wpdb->esc_like( $query_args['s'] ) . '%';
}
if ( ! empty( $query_args['trigger_name'] ) ) {
$query[] = ' trigger_name = %s ';
$args[] = $query_args['trigger_name'];
}
if ( ! empty( $query_args['trigger_names'] ) ) {
$trigger_names_count = count( $query_args['trigger_names'] );
$trigger_names_placeholders = array_fill( 0, $trigger_names_count, '%s' );
$query[] = ' trigger_name IN( ' . implode( ',', $trigger_names_placeholders ) . ' )';
$args = array_merge( $args, $query_args['trigger_names'] );
}
if ( isset( $query_args['status'] ) ) {
$query[] = ' status = %d ';
$args[] = $query_args['status'];
}
if ( isset( $query_args['type'] ) ) {
if ( is_numeric( $query_args['type'] ) ) {
$query[] = ' type = %d ';
$args[] = $query_args['type'];
} elseif ( is_array( $query_args['type'] ) && count( $query_args['type'] ) > 0 ) {
$type_count = count( $query_args['type'] );
$type_placeholders = array_fill( 0, $type_count, '%d' );
$query[] = ' type IN( ' . implode( ',', $type_placeholders ) . ' )';
$args = array_merge( $args, $query_args['type'] );
}
}
$query = apply_filters( 'ig_es_workflow_list_where_caluse', $query );
if ( count( $query ) > 0 ) {
$sql .= ' WHERE ';
$sql .= implode( ' AND ', $query );
if ( count( $args ) > 0 ) {
$sql = $wpbd->prepare( $sql, $args ); // phpcs:ignore
}
}
if ( ! $do_count_only ) {
$order = ! empty( $query_args['order'] ) ? strtolower( $query_args['order'] ) : 'desc';
$expected_order_values = array( 'asc', 'desc' );
if ( ! in_array( $order, $expected_order_values, true ) ) {
$order = 'desc';
}
$default_order_by = esc_sql( 'created_at' );
$expected_order_by_values = array( 'title', 'created_at', 'priority' );
if ( empty( $query_args['order_by'] ) || ! in_array( $query_args['order_by'], $expected_order_by_values, true ) ) {
$order_by_clause = " ORDER BY {$default_order_by} DESC";
} else {
$order_by = esc_sql( $query_args['order_by'] );
$order_by_clause = " ORDER BY {$order_by} {$order}, {$default_order_by} DESC";
}
$sql .= $order_by_clause;
if ( ! empty( $query_args['per_page'] ) ) {
$sql .= ' LIMIT ' . $query_args['per_page'];
if ( ! empty( $query_args['page_number'] ) ) {
$sql .= ' OFFSET ' . ( $query_args['page_number'] - 1 ) * $query_args['per_page'];
}
}
$result = $wpbd->get_results( $sql, $output ); // phpcs:ignore
} else {
$result = $wpbd->get_var( $sql ); // phpcs:ignore
}
return $result;
}
/**
* Get workflows by id
*
* @since 4.4.1
*
* @param int $id Workflow.
* @param string $output Output format.
*
* @return array|object|null
*/
public function get_workflow( $id = 0, $output = ARRAY_A ) {
if ( empty( $id ) ) {
return array();
}
$args = array(
'ids' => array( $id ),
);
$workflows = $this->get_workflows( $args, $output );
$workflow = array();
if ( ! empty( $workflows ) ) {
$workflow = array_shift( $workflows );
}
return $workflow;
}
/**
* Add workflow into database
*
* @since 4.4.1
*
* @param array $workflow_data Workflow data.
*
* @return int
*/
public function insert_workflow( $workflow_data = array() ) {
if ( empty( $workflow_data ) || ! is_array( $workflow_data ) ) {
return 0;
}
$workflow_id = $this->insert( $workflow_data );
if ( $workflow_id ) {
do_action( 'ig_es_workflow_inserted', $workflow_id, $workflow_data );
}
return $workflow_id;
}
/**
* Update Workflow
*
* @param int $workflow_id Workflow ID.
* @param array $workflow_data Workflow data.
*
* @return bool|void
*
* @since 4.4.1
*/
public function update_workflow( $workflow_id = 0, $workflow_data = array() ) {
if ( empty( $workflow_id ) || empty( $workflow_data ) || ! is_array( $workflow_data ) ) {
return;
}
// Set updated_at if not set.
$workflow_data['updated_at'] = ! empty( $workflow_data['updated_at'] ) ? $workflow_data['updated_at'] : ig_get_current_date_time();
$updated = $this->update( $workflow_id, $workflow_data );
if ( $updated ) {
// Clear workflow cache, so that while fetching we can get latest workflow data.
ES_Cache::delete( $workflow_id, 'workflows' );
do_action( 'ig_es_workflow_updated', $workflow_id, $workflow_data );
}
return $updated;
}
/**
* Delete Workflows
*
* @since 4.4.1
*
* @param array $ids Workflow IDs.
*/
public function delete_workflows( $ids = array() ) {
global $wpbd;
if ( ! is_array( $ids ) ) {
$ids = array( absint( $ids ) );
}
if ( is_array( $ids ) && count( $ids ) > 0 ) {
foreach ( $ids as $id ) {
$id = absint( $id );
$where = $wpbd->prepare( "$this->primary_key = %d AND type != %d", $id, IG_ES_WORKFLOW_TYPE_SYSTEM );
$workflow_deleted = $this->delete_by_condition( $where );
if ( $workflow_deleted ) {
/**
* Take necessary cleanup steps using this hook
*
* @since 4.4.1
*/
do_action( 'ig_es_workflow_deleted', $id );
}
}
return true;
}
return false;
}
/**
* Method to update workflow status
*
* @param array $workflow_ids Workflow IDs.
* @param integer $status New status.
* @return bool $updated Update status
*
* @since 4.4.1
*/
public function update_status( $workflow_ids = array(), $status = 0 ) {
global $wpbd;
$updated = false;
if ( empty( $workflow_ids ) ) {
return $updated;
}
$workflow_ids = esc_sql( $workflow_ids );
// Variable to hold workflow ids seperated by commas.
$workflow_ids_str = '';
if ( is_array( $workflow_ids ) && count( $workflow_ids ) > 0 ) {
$workflow_ids_str = implode( ',', $workflow_ids );
} elseif ( is_numeric( $workflow_ids ) ) {
$workflow_ids_str = $workflow_ids;
}
if ( ! empty( $workflow_ids_str ) ) {
$updated = $wpbd->query( $wpbd->prepare( "UPDATE {$wpbd->prefix}ig_workflows SET status = %d WHERE id IN ($workflow_ids_str)", $status ) );
}
do_action( 'ig_es_workflow_status_changed', $workflow_ids, $status );
return $updated;
}
/**
* Method to migrate existing audience sync settings to workflows
*
* @since 4.4.1
*/
public function migrate_audience_sync_settings_to_workflows() {
$audience_sync_settings = array(
'ig_es_sync_wp_users' => array(
'workflow_title' => __( 'User Registered', 'email-subscribers' ),
'trigger_name' => 'ig_es_user_registered',
),
'ig_es_sync_comment_users' => array(
'workflow_title' => __( 'Comment Added', 'email-subscribers' ),
'trigger_name' => 'ig_es_comment_added',
),
'ig_es_sync_woocommerce_users' => array(
'workflow_title' => __( 'WooCommerce Order Completed', 'email-subscribers' ),
'trigger_name' => 'ig_es_wc_order_completed',
),
'ig_es_sync_edd_users' => array(
'workflow_title' => __( 'EDD Purchase Completed', 'email-subscribers' ),
'trigger_name' => 'ig_es_edd_complete_purchase',
),
'ig_es_sync_cf7_users' => array(
'workflow_title' => __( 'Contact Form 7 Submitted', 'email-subscribers' ),
'trigger_name' => 'ig_es_cf7_submitted',
),
'ig_es_sync_ninja_forms_users' => array(
'workflow_title' => __( 'Ninja Form Submitted', 'email-subscribers' ),
'trigger_name' => 'ig_es_ninja_forms_submitted',
),
'ig_es_sync_wpforms_users' => array(
'workflow_title' => __( 'WP Form Submitted', 'email-subscribers' ),
'trigger_name' => 'ig_es_wpforms_submitted',
),
'ig_es_sync_give_users' => array(
'workflow_title' => __( 'Give Donation Added', 'email-subscribers' ),
'trigger_name' => 'ig_es_give_donation_made',
),
'ig_es_sync_gravity_forms_users' => array(
'workflow_title' => __( 'Gravity Form Submitted', 'email-subscribers' ),
'trigger_name' => 'ig_es_gravity_forms_submitted',
),
);
$workflows_data = array();
foreach ( $audience_sync_settings as $sync_setting_name => $setting_workflow_data ) {
$sync_settings = get_site_option( $sync_setting_name, false );
$workflow_data = array();
if ( ! empty( $sync_settings ) && is_array( $sync_settings ) ) {
$workflow_data = $this->convert_audience_sync_setting_to_workflow( $sync_setting_name, $setting_workflow_data, $sync_settings );
}
if ( ! empty( $workflow_data ) ) {
$workflows_data[] = $workflow_data;
}
}
// Additional workflow required to support existing Audience synce settings e.g. Updating/Deleting contact list when a user gets updated/deleted.
$additional_workflows = array(
array(
'workflow_title' => __( 'User deleted', 'email-subscribers' ),
'trigger_name' => 'ig_es_user_deleted',
'actions' => array(
'ig_es_delete_contact',
),
'requires' => 'ig_es_sync_wp_users', // Sync setting required for the workflow to be active.
),
array(
'workflow_title' => __( 'User updated', 'email-subscribers' ),
'trigger_name' => 'ig_es_user_updated',
'actions' => array(
'ig_es_update_contact',
),
'requires' => 'ig_es_sync_wp_users', // Sync setting required for the workflow to be active.
),
);
foreach ( $additional_workflows as $workflow ) {
$workflow_data = $this->get_additional_workflow( $workflow );
if ( ! empty( $workflow_data ) ) {
$workflows_data[] = $workflow_data;
}
}
if ( ! empty( $workflows_data ) ) {
return $this->bulk_insert( $workflows_data );
}
return false;
}
/**
* Method to convert audience sync setting to workflow
*
* @param string $sync_setting_name Sync setting option name.
* @param array $setting_workflow_data Sync workflow name.
* @param array $sync_settings Sync setting.
*
* @return array $workflows_data Workflow data
*
* @since 4.4.1
*/
public function convert_audience_sync_setting_to_workflow( $sync_setting_name = '', $setting_workflow_data = array(), $sync_settings = array() ) {
$workflow_data = array();
if ( empty( $sync_setting_name ) || empty( $setting_workflow_data ) || empty( $sync_settings ) ) {
return $workflow_data;
}
$workflow_title = ! empty( $setting_workflow_data['workflow_title'] ) ? ES_Clean::string( $setting_workflow_data['workflow_title'] ) : '';
$workflow_name = ! empty( $workflow_title ) ? sanitize_title( $workflow_title ) : '';
$trigger_name = isset( $setting_workflow_data['trigger_name'] ) ? $setting_workflow_data['trigger_name'] : '';
$actions = array();
// For ig_es_sync_wp_users option, list id is stored in 'es_registered_group' key, for others it is stored in 'list_id'.
$list_key = ( 'ig_es_sync_wp_users' === $sync_setting_name ) ? 'es_registered_group' : 'list_id';
if ( ! empty( $sync_settings[ $list_key ] ) ) {
$list_id = $sync_settings[ $list_key ];
if ( ! empty( $list_id ) ) {
$actions[] = array(
'action_name' => 'ig_es_add_to_list',
'ig-es-list' => ES_Clean::id( $list_id ),
);
}
}
$status = 0;
// For ig_es_sync_wp_users option, enabled state is stored in 'es_registered' key, for others it is stored in 'enable'.
$enabled_key = ( 'ig_es_sync_wp_users' === $sync_setting_name ) ? 'es_registered' : 'enable';
if ( ! empty( $sync_settings[ $enabled_key ] ) ) {
$is_sync_enabled = $sync_settings[ $enabled_key ];
$is_sync_enabled = strtolower( $is_sync_enabled );
if ( 'yes' === $is_sync_enabled ) {
$status = 1;
}
}
$workflow_meta = array();
$workflow_meta['when_to_run'] = 'immediately';
$workflow_data = array(
'name' => $workflow_name,
'title' => $workflow_title,
'trigger_name' => $trigger_name,
'actions' => maybe_serialize( $actions ),
'meta' => maybe_serialize( $workflow_meta ),
'priority' => 0,
'status' => $status,
);
return $workflow_data;
}
/**
* Migrate site notication setting into workflows
*
* @since 5.0.1
*/
public function migrate_notifications_to_workflows() {
$notification_workflows = $this->get_notification_workflows();
if ( ! empty( $notification_workflows ) ) {
foreach ( $notification_workflows as $workflow ) {
$workflow_title = $workflow['title'];
$workflow_name = sanitize_title( $workflow_title );
$trigger_name = $workflow['trigger_name'];
$workflow_meta = array();
$workflow_meta['when_to_run'] = 'immediately';
$workflow_status = $workflow['status'];
$workflow_actions = $workflow['actions'];
$workflow_data = array(
'name' => $workflow_name,
'title' => $workflow_title,
'trigger_name' => $trigger_name,
'actions' => maybe_serialize( $workflow_actions ),
'meta' => maybe_serialize( $workflow_meta ),
'priority' => 0,
'status' => $workflow_status,
);
ES()->workflows_db->insert_workflow( $workflow_data );
}
}
}
/**
* Get workflows for site notifications(user subscribed,confirmed,campaign sent)
*
* @return array $notification_workflows Workflow data
*
* @since 5.0.1
*/
public function get_notification_workflows() {
$admin_emails = ES()->mailer->get_admin_emails();
if ( ! empty( $admin_emails ) ) {
$admin_emails = implode( ',', $admin_emails );
}
$notification_workflows = array(
array(
'trigger_name' => 'ig_es_user_subscribed',
'title' => __( 'Send welcome email when someone subscribes', 'email-subscribers' ),
'actions' => array(
array(
'action_name' => 'ig_es_send_email',
'ig-es-send-to' => '{{EMAIL}}',
'ig-es-email-subject' => ES()->mailer->get_welcome_email_subject(),
'ig-es-email-content' => ES()->mailer->get_welcome_email_content(),
),
),
'status' => ES()->mailer->can_send_welcome_email() ? 1 : 0,
'type' => IG_ES_WORKFLOW_TYPE_SYSTEM,
),
array(
'trigger_name' => 'ig_es_user_unconfirmed',
'title' => __( 'Send confirmation email', 'email-subscribers' ),
'actions' => array(
array(
'action_name' => 'ig_es_send_email',
'ig-es-send-to' => '{{EMAIL}}',
'ig-es-email-subject' => ES()->mailer->get_confirmation_email_subject(),
'ig-es-email-content' => ES()->mailer->get_confirmation_email_content(),
)
),
'status' => 1,
'type' => IG_ES_WORKFLOW_TYPE_SYSTEM,
),
array(
'trigger_name' => 'ig_es_user_subscribed',
'title' => __( 'Notify admin when someone subscribes', 'email-subscribers' ),
'actions' => array(
array(
'action_name' => 'ig_es_send_email',
'ig-es-send-to' => $admin_emails,
'ig-es-email-subject' => ES()->mailer->get_admin_new_contact_email_subject(),
'ig-es-email-content' => ES()->mailer->get_admin_new_contact_email_content(),
),
),
'status' => ES()->mailer->can_send_add_new_contact_notification() ? 1 : 0,
'type' => IG_ES_WORKFLOW_TYPE_SYSTEM,
),
array(
'trigger_name' => 'ig_es_campaign_sent',
'title' => __( 'Notify admin when campaign is sent', 'email-subscribers' ),
'actions' => array(
array(
'action_name' => 'ig_es_send_email',
'ig-es-send-to' => $admin_emails,
'ig-es-email-subject' => ES()->mailer->get_cron_admin_email_subject(),
'ig-es-email-content' => ES()->mailer->get_cron_admin_email_content(),
),
),
'status' => ES()->mailer->can_send_cron_admin_email() ? 1 : 0,
'type' => IG_ES_WORKFLOW_TYPE_SYSTEM,
),
);
return $notification_workflows;
}
/**
* Method to convert audience sync setting to workflow
*
* @param array $workflow workflow array.
*
* @return array $workflows_data Workflow data
*
* @since 4.4.1
*/
public function get_additional_workflow( $workflow = array() ) {
$workflow_data = array();
if ( empty( $workflow ) ) {
return array();
}
$workflow_title = ! empty( $workflow['workflow_title'] ) ? ES_Clean::string( $workflow['workflow_title'] ) : '';
$workflow_name = ! empty( $workflow_title ) ? sanitize_title( $workflow_title ) : '';
$trigger_name = isset( $workflow['trigger_name'] ) ? $workflow['trigger_name'] : '';
$actions = array();
if ( ! empty( $workflow['actions'] ) ) {
foreach ( $workflow['actions'] as $action_name ) {
$actions[] = array(
'action_name' => $action_name,
);
}
}
$status = 0;
if ( ! empty( $workflow['requires'] ) ) {
$sync_setting_name = $workflow['requires'];
$sync_setting = get_site_option( $sync_setting_name );
if ( ! empty( $sync_setting ) ) {
// For ig_es_sync_wp_users option, enabled state is stored in 'es_registered' key, for others it is stored in 'enable'.
$enabled_key = ( 'ig_es_sync_wp_users' === $sync_setting_name ) ? 'es_registered' : 'enable';
if ( ! empty( $sync_setting[ $enabled_key ] ) ) {
$is_sync_enabled = $sync_setting[ $enabled_key ];
$is_sync_enabled = strtolower( $is_sync_enabled );
if ( 'yes' === $is_sync_enabled ) {
$status = 1;
}
}
} else {
return array();
}
}
$workflow_meta = array();
$workflow_meta['when_to_run'] = 'immediately';
$workflow_data = array(
'name' => $workflow_name,
'title' => $workflow_title,
'trigger_name' => $trigger_name,
'actions' => maybe_serialize( $actions ),
'meta' => maybe_serialize( $workflow_meta ),
'priority' => 0,
'status' => $status,
);
return $workflow_data;
}
/**
* Method to migrate existing audience sync settings to ES admin settings
*
* @since 4.4.1
*/
public function migrate_audience_sync_settings_to_admin_settings() {
$ig_es_sync_comment_users = get_option( 'ig_es_sync_comment_users', array() );
if ( ! empty( $ig_es_sync_comment_users ) ) {
$show_opt_in_consent = ! empty( $ig_es_sync_comment_users['enable'] ) && 'yes' === $ig_es_sync_comment_users['enable'] ? 'yes' : 'no';
$opt_in_consent_text = ! empty( $ig_es_sync_comment_users['consent_text'] ) ? $ig_es_sync_comment_users['consent_text'] : '';
update_site_option( 'ig_es_show_opt_in_consent', $show_opt_in_consent );
update_site_option( 'ig_es_opt_in_consent_text', $opt_in_consent_text );
}
}
/**
* Get workflow campaign ID
*
* @since 4.5.3
*
* @param int $workflow_id Wordkfow ID
*
* @return int $campaign_id ID of campaign used for tracking workflow emails.
*/
public function get_workflow_parent_campaign_id( $workflow_id ) {
$campaign_id = 0;
$parent_id = $workflow_id;
$campaigns_ids = ES()->campaigns_db->get_campaigns_by_parent_id( $parent_id );
if ( ! empty( $campaigns_ids ) ) {
$campaign_id = $campaigns_ids[0];
}
return $campaign_id;
}
/**
* Create parent campaign for workflow
*
* @since 4.5.3
*
* @param string $workflow_title Wordkfow title
*
* @return int $tracking_campaign_id Created tracking campaign ID.
*/
public function create_parent_workflow_campaign( $workflow_id, $workflow_data ) {
$campaign_name = ! empty( $workflow_data['title'] ) ? $workflow_data['title'] : '';
$campaign_slug = ! empty( $campaign_name ) ? sanitize_title( $campaign_name ) : '';
$campaign_type = IG_CAMPAIGN_TYPE_WORKFLOW;
$campaign_status = 1;
$parent_id = $workflow_id;
$parent_type = 'workflow';
$campaing_data = array(
'name' => $campaign_name,
'slug' => $campaign_slug,
'type' => $campaign_type,
'status' => $campaign_status,
'parent_id' => $parent_id,
'parent_type' => $parent_type,
);
$campaign_id = ES()->campaigns_db->save_campaign( $campaing_data );
return $campaign_id;
}
/**
* Create parent campaign for workflow
*
* @since 4.5.3
*
* @param string $workflow_title Wordkfow title
*
* @return int $tracking_campaign_id Created tracking campaign ID.
*/
public function update_parent_workflow_campaign( $parent_campaign_id, $workflow_data ) {
if ( empty( $parent_campaign_id ) ) {
return;
}
$campaign_name = ! empty( $workflow_data['title'] ) ? $workflow_data['title'] : '';
$campaign_slug = ! empty( $campaign_name ) ? sanitize_title( $campaign_name ) : '';
$campaign_status = $workflow_data['status'];
$campaing_data = array(
'name' => $campaign_name,
'slug' => $campaign_slug,
'status' => $campaign_status,
);
$campaign_id = ES()->campaigns_db->save_campaign( $campaing_data, $parent_campaign_id );
return $campaign_id;
}
/**
* Delete workflows campaign
*
* @param array $workflow_ids
* @return boolean
*/
public function delete_workflows_campaign( $workflow_ids ) {
$delete_status = array();
if ( ! is_array( $workflow_ids ) ) {
$workflow_ids = array( absint( $workflow_ids ) );
}
if ( is_array( $workflow_ids ) && count( $workflow_ids ) > 0 ) {
foreach ( $workflow_ids as $workflow_id ) {
$parent_campaign_id = $this->get_workflow_parent_campaign_id( $workflow_id );
if ( ! empty( $parent_campaign_id ) ) {
$is_deleted = ES()->campaigns_db->delete_campaigns( $parent_campaign_id );
if ( $is_deleted ) {
$delete_status[ $workflow_id ] = 'deleted';
} else {
$delete_status[ $workflow_id ] = 'failed';
}
}
}
}
return $delete_status;
}
/**
* Create child campaign for individual workflow send email action
*
* @since 4.5.3
*
* @param int $parent_campaign_id Parent campaign ID
* @param array $action Action.
*
* @return int $tracking_campaign_id Created tracking campaign ID.
*/
public function create_child_tracking_campaign( $parent_campaign_id, $action ) {
$campaign_name = ! empty( $action['ig-es-email-subject'] ) ? $action['ig-es-email-subject'] : '';
$campaign_body = ! empty( $action['ig-es-email-content'] ) ? $action['ig-es-email-content'] : '';
$campaign_slug = ! empty( $campaign_name ) ? sanitize_title( $campaign_name ) : '';
$campaign_type = 'workflow_email';
$parent_type = 'workflow';
$campaign_status = 1;
$campaign_meta = array(
'enable_open_tracking' => ! empty( $action['ig-es-email-tracking-enabled'] ) ? 'yes' : 'no',
'enable_link_tracking' => ! empty( $action['ig-es-email-tracking-enabled'] ) ? 'yes' : 'no',
'attachments' => ! empty( $action['attachments'] ) ? $action['attachments'] : array(),
);
$campaing_data = array(
'name' => $campaign_name,
'subject' => $campaign_name,
'body' => $campaign_body,
'slug' => $campaign_slug,
'type' => $campaign_type,
'status' => $campaign_status,
'parent_id' => $parent_campaign_id,
'parent_type' => $parent_type,
'meta' => maybe_serialize( $campaign_meta ),
);
$campaign_id = ES()->campaigns_db->save_campaign( $campaing_data );
return $campaign_id;
}
/**
* Update child campaign for individual workflow send email action
*
* @since 4.5.3
*
* @param int $campaign_id Campaign ID
* @param array $action Action.
*
* @return boolean $updated Created tracking campaign ID.
*/
public function update_child_tracking_campaign( $campaign_id, $action ) {
$campaign_name = ! empty( $action['ig-es-email-subject'] ) ? $action['ig-es-email-subject'] : '';
$campaign_body = ! empty( $action['ig-es-email-content'] ) ? $action['ig-es-email-content'] : '';
$campaign_meta = array(
'enable_open_tracking' => ! empty( $action['ig-es-email-tracking-enabled'] ) ? 'yes' : 'no',
'enable_link_tracking' => ! empty( $action['ig-es-email-tracking-enabled'] ) ? 'yes' : 'no',
'attachments' => ! empty( $action['attachments'] ) ? $action['attachments'] : array(),
);
$campaing_data = array(
'name' => $campaign_name,
'subject' => $campaign_name,
'body' => $campaign_body,
'meta' => maybe_serialize( $campaign_meta ),
);
$updated = ES()->campaigns_db->update( $campaign_id, $campaing_data );
return $updated;
}
/**
* Get ids of child tracking campaigns
*
* @param int $workflow_id
* @return array $child_tracking_campaign_ids
*
* @since 5.0.6
*/
public function get_all_child_tracking_campaign_ids( $workflow_id ) {
$child_tracking_campaign_ids = array();
$parent_campaign_id = $this->get_workflow_parent_campaign_id( $workflow_id );
$child_tracking_campaigns = ES()->campaigns_db->get_campaign_by_parent_id( $parent_campaign_id );
if ( ! empty( $child_tracking_campaigns ) ) {
foreach ( $child_tracking_campaigns as $tracking_campaign ) {
$child_tracking_campaign_ids[] = $tracking_campaign['id'];
}
}
return $child_tracking_campaign_ids;
}
/**
* Get count of active workflows
*
* @return int active workflows count
*
* @since 5.5.7
*/
public function get_active_workflows_count() {
$args = array(
'status' => '1',
);
return self::get_workflows( $args, null, true );
}
/**
* Get count of active workflows grouped by common trigger names
*
* @return array $result
*
* @since 5.5.7
*/
public function get_workflows_count_by_triggername() {
global $wpdb;
$result = $wpdb->get_results( "SELECT trigger_name, count(trigger_name) AS trigger_count FROM {$wpdb->prefix}ig_workflows WHERE status = 1 GROUP BY trigger_name", ARRAY_A );
return $result;
}
}

View File

@@ -0,0 +1,65 @@
<?php
/**
* Email Subscribers' checkbox field
*
* @since 4.4.3
* @version 1.0
* @package Email Subscribers
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Class for workflow checkbox field
*
* @class ES_Checkbox
*/
class ES_Checkbox extends ES_Field {
protected $name = 'checkbox';
protected $type = 'checkbox';
public $default_to_checked = false;
/**
* Constructor
*
* @since 4.4.3
*/
public function __construct() {
parent::__construct();
$this->set_title( __( 'Checkbox', 'email-subscribers' ) );
}
/**
* Render checkbox field
*
* @param $value
*
* @since 4.4.3
*/
public function render( $value ) {
if ( null === $value || '' === $value ) {
$value = $this->default_to_checked;
}
?>
<label>
<input type="checkbox"
name="<?php echo esc_attr( $this->get_full_name() ); ?>"
value="1"
<?php echo ( $value ? 'checked' : '' ); ?>
class="<?php echo esc_attr( $this->get_classes() ); ?>"
<?php $this->output_extra_attrs(); ?>
>
<?php
echo esc_html( $this->get_title() );
?>
</label>
<?php
}
}

View File

@@ -0,0 +1,38 @@
<?php
/**
* Email Subscribers' date field
*
* @since 4.4.1
* @version 1.0
* @package Email Subscribers
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Class for date field
*
* @class Date
*
* @since 4.4.1
*/
class ES_Date extends ES_Text {
/**
* Constructor
*
* @since 4.4.1
*/
public function __construct() {
parent::__construct();
$this->title = __( 'Date', 'email-subscribers' );
$this->name = 'date';
$this->set_placeholder( 'YYYY-MM-DD' );
$this->add_extra_attr( 'autocomplete', 'off' );
$this->add_classes( 'ig-es-date-picker date-picker' );
}
}

View File

@@ -0,0 +1,475 @@
<?php
/**
* Email Subscribers' field abstract class
*
* @since 4.4.1
* @version 1.0
* @package Email Subscribers
*/
/**
* Abstract class Email Subsribers' fields.
*
* @class ES_Field
*
* @since 4.4.1
*/
abstract class ES_Field {
/**
* Field title
*
* @since 4.4.1
*
* @var string
*/
protected $title;
/**
* Field id
*
* @since 5.0.8
*
* @var string
*/
protected $id;
/**
* Field name
*
* @since 4.4.1
*
* @var string
*/
protected $name;
/**
* Field type
*
* @since 4.4.1
*
* @var string
*/
protected $type;
/**
* Field description
*
* @since 4.4.6
*
* @var string
*/
protected $description;
/**
* Field name base
*
* @since 4.4.1
*
* @var string
*/
protected $name_base;
/**
* Is field required
*
* @since 4.4.1
*
* @var bool
*/
protected $required = false;
/**
* Field classes
*
* @since 4.4.1
*
* @var array
*/
protected $classes = array();
/**
* Container element classes
*
* @since 5.3.9
*
* @var array
*/
protected $container_classes = array();
/**
* Extra attributes that will appended to the HTML field element.
*
* @since 4.4.1
*
* @var array
*/
protected $extra_attrs = array();
/**
* Field placeholder
*
* @since 4.4.1
*
* @var string
*/
protected $placeholder = '';
/**
* Output the field HTML.
*
* @since 4.4.1
*
* @param mixed $value Field value.
*/
abstract public function render( $value );
/**
* Field constructor.
*
* @since 4.4.1
*/
public function __construct() {
$this->classes[] = 'ig-es-field';
$this->classes[] = 'ig-es-field--type-' . $this->type;
}
/**
* Set field id
*
* @since 5.0.8
*
* @param string $name Field name.
*
* @return $this
*/
public function set_id( $id ) {
$this->id = $id;
return $this;
}
/**
* Set field name
*
* @since 4.4.1
*
* @param string $name Field name.
*
* @return $this
*/
public function set_name( $name ) {
$this->name = $name;
return $this;
}
/**
* Set field title
*
* @since 4.4.1
*
* @param string $title Field title.
*
* @return $this
*/
public function set_title( $title ) {
$this->title = $title;
return $this;
}
/**
*
* Get field title
*
* @since 4.4.1
*
* @return string
*/
public function get_title() {
return $this->title ? $this->title : '';
}
/**
*
* Get field id
*
* @since 5.0.8
*
* @return string
*/
public function get_id() {
return $this->id ? $this->id : '';
}
/**
*
* Get field name
*
* @since 4.4.1
*
* @return string
*/
public function get_name() {
return $this->name ? $this->name : '';
}
/**
* Get field type
*
* @since 4.4.1
*
* @return string
*/
public function get_type() {
return $this->type;
}
/**
* Set field description
*
* @since 4.4.6
*
* @param $description
*
* @return $this
*/
public function set_description( $description ) {
$this->description = $description;
return $this;
}
/**
* Get field description
*
* @since 4.4.6
*
* @return string
*/
public function get_description() {
return $this->description;
}
/**
*
* Set field placeholder
*
* @since 4.4.1
*
* @param string $placeholder Field placeholder.
*
* @return $this
*/
public function set_placeholder( $placeholder ) {
$this->placeholder = $placeholder;
return $this;
}
/**
*
* Get field placeholder
*
* @since 4.4.1
*
* @return string
*/
public function get_placeholder() {
return $this->placeholder;
}
/**
*
* Add field classes
*
* @since 4.4.1
*
* @param string $classes field classes.
*
* @return $this
*/
public function add_classes( $classes ) {
$this->classes = array_merge( $this->classes, explode( ' ', $classes ) );
return $this;
}
/**
* Get field classes
*
* @since 4.4.1
*
* @param bool $implode Should implode.
*
* @return array|string
*/
public function get_classes( $implode = true ) {
if ( $implode ) {
return implode( ' ', $this->classes );
}
return $this->classes;
}
/**
*
* Add container field classes
*
* @since 5.3.9
*
* @param string $container_classes container field classes.
*
* @return $this
*/
public function add_container_classes( $container_classes ) {
$this->container_classes = array_merge( $this->container_classes, explode( ' ', $container_classes ) );
return $this;
}
/**
* Get container field classes
*
* @since 5.3.9
*
* @param bool $implode Should implode.
*
* @return array|string
*/
public function get_container_classes( $implode = true ) {
if ( $implode ) {
return implode( ' ', $this->container_classes );
}
return $this->container_classes;
}
/**
* Get extra attributes for field.
*
* @since 4.4.1
*
* @param string $name Field name.
* @param string $value Field value.
*
* @return $this
*/
public function add_extra_attr( $name, $value = null ) {
$this->extra_attrs[ $name ] = $value;
return $this;
}
/**
* Add data attribute to field
*
* @since 4.4.1
*
* @param string $name Field name.
* @param string $value Field value.
*
* @return $this
*/
public function add_data_attr( $name, $value = null ) {
$this->add_extra_attr( 'data-' . $name, $value );
return $this;
}
/**
* Outputs the extra field attrs in HTML attribute format.
*
* @since 4.4.1
*
* @modified 4.5.4 Removed echo to allow escaping of attribute.
*/
public function output_extra_attrs() {
foreach ( $this->extra_attrs as $name => $value ) {
if ( is_null( $value ) ) {
echo esc_attr( $name ) . ' ';
} else {
echo esc_attr( $name ) . '="' . esc_attr( $value ) . '" ';
}
}
}
/**
* Set field to be required
*
* @since 4.4.1
*
* @param bool $required Should be required.
*
* @return $this
*/
public function set_required( $required = true ) {
$this->required = $required;
return $this;
}
/**
* Check if field is required
*
* @since 4.4.1
*
* @return bool
*/
public function get_required() {
return $this->required;
}
/**
* Set field name attribute
*
* @since 4.4.1
*
* @param string $name_base Field base name.
*
* @return $this
*/
public function set_name_base( $name_base ) {
$this->name_base = $name_base;
return $this;
}
/**
* Get field base name value
*
* @since 4.4.1
*
* @return bool
*/
public function get_name_base() {
return $this->name_base;
}
/**
* Get field full name including base name and field name.
*
* @since 4.4.1
*
* @return string
*/
public function get_full_name() {
return ( $this->get_name_base() ? $this->get_name_base() . '[' . $this->get_name() . ']' : $this->get_name() );
}
/**
* Sanitizes the value of the field.
*
* This method runs before WRITING a value to the DB but doesn't run before READING.
*
* Defaults to sanitize as a single line string. Override this method for fields that should be sanitized differently.
*
* @since 4.4.1
*
* @param string $value Field value.
*
* @return string
*/
public function sanitize_value( $value ) {
return ES_Clean::string( $value );
}
}

View File

@@ -0,0 +1,104 @@
<?php
/**
* Email Subscribers' text field
*
* @since 5.0.2
* @version 1.0
* @package Email Subscribers
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Class for text field
*
* @class ES_Hidden_Field
*/
class ES_Hidden_Field extends ES_Field {
/**
* Input name
*
* @since 5.0.2
*
* @var string
*/
protected $name = 'hidden_input';
/**
* Input type
*
* @since 5.0.2
*
* @var string
*/
protected $type = 'hidden';
/**
* Is multiple
*
* @since 5.0.2
*
* @var boolean
*/
public $multiple = false;
/**
* Define whether HTML entities should be decoded before the field is rendered.
*
* @since 5.0.2
*
* @var bool
*/
public $decode_html_entities_before_render = true;
/**
* Constructor
*
* @since 5.0.2
*/
public function __construct() {
parent::__construct();
}
/**
* Set multiple
*
* @since 5.0.2
*
* @param bool $multi Flag for multiple field.
*
* @return $this
*/
public function set_multiple( $multi = true ) {
$this->multiple = $multi;
return $this;
}
/**
* Output the field HTML.
*
* @since 5.0.2
*
* @param string $value Field value.
*/
public function render( $value ) {
if ( $this->decode_html_entities_before_render ) {
$value = html_entity_decode( $value );
}
?>
<input type="<?php echo esc_attr( $this->get_type() ); ?>"
name="<?php echo esc_attr( $this->get_full_name() ); ?><?php echo $this->multiple ? '[]' : ''; ?>"
value="<?php echo esc_attr( $value ); ?>"
class="<?php echo esc_attr( $this->get_classes() ); ?>"
placeholder="<?php echo esc_attr( $this->get_placeholder() ); ?>"
<?php $this->output_extra_attrs(); ?>
<?php echo ( $this->get_required() ? 'required' : '' ); ?>
>
<?php
}
}

View File

@@ -0,0 +1,105 @@
<?php
/**
* Icegram Express' number field
*
* @since 4.4.1
* @version 1.0
* @package Email Subscribers
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Class for number field
*
* @class ES_Number
*
* @since 4.4.1
*/
class ES_Number extends ES_Text {
/**
* Field type
*
* @since 4.4.1
*
* @var string
*/
protected $name = 'number_input';
/**
* Field type
*
* @since 4.4.1
*
* @var string
*/
protected $type = 'number';
/**
* Constructor
*
* @since 4.4.1
*/
public function __construct() {
parent::__construct();
$this->title = __( 'Number', 'email-subscribers' );
}
/**
* Set minimumu allowed value
*
* @since 4.4.1
*
* @param string $min minimum value.
*
* @return $this
*/
public function set_min( $min ) {
$this->add_extra_attr( 'min', $min );
return $this;
}
/**
* Set maimum allowed value
*
* @since 4.4.1
*
* @param string $max maximum value.
*
* @return $this
*/
public function set_max( $max ) {
$this->add_extra_attr( 'max', $max );
return $this;
}
/**
* Sanitizes the value of a number field.
*
* If the field is not required, the field can be left blank.
*
* @since 4.4.0
*
* @param string $value Field value.
*
* @return string|float
*/
public function sanitize_value( $value ) {
$value = trim( $value );
if ( ! $this->get_required() ) {
// preserve empty string values, don't cast to float.
if ( '' === $value ) {
return '';
}
}
return (float) $value;
}
}

View File

@@ -0,0 +1,263 @@
<?php
/**
* Icegram Express' select field
*
* @since 4.4.1
* @version 1.0
* @package Email Subscribers
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Class for select field
*
* @class Select
*/
class ES_Select extends ES_Field {
/**
* Field name
*
* @since 4.4.1
*
* @var string
*/
protected $name = 'select';
/**
* Field type
*
* @since 4.4.1
*
* @var string
*/
protected $type = 'select';
/**
* Field options
*
* @since 4.4.1
*
* @var string
*/
protected $default_option;
/**
* Allow multiple choices
*
* @since 4.4.1
*
* @var boolean
*/
public $multiple = false;
/**
* Select field options
*
* @since 4.4.1
*
* @var array
*/
protected $options = array();
/**
* Construct
*
* @since 4.4.1
*
* @param bool $show_placeholder Should show placeholder.
*/
public function __construct( $show_placeholder = true ) {
parent::__construct();
$this->set_title( __( 'Select', 'email-subscribers' ) );
if ( $show_placeholder ) {
$this->set_placeholder( __( '[Select]', 'email-subscribers' ) );
}
}
/**
* Set select options
*
* @since 4.4.1
*
* @param array $options Select options.
*
* @return $this
*/
public function set_options( $options ) {
$this->options = $options;
return $this;
}
/**
* Get select options
*
* @since 4.4.1
*
* @return array
*/
public function get_options() {
return $this->options;
}
/**
* Set select default option
*
* @since 4.4.1
*
* @param string $option default option.
*
* @return $this
*/
public function set_default( $option ) {
$this->default_option = $option;
return $this;
}
/**
* Set multiple flag
*
* @since 4.4.1
*
* @param bool $multi Is multiple.
*
* @return $this
*/
public function set_multiple( $multi = true ) {
$this->multiple = $multi;
return $this;
}
/**
* Render field
*
* @since 4.4.1
*
* @param string $value field value.
*
* @return void
*/
public function render( $value = false ) {
$value = ES_Clean::recursive( $value );
if ( $this->multiple ) {
if ( ! $value ) {
$value = $this->default_option ? $this->default_option : array();
}
$this->render_multiple( (array) $value );
} else {
if ( empty( $value ) && $this->default_option ) {
$value = $this->default_option;
}
$this->render_single( (string) $value );
}
}
/**
* Render a single select box.
*
* @since 4.4.1
*
* @param string $value field value.
*/
protected function render_single( $value ) {
?>
<select name="<?php echo esc_attr( $this->get_full_name() ); ?>"
data-name="<?php echo esc_attr( $this->get_name() ); ?>"
class="<?php echo esc_attr( $this->get_classes() ); ?>"
<?php $this->output_extra_attrs(); ?>
<?php echo ( $this->get_required() ? 'required' : '' ); ?>
>
<?php if ( $this->get_placeholder() ) : ?>
<option value=""><?php echo esc_html( $this->get_placeholder() ); ?></option>
<?php endif; ?>
<?php foreach ( $this->get_options() as $opt_name => $opt_value ) : ?>
<?php if ( is_array( $opt_value ) ) : ?>
<optgroup label="<?php echo esc_attr( $opt_name ); ?>">
<?php foreach ( $opt_value as $opt_sub_name => $opt_sub_value ) : ?>
<option value="<?php echo esc_attr( $opt_sub_name ); ?>" <?php selected( $value, $opt_sub_name ); ?>><?php echo esc_html( $opt_sub_value ); ?></option>
<?php endforeach ?>
</optgroup>
<?php else : ?>
<option value="<?php echo esc_attr( $opt_name ); ?>" <?php selected( $value, $opt_name ); ?>><?php echo esc_html( $opt_value ); ?></option>
<?php endif; ?>
<?php endforeach; ?>
</select>
<?php
}
/**
* Render a multi-select box.
*
* @since 4.4.1
*
* @param array $values field value.
*/
protected function render_multiple( $values ) {
?>
<select name="<?php echo esc_attr( $this->get_full_name() ); ?>[]"
data-name="<?php echo esc_attr( $this->get_name() ); ?>"
class="<?php echo esc_attr( $this->get_classes() ); ?> wc-enhanced-select"
multiple="multiple"
data-placeholder="<?php echo esc_attr( $this->get_placeholder() ); ?>"
<?php $this->output_extra_attrs(); ?>
>
<?php foreach ( $this->get_options() as $opt_name => $opt_value ) : ?>
<option value="<?php echo esc_attr( $opt_name ); ?>"
<?php echo in_array( (string) $opt_name, $values, true ) ? 'selected="selected"' : ''; ?>
><?php echo esc_html( $opt_value ); ?></option>
<?php endforeach; ?>
</select>
<script type="text/javascript">
jQuery(document).ready(function(){
if( 'function' === typeof jQuery.fn.ig_es_select2 ) {
jQuery('.ig-es-form-multiselect[data-name="<?php echo esc_attr( $this->get_name() ); ?>"]').ig_es_select2();
}
});
</script>
<?php
}
/**
* Sanitizes the value of the field.
*
* @since 4.4.1
*
* @param array|string $value Field value.
*
* @return array|string
*/
public function sanitize_value( $value ) {
if ( $this->multiple ) {
return ES_Clean::recursive( $value );
} else {
return ES_Clean::string( $value );
}
}
}

View File

@@ -0,0 +1,105 @@
<?php
/**
* Icegram Express' text field
*
* @since 4.4.1
* @version 1.0
* @package Email Subscribers
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Class for text field
*
* @class ES_Text
*/
class ES_Text extends ES_Field {
/**
* Input name
*
* @since 4.4.1
*
* @var string
*/
protected $name = 'text_input';
/**
* Input type
*
* @since 4.4.1
*
* @var string
*/
protected $type = 'text';
/**
* Is multiple
*
* @since 4.4.1
*
* @var boolean
*/
public $multiple = false;
/**
* Define whether HTML entities should be decoded before the field is rendered.
*
* @since 4.4.1
*
* @var bool
*/
public $decode_html_entities_before_render = true;
/**
* Constructor
*
* @since 4.4.1
*/
public function __construct() {
parent::__construct();
$this->title = __( 'Text Input', 'email-subscribers' );
}
/**
* Set multiple
*
* @since 4.4.1
*
* @param bool $multi Flag for multiple field.
*
* @return $this
*/
public function set_multiple( $multi = true ) {
$this->multiple = $multi;
return $this;
}
/**
* Output the field HTML.
*
* @since 4.4.1
*
* @param string $value Field value.
*/
public function render( $value ) {
if ( $this->decode_html_entities_before_render ) {
$value = html_entity_decode( $value );
}
?>
<input type="<?php echo esc_attr( $this->get_type() ); ?>"
name="<?php echo esc_attr( $this->get_full_name() ); ?><?php echo $this->multiple ? '[]' : ''; ?>"
value="<?php echo esc_attr( $value ); ?>"
class="<?php echo esc_attr( $this->get_classes() ); ?>"
placeholder="<?php echo esc_attr( $this->get_placeholder() ); ?>"
<?php $this->output_extra_attrs(); ?>
<?php echo ( $this->get_required() ? 'required' : '' ); ?>
>
<?php
}
}

View File

@@ -0,0 +1,152 @@
<?php
/**
* Icegram Express' time field
*
* @since 4.4.1
* @version 1.0
* @package Email Subscribers
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Class for Time field
*
* @class ES_Time
*
* @since 4.4.1
*/
class ES_Time extends ES_Field {
/**
* Field name
*
* @since 4.4.1
*
* @var string
*/
protected $name = 'time';
/**
* Field type
*
* @var string
*/
protected $type = 'text';
/**
* Flag to show 24 hours note
*
* @since 4.4.1
*
* @var boolean
*/
protected $show_24hr_note = true;
/**
* Set the maximum value for the hours field.
*
* @since 4.4.1
*
* @var int
*/
public $max_hours = 23;
/**
* Constructor
*
* @since 4.4.1
*/
public function __construct() {
parent::__construct();
$this->title = __( 'Time', 'email-subscribers' );
}
/**
* Set 24 Hours note
*
* @since 4.4.1
*
* @param boolean $show Flag to show 24 hours notice.
*
* @return $this
*/
public function set_show_24hr_note( $show ) {
$this->show_24hr_note = $show;
return $this;
}
/**
* Render field
*
* @since 4.4.1
*
* @param array $value Field value.
*/
public function render( $value ) {
if ( $value ) {
$value = ES_Clean::recursive( (array) $value );
} else {
$value = array( '', '' );
}
?>
<div class="ig-es-time-field-group">
<div class="ig-es-time-field-group__fields">
<?php
$field = new ES_Number();
$field
->set_name_base( $this->get_name_base() )
->set_name( $this->get_name() )
->set_min( 0 )
->set_max( $this->max_hours )
->set_multiple()
->set_placeholder( _x( 'HH', 'time field', 'email-subscribers' ) )
->render( $value[0] );
echo '<div class="ig-es-time-field-group__sep">:</div>';
$field = new ES_Number();
$field
->set_name_base( $this->get_name_base() )
->set_name( $this->get_name() )
->set_min( 0 )
->set_max( 59 )
->set_multiple()
->set_placeholder( _x( 'MM', 'time field', 'email-subscribers' ) )
->render( $value[1] );
?>
</div>
<?php if ( $this->show_24hr_note ) : ?>
<span class="ig-es-time-field-group__24hr-note"><?php esc_html_e( '(24 hour time)', 'email-subscribers' ); ?></span>
<?php endif; ?>
</div>
<?php
}
/**
* Sanitizes the value of the field.
*
* @since 4.4.1
*
* @param array $value Field value.
*
* @return array
*/
public function sanitize_value( $value ) {
$value = ES_Clean::recursive( $value );
$value[0] = min( $this->max_hours, $value[0] );
return $value;
}
}

View File

@@ -0,0 +1,90 @@
<?php
/**
* Icegram Express' WP Editor field
*
* @since 4.5.3
* @version 1.0
* @package Email Subscribers
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Class for WP Editor field
*
* @class ES_WP_Editor
*
* @since 4.5.3
*/
class ES_WP_Editor extends ES_Field {
protected $name = 'ig_es_wp_editor';
protected $type = 'textarea';
public function __construct() {
parent::__construct();
$this->set_title( __( 'WP Editor', 'email-subscribers' ) );
}
/**
* Render wp editor field
*
* @param string $value
*
* @since 4.5.3
*/
public function render( $value ) {
$id = $this->get_id();
$value = ES_Clean::editor_content( $value );
// If it is an ajax request then load wp editor using js library.
if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) {
?>
<textarea name="<?php echo esc_attr( $this->get_full_name() ); ?>" id="<?php echo esc_attr( $id ); ?>">
</textarea>
<?php
$this->ajax_init( $id );
} else {
// If it is not an ajax request then load wp editor using WordPress wp_editor PHP function.
wp_editor( $value, $id, array(
'textarea_name' => $this->get_full_name(),
'tinymce' => true, // default to visual
'quicktags' => true,
));
}
}
/**
* Initialize ajax loading of wp editor field
*
* @param int $id ID of the field.
*
* @since 4.5.3
*/
public function ajax_init( $id ) {
?>
<script type="text/javascript">
jQuery(document).ready(function(){
jQuery('#<?php echo esc_js( $id ); ?>').wp_js_editor();
});
</script>
<?php
}
/**
* Sanitizes the value of the field.
*
* @param string $value
*
* @return string
*
* @since 4.5.3
*/
public function sanitize_value( $value ) {
return ES_Clean::editor_content( $value );
}
}

View File

@@ -0,0 +1,30 @@
<?php
$email_content = <<<EMAIL
Hi {{NAME}},
Just one more step before we share the awesomeness from {{SITENAME}}!
Please confirm your subscription by clicking on <a href='{{SUBSCRIBE-LINK}}'>this link</a>
Thanks!
EMAIL;
return array(
'title' => __( 'Subscriber: Confirmation email', 'email-subscribers' ),
'description' => __( 'Send confirmation email when someone subscribes.', 'email-subscribers' ),
'type' => IG_ES_WORKFLOW_TYPE_USER,
'trigger_name'=> 'ig_es_user_unconfirmed',
'rules' => array(),
'meta' => array(
'when_to_run' => 'immediately',
),
'actions' => array(
array(
'action_name' => 'ig_es_send_email',
'ig-es-send-to' => '{{EMAIL}}',
'ig-es-email-subject' => __( 'Thanks!', 'email-subscribers' ),
'ig-es-email-content' => $email_content,
),
),
);

View File

@@ -0,0 +1,47 @@
<?php
if ( ES()->is_pro() ) {
$reason='Reason:{{subscriber.unsubscriber_reason}}';
} else {
$reason='';
}
$admin_email = ES_Common::get_admin_email();
if ( is_email( $admin_email ) ) {
$user = get_user_by( 'email', $admin_email );
$admin_name = '';
if ( $user instanceof WP_User ) {
$admin_name = ucfirst($user->display_name);
}
}
$email_content = <<<EMAIL
Hi {$admin_name},
A user has chosen to unsubscribe from our services. Please find the following details:
Name:{{subscriber.name}}
Email:{{subscriber.email}}
{$reason}
Thanks!
EMAIL;
return array(
'title' => __( 'Subscriber: Unsubscribe email', 'email-subscribers' ),
'description' => __( 'When a user unsubscribes,Send an unsubscribe notification email to the administrator', 'email-subscribers' ),
'type' => IG_ES_WORKFLOW_TYPE_USER,
'trigger_name'=> 'ig_es_user_unsubscribed',
'rules' => array(),
'meta' => array(
'when_to_run' => 'immediately',
),
'actions' => array(
array(
'action_name' => 'ig_es_send_email',
'ig-es-send-to' => $admin_email,
'ig-es-email-subject' => __( ' Important: User unsubscription notification', 'email-subscribers' ),
'ig-es-email-content' => $email_content,
),
),
);

View File

@@ -0,0 +1,32 @@
<?php
$email_content = <<<EMAIL
Hi {{NAME}},
Just wanted to send you a quick note...
Thank you for joining the awesome {{SITENAME}} tribe.
Only valuable emails from me, promise!
Thanks!
EMAIL;
return array(
'title' => __( 'Subscriber: Welcome email', 'email-subscribers' ),
'description' => __( 'Send welcome email when someone subscribes.', 'email-subscribers' ),
'type' => IG_ES_WORKFLOW_TYPE_USER,
'trigger_name'=> 'ig_es_user_subscribed',
'rules' => array(),
'meta' => array(
'when_to_run' => 'immediately',
),
'actions' => array(
array(
'action_name' => 'ig_es_send_email',
'ig-es-send-to' => '{{EMAIL}}',
'ig-es-email-subject' => __( 'Welcome to {{SITENAME}}', 'email-subscribers' ),
'ig-es-email-content' => $email_content,
),
),
);

View File

@@ -0,0 +1,40 @@
<?php
/**
* Factory class for ES_Workflow_Queue object
*
* @since 4.4.1
* @version 1.0
* @package Email Subscribers
*/
/**
* Factory class for ES_Workflow_Queue object
*
* @since 4.4.1
*/
class ES_Workflow_Queue_Factory {
/**
* Method to get ES_Workflow_Queue based on queue ID
*
* @param int $id Workflow Queue ID.
*
* @return bool|ES_Workflow_Queue
*/
public static function get( $id ) {
$id = ES_Clean::id( $id );
if ( ! $id ) {
return false;
}
$queued_event = new ES_Workflow_Queue( $id );
if ( ! $queued_event->exists ) {
return false;
}
return $queued_event;
}
}

View File

@@ -0,0 +1,57 @@
<?php
/**
* Workflow Queue Handler
*
* @since 4.4.1
* @version 1.0
* @package Email Subscribers
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Background processor for the queue
*/
class ES_Workflow_Queue_Handler {
/**
* Constructor
*/
public function __construct() {
add_action( 'ig_es_process_workflow_queue', array( &$this, 'process_workflow_queue' ) );
}
/**
* Method to process workflows queue.
*
* @param array $args action arguements.
*/
public function process_workflow_queue( $args = array() ) {
if ( empty( $args['queue_id'] ) || ! is_numeric( $args['queue_id'] ) ) {
return false;
}
$queue = ES_Workflow_Queue_Factory::get( $args['queue_id'] );
if ( ! $queue ) {
return false;
}
/** IMPORTANT - since we are running this in background, check if the queue is failed.
This ensures the queue has not already begun to run in a different process
since we are preemptively marking events as failed when they begin to run */
if ( $queue->is_failed() ) {
return false;
}
$queue->run();
return false;
}
}
return new ES_Workflow_Queue_Handler();

View File

@@ -0,0 +1,64 @@
<?php
/**
* Workflow Queue Runner
*
* @since 4.4.1
* @version 1.0
* @package Email Subscribers
*/
defined( 'ABSPATH' ) || exit;
/**
* ES_Workflow_Queue_Runner plugin singleton.
*
* @class ES_Workflow_Queue_Runner
*/
class ES_Workflow_Queue_Runner {
/**
* Instance of singleton.
*
* @var ES_Workflow_Queue_Runner
*/
private static $instance = null;
/**
* Constructor
*/
private function __construct() {
add_action( 'init', array( __CLASS__, 'init' ) );
}
/**
* Init queue runner to process workflow queue
*/
public static function init() {
add_action( 'wp_ajax_ig_es_trigger_workflow_queue_processing', array( __CLASS__, 'init_queue_runner' ) );
add_action( 'wp_ajax_nopriv_ig_es_trigger_workflow_queue_processing', array( __CLASS__, 'init_queue_runner' ) );
}
/**
* Return the singleton instance.
*
* @return ES_Workflow_Queue_Runner
*/
public static function instance() {
if ( is_null( self::$instance ) ) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Furnction to initiate action scheduler queue runner when a trigger is fired. It gets called after queueing workflows in the queue table.
*/
public static function init_queue_runner() {
if ( class_exists( 'ActionScheduler_QueueRunner' ) ) {
$queue_runner = ActionScheduler_QueueRunner::instance();
$queue_runner->run();
}
}
}
ES_Workflow_Queue_Runner::instance();

View File

@@ -0,0 +1,596 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Class to handle workflow queue
*
* @class ES_Workflow_Queue
*
* @property array $data_items (legacy)
*/
class ES_Workflow_Queue extends ES_DB_Workflows_Queue {
/**
* Raw data received from workflow trigger
*
* @since 4.4.1
* @var bool
*/
private $uncompressed_data_layer;
// error messages
const F_WORKFLOW_INACTIVE = 100;
const F_MISSING_DATA = 101;
const F_FATAL_ERROR = 102;
/**
* Workflow queue id
*
* @since 4.4.1
* @var integer queue id
*/
public $id = 0;
/**
* Flag to check whether workflow queue is valid
*
* @since 4.4.1
* @var bool
*/
public $exists = false;
/**
* Workflow queue data
*
* @since 4.4.1
* @var array
*/
public $data = array();
/**
* Variable to store changed field in workflow queue data
*
* @since 4.4.1
* @var array
*/
public $changed_fields = array();
/**
* Added Logger Context
*
* @since 4.2.0
* @var array
*
*/
public $logger_context = array(
'source' => 'ig_es_workflows_queue'
);
/**
* Class constructor
*
* @param bool|int $id
*/
public function __construct( $id = false ) {
parent::__construct();
if ( is_numeric( $id ) ) {
$queue_data = $this->get_by( 'id', $id );
if ( ! empty( $queue_data ) && is_array( $queue_data ) ) {
$this->id = $id;
$queue_data['meta'] = ! empty( $queue_data['meta'] ) ? maybe_unserialize( $queue_data['meta'] ) : array();
$this->data = $queue_data;
$this->exists = true;
}
}
}
/**
* Get workflow queue id
*
* @return int
*/
public function get_id() {
return $this->id ? (int) $this->id : 0;
}
/**
* Set workflow queue id
*
* @param int $id
*/
public function set_id( $id ) {
$this->id = $id;
}
/**
* Set workflow id
*
* @param int $id
*/
public function set_workflow_id( $id ) {
$this->set_prop( 'workflow_id', ES_Clean::id( $id ) );
}
/**
* Get workflow id
*
* @return int
*/
public function get_workflow_id() {
return ES_Clean::id( $this->get_prop( 'workflow_id' ) );
}
/**
* Change workflow queue status as failed.
*
* @param bool $failed
*/
public function set_failed( $failed = true ) {
$this->set_prop( 'failed', ig_es_bool_int( $failed ) );
}
/**
* Check if workflow queue has failed
*
* @return bool
*/
public function is_failed() {
return (bool) $this->get_prop( 'failed' );
}
/**
* Set workflow queue failure code
*
* @param int $failure_code
*/
public function set_failure_code( $failure_code ) {
$this->set_prop( 'failure_code', absint( $failure_code ) );
}
/**
* Get workflow queue failure code
*
* @return int
*/
public function get_failure_code() {
return absint( $this->get_prop( 'failure_code' ) );
}
/**
* Set workflow queue creation date/time
*
* @param DateTime $date
*/
public function set_created_at( $date ) {
$this->set_date_column( 'created_at', $date );
}
/**
* Get workflow queue creation date/time
*
* @return bool|DateTime
*/
public function get_created_at() {
return $this->get_date_column( 'created_at' );
}
/**
* Set schedule time to run the workflow queue
*
* @param DateTime $date
*/
public function set_scheduled_at( $date ) {
$this->set_date_column( 'scheduled_at', $date );
}
/**
* Get schedule time to run the workflow queue
*
* @return bool|DateTime
*/
public function get_scheduled_at() {
return $this->get_date_column( 'scheduled_at' );
}
/**
* Store workflow data in the queue
*
* @param ES_Workflow_Data_Layer $data_layer
*/
public function store_data_layer( $data_layer ) {
$this->uncompressed_data_layer = $data_layer->get_raw_data();
$items_data = array();
foreach ( $this->uncompressed_data_layer as $data_type_id => $data_item ) {
$item_data = $this->get_item_data( $data_type_id, $data_item );
if ( ! empty( $item_data ) ) {
$items_data = array_merge( $items_data, $item_data );
}
}
if ( ! empty( $items_data ) ) {
$queue_id = $this->get_id();
$this->update_meta( $queue_id, $items_data );
}
}
/**
* Get workflow data item's data
*
* @param $data_type_id
* @param $data_item
*
* @return $item_data
*/
private function get_item_data( $data_type_id, $data_item ) {
$data_type = ES_Workflow_Data_Types::get( $data_type_id );
if ( ! $data_type || ! $data_type->validate( $data_item ) ) {
return array();
}
$storage_key = $data_type_id;
$storage_value = $data_type->compress( $data_item );
$item_data = array();
if ( $storage_key ) {
$item_data = array(
$storage_key => $storage_value
);
}
return $item_data;
}
/**
* Get workflow data layer
*
* @return ES_Workflow_Data_Layer
*/
public function get_data_layer() {
if ( ! isset( $this->uncompressed_data_layer ) ) {
$uncompressed_data_layer = array();
$compressed_data_layer = $this->get_compressed_data_layer();
if ( $compressed_data_layer ) {
foreach ( $compressed_data_layer as $data_type_id => $compressed_item ) {
$data_type = ES_Workflow_Data_Types::get( $data_type_id );
if ( $data_type ) {
$uncompressed_data_layer[$data_type_id] = $data_type->decompress( $compressed_item, $compressed_data_layer );
}
}
}
$this->uncompressed_data_layer = new ES_Workflow_Data_Layer( $uncompressed_data_layer );
}
return $this->uncompressed_data_layer;
}
/**
* Fetches the data layer from queue meta, but does not decompress
* Uses the the supplied_data_items field on the workflows trigger
*
* @return array|false
*/
public function get_compressed_data_layer() {
$workflow = $this->get_workflow();
if ( ! $workflow ) {
return false; // workflow must be set
}
if ( ! $this->exists ) {
return false; // queue must be saved
}
$trigger = $workflow->get_trigger();
if ( ! $trigger ) {
return false; // need a trigger
}
$data_layer = array();
$supplied_items = $trigger->get_supplied_data_items();
foreach ( $supplied_items as $data_type_id ) {
$data_item_value = $this->get_compressed_data_item( $data_type_id, $supplied_items );
if ( false !== $data_item_value ) {
$data_layer[ $data_type_id ] = $data_item_value;
}
}
return $data_layer;
}
/**
* Get data item id from stored queue data
*
* @param $data_type_id
* @param array $supplied_data_items
* @return string|false
*/
private function get_compressed_data_item( $data_type_id, $supplied_data_items ) {
$storage_key = $data_type_id;
if ( ! $storage_key ) {
return false;
}
return ES_Clean::recursive( $this->get_meta( $storage_key ) );
}
/**
* Get workflow object
*
* Returns the workflow without a data layer
*
* @return ES_Workflow|false
*/
public function get_workflow() {
return ES_Workflow_Factory::get( $this->get_workflow_id() );
}
/**
* Run workflow in the queue
*
* @return bool
*/
public function run() {
if ( ! $this->exists ) {
return false;
}
// mark as failed and then delete if complete, so fatal error will not cause it to run repeatedly
$this->mark_as_failed( self::F_FATAL_ERROR );
$this->save();
$success = false;
$workflow = $this->get_workflow();
$data_layer = $this->get_data_layer();
$workflow->setup( $data_layer );
$failure = $this->do_failure_check( $workflow );
if ( $failure ) {
// queued event failed
$this->mark_as_failed( $failure );
} else {
$success = true;
// passed fail check so validate workflow and then delete
if ( $this->validate_workflow( $workflow ) ) {
$workflow->run();
$this->delete_queue();
}
}
// important to always clean up
$workflow->cleanup();
return $success;
}
/**
* Returns false if no failure occurred
*
* @param Workflow $workflow
* @return bool|int
*/
public function do_failure_check( $workflow ) {
if ( ! $workflow || ! $workflow->is_active() ) {
return self::F_WORKFLOW_INACTIVE;
}
if ( $this->get_data_layer()->is_missing_data() ) {
return self::F_MISSING_DATA;
}
return false;
}
/**
* Validate the workflow before running it from the queue.
* This validation is different from the initial trigger validation.
*
* @param $workflow Workflow
* @return bool
*/
public function validate_workflow( $workflow ) {
$trigger = $workflow->get_trigger();
if ( ! $trigger ) {
return false;
}
return true;
}
/**
* Inserts or updates the model
* Only updates modified fields
*
* @return bool True on success, false on error.
*/
public function save() {
if ( $this->exists ) {
// update changed fields
$changed_data = array_intersect_key( $this->data, array_flip( $this->changed_fields ) );
// serialize
$changed_data = array_map( 'maybe_serialize', $changed_data );
if ( empty( $changed_data ) ) {
return true;
}
$queue_id = $this->get_id();
$updated = $this->update( $queue_id, $changed_data );
if ( false === $updated ) {
// Return here to prevent cache updates on error
return false;
}
do_action( 'ig_es_object_update', $this ); // cleans object cache
} else {
$this->set_created_at( new DateTime() );
$this->data = array_map( 'maybe_serialize', $this->data );
// insert row
$queue_id = $this->insert( $this->data );
if ( $queue_id ) {
$this->exists = true;
$this->id = $queue_id;
} else {
/* translators: %s: Table name */
ES()->logger->error( sprintf( __( 'Could not insert into \'%1$s\' table. \'%1$s\' may not be present in the database.', 'email-subscribers' ), $this->table_name ), $this->logger_context );
// Return here to prevent cache updates on error
return false;
}
}
// reset changed data
// important to reset after cache hooks
$this->changed_fields = array();
$this->original_data = $this->data;
return true;
}
/**
* Delete workflow queue item.
*/
public function delete_queue() {
$queue_id = $this->get_id();
parent::delete($queue_id);
}
/**
* Mark workflow queue item as failed
*
* @param int $code
*/
public function mark_as_failed( $code ) {
$this->set_failed();
$this->set_failure_code( $code );
$this->save();
}
/**
* Set workflow queue data option
*
* @param $key
* @param $value
*/
public function set_prop( $key, $value ) {
if ( is_array( $value ) && ! $value ) {
$value = ''; // convert empty arrays to blank
}
$this->data[$key] = $value;
$this->changed_fields[] = $key;
}
/**
* Get workflow queue data option
*
* @param $key
* @return mixed
*/
public function get_prop( $key ) {
if ( ! isset( $this->data[$key] ) ) {
return false;
}
$value = $this->data[$key];
$value = maybe_unserialize( $value );
return $value;
}
/**
* Sets the value of a date column from a mixed input.
*
* $value can be an instance of WC_DateTime the timezone will be ignored.
* If $value is a string it must be MYSQL formatted.
*
* @param string $column
* @param DateTime|\DateTime|string $value
*/
protected function set_date_column( $column, $value ) {
if ( is_a( $value, 'DateTime' ) ) {
// convert to UTC time
$utc_date = new DateTime();
$utc_date->setTimestamp( $value->getTimestamp() );
$this->set_prop( $column, $utc_date->format( 'Y-m-d H:i:s' ) );
} elseif ( $value ) {
$this->set_prop( $column, ES_Clean::string( $value ) );
}
}
/**
* Get datetime of workflow queue
*
* @param $column
* @return bool|DateTime
*/
protected function get_date_column( $column ) {
$prop = $this->get_prop( $column );
if ( $column && $prop ) {
return new DateTime( $prop );
}
return false;
}
/**
* Get a single meta value by key.
*
* Returns an empty string if field is empty or doesn't exist.
*
* @param string $key
*
* @return mixed
*/
public function get_meta( $meta_key = '' ) {
return ! empty( $this->data['meta'][ $meta_key ] ) ? $this->data['meta'][ $meta_key ] : array();
}
}

View File

@@ -0,0 +1,67 @@
<?php
/**
* Select Abstract rule class.
*
* @since 5.4.15
* @version 1.0
* @package Email Subscribers
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
if ( ! class_exists( 'Es_Rule_Number_Abstract' ) ) {
abstract class Es_Rule_Number_Abstract extends Es_Workflow_Rule {
/**
* The rule type.
*
* @var string
*/
public $type = 'number';
/**
* Init.
*/
public function init() {
$this->compare_types = $this->get_numeric_select_compare_types();
}
/**
* Validate a number rule.
*
* @param string|array $actual Will be an array
* @param string $compare_type
* @param array|string $expected
*
* @return bool
*/
public function validate_number( $actual, $compare_type, $expected ) {
$expected = (float) $expected;
$actual = (float) $actual;
switch ( $compare_type ) {
case 'exactly_equal_to':
return $actual === $expected;
case 'less_than_or_equal_to':
return $actual <= $expected;
case 'more_than_or_equal_to':
return $actual >= $expected;
}
return false;
}
}
}

View File

@@ -0,0 +1,47 @@
<?php
/**
* Select Abstract rule class.
*
* @since 5.5.0
* @version 1.0
* @package Email Subscribers
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
if ( ! class_exists( 'Es_Rule_Preloaded_Select_Abstract' ) ) {
abstract class Es_Rule_Preloaded_Select_Abstract extends Es_Rule_Select {
/**
* Cached select options. Leave public for JSON.
*
* @var array
*/
public $select_choices;
/**
* Load select choices for rule.
*
* @return array
*/
public function get_select_choices() {
return [];
}
/**
* Get the select choices for the rule.
*
* Choices are cached in memory.
*
* @return array
*/
public function load_select_choices() {
if ( ! isset( $this->select_choices ) ) {
$this->select_choices = apply_filters( 'ig_es_rules_preloaded_select_choices', $this->get_select_choices(), $this );
}
return $this->select_choices;
}
}
}

View File

@@ -0,0 +1,55 @@
<?php
/**
* Select Abstract rule class.
*
* @since 5.5.0
* @version 1.0
* @package Email Subscribers
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
if ( ! class_exists( 'Es_Rule_Product_Select_Abstract' ) ) {
abstract class Es_Rule_Product_Select_Abstract extends Es_Rule_Searchable_Select_Abstract {
/**
* The CSS class to use on the search field.
*
* @var string
*/
public $class = 'wc-product-search';
/**
* Init.
*/
public function init() {
parent::init();
$this->placeholder = __( 'Search products...', 'email-subscribers' );
}
/**
* Display product name on frontend.
*
* @param int $value
*
* @return string|int
*/
public function get_object_display_value( $value ) {
$value = absint( $value );
$product = wc_get_product( $value );
return $product ? $product->get_formatted_name() : $value;
}
/**
* Get the ajax action to use for the AJAX search.
*
* @return string
*/
public function get_search_ajax_action() {
return 'woocommerce_json_search_products_and_variations';
}
}
}

View File

@@ -0,0 +1,69 @@
<?php
/**
* Select Abstract rule class.
*
* @since 5.5.0
* @version 1.0
* @package Email Subscribers
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
if ( ! class_exists( 'Es_Rule_Searchable_Select_Abstract' ) ) {
abstract class Es_Rule_Searchable_Select_Abstract extends Es_Rule_Select {
/**
* The rule type.
*
* @var string
*/
public $type = 'object';
/**
* The CSS class to use on the search field.
*
* @var string
*/
public $class = 'ig-es-json-search';
/**
* The field placeholder.
*
* @var string
*/
public $placeholder;
/**
* Get the ajax action to use for the AJAX search.
*
* @return string
*/
abstract public function get_search_ajax_action();
/**
* Init.
*/
public function init() {
parent::init();
$this->placeholder = __( 'Search...', 'email-subscribers' );
if ( ! $this->is_multi ) {
$this->compare_types = $this->get_includes_or_not_compare_types();
}
}
/**
* Override this method to alter how saved values are displayed.
*
* @param string $value
*
* @return string
*/
public function get_object_display_value( $value ) {
return $value;
}
}
}

View File

@@ -0,0 +1,132 @@
<?php
/**
* Select Abstract rule class.
*
* @since 5.5.0
* @version 1.0
* @package Email Subscribers
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
if ( ! class_exists( 'Es_Rule_Select' ) ) {
abstract class Es_Rule_Select extends Es_Workflow_Rule {
/**
* The rule type.
*
* @var string
*/
public $type = 'select';
/**
* Allow multiple selections?
*
* @var bool
*/
public $is_multi = false;
/**
* Init rule.
*/
public function init() {
if ( $this->is_multi ) {
$this->compare_types = $this->get_multi_select_compare_types();
} else {
$this->compare_types = $this->get_is_or_not_compare_types();
}
}
/**
* Validate a select rule.
*
* @param string|array $actual Will be an array when is_multi prop is true.
* @param string $compare_type
* @param array|string $expected
*
* @return bool
*/
public function validate_select( $actual, $compare_type, $expected ) {
if ( $this->is_multi ) {
// actual can be empty
if ( ! $actual ) {
$actual = [];
}
// expected must have a value
if ( ! $expected ) {
return false;
}
$actual = (array) $actual;
$expected = (array) $expected;
switch ( $compare_type ) {
case 'matches_all':
return count( array_intersect( $expected, $actual ) ) === count( $expected );
case 'matches_none':
return count( array_intersect( $expected, $actual ) ) === 0;
case 'matches_any':
return count( array_intersect( $expected, $actual ) ) >= 1;
}
} else {
// actual must be scalar, but expected could be multiple values
if ( ! is_scalar( $actual ) ) {
return false;
}
// TODO review above exclusions
// phpcs:disable WordPress.PHP.StrictComparisons.LooseComparison
// phpcs:disable WordPress.PHP.StrictInArray.MissingTrueStrict
if ( is_array( $expected ) ) {
$is_equal = in_array( $actual, $expected );
} else {
$is_equal = $expected == $actual;
}
// phpcs:enable
switch ( $compare_type ) {
case 'is':
return $is_equal;
case 'is_not':
return ! $is_equal;
}
}
return false;
}
/**
* Validate select rule, but case insensitive.
*
* @param array|string $actual Will be an array when is_multi prop is true.
* @param string $compare_type
* @param array|string $expected
*
* @return bool
* @since 4.4.0
*
*/
public function validate_select_case_insensitive( $actual, $compare_type, $expected ) {
if ( is_array( $actual ) ) {
$actual = array_map( 'wc_strtolower', $actual );
} else {
$actual = strtolower( (string) $actual );
}
$expected = array_map( 'wc_strtolower', (array) $expected );
return $this->validate_select( $actual, $compare_type, $expected );
}
}
}

View File

@@ -0,0 +1,44 @@
<?php
/**
* Abstract trigger for form related triggers
*
* @since 4.4.6
* @version 1.0
* @package Email Subscribers
*/
defined( 'ABSPATH' ) || exit;
/***
* ES_Trigger_Form_Submitted class.
*
* @since 4.4.6
*/
abstract class ES_Trigger_Form_Submitted extends ES_Workflow_Trigger {
/**
* Declares data items available in trigger.
*
* @var array
*/
public $supplied_data_items = array( 'form_data' );
/**
* Validate a workflow.
*
* @param ES_Workflow $workflow Workglow object.
*
* @return bool
*/
public function validate_workflow( $workflow ) {
$form_data = $workflow->data_layer()->get_item( 'form_data' );
if ( ! is_array( $form_data ) || empty( $form_data['email'] ) ) {
return false;
}
return true;
}
}

View File

@@ -0,0 +1,81 @@
<?php
/**
* Triggers when a user gets subscribed
*
* @since 5.0.1
* @version 1.0
* @package Email Subscribers
*/
defined( 'ABSPATH' ) || exit;
/***
* ES_Trigger_Campaign_Failed class.
*
* @since 5.0.1
*/
class ES_Trigger_Campaign_Failed extends ES_Workflow_Trigger {
/**
* Declares data items available in trigger.
*
* @var array
*/
public $supplied_data_items = array( 'campaign' );
/**
* Load trigger admin props.
*/
public function load_admin_details() {
$this->title = __( 'Campaign failed', 'email-subscribers' );
$this->description = __( 'Fires when a campaign isn\'t sent after trying 3 times.', 'email-subscribers' );
$this->group = __( 'Admin', 'email-subscribers' );
}
/**
* Register trigger hooks.
*/
public function register_hooks() {
add_action( 'ig_es_campaign_failed', array( $this, 'handle_campaign_failed' ) );
}
/**
* Catch campaign failed hook
*
* @param array $trigger_data.
*/
public function handle_campaign_failed( $trigger_data ) {
$notification_guid = $trigger_data['notification_guid'];
// Prepare data.
$data = array(
'campaign' => array(
'notification_guid' => $notification_guid
)
);
$this->maybe_run( $data );
}
/**
* Validate a workflow.
*
* @param ES_Workflow $workflow Workflow object.
*
* @return bool
*/
public function validate_workflow( $workflow ) {
$campaign = $workflow->data_layer()->get_item( 'campaign' );
if ( empty( $campaign ) ) {
return false;
}
return true;
}
}

View File

@@ -0,0 +1,79 @@
<?php
/**
* Triggers when a user gets subscribed
*
* @since 5.0.1
* @version 1.0
* @package Email Subscribers
*/
defined( 'ABSPATH' ) || exit;
/***
* ES_Trigger_Campaign_Sent class.
*
* @since 5.0.1
*/
class ES_Trigger_Campaign_Sent extends ES_Workflow_Trigger {
/**
* Declares data items available in trigger.
*
* @var array
*/
public $supplied_data_items = array( 'campaign' );
/**
* Load trigger admin props.
*/
public function load_admin_details() {
$this->title = __( 'Campaign sent', 'email-subscribers' );
$this->description = __( 'Fires when a campaign is sent successfully.', 'email-subscribers' );
$this->group = __( 'Admin', 'email-subscribers' );
}
/**
* Register trigger hooks.
*/
public function register_hooks() {
add_action( 'ig_es_campaign_sent', array( $this, 'handle_campaign_sent' ) );
}
/**
* Catch user subscribed hook
*
* @param int $notification_guid Notification ID.
*/
public function handle_campaign_sent( $notification_guid ) {
// Prepare data.
$data = array(
'campaign' => array(
'notification_guid' => $notification_guid
)
);
$this->maybe_run( $data );
}
/**
* Validate a workflow.
*
* @param ES_Workflow $workflow Workflow object.
*
* @return bool
*/
public function validate_workflow( $workflow ) {
$campaign = $workflow->data_layer()->get_item( 'campaign' );
if ( empty( $campaign ) ) {
return false;
}
return true;
}
}

View File

@@ -0,0 +1,93 @@
<?php
/**
* Triggers when a user gets deleted
*
* @since 4.4.1
* @version 1.0
* @package Email Subscribers
*/
defined( 'ABSPATH' ) || exit;
/***
* ES_Trigger_User_Deleted class.
*
* @since 4.4.1
*/
class ES_Trigger_User_Deleted extends ES_Workflow_Trigger {
/**
* Declares data items available in trigger.
*
* @var array
*/
public $supplied_data_items = array( 'subscriber' );
/**
* Load trigger admin props.
*/
public function load_admin_details() {
$this->title = __( 'User Deleted', 'email-subscribers' );
$this->description = __( 'Fires when user deleted from WordPress.', 'email-subscribers' );
$this->group = __( 'User', 'email-subscribers' );
}
/**
* Register trigger hooks.
*/
public function register_hooks() {
add_action( 'delete_user', array( $this, 'handle_user_delete' ), 10, 1 );
}
/**
* Catch user deleted hook
*
* @param int $user_id User ID.
*/
public function handle_user_delete( $user_id = 0 ) {
// Get user info.
$user = get_userdata( $user_id );
if ( ! ( $user instanceof WP_User ) ) {
return;
}
$email = $user->user_email;
if ( ! empty( $email ) ) {
$subscriber = array(
'email' => $email,
);
// Prepare data.
$data = array(
'subscriber' => $subscriber,
);
$this->maybe_run( $data );
}
}
/**
* Validate a workflow.
*
* @param ES_Workflow $workflow Workflow object.
*
* @return bool
*/
public function validate_workflow( $workflow ) {
$subscriber = $workflow->data_layer()->get_item( 'subscriber' );
if ( ! $subscriber ) {
return false;
}
return true;
}
}

View File

@@ -0,0 +1,83 @@
<?php
/**
* Triggers when a user gets registered
*
* @since 4.4.1
* @version 1.0
* @package Email Subscribers
*/
defined( 'ABSPATH' ) || exit;
/***
* ES_Trigger_User_Registered class.
*
* @since 4.4.0
*/
class ES_Trigger_User_Registered extends ES_Workflow_Trigger {
/**
* Declares data items available in trigger.
*
* @var array
*/
public $supplied_data_items = array( 'user', 'coupon' );
/**
* Load trigger admin props.
*/
public function load_admin_details() {
$this->title = __( 'User Registered', 'email-subscribers' );
$this->description = __( 'Fires when someone signup.', 'email-subscribers' );
$this->group = __( 'User', 'email-subscribers' );
}
/**
* Register trigger hooks.
*/
public function register_hooks() {
add_action( 'user_register', array( $this, 'handle_user_register' ) );
}
/**
* Catch user registered hook
*
* @param int $user_id User ID.
*/
public function handle_user_register( $user_id ) {
// Get user info.
$user = get_userdata( $user_id );
if ( ! ( $user instanceof WP_User ) ) {
return;
}
// Prepare data.
$data = array(
'user' => $user,
);
$this->maybe_run( $data );
}
/**
* Validate a workflow.
*
* @param ES_Workflow $workflow Workflow object.
*
* @return bool
*/
public function validate_workflow( $workflow ) {
$user = $workflow->data_layer()->get_item( 'user' );
if ( ! $user ) {
return false;
}
return true;
}
}

View File

@@ -0,0 +1,77 @@
<?php
/**
* Triggers when a user gets subscribed
*
* @since 5.0.1
* @version 1.0
* @package Email Subscribers
*/
defined( 'ABSPATH' ) || exit;
/***
* ES_Trigger_User_Subscribed class.
*
* @since 5.0.1
*/
class ES_Trigger_User_Subscribed extends ES_Workflow_Trigger {
/**
* Declares data items available in trigger.
*
* @var array
*/
public $supplied_data_items = array( 'subscriber', 'coupon' );
/**
* Load trigger admin props.
*/
public function load_admin_details() {
$this->title = __( 'User Subscribed', 'email-subscribers' );
$this->description = __( 'Fires when someone subscribes.', 'email-subscribers' );
$this->group = __( 'Subscriber', 'email-subscribers' );
}
/**
* Register trigger hooks.
*/
public function register_hooks() {
add_action( 'ig_es_contact_subscribed', array( $this, 'handle_user_subscribe' ) );
}
/**
* Catch user subscribed hook
*
* @param int $subscriber_id User ID.
*/
public function handle_user_subscribe( $subscriber ) {
// Prepare data.
$data = array(
'subscriber' => $subscriber
);
$this->maybe_run( $data );
}
/**
* Validate a workflow.
*
* @param ES_Workflow $workflow Workflow object.
*
* @return bool
*/
public function validate_workflow( $workflow ) {
$subscriber = $workflow->data_layer()->get_item( 'subscriber' );
if ( empty( $subscriber ) ) {
return false;
}
return true;
}
}

View File

@@ -0,0 +1,77 @@
<?php
/**
* Triggers when a user gets unconfirmed
*
* @since 5.0.1
* @version 1.0
* @package Email Subscribers
*/
defined( 'ABSPATH' ) || exit;
/***
* ES_Trigger_User_Unconfirmed class.
*
* @since 5.0.1
*/
class ES_Trigger_User_Unconfirmed extends ES_Workflow_Trigger {
/**
* Declares data items available in trigger.
*
* @var array
*/
public $supplied_data_items = array( 'subscriber' );
/**
* Load trigger admin props.
*/
public function load_admin_details() {
$this->title = __( 'User Unconfirmed', 'email-subscribers' );
$this->description = __( 'Fires when someone submits subscription form.', 'email-subscribers' );
$this->group = __( 'Subscriber', 'email-subscribers' );
}
/**
* Register trigger hooks.
*/
public function register_hooks() {
add_action( 'ig_es_contact_unconfirmed', array( $this, 'handle_user_subscribe' ) );
}
/**
* Catch user unconfirmed hook
*
* @param int $subscriber_id User ID.
*/
public function handle_user_subscribe( $subscriber ) {
// Prepare data.
$data = array(
'subscriber' => $subscriber
);
$this->maybe_run( $data );
}
/**
* Validate a workflow.
*
* @param ES_Workflow $workflow Workflow object.
*
* @return bool
*/
public function validate_workflow( $workflow ) {
$subscriber = $workflow->data_layer()->get_item( 'subscriber' );
if ( empty( $subscriber ) ) {
return false;
}
return true;
}
}

View File

@@ -0,0 +1,80 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
if ( ! class_exists( 'ES_Trigger_User_Unsubscribed' ) ) {
/**
* Class ES_Trigger_User_Unsubscribed
*/
class ES_Trigger_User_Unsubscribed extends ES_Workflow_Trigger {
/**
* Declares data items available in trigger.
*
* @var array
*/
public $supplied_data_items = array( 'subscriber' );
/**
* Load trigger admin details.
*/
public function load_admin_details() {
$this->title = __( 'User Unsubscribed', 'email-subscribers' );
$this->description = __( 'Fires when someone unsubscribes.', 'email-subscribers' );
$this->group = __( 'Subscriber', 'email-subscribers' );
}
/**
* Register trigger's hooks.
*/
public function register_hooks() {
// Add action for custom trigger event
add_action( 'ig_es_contact_unsubscribe', array( $this, 'handle_trigger_event' ), 999, 4 );
}
/**
* Handle custom trigger event.
*
* @param array $user_data
*/
public function handle_trigger_event( $subscriber_id, $message_id, $campaign_id, $unsubscribe_lists ) {
if ( ! empty( $subscriber_id ) ) {
$subscriber = ES()->contacts_db->get( $subscriber_id );
$list_id = is_array($unsubscribe_lists) ? $unsubscribe_lists[0] : $unsubscribe_lists;
$unsubscriber_reason = ES()->contacts_db->get_unsubscriber_reason($subscriber_id, $list_id);
if ( ! empty( $subscriber ) ) {
$email = ! empty( $subscriber['email'] ) && is_email( $subscriber['email'] ) ? $subscriber['email'] : '';
$first_name = ! empty( $subscriber['first_name'] ) ? $subscriber['first_name'] : '';
$last_name = ! empty( $subscriber['last_name'] ) ? $subscriber['last_name'] : '';
$unsubscribe_feedback_text = ! empty( $unsubscriber_reason ) ? $unsubscriber_reason :'';
if ( ! empty( $email ) ) {
$subscriber = array(
'email' => $email,
'name' => $first_name . ' ' . $last_name,
'unsubscribe_feedback_text'=>$unsubscribe_feedback_text,
);
// Prepare data.
$data = array(
'subscriber' => $subscriber,
);
$this->maybe_run( $data );
}
}
}
}
}
}

View File

@@ -0,0 +1,84 @@
<?php
/**
* Triggers when a user is updated
*
* @since 4.4.1
* @version 1.0
* @package Email Subscribers
*/
defined( 'ABSPATH' ) || exit;
/***
* ES_Trigger_User_Updated class.
*
* @since 4.4.1
*/
class ES_Trigger_User_Updated extends ES_Workflow_Trigger {
/**
* Declares data items available in trigger.
*
* @var array
*/
public $supplied_data_items = array( 'user' );
/**
* Load trigger admin props.
*/
public function load_admin_details() {
$this->title = __( 'User Updated', 'email-subscribers' );
$this->description = __( 'Fires when a user is updated.', 'email-subscribers' );
$this->group = __( 'User', 'email-subscribers' );
}
/**
* Register trigger hooks.
*/
public function register_hooks() {
add_action( 'profile_update', array( $this, 'handle_user_updated' ), 10, 2 );
}
/**
* Catch user updated hook
*
* @param int $user_id User ID.
* @param WP_User $old_user_data User object having old data.
*/
public function handle_user_updated( $user_id, $old_user_data ) {
// Get user info.
$user = get_userdata( $user_id );
if ( ! ( $user instanceof WP_User ) ) {
return;
}
// Prepare data.
$data = array(
'user' => $user,
);
$this->maybe_run( $data );
}
/**
* Validate a workflow.
*
* @param ES_Workflow $workflow Workflow object.
*
* @return bool
*/
public function validate_workflow( $workflow ) {
$user = $workflow->data_layer()->get_item( 'user' );
if ( ! $user ) {
return false;
}
return true;
}
}

View File

@@ -0,0 +1,32 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Variable class to get value for {{subscriber.email}} placeholder
*/
class IG_ES_Variable_Subscriber_Email extends IG_ES_Workflow_Variable {
/**
* Method to set description and other admin props
*
*/
public function load_admin_details() {
$this->description = __( 'Displays subscriber email.', 'email-subscribers' );
}
/**
* Get subscriber email from order
*
* @param $parameters array
* @return string
*/
public function get_value( $subscriber, $parameters ) {
return $subscriber['email'];
}
}
return new IG_ES_Variable_Subscriber_Email();

View File

@@ -0,0 +1,32 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Variable class to get value for {{subscriber.first_name}} placeholder
*/
class IG_ES_Variable_Subscriber_First_Name extends IG_ES_Workflow_Variable {
/**
* Method to set description and other admin props
*
*/
public function load_admin_details() {
$this->description = __( 'Displays subscriber first name.', 'email-subscribers' );
}
/**
* Get subscriber name from order
*
* @param $parameters array
* @return string
*/
public function get_value( $subscriber, $parameters ) {
return $subscriber['first_name'];
}
}
return new IG_ES_Variable_Subscriber_First_Name();

View File

@@ -0,0 +1,32 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Variable class to get value for {{subscriber.last_name}} placeholder
*/
class IG_ES_Variable_Subscriber_Last_Name extends IG_ES_Workflow_Variable {
/**
* Method to set description and other admin props
*
*/
public function load_admin_details() {
$this->description = __( 'Displays subscriber name.', 'email-subscribers' );
}
/**
* Get subscriber name from order
*
* @param $parameters array
* @return string
*/
public function get_value( $subscriber, $parameters ) {
return $subscriber['last_name'];
}
}
return new IG_ES_Variable_Subscriber_Last_Name();

View File

@@ -0,0 +1,32 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Variable class to get value for {{subscriber.name}} placeholder
*/
class IG_ES_Variable_Subscriber_Name extends IG_ES_Workflow_Variable {
/**
* Method to set description and other admin props
*
*/
public function load_admin_details() {
$this->description = __( 'Displays subscriber name.', 'email-subscribers' );
}
/**
* Get subscriber name from order
*
* @param $parameters array
* @return string
*/
public function get_value( $subscriber, $parameters ) {
return $subscriber['name'];
}
}
return new IG_ES_Variable_Subscriber_Name();

View File

@@ -0,0 +1,33 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Variable class to get value for {{subscriber.unsubscriber_reason}} placeholder
*/
class IG_ES_Variable_Subscriber_Unsubscribe_Reason extends IG_ES_Workflow_Variable {
/**
* Method to set description and other admin props
*
*/
public function load_admin_details() {
$this->description = __( 'Displays unsubscriber feedback.', 'email-subscribers' );
}
/**
* Get unsubscriber feedback
*
* @param $parameters array
* @return string
*/
public function get_value( $subscriber, $parameters ) {
return $subscriber['unsubscribe_feedback_text'];
}
}
return new IG_ES_Variable_Subscriber_Unsubscribe_Reason();

View File

@@ -0,0 +1,181 @@
<?php
/**
* Workflow helper functions
*
* @since 4.4.1
* @version 1.0
* @package Email Subscribers
*/
/**
* Function to validate workflow data item
*
* @param string $type Data item type.
* @param ES_Workflow_Data_Type $item Data item.
*
* @return mixed item of false
*
* @since 4.4.1
*/
function ig_es_validate_data_item( $type, $item ) {
if ( ! $type || ! $item ) {
return false;
}
$valid = false;
// Validate with the data type classes.
$data_type = ES_Workflow_Data_Types::get( $type );
if ( $data_type ) {
$valid = $data_type->validate( $item );
}
/**
* Filter to override data item validation
*
* @since 4.4.1
*/
$valid = apply_filters( 'ig_es_validate_data_item', $valid, $type, $item );
if ( $valid ) {
return $item;
}
return false;
}
/**
* Function to convert bool values to int values.
*
* @param mixed $val Mixed values.
* @return int
*
* @since 4.4.1
*/
function ig_es_bool_int( $val ) {
return intval( (bool) $val );
}
/**
* Generate tracking key
*
* @param $length int
* @param bool $case_sensitive When false only lowercase letters will be included
* @param bool $more_numbers
* @return string
*/
function ig_es_generate_key( $length = 25, $case_sensitive = true, $more_numbers = false ) {
$chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
if ( $case_sensitive ) {
$chars .= 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
}
if ( $more_numbers ) {
$chars .= '01234567890123456789';
}
$password = '';
$chars_length = strlen( $chars );
for ( $i = 0; $i < $length; $i++ ) {
$password .= substr($chars, wp_rand( 0, $chars_length - 1), 1);
}
return $password;
}
/**
* Does str_replace but limited to one replacement
*
* @param string$subject
* @param string$find
* @param string $replace
* @return string
*/
function ig_es_str_replace_first_match( $subject, $find, $replace = '' ) {
$pos = strpos($subject, $find);
if ( false !== $pos ) {
return substr_replace($subject, $replace, $pos, strlen($find));
}
return $subject;
}
/**
* Get country name from country code
*
* @param string $country_code
* @return string|bool
*
* @since 4.6.9
*/
function ig_es_get_country_name( $country_code ) {
$countries = WC()->countries->get_countries();
return isset( $countries[ $country_code ] ) ? $countries[ $country_code ] : false;
}
/**
* Get state name from country and state code
*
* @param string $country_code
* @param string $state_code
* @return string|bool
*
* @since 4.6.9
*/
function ig_es_get_state_name( $country_code, $state_code ) {
$states = WC()->countries->get_states( $country_code );
return isset( $states[ $state_code ] ) ? $states[ $state_code ] : false;
}
/**
* Get product image
*
* @param WC_Product $product
* @param string $size
* @return array|false|string
*
* @since 4.6.9
*/
function ig_es_get_wc_product_image_url( $product, $size = 'shop_catalog' ) {
$image_id = $product->get_image_id();
if ( $image_id ) {
$image_url = wp_get_attachment_image_url( $image_id, $size );
return apply_filters( 'ig_es_email_product_image_src', $image_url, $size, $product );
} else {
$image_url = wc_placeholder_img_src( $size );
return apply_filters( 'ig_es_email_product_placeholder_image_src', $image_url, $size, $product );
}
}
function ig_es_create_list_from_product( $product ) {
$list_id = 0;
if ( ! ( $product instanceof WC_Product ) ) {
return $list_id;
}
$product_name = $product->get_name();
$product_sku = $product->get_sku();
$list_name = $product_name;
if ( empty( $product_sku ) ) {
$list_slug = $product_name;
} else {
$list_slug = $product_sku;
}
$list = ES()->lists_db->get_list_by_slug( $list_slug );
if ( ! empty( $list ) ) {
$list_id = $list['id'];
} else {
$list_id = ES()->lists_db->add_list( $list_name, $list_slug );
}
return $list_id;
}