version 4.13.0

This commit is contained in:
2021-12-07 11:08:05 +00:00
commit cb26d2c0c4
1285 changed files with 254735 additions and 0 deletions

314
core/components/Cache.php Normal file
View File

@ -0,0 +1,314 @@
<?php
/**
* Core Cache implements an object cache.
*
* The Object Cache stores all of the cache data to memory only for the duration of the request.
*
* @package Core\Cache
*/
/**
* Core class that implements an object cache.
*
* The Object Cache is used to save on trips to the database. The
* Object Cache stores all of the cache data to memory and makes the cache
* contents available by using a key, which is used to name and later retrieve
* the cache contents.
*
* @private
*
* @package ET\Core\Cache
*/
final class ET_Core_Cache {
/**
* Cached data.
*
* @since 1.0.0
*
* @type array
*/
private static $cache = array();
/**
* Adds data to the cache if it doesn't already exist.
*
* @since 1.0.0
*
* @param int|string $key What to call the contents in the cache.
* @param mixed $data The contents to store in the cache.
* @param string $group Optional. Where to group the cache contents. Default 'default'.
* @return bool False if cache key and group already exist, true on success
*/
public static function add( $key, $data, $group = 'default' ) {
if ( empty( $group ) ) {
$group = 'default';
}
if ( self::_exists( $key, $group ) ) {
return false;
}
return self::set( $key, $data, $group );
}
/**
* Sets the data contents into the cache.
*
* The cache contents is grouped by the $group parameter followed by the
* $key. This allows for duplicate ids in unique groups.
*
* @since 1.0.0
*
* @param int|string $key What to call the contents in the cache.
* @param mixed $data The contents to store in the cache.
* @param string $group Optional. Where to group the cache contents. Default 'default'.
* @param int $expire Not Used.
* @return true Always returns true.
*/
public static function set( $key, $data, $group = 'default' ) {
if ( empty( $group ) ) {
$group = 'default';
}
if ( is_object( $data ) ) {
$data = clone $data;
}
self::$cache[ $group ][ $key ] = $data;
return true;
}
/**
* Retrieves the cache contents, if it exists.
*
* The contents will be first attempted to be retrieved by searching by the
* key in the cache group. If the cache is hit (success) then the contents
* are returned.
*
* @since 1.0.0
*
* @param int|string $key What the contents in the cache are called.
* @param string $group Optional. Where the cache contents are grouped. Default 'default'.
* @return false|mixed False on failure to retrieve contents or the cache contents on success.
*/
public static function get( $key, $group = 'default' ) {
if ( empty( $group ) ) {
$group = 'default';
}
if ( self::_exists( $key, $group ) ) {
if ( is_object( self::$cache[ $group ][ $key ] ) ) {
return clone self::$cache[ $group ][ $key ];
} else {
return self::$cache[ $group ][ $key ];
}
}
return false;
}
/**
* Retrieves the cache contents for entire group, if it exists.
*
* If the cache is hit (success) then the contents of the group
* are returned.
*
* @since 1.0.0
*
* @param string $group Where the cache contents are grouped.
* @return false|mixed False on failure to retrieve contents or the cache contents on success.
*/
public static function get_group( $group ) {
if ( isset( self::$cache[ $group ] ) ) {
return self::$cache[ $group ];
}
return false;
}
/**
* Check the cache contents, if given key and (optional) group exists.
*
* @since 1.0.0
*
* @param int|string $key What the contents in the cache are called.
* @param string $group Optional. Where the cache contents are grouped. Default 'default'.
* @return bool False on failure to retrieve contents or True on success.
*/
public static function has( $key, $group = 'default' ) {
if ( empty( $group ) ) {
$group = 'default';
}
return self::_exists( $key, $group );
}
/**
* Removes the contents of the cache key in the group.
*
* If the cache key does not exist in the group, then nothing will happen.
*
* @since 1.0.0
*
* @param int|string $key What the contents in the cache are called.
* @param string $group Optional. Where the cache contents are grouped. Default 'default'.
* @return bool False if the contents weren't deleted and true on success.
*/
public static function delete( $key, $group = 'default' ) {
if ( empty( $group ) ) {
$group = 'default';
}
if ( ! self::_exists( $key, $group ) ) {
return false;
}
unset( self::$cache[ $group ][ $key ] );
return true;
}
/**
* Clears the object cache of all data.
*
* @since 1.0.0
*
* @return true Always returns true.
*/
public static function flush() {
self::$cache = array();
return true;
}
/**
* Serves as a utility function to determine whether a key exists in the cache.
*
* @since 1.0.0
* @private
*
* @param int|string $key Cache key to check for existence.
* @param string $group Cache group for the key existence check.
* @return bool Whether the key exists in the cache for the given group.
*/
private static function _exists( $key, $group ) {
return isset( self::$cache[ $group ] ) && isset( self::$cache[ $group ][ $key ] );
}
}
if ( ! function_exists( 'et_core_cache_add' ) ) :
/**
* Adds data to the cache if it doesn't already exist.
*
* @since 1.0.0
*
* @param int|string $key What to call the contents in the cache.
* @param mixed $data The contents to store in the cache.
* @param string $group Optional. Where to group the cache contents. Default 'default'.
* @return bool False if cache key and group already exist, true on success
*/
function et_core_cache_add( $key, $data, $group = '' ) {
return ET_Core_Cache::add( $key, $data, $group );
}
endif;
if ( ! function_exists( 'et_core_cache_set' ) ) :
/**
* Sets the data contents into the cache.
*
* The cache contents is grouped by the $group parameter followed by the
* $key. This allows for duplicate ids in unique groups.
*
* @since 1.0.0
*
* @param int|string $key What to call the contents in the cache.
* @param mixed $data The contents to store in the cache.
* @param string $group Optional. Where to group the cache contents. Default 'default'.
* @param int $expire Not Used.
* @return true Always returns true.
*/
function et_core_cache_set( $key, $data, $group = '' ) {
return ET_Core_Cache::set( $key, $data, $group );
}
endif;
if ( ! function_exists( 'et_core_cache_get' ) ) :
/**
* Retrieves the cache contents, if it exists.
*
* The contents will be first attempted to be retrieved by searching by the
* key in the cache group. If the cache is hit (success) then the contents
* are returned.
*
* @since 1.0.0
*
* @param int|string $key What the contents in the cache are called.
* @param string $group Optional. Where the cache contents are grouped. Default 'default'.
* @return false|mixed False on failure to retrieve contents or the cache contents on success.
*/
function et_core_cache_get( $key, $group = '' ) {
return ET_Core_Cache::get( $key, $group );
}
endif;
if ( ! function_exists( 'et_core_cache_get_group' ) ) :
/**
* Retrieves the cache contents for entire group, if it exists.
*
* If the cache is hit (success) then the contents of the group
* are returned.
*
* @since 1.0.0
*
* @param string $group Where the cache contents are grouped.
* @return false|mixed False on failure to retrieve contents or the cache contents on success.
*/
function et_core_cache_get_group( $group ) {
return ET_Core_Cache::get_group( $group );
}
endif;
if ( ! function_exists( 'et_core_cache_has' ) ) :
/**
* Check the cache contents, if given key and (optional) group exists.
*
* @since 1.0.0
*
* @param int|string $key What the contents in the cache are called.
* @param string $group Optional. Where the cache contents are grouped. Default 'default'.
* @return bool False on failure to retrieve contents or True on success.
*/
function et_core_cache_has( $key, $group = '' ) {
return ET_Core_Cache::has( $key, $group );
}
endif;
if ( ! function_exists( 'et_core_cache_delete' ) ) :
/**
* Removes the contents of the cache key in the group.
*
* If the cache key does not exist in the group, then nothing will happen.
*
* @since 1.0.0
*
* @param int|string $key What the contents in the cache are called.
* @param string $group Optional. Where the cache contents are grouped. Default 'default'.
* @return bool False if the contents weren't deleted and true on success.
*/
function et_core_cache_delete( $key, $group = '' ) {
return ET_Core_Cache::delete( $key, $group );
}
endif;
if ( ! function_exists( 'et_core_cache_flush' ) ) :
/**
* Clears the object cache of all data.
*
* @since 1.0.0
* @return true Always returns true.
*/
function et_core_cache_flush() {
return ET_Core_Cache::flush();
}
endif;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,463 @@
<?php
/**
* Simple object to hold HTTP request details.
*
* @since 1.1.0
*
* @package ET\Core\HTTP
*/
class ET_Core_HTTPRequest {
/**
* @var array
*/
public $ARGS;
/**
* @var bool
*/
public $BLOCKING = true;
/**
* @var null|array
*/
public $BODY = null;
/**
* @var bool
*/
public $COMPLETE = false;
/**
* @var array
*/
public $COOKIES = array();
/**
* @var array
*/
public $HEADERS = array();
/**
* @var bool
*/
public $IS_AUTH = false;
/**
* @var string
*/
public $METHOD = 'GET';
/**
* @var string
*/
public $OWNER;
/**
* @var string
*/
public $URL;
/**
* @var string
*/
public $USER_AGENT;
/**
* ET_Core_HTTP_Request constructor.
*
* @param string $url The request URL.
* @param string $method HTTP request method. Default is 'GET'.
* @param string $owner The name of the owner of this request instance. Default is 'ET_Core'.
* @param bool $is_auth Whether or not this request is auth-related. Cache disabled if `true`. Default is `false`.
* @param array $body The request body. Default is `null`.
* @param bool $is_json_body
*/
public function __construct( $url, $method = 'GET', $owner = 'ET_Core', $is_auth = false, $body = null, $is_json_body = false, $ssl_verify = true ) {
$this->URL = esc_url_raw( $url );
$this->METHOD = $method;
$this->BODY = $body;
$this->IS_AUTH = $is_auth;
$this->OWNER = $owner;
$this->data_format = null;
$this->JSON_BODY = $is_json_body;
$this->SSL_VERIFY = $ssl_verify;
$this->_set_user_agent();
$this->prepare_args();
}
/**
* Only include necessary properties when printing this object using {@link var_dump}.
*
* @return array
*/
public function __debugInfo() {
return array(
'ARGS' => $this->ARGS,
'URL' => $this->URL,
'METHOD' => $this->METHOD,
'BODY' => $this->BODY,
'IS_AUTH' => $this->IS_AUTH,
'IS_JSON_BODY' => $this->JSON_BODY,
'OWNER' => $this->OWNER,
);
}
/**
* Sets the user agent string.
*/
private function _set_user_agent() {
global $wp_version;
$owner = $this->OWNER;
$version = 'bloom' === $owner ? $GLOBALS['et_bloom']->plugin_version : ET_CORE_VERSION;
if ( 'builder' === $owner ) {
$owner = 'Divi Builder';
}
if ( '' === $owner ) {
$this->OWNER = $owner = 'ET_Core';
} else {
$owner = ucfirst( $owner );
}
$this->USER_AGENT = "WordPress/{$wp_version}; {$owner}/{$version}; " . esc_url_raw( get_bloginfo( 'url' ) );
}
/**
* Prepares the request arguments (to be passed to wp_remote_*())
*/
public function prepare_args() {
$this->ARGS = array(
'blocking' => $this->BLOCKING,
'body' => $this->BODY,
'cookies' => $this->COOKIES,
'headers' => $this->HEADERS,
'method' => $this->METHOD,
'sslverify' => $this->SSL_VERIFY,
'user-agent' => $this->USER_AGENT,
'timeout' => 30,
);
}
}
/**
* Simple object to hold HTTP response details.
*
* @since 1.1.0
*
* @package ET\Core\HTTP
*/
class ET_Core_HTTPResponse {
/**
* @var array
*/
public $COOKIES;
/**
* @var string|array
*/
public $DATA;
/**
* @var bool
*/
public $ERROR = false;
/**
* The error message if `self::$ERROR` is `true`.
*
* @var string
*/
public $ERROR_MESSAGE;
/**
* @var array
*/
public $HEADERS;
/**
* @var array|WP_Error
*/
public $RAW_RESPONSE;
/**
* @var ET_Core_HTTPRequest
*/
public $REQUEST;
/**
* The response's HTTP status code.
*
* @var int
*/
public $STATUS_CODE;
/**
* @var string
*/
public $STATUS_MESSAGE;
/**
* ET_Core_HTTP_Response constructor.
*
* @param ET_Core_HTTPRequest $request
* @param array|WP_Error $response
*/
public function __construct( $request, $response ) {
$this->REQUEST = $request;
$this->RAW_RESPONSE = $response;
$this->_parse_response();
}
/**
* Parse response and save relevant details.
*/
private function _parse_response() {
if ( is_wp_error( $this->RAW_RESPONSE ) ) {
$this->ERROR = true;
$this->ERROR_MESSAGE = $this->RAW_RESPONSE->get_error_message();
$this->STATUS_CODE = $this->RAW_RESPONSE->get_error_code();
$this->STATUS_MESSAGE = $this->ERROR_MESSAGE;
return;
}
$this->DATA = $this->RAW_RESPONSE['body'];
$this->HEADERS = $this->RAW_RESPONSE['headers'];
$this->COOKIES = $this->RAW_RESPONSE['cookies'];
$this->STATUS_CODE = $this->RAW_RESPONSE['response']['code'];
$this->STATUS_MESSAGE = $this->RAW_RESPONSE['response']['message'];
if ( $this->STATUS_CODE >= 400 ) {
$this->ERROR = true;
$this->ERROR_MESSAGE = $this->STATUS_MESSAGE;
}
}
/**
* Only include necessary properties when printing this object using {@link var_dump}.
*
* @return array
*/
public function __debugInfo() {
return array(
'STATUS_CODE' => $this->STATUS_CODE,
'STATUS_MESSAGE' => $this->STATUS_MESSAGE,
'ERROR' => $this->ERROR,
'ERROR_MESSAGE' => $this->ERROR_MESSAGE,
'DATA' => $this->DATA,
);
}
/**
* Only include necessary properties when serializing this object for
* storage in the WP Transient Cache.
*
* @return array
*/
public function __sleep() {
return array( 'ERROR', 'ERROR_MESSAGE', 'STATUS_CODE', 'STATUS_MESSAGE', 'DATA' );
}
}
/**
* High level, generic, wrapper for making HTTP requests. It uses WordPress HTTP API under-the-hood.
*
* @since 1.1.0
*
* @package ET\Core\HTTP
*/
class ET_Core_HTTPInterface {
/**
* How much time responses are cached (in seconds).
*
* @since 1.1.0
* @var int
*/
protected $cache_timeout;
/**
* @var ET_Core_HTTPRequest
*/
public $request;
/**
* @var ET_Core_HTTPResponse
*/
public $response;
/**
* ET_Core_API_HTTP_Interface constructor.
*
* @since 1.1.0
*
* @param string $owner The name of the theme/plugin that created this class instance. Default: 'ET_Core'.
* @param array $request_details Array of config values for the request. Optional.
* @param bool $json Whether or not json responses are expected to be received. Default is `true`.
*/
public function __construct( $owner = 'ET_Core', $request_details = array(), $json = true ) {
$this->expects_json = $json;
$this->cache_timeout = 15 * MINUTE_IN_SECONDS;
$this->owner = $owner;
if ( ! empty( $request_details ) ) {
list( $url, $method, $is_auth, $body ) = $request_details;
$this->prepare_request( $url, $method, $is_auth, $body );
}
}
/**
* Only include necessary properties when printing this object using {@link var_dump}.
*
* @return array
*/
public function __debugInfo() {
return array(
'REQUEST' => $this->request,
'RESPONSE' => $this->response,
);
}
/**
* Only include necessary properties when serializing this object for
* storage in the WP Transient Cache.
*
* @return array
*/
public function __sleep() {
return array( 'request', 'response' );
}
/**
* Creates an identifier key for a request based on the URL and body content.
*
* @internal
* @since 1.1.0
*
* @param string $url The request URL.
* @param string|string[] $body The request body.
*
* @return string
*/
protected static function _get_cache_key_for_request( $url, $body ) {
if ( is_array( $body ) ) {
$url .= json_encode( $body );
} else if ( ! empty( $body ) ) {
$url .= $body;
}
return 'et-core-http-response-' . md5( $url );
}
/**
* Writes request/response info to the error log for failed requests.
*
* @internal
* @since 1.1.0
*/
protected function _log_failed_request() {
$details = print_r( $this, true );
$class_name = get_class( $this );
$msg_part = "{$class_name} ERROR :: Remote request failed...\n\n";
$msg = "{$msg_part}Details: {$details}";
$max_len = @ini_get( 'log_errors_max_len' );
@ini_set( 'log_errors_max_len', 0 );
ET_Core_Logger::error( $msg );
if ( $max_len ) {
@ini_set( 'log_errors_max_len', $max_len );
}
}
/**
* Prepares request to send JSON data.
*/
protected function _setup_json_request() {
$this->request->HEADERS['Accept'] = 'application/json';
if ( $this->request->JSON_BODY ) {
$this->request->HEADERS['Content-Type'] = 'application/json';
$is_json = is_string( $this->request->BODY ) && in_array( $this->request->BODY[0], array( '[', '{' ) );
if ( $is_json || null === $this->request->BODY ) {
return;
}
$this->request->BODY = json_encode( $this->request->BODY );
}
}
/**
* Performs a remote HTTP request. Responses are cached for {@see self::$cache_timeout} seconds using
* the {@link https://goo.gl/c0FSMH WP Transients API}.
*
* @since 1.1.0
*/
public function make_remote_request() {
$response = null;
if ( $this->expects_json && ! isset( $this->request->HEADERS['Content-Type'] ) ) {
$this->_setup_json_request();
}
// Make sure we include any changes made after request object was instantiated.
$this->request->prepare_args();
if ( 'POST' === $this->request->METHOD ) {
$response = wp_remote_post( $this->request->URL, $this->request->ARGS );
} else if ( 'GET' === $this->request->METHOD && null === $this->request->data_format ) {
$response = wp_remote_get( $this->request->URL, $this->request->ARGS );
} else if ( 'GET' === $this->request->METHOD && null !== $this->request->data_format ) {
// WordPress sends data as query args for GET and HEAD requests and provides no way
// to alter that behavior. Thus, we need to monkey patch it for now. See the mp'd class
// for more details.
require_once 'lib/WPHttp.php';
$wp_http = new ET_Core_LIB_WPHttp();
$this->request->ARGS['data_format'] = $this->request->data_format;
$response = $wp_http->request( $this->request->URL, $this->request->ARGS );
} else if ( 'PUT' === $this->request->METHOD ) {
$this->request->ARGS['method'] = 'PUT';
$response = wp_remote_request( $this->request->URL, $this->request->ARGS );
}
$this->response = $response = new ET_Core_HTTPResponse( $this->request, $response );
if ( $response->ERROR || defined( 'ET_DEBUG' ) ) {
$this->_log_failed_request();
}
if ( $this->expects_json ) {
$response->DATA = json_decode( $response->DATA, true );
}
$this->request->COMPLETE = true;
}
/**
* Replaces the current request object with a new instance.
*
* @param string $url
* @param string $method
* @param bool $is_auth
* @param mixed? $body
* @param bool $json_body
* @param bool $ssl_verify
*/
public function prepare_request( $url, $method = 'GET', $is_auth = false, $body = null, $json_body = false, $ssl_verify = true ) {
$this->request = new ET_Core_HTTPRequest( $url, $method, $this->owner, $is_auth, $body, $json_body, $ssl_verify );
}
}

153
core/components/Logger.php Normal file
View File

@ -0,0 +1,153 @@
<?php
class ET_Core_Logger {
/**
* @var ET_Core_Data_Utils
*/
protected static $_;
/**
* Checksum for every log message output during the current request.
*
* @var string[]
*/
protected static $HISTORY = array();
/**
* Writes a message to the debug log if it hasn't already been written once.
*
* @since 3.10
*
* @param mixed $message
* @param int $bt_index
* @param boolean $log_ajax Whether or not to log on AJAX calls.
*/
protected static function _maybe_write_log( $message, $bt_index = 4, $log_ajax = true ) {
global $ET_IS_TESTING_DEPRECATIONS;
if ( ! is_scalar( $message ) ) {
$message = print_r( $message, true );
}
$message = (string) $message;
$hash = md5( $message );
if ( ! $log_ajax && wp_doing_ajax() ) {
return;
}
if ( $ET_IS_TESTING_DEPRECATIONS ) {
trigger_error( $message );
} else if ( getenv( 'CI' ) || ! in_array( $hash, self::$HISTORY ) ) {
self::$HISTORY[] = $hash;
self::_write_log( $message, $bt_index );
}
}
/**
* Writes a message to the WP Debug and PHP Error logs.
*
* @param string $message
* @param int $bt_index
*/
private static function _write_log( $message, $bt_index = 4 ) {
$message = trim( $message );
$backtrace = debug_backtrace( 1 );
$class = '';
$function = '';
if ( ! isset( $backtrace[ $bt_index ] ) ) {
while ( $bt_index > 0 && ! isset( $backtrace[ $bt_index ] ) ) {
$bt_index--;
}
// We need two stacks to get all the data we need so let's go down one more
$bt_index--;
}
$stack = $backtrace[ $bt_index ];
$file = self::$_->array_get( $stack, 'file', '<unknown file>' );
$line = self::$_->array_get( $stack, 'line', '<unknown line>' );
// Name of the function and class (if applicable) are in the previous stack (stacks are in reverse order)
$stack = $backtrace[ $bt_index + 1 ];
$class = self::$_->array_get( $stack, 'class', '' );
$function = self::$_->array_get( $stack, 'function', '<unknown function>' );
if ( $class ) {
$class .= '::';
}
if ( '<unknown file>' !== $file ) {
$file = _et_core_normalize_path( $file );
$parts = explode( '/', $file );
$parts = array_slice( $parts, -2 );
$file = ".../{$parts[0]}/{$parts[1]}";
}
$message = " {$file}:{$line} {$class}{$function}():\n{$message}\n";
error_log( $message );
}
/**
* Writes message to the logs if {@link WP_DEBUG} is `true`, otherwise does nothing.
*
* @since 1.1.0
*
* @param mixed $message
* @param int $bt_index
* @param boolean $log_ajax Whether or not to log on AJAX calls.
*/
public static function debug( $message, $bt_index = 4, $log_ajax = true ) {
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
self::_maybe_write_log( $message, $bt_index, $log_ajax );
}
}
public static function disable_php_notices() {
$error_reporting = error_reporting();
$notices_enabled = $error_reporting & E_NOTICE;
if ( $notices_enabled ) {
error_reporting( $error_reporting & ~E_NOTICE );
}
}
/**
* Writes an error message to the logs regardless of whether or not debug mode is enabled.
*
* @since 1.1.0
*
* @param mixed $message
* @param int $bt_index
*/
public static function error( $message, $bt_index = 4 ) {
self::_maybe_write_log( $message, $bt_index );
}
public static function enable_php_notices() {
$error_reporting = error_reporting();
$notices_enabled = $error_reporting & E_NOTICE;
if ( ! $notices_enabled ) {
error_reporting( $error_reporting | E_NOTICE );
}
}
public static function initialize() {
self::$_ = ET_Core_Data_Utils::instance();
}
public static function php_notices_enabled() {
$error_reporting = error_reporting();
return $error_reporting & E_NOTICE;
}
}
ET_Core_Logger::initialize();

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

19
core/components/README.md Normal file
View File

@ -0,0 +1,19 @@
## Core Components
This directory contains all of Core's PHP Components. Components are organized into groups using a nested directory structure.
### What's Inside
|File|Name|Description|
|:--------|:----------|:----------|
|**api**|**ET_Core_API**|Communicate with 3rd-party APIs|
|**data**|**ET_Core_Data**|Work with data (consume, format, etc)|
|**lib**|**ET_Core_LIB**|3rd-Party Libraries|
|**post**|**ET_Core_Post**|Work with WP Objects|
|Cache.php|ET_Core_Cache|Simple object cache|
|HTTPInterface.php|ET_Core_HTTPInterface|Wrapper for WP's HTTP API|
|Logger.php|ET_Core_Logger|Write to the debug log.
|PageResource.php|ET_Core_PageResource|Cache inline styles & scripts as static files.|
|Portability.php|ET_Core_Portability|Portability import/export|
|Rollback.php|ET_Core_VersionRollback|Theme & plugin version rollback|
|Updates.php|ET_Core_Updates|Theme & plugin updates|
> ***Note:*** Component groups are in **bold**.

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,42 @@
<?php
/**
* Plugin Name: ET Support Center :: Must-Use Plugins Autoloader
* Plugin URI: http://www.elegantthemes.com
* Description: This plugin enables the Elegant Themes Support Center to provide more consistent functionality when Safe Mode is active.
* Author: Elegant Themes
* Author URI: http://www.elegantthemes.com
* License: GPLv2 or later
*
* @package ET\Core\SupportCenter\SafeModeDisablePlugins
* @author Elegant Themes <http://www.elegantthemes.com>
* @license GNU General Public License v2 <http://www.gnu.org/licenses/gpl-2.0.html>
*/
// The general idea here is loosely based on <https://codex.wordpress.org/Must_Use_Plugins#Autoloader_Example>.
// Quick exit if accessed directly
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
// We only want to load these MU Plugins if Support Center is installed
$support_center_installed = get_option( 'et_support_center_installed' );
if ( $support_center_installed ) {
// Compile a list of plugins in the `mu-plugins/et-safe-mode` directory
// (see `$pathname_to` in `ET_Core_SupportCenter::maybe_add_mu_autoloader()`)
if ( $mu_plugins = glob( dirname( __FILE__ ) . '/et-safe-mode/*.php' ) ) {
// Verbose logging: only log if `wp-config.php` has defined `ET_DEBUG='support_center'`
$DEBUG_ET_SUPPORT_CENTER = defined( 'ET_DEBUG' ) && 'support_center' === ET_DEBUG;
// Loop through the list of plugins and require each in turn
foreach ( $mu_plugins as $plugin ) {
if ( file_exists( $plugin ) ) {
if ( $DEBUG_ET_SUPPORT_CENTER ) {
error_log( 'ET Support Center: loading mu-plugin: ' . $plugin );
}
require_once( $plugin );
}
}
}
}

805
core/components/Updates.php Normal file
View File

@ -0,0 +1,805 @@
<?php
if ( ! class_exists( 'ET_Core_Updates' ) ):
/**
* Handles the updates workflow.
*
* @private
*
* @package ET\Core\Updates
*/
final class ET_Core_Updates {
protected $core_url;
protected $options;
protected $account_status;
protected $product_version;
protected $all_et_products_domains;
protected $upgrading_et_product;
protected $up_to_date_products_data;
// class version
protected $version;
private static $_this;
function __construct( $core_url, $product_version ) {
// Don't allow more than one instance of the class
if ( isset( self::$_this ) ) {
wp_die( sprintf( esc_html__( '%s: You cannot create a second instance of this class.', 'et-core' ),
esc_html( get_class( $this ) )
) );
}
self::$_this = $this;
$this->core_url = $core_url;
$this->version = '1.2';
$this->up_to_date_products_data = array();
$this->product_version = $product_version;
$this->get_options();
$this->upgrading_et_product = false;
$this->update_product_domains();
$this->maybe_force_update_requests();
add_filter( 'wp_prepare_themes_for_js', array( $this, 'replace_theme_update_notification' ) );
add_filter( 'upgrader_package_options', array( $this, 'check_upgrading_product' ) );
add_filter( 'upgrader_pre_download', array( $this, 'update_error_message' ), 20, 2 );
add_filter( 'pre_set_site_transient_update_plugins', array( $this, 'check_plugins_updates' ) );
add_filter( 'plugins_api', array( $this, 'maybe_modify_plugins_changelog' ), 20, 3 );
add_filter( 'pre_set_site_transient_update_themes', array( $this, 'check_themes_updates' ) );
add_filter( 'self_admin_url', array( $this, 'change_plugin_changelog_url' ), 10, 2 );
add_filter( 'admin_url', array( $this, 'change_plugin_changelog_url' ), 10, 2 );
add_filter( 'network_admin_url', array( $this, 'change_plugin_changelog_url' ), 10, 2 );
add_action( 'admin_notices', array( $this, 'maybe_show_account_notice' ) );
add_action( 'admin_enqueue_scripts', array( $this, 'load_scripts_styles' ) );
add_action( 'plugins_loaded', array( $this, 'remove_updater_plugin_actions' ), 30 );
add_action( 'after_setup_theme', array( $this, 'remove_theme_update_actions' ), 11 );
add_action( 'admin_init', array( $this, 'remove_plugin_update_actions' ) );
add_action( 'update_site_option_et_automatic_updates_options', array( $this, 'force_update_requests' ) );
add_action( 'update_option_et_automatic_updates_options', array( $this, 'force_update_requests' ) );
add_action( 'deleted_site_transient', array( $this, 'maybe_reset_et_products_update_transient' ) );
}
function check_upgrading_product( $options ) {
if ( ! isset( $options['hook_extra'] ) ) {
return $options;
}
$hook_name = isset( $options['hook_extra']['plugin'] ) ? 'plugin' : 'theme';
// set the upgrading_et_product flag if one of ET plugins or themes is about to upgrade
if ( isset( $options['hook_extra'][ $hook_name ] ) && in_array( $options['hook_extra'][ $hook_name ], $this->all_et_products_domains[ $hook_name ] ) ) {
$this->upgrading_et_product = true;
}
return $options;
}
function maybe_append_custom_notification( $plugin_data, $response ) {
if ( empty( $response ) ) {
$package_available = false;
} else {
// for themes response is array for plugins - object, so check the format of data to get the correct results
$package_available = is_array( $response ) ? ! empty( $response['package'] ) : ! empty( $response->package );
}
if ( $package_available ) {
return;
}
$message = et_get_safe_localization( __( 'For all Elegant Themes products, please <a href="http://www.elegantthemes.com/gallery/divi/documentation/update/" target="_blank">authenticate your subscription</a> via the Updates tab in your theme & plugin settings to enable product updates. Make sure that your Username and API Key have been entered correctly.', 'et-core' ) );
echo "</p><p>{$message}";
}
/**
* Check if we need to force update options removal in case a customer clicked on "Check Again" button
* in the notification area.
*/
function maybe_force_update_requests() {
if ( wp_doing_ajax() ) {
return;
}
if ( empty( $_GET['et_action'] ) || 'update_account_details' !== $_GET['et_action'] ) {
return;
}
if ( empty( $_GET['et_update_account_details_nonce'] ) || ! wp_verify_nonce( $_GET['et_update_account_details_nonce'], 'et_update_account_details' )
) {
return;
}
$this->force_update_requests();
}
function replace_theme_update_notification( $themes_array ) {
if ( empty( $themes_array ) ) {
return $themes_array;
}
if ( empty( $this->all_et_products_domains['theme'] ) ) {
return $themes_array;
}
foreach ( $themes_array as $id => $theme_data ) {
// replace default error message with custom message for ET themes.
if (
in_array( $id, $this->all_et_products_domains['theme'] )
&& false !== strpos( $theme_data['update'], 'Automatic update is unavailable for this theme' )
) {
$themes_array[ $id ]['update'] = sprintf(
'<p>%1$s<br/> %2$s</p>',
$theme_data['update'],
et_get_safe_localization( __( '<em>Before you can receive product updates, you must first authenticate your Elegant Themes subscription. To do this, you need to enter both your Elegant Themes Username and your Elegant Themes API Key into the Updates Tab in your theme and plugin settings. To locate your API Key, <a href="https://www.elegantthemes.com/members-area/api/" target="_blank">log in</a> to your Elegant Themes account and navigate to the <strong>Account > API Key</strong> page. <a href="http://www.elegantthemes.com/gallery/divi/documentation/update/" target="_blank">Learn more here</a></em>. If you still get this message, please make sure that your Username and API Key have been entered correctly', 'et-core' ) )
);
}
}
return $themes_array;
}
function update_error_message( $reply, $package ) {
if ( ! $this->upgrading_et_product ) {
return $reply;
}
// reset the upgrading_et_product flag
$this->upgrading_et_product = false;
if ( ! empty( $package ) ) {
return $reply;
}
// output custom error message for ET Products if package is empty
$error_message = et_get_safe_localization( __( '<em>Before you can receive product updates, you must first authenticate your Elegant Themes subscription. To do this, you need to enter both your Elegant Themes Username and your Elegant Themes API Key into the Updates Tab in your theme and plugin settings. To locate your API Key, <a href="https://www.elegantthemes.com/members-area/api/" target="_blank">log in</a> to your Elegant Themes account and navigate to the <strong>Account > API Key</strong> page. <a href="http://www.elegantthemes.com/gallery/divi/documentation/update/" target="_blank">Learn more here</a></em>. If you still get this message, please make sure that your Username and API Key have been entered correctly', 'et-core' ) );
return new WP_Error( 'no_package', $error_message );
}
/**
* Get all Elegant Themes products, returned from the API request
*/
function get_et_api_products() {
$products = array(
'theme' => array(),
'plugin' => array(),
);
$update_transients = array(
'et_update_themes',
'et_update_all_plugins',
);
foreach ( $update_transients as $update_transient_name ) {
$type = 'et_update_themes' === $update_transient_name ? 'theme' : 'plugin';
if (
false !== ( $update_transient = get_site_transient( $update_transient_name ) )
&& ! empty( $update_transient->response )
&& is_array( $update_transient->response )
) {
$et_product_stylesheet_names = array_keys( $update_transient->response );
foreach ( $et_product_stylesheet_names as $et_product_stylesheet_name ) {
$products[ $type ][] = $et_product_stylesheet_name;
}
}
}
return $products;
}
function get_all_et_products() {
$checked_et_products = $this->get_et_api_products();
return $checked_et_products;
}
function remove_theme_update_actions() {
remove_filter( 'pre_set_site_transient_update_themes', 'et_check_themes_updates' );
remove_filter( 'site_transient_update_themes', 'et_add_themes_to_update_notification' );
}
function remove_plugin_update_actions() {
remove_filter( 'pre_set_site_transient_update_plugins', 'et_shortcodes_plugin_check_updates' );
remove_filter( 'site_transient_update_plugins', 'et_shortcodes_plugin_add_to_update_notification' );
}
/**
* Removes Updater plugin actions and filters,
* so it doesn't make additional requests to API
*
* @return void
*/
function remove_updater_plugin_actions() {
if ( ! class_exists( 'ET_Automatic_Updates' ) ) {
return;
}
$updates_class = ET_Automatic_Updates::get_this();
remove_filter( 'after_setup_theme', array( $updates_class, 'remove_default_updates' ), 11 );
remove_filter( 'init', array( $updates_class, 'remove_default_plugins_updates' ), 20 );
remove_action( 'admin_notices', array( $updates_class, 'maybe_display_expired_message' ) );
}
/**
* Returns an instance of the object
*
* @return object
*/
static function get_this() {
return self::$_this;
}
/**
* Adds automatic updates data only if Username and API key options are set
*
* @param array $send_to_api Data sent to server
* @return array Modified data set if Username and API key are set, original data if not
*/
function maybe_add_automatic_updates_data( $send_to_api ) {
if ( $this->options && isset( $this->options['username'] ) && isset( $this->options['api_key'] ) ) {
$send_to_api['automatic_updates'] = 'on';
$send_to_api['username'] = urlencode( sanitize_text_field( $this->options['username'] ) );
$send_to_api['api_key'] = sanitize_text_field( $this->options['api_key'] );
$send_to_api = apply_filters( 'et_add_automatic_updates_data', $send_to_api );
}
return $send_to_api;
}
/**
* Gets plugin options
*
* @return void
*/
function get_options() {
if ( ! $this->options = get_site_option( 'et_automatic_updates_options' ) ) {
$this->options = get_option( 'et_automatic_updates_options' );
}
if ( ! $this->account_status = get_site_option( 'et_account_status' ) ) {
$this->account_status = get_option( 'et_account_status' );
}
}
function load_scripts_styles( $hook ) {
if ( 'plugin-install.php' !== $hook ) {
return;
}
wp_enqueue_style( 'et_core_updates', $this->core_url . 'admin/css/updates.css', array(), $this->product_version );
}
function add_up_to_date_products_data( $update_data, $settings = array() ) {
$settings = $this->process_request_settings( $settings );
$products_category = $settings['is_plugin_response'] ? 'plugins' : 'themes';
if ( ! empty( $this->up_to_date_products_data[ $products_category ] ) ) {
$update_data->no_update = $this->up_to_date_products_data[ $products_category ];
}
return $update_data;
}
function merge_et_products_response( $update_transient, $et_update_products_data ) {
if (
empty( $et_update_products_data )
|| (
empty( $et_update_products_data->response )
&& empty( $et_update_products_data->no_update )
)
) {
return $update_transient;
}
$merge_data_fields = array(
'response',
'no_update',
);
foreach ( $merge_data_fields as $data_field_name ) {
if ( empty( $et_update_products_data->$data_field_name ) ) {
continue;
}
$default_response_data = ! empty( $update_transient->$data_field_name ) ? $update_transient->$data_field_name : array();
$update_transient->$data_field_name = array_merge( $default_response_data, $et_update_products_data->$data_field_name );
}
return $update_transient;
}
function check_plugins_updates( $update_transient ) {
global $wp_version;
if ( ! isset( $update_transient->response ) ) {
return $update_transient;
}
$plugins = [];
$et_update_plugins = get_site_transient( 'et_update_all_plugins' );
// update_plugins transient gets set two times, so we ensure we make a request once
if (
isset( $et_update_plugins->last_checked )
&& isset( $update_transient->last_checked )
&& $et_update_plugins->last_checked > ( $update_transient->last_checked - 60 )
) {
return $this->merge_et_products_response( $update_transient, $et_update_plugins );
}
$_plugins = get_plugins();
if ( empty( $_plugins ) ) {
return $update_transient;
}
foreach ( $_plugins as $file => $plugin ) {
$plugins[ $file ] = $plugin['Version'];
}
do_action( 'et_core_updates_before_request' );
$send_to_api = array(
'action' => 'check_all_plugins_updates',
'installed_plugins' => $plugins,
'class_version' => $this->version,
);
// Add automatic updates data if Username and API key are set correctly
$send_to_api = $this->maybe_add_automatic_updates_data( $send_to_api );
$options = array(
'timeout' => ( ( defined('DOING_CRON') && DOING_CRON ) ? 30 : 3),
'body' => $send_to_api,
'user-agent' => 'WordPress/' . $wp_version . '; ' . home_url( '/' ),
);
$last_update = new stdClass();
$plugins_request = wp_remote_post( 'https://www.elegantthemes.com/api/api.php', $options );
if ( is_wp_error( $plugins_request ) ) {
$options['body']['failed_request'] = 'true';
$plugins_request = wp_remote_post( 'https://cdn.elegantthemes.com/api/api.php', $options );
}
$plugins_response = array();
if ( ! is_wp_error( $plugins_request ) && wp_remote_retrieve_response_code( $plugins_request ) === 200 ){
$plugins_response = maybe_unserialize( wp_remote_retrieve_body( $plugins_request ) );
if ( ! empty( $plugins_response ) && is_array( $plugins_response ) ) {
$request_settings = array( 'is_plugin_response' => true );
$plugins_response = $this->process_additional_response_settings( $plugins_response, $request_settings );
$last_update->checked = $plugins;
$last_update->response = $plugins_response;
$last_update = $this->add_up_to_date_products_data( $last_update, $request_settings );
}
}
$last_update->last_checked = time();
$update_transient = $this->merge_et_products_response( $update_transient, $plugins_response );
set_site_transient( 'et_update_all_plugins', $last_update );
$this->update_product_domains();
return $update_transient;
}
public function maybe_modify_plugins_changelog( $false, $action, $args ) {
if ( 'plugin_information' !== $action ) {
return $false;
}
if ( isset( $args->slug ) ) {
$et_update_lb_plugin = get_site_transient( 'et_update_all_plugins' );
$plugin_basename = sprintf( '%1$s/%1$s.php', sanitize_text_field( $args->slug ) );
if ( isset( $et_update_lb_plugin->response[ $plugin_basename ] ) ) {
$plugin_info = $et_update_lb_plugin->response[ $plugin_basename ];
if ( isset( $plugin_info->et_sections_used ) && 'on' === $plugin_info->et_sections_used ) {
return $plugin_info;
}
}
}
return $false;
}
function process_account_settings( $response ) {
if ( empty( $response['et_account_data'] ) ) {
return $response;
}
$additional_settings_fields = array(
'et_username_status',
'et_api_key_status',
'et_expired_subscription',
);
$et_account_data = $response['et_account_data'];
$additional_settings = array();
$is_theme_response = is_array( $et_account_data );
foreach ( $additional_settings_fields as $additional_settings_field ) {
$field = '';
$field_exists = $is_theme_response ? array_key_exists( $additional_settings_field, $et_account_data ) : ! empty( $et_account_data->$additional_settings_field );
if ( $field_exists ) {
$field = $is_theme_response ? $et_account_data[ $additional_settings_field ] : $et_account_data->$additional_settings_field;
}
$additional_settings[ $additional_settings_field ] = $field;
}
if (
! empty( $additional_settings[ 'et_username_status' ] )
&& in_array( $additional_settings[ 'et_username_status' ], array( 'active', 'expired', 'not_found' ) )
) {
$this->account_status = sanitize_text_field( $additional_settings['et_username_status'] );
} else {
// Set the account status to expired if the response array has 'et_expired_subscription' key
$this->account_status = ! empty( $additional_settings[ 'et_expired_subscription' ] ) ? 'expired' : 'active';
}
update_site_option( 'et_account_status', $this->account_status );
if ( ! empty( $additional_settings[ 'et_api_key_status' ] ) ) {
update_site_option( 'et_account_api_key_status', sanitize_text_field( $additional_settings['et_api_key_status'] ) );
} else {
delete_site_option( 'et_account_api_key_status' );
}
unset( $response['et_account_data'] );
return $response;
}
function process_up_to_date_products_settings( $response, $settings ) {
if ( empty( $response['et_up_to_date_products'] ) ) {
return $response;
}
$products_category = $settings['is_plugin_response'] ? 'plugins' : 'themes';
$this->up_to_date_products_data[ $products_category ] = $response['et_up_to_date_products'];
unset( $response['et_up_to_date_products'] );
return $response;
}
function process_request_settings( $settings = array() ) {
$defaults = array(
'is_plugin_response' => false,
);
return array_merge( $defaults, $settings );
}
function process_additional_response_settings( $response, $settings = array() ) {
if ( empty( $response ) ) {
return $response;
}
$settings = $this->process_request_settings( $settings );
$response = $this->process_account_settings( $response );
$response = $this->process_up_to_date_products_settings( $response, $settings );
return $response;
}
/**
* Sends a request to server, gets current themes versions
*
* @param object $update_transient Update transient option
* @return object Update transient option
*/
function check_themes_updates( $update_transient ){
global $wp_version;
$et_update_themes = get_site_transient( 'et_update_themes' );
// update_themes transient gets set two times, so we ensure we make a request once
if (
isset( $et_update_themes->last_checked )
&& isset( $update_transient->last_checked )
&& $et_update_themes->last_checked > ( $update_transient->last_checked - 60 )
) {
return $this->merge_et_products_response( $update_transient, $et_update_themes );
}
if ( ! isset( $update_transient->checked ) ) {
return $update_transient;
}
$themes = $update_transient->checked;
do_action( 'et_core_updates_before_request' );
$send_to_api = array(
'action' => 'check_theme_updates',
'installed_themes' => $themes,
'class_version' => $this->version,
);
// Add automatic updates data if Username and API key are set correctly
$send_to_api = $this->maybe_add_automatic_updates_data( $send_to_api );
$options = array(
'timeout' => ( ( defined('DOING_CRON') && DOING_CRON ) ? 30 : 3 ),
'body' => $send_to_api,
'user-agent' => 'WordPress/' . $wp_version . '; ' . home_url()
);
$last_update = new stdClass();
$theme_request = wp_remote_post( 'https://www.elegantthemes.com/api/api.php', $options );
if ( is_wp_error( $theme_request ) ) {
$options['body']['failed_request'] = 'true';
$theme_request = wp_remote_post( 'https://cdn.elegantthemes.com/api/api.php', $options );
}
if ( ! is_wp_error( $theme_request ) && wp_remote_retrieve_response_code( $theme_request ) === 200 ){
$theme_response = maybe_unserialize( wp_remote_retrieve_body( $theme_request ) );
if ( ! empty( $theme_response ) && is_array( $theme_response ) ) {
$theme_response = $this->process_additional_response_settings( $theme_response );
$last_update->checked = $themes;
$last_update->response = $theme_response;
$last_update = $this->add_up_to_date_products_data( $last_update );
}
}
$last_update->last_checked = time();
$update_transient = $this->merge_et_products_response( $update_transient, $last_update );
set_site_transient( 'et_update_themes', $last_update );
$this->update_product_domains();
return $update_transient;
}
function maybe_show_account_notice() {
if ( empty( $this->options['username'] ) || empty( $this->options['api_key'] ) ) {
return;
}
$output = '';
$messages = array();
$account_api_key_status = get_site_option( 'et_account_api_key_status' );
$is_expired_account = 'expired' === $this->account_status;
$is_invalid_account = 'not_found' === $this->account_status;
if (
! $is_expired_account
&& ! $is_invalid_account
&& empty( $account_api_key_status )
) {
return;
}
if ( $is_expired_account ) {
$messages[] = et_get_safe_localization( __( 'Your Elegant Themes subscription has expired. You must <a href="https://www.elegantthemes.com/members-area/" target="_blank">renew your account</a> to regain access to product updates and support. To ensure compatibility and security, it is important to always keep your themes and plugins updated.', 'et-core' ) );
} else if ( $is_invalid_account ) {
$messages[] = et_get_safe_localization( __( 'The Elegant Themes username you entered is invalid. Please enter a valid username to receive product updates. If you forgot your username you can <a href="https://www.elegantthemes.com/members-area/retrieve-username/" target="_blank">request it here</a>.', 'et-core' ) );
}
if ( ! empty( $account_api_key_status ) ) {
switch ( $account_api_key_status ) {
case 'deactivated':
$status = 'not active';
break;
default:
$status = 'invalid';
break;
}
$messages[] = et_get_safe_localization( __(
sprintf( 'The Elegant Themes API key you entered is %1$s. Please make sure that your API has been entered correctly and that it is <a href="https://www.elegantthemes.com/members-area/api/" target="_blank">enabled</a> in your account.', $status ),
'et-core'
) );
}
foreach ( $messages as $message ) {
$output .= sprintf( '<p>%1$s</p>', $message );
}
if ( empty( $output ) ) {
return;
}
$dashboard_url = add_query_arg( 'et_action', 'update_account_details', admin_url( 'update-core.php' ) );
printf(
'<div class="notice notice-warning">
%1$s
<p><a href="%2$s">%3$s</a></p>
</div>',
$output,
esc_url( wp_nonce_url( $dashboard_url, 'et_update_account_details', 'et_update_account_details_nonce' ) ),
esc_html__( 'Check Again', 'et-core' )
);
}
function change_plugin_changelog_url( $url, $path ) {
if ( 0 !== strpos( $path, 'plugin-install.php?tab=plugin-information&plugin=' ) ) {
return $url;
}
$matches = array();
$update_transient = get_site_transient( 'et_update_all_plugins' );
if ( ! is_object( $update_transient ) || empty( $update_transient->response ) ) {
return $url;
}
$et_updated_plugins_data = get_transient( 'et_updated_plugins_data' );
$has_last_checked = ! empty( $update_transient->last_checked ) && ! empty( $et_updated_plugins_data->last_checked );
/*
* Attempt to use a cached list of updated plugins.
* Re-save the list, whenever the update transient last checked time changes.
*/
if ( false === $et_updated_plugins_data || ( $has_last_checked && $update_transient->last_checked !== $et_updated_plugins_data->last_checked ) ) {
$et_updated_plugins_data = new stdClass();
if ( ! empty( $update_transient->last_checked ) ) {
$et_updated_plugins_data->last_checked = $update_transient->last_checked;
}
foreach ( $update_transient->response as $response_plugin_settings ) {
$slug = sanitize_text_field( $response_plugin_settings->slug );
$et_updated_plugins_data->changelogs[ $slug ] = $response_plugin_settings->url . '?TB_iframe=true&width=1024&height=800';
}
set_transient( 'et_updated_plugins_data', $et_updated_plugins_data );
}
if ( empty( $et_updated_plugins_data->changelogs ) ) {
return $url;
}
preg_match( '/plugin=([^&]*)/', $path, $matches );
$current_plugin_slug = $matches[1];
// Check if we're dealing with a product that has a custom changelog URL
if ( ! empty( $et_updated_plugins_data->changelogs[ $current_plugin_slug ] ) ) {
$url = esc_url_raw( $et_updated_plugins_data->changelogs[ $current_plugin_slug ] );
}
return $url;
}
function force_update_requests() {
$update_transients = array(
'update_themes',
'update_plugins',
'et_update_themes',
'et_update_all_plugins',
);
foreach ( $update_transients as $update_transient ) {
if ( get_site_transient( $update_transient ) ) {
delete_site_transient( $update_transient );
}
}
}
function update_product_domains() {
$this->all_et_products_domains = $this->get_all_et_products();
$append_notification_action_name = 'maybe_append_custom_notification';
// update notifications for ET products if needed
foreach ( array( 'theme', 'plugin' ) as $product_type ) {
if ( empty( $this->all_et_products_domains[ $product_type] ) ) {
continue;
}
foreach ( $this->all_et_products_domains[ $product_type ] as $product_key ) {
$action_name = sanitize_text_field( sprintf(
'in_%1$s_update_message-%2$s',
$product_type,
$product_key
) );
if ( has_action( $action_name, array( $this, $append_notification_action_name ) ) ) {
continue;
}
add_action( $action_name, array( $this, $append_notification_action_name ), 10, 2 );
}
}
}
/**
* Delete Elegant Themes update products transient, whenever default WordPress update transient gets removed
*/
function maybe_reset_et_products_update_transient( $transient_name ) {
$update_transients_names = array(
'update_themes' => 'et_update_themes',
'update_plugins' => 'et_update_all_plugins',
);
if ( empty( $update_transients_names[ $transient_name ] ) ) {
return;
}
delete_site_transient( $update_transients_names[ $transient_name ] );
}
}
endif;
if ( ! function_exists( 'et_core_enable_automatic_updates' ) ) :
function et_core_enable_automatic_updates( $deprecated, $version ) {
if ( ! is_admin() && ! wp_doing_cron() ) {
return;
}
if ( isset( $GLOBALS['et_core_updates'] ) ) {
return;
}
if ( defined( 'ET_CORE_URL' ) ) {
$url = ET_CORE_URL;
} else {
$url = trailingslashit( $deprecated ) . 'core/';
}
$GLOBALS['et_core_updates'] = new ET_Core_Updates( $url, $version );
}
endif;

View File

@ -0,0 +1,644 @@
<?php
// phpcs:disable Generic.WhiteSpace.ScopeIndent -- our preference is to not indent the whole inner function in this scenario.
if ( ! class_exists( 'ET_Core_VersionRollback' ) ) :
/**
* Handles version rollback.
*
* @since 3.10
*
* @private
*
* @package ET\Core\VersionRollback
*/
class ET_Core_VersionRollback {
/**
* Product name.
*
* @var string
*/
protected $product_name = '';
/**
* Product shortname.
*
* @var string
*/
protected $product_shortname = '';
/**
* Current product version.
*
* @var string
*/
protected $product_version = '';
/**
* Is rollback service enabled.
*
* @var bool
*/
protected $enabled = false;
/**
* API Username.
*
* @var string
*/
protected $api_username = '';
/**
* API Key.
*
* @var string
*/
protected $api_key = '';
/**
* ET_Core_VersionRollback constructor.
*
* @since 3.10
*
* @param string $product_name Product name.
* @param string $product_shortname Product shortname.
* @param string $product_version Product version.
*/
public function __construct( $product_name, $product_shortname, $product_version ) {
$this->product_name = sanitize_text_field( $product_name );
$this->product_shortname = sanitize_text_field( $product_shortname );
$this->product_version = sanitize_text_field( $product_version );
if ( ! $options = get_site_option( 'et_automatic_updates_options' ) ) {
$options = get_option( 'et_automatic_updates_options' );
}
$this->api_username = isset( $options['username'] ) ? sanitize_text_field( $options['username'] ) : '';
$this->api_key = isset( $options['api_key'] ) ? sanitize_text_field( $options['api_key'] ) : '';
}
/**
* Enqueue assets.
*
* @since ?.? Script `et-core-version-rollback` now loads in footer.
* @since 3.10
*/
public function assets() {
wp_enqueue_style(
'et-core-version-rollback',
ET_CORE_URL . 'admin/css/version-rollback.css',
array(
'et-core-admin',
),
ET_CORE_VERSION
);
wp_enqueue_script(
'et-core-version-rollback',
ET_CORE_URL . 'admin/js/version-rollback.js',
array(
'jquery',
'jquery-ui-tabs',
'jquery-form',
'et-core-admin',
),
ET_CORE_VERSION,
true
);
wp_localize_script(
'et-core-version-rollback',
'etCoreVersionRollbackI18n',
array(
'unknownError' => esc_html__( 'An unknown error has occurred. Please try again later.', 'et-core' ),
)
);
}
/**
* Get previous installed version, if any.
*
* @since 3.10
*
* @return string
*/
protected function _get_previous_installed_version() {
return et_get_option( "{$this->product_shortname}_previous_installed_version", '' );
}
/**
* Set previous installed version.
*
* @since 3.10
*
* @param string $version
*
* @return void
*/
protected function _set_previous_installed_version( $version ) {
et_update_option( "{$this->product_shortname}_previous_installed_version", sanitize_text_field( $version ) );
}
/**
* Get latest installed version, if any.
*
* @since 3.10
*
* @return string
*/
protected function _get_latest_installed_version() {
return et_get_option( "{$this->product_shortname}_latest_installed_version", '' );
}
/**
* Set latest installed version.
*
* @since 3.10
*
* @param string $version
*
* @return void
*/
protected function _set_latest_installed_version( $version ) {
et_update_option( "{$this->product_shortname}_latest_installed_version", sanitize_text_field( $version ) );
}
/**
* Check if the product has already been rolled back.
*
* @since 3.10
*
* @return bool
*/
protected function _is_rolled_back() {
return version_compare( $this->_get_latest_installed_version(), $this->_get_previous_installed_version(), '<=' );
}
/**
* Get unique ajax action.
*
* @since 3.10
*
* @return string
*/
protected function _get_ajax_action() {
return 'et_core_version_rollback';
}
/**
* Enable update rollback.
*
* @since 3.10
*
* @return void
*/
public function enable() {
if ( $this->enabled ) {
return;
}
$this->enabled = true;
add_action( 'admin_enqueue_scripts', array( $this, 'assets' ) );
add_action( 'wp_ajax_' . $this->_get_ajax_action(), array( $this, 'ajax_rollback' ) );
// Update version number when theme is manually replaced.
add_action( 'admin_init', array( $this, 'store_previous_version_number' ) );
// Update version number when theme is activated.
add_action( 'after_switch_theme', array( $this, 'store_previous_version_number' ) );
// Update version number when theme is updated.
add_action( 'upgrader_process_complete', array( $this, 'store_previous_version_number' ), 10, 0 );
}
/**
* Handle REST API requests to rollback.
*
* @since 3.10
*
* @return void
*/
public function ajax_rollback() {
if ( ! isset( $_GET['nonce'] ) || ! wp_verify_nonce( $_GET['nonce'], $this->_get_ajax_action() ) ) {
wp_send_json_error( array(
'errorCode' => 'et_unknown',
'error' => esc_html__( 'Security check failed. Please refresh and try again.', 'et-core' ),
), 400 );
}
if ( ! current_user_can( 'install_themes' ) ) {
wp_send_json_error( array(
'errorCode' => 'et_unknown',
'error' => esc_html__( 'You don\'t have sufficient permissions to access this page.', 'et-core' ),
), 400 );
}
if ( $this->_is_rolled_back() ) {
$error = '
<p>
' . et_get_safe_localization( sprintf(
__( 'You\'re currently rolled back to <strong>Version %1$s</strong> from <strong>Version %2$s</strong>.', 'et-core' ),
esc_html( $this->_get_latest_installed_version() ),
esc_html( $this->_get_previous_installed_version() )
) ) . '
</p>
<p>
' . et_get_safe_localization( sprintf(
__( 'Update to the latest version to unlock the full power of %1$s. <a href="%2$s" target="_blank">Learn more here</a>.', 'et-core' ),
esc_html( $this->product_name ),
esc_url( $this->_get_update_documentation_url() )
) ) . '
</p>
';
wp_send_json_error( array(
'errorCode' => 'et_unknown',
'error' => $error,
), 400 );
}
$success = $this->rollback();
if ( is_wp_error( $success ) ) {
$error = $success->get_error_message();
if ( $success->get_error_code() === 'et_version_rollback_blocklisted' ) {
$error = '
<p>
' . et_get_safe_localization( sprintf(
__( 'For privacy and security reasons, you cannot rollback to <strong>Version %1$s</strong>.', 'et-core' ),
esc_html( $this->_get_previous_installed_version() )
) ) . '
</p>
<p>
<a href="' . esc_url( $this->_get_update_documentation_url() ) . '" target="_blank">
' . esc_html__( 'Learn more here.', 'et-core' ) . '
</a>
</p>
';
}
wp_send_json_error( array(
'errorIsUnrecoverable' => in_array( $success->get_error_code(), array( 'et_version_rollback_not_available', 'et_version_rollback_blocklisted' ) ),
'errorCode' => $success->get_error_code(),
'error' => $error,
), 400 );
}
wp_send_json_success();
}
/**
* Execute a version rollback.
*
* @since 3.10
*
* @return bool|WP_Error
*/
public function rollback() {
// Load versions before rollback so they are not affected.
$previous_version = $this->_get_previous_installed_version();
$latest_version = $this->_get_latest_installed_version();
$api = new ET_Core_API_ElegantThemes( $this->api_username, $this->api_key );
$available = $api->is_product_available( $this->product_name, $previous_version );
if ( is_wp_error( $available ) ) {
$major_minor = implode( '.', array_slice( explode( '.', $previous_version ), 0, 2 ) );
if ( $major_minor . '.0' === $previous_version ) {
// Skip the trailing 0 in the version number and retry.
$previous_version = $major_minor;
$available = $api->is_product_available( $this->product_name, $previous_version );
}
if ( is_wp_error( $available ) ) {
return $available;
}
}
$download_url = $api->get_download_url( $this->product_name, $previous_version );
// Buffer and discard output as upgrader classes still output content even if the upgrader skin is silent.
$buffer_started = ob_start();
$result = $this->_install_theme( $download_url );
if ( $buffer_started ) {
ob_end_clean();
}
if ( is_wp_error( $result ) ) {
return $result;
}
if ( true !== $result ) {
return new WP_Error( 'et_unknown', esc_html__( 'An unknown error has occurred. Please try again later.', 'et-core' ) );
}
/**
* Fires after successful product version rollback.
*
* @since 3.26
*
* @param string $product_short_name - The short name of the product rolling back.
* @param string $rollback_from_version - The product version rolling back from.
* @param string $rollback_to_version - The product version rolling back to.
*/
do_action( 'et_after_version_rollback', $this->product_shortname, $latest_version, $previous_version );
// Swap version numbers after a successful rollback.
$this->_set_previous_installed_version( $latest_version );
$this->_set_latest_installed_version( $previous_version );
}
/**
* Install a theme overwriting it if it already exists.
* Copied from Theme_Upgrader::install() due to lack of control over the clear_desination argument.
*
* @see Theme_Upgrader::install() @ WordPress 4.9.4
*
* @since 3.10
*
* @param string $package
*
* @return bool|WP_Error
*/
protected function _install_theme( $package ) {
require_once( ABSPATH . 'wp-admin/includes/class-wp-upgrader.php' );
$upgrader = new Theme_Upgrader( new ET_Core_LIB_SilentThemeUpgraderSkin() );
$defaults = array(
'clear_update_cache' => true,
);
$parsed_args = wp_parse_args( array(), $defaults );
$upgrader->init();
$upgrader->install_strings();
add_filter('upgrader_source_selection', array( $upgrader, 'check_package' ) );
add_filter('upgrader_post_install', array( $upgrader, 'check_parent_theme_filter' ), 10, 3 );
if ( $parsed_args['clear_update_cache'] ) {
// Clear cache so wp_update_themes() knows about the new theme.
add_action( 'upgrader_process_complete', 'wp_clean_themes_cache', 9, 0 );
}
$upgrader->run( array(
'package' => $package,
'destination' => get_theme_root(),
'clear_destination' => true, // Overwrite theme.
'clear_working' => true,
'hook_extra' => array(
'type' => 'theme',
'action' => 'install',
),
) );
remove_action( 'upgrader_process_complete', 'wp_clean_themes_cache', 9 );
remove_filter( 'upgrader_source_selection', array( $upgrader, 'check_package' ) );
remove_filter( 'upgrader_post_install', array( $upgrader, 'check_parent_theme_filter' ) );
if ( ! $upgrader->result || is_wp_error( $upgrader->result ) ) {
return $upgrader->result;
}
// Refresh the Theme Update information.
wp_clean_themes_cache( $parsed_args['clear_update_cache'] );
return true;
}
/**
* Get update documentation url for the product.
*
* @since 3.10
*
* @return string
*/
protected function _get_update_documentation_url() {
return "https://www.elegantthemes.com/documentation/{$this->product_shortname}/update-{$this->product_shortname}/";
}
/**
* Return ePanel option.
*
* @since 3.10
*
* @return array
*/
public function get_epanel_option() {
return array(
'name' => esc_html__( 'Version Rollback', 'et-core' ),
'id' => 'et_version_rollback',
'type' => 'callback_function',
'function_name' => array( $this, 'render_epanel_option' ),
'desc' => et_get_safe_localization( __( 'If you recently updated to a new version and are experiencing problems, you can easily roll back to the previously-installed version. We always recommend using the latest version and testing updates on a staging site. However, if you run into problems after updating you always have the option to roll back.', 'et-core' ) ),
);
}
/**
* Render ePanel option.
*
* @since 3.10
*
* @return void
*/
public function render_epanel_option() {
$previous = $this->_get_previous_installed_version();
$modal_renderer = array( $this, 'render_epanel_no_previous_version_modal' );
if ( ! empty( $previous ) ) {
$modal_renderer = array( $this, 'render_epanel_confirm_rollback_modal' );
if ( $this->_is_rolled_back() ) {
$modal_renderer = array( $this, 'render_epanel_already_rolled_back_modal' );
}
}
add_action( 'admin_footer', $modal_renderer );
?>
<button type="button" class="et-button et-button--simple" data-et-core-modal=".et-core-version-rollback-modal">
<?php esc_html_e( 'Rollback to the previous version', 'et-core' ); ?>
</button>
<?php
}
/**
* Render ePanel warning modal when no previous supported version has been used.
*
* @since 3.10
*
* @return void
*/
public function render_epanel_no_previous_version_modal() {
?>
<div class="et-core-modal-overlay et-core-form et-core-version-rollback-modal et-core-modal-actionless">
<div class="et-core-modal">
<div class="et-core-modal-header">
<h3 class="et-core-modal-title">
<?php esc_html_e( 'Version Rollback', 'et-core' ); ?>
</h3>
<a href="#" class="et-core-modal-close" data-et-core-modal="close"></a>
</div>
<div id="et-core-version-rollback-modal-content">
<div class="et-core-modal-content">
<p>
<?php
printf(
esc_html__( 'The previously used version of %1$s does not support version rollback.', 'et-core' ),
esc_html( $this->product_name )
);
?>
</p>
</div>
</div>
</div>
</div>
<?php
}
/**
* Render ePanel confirmation modal for rollback.
*
* @since 3.10
*
* @return void
*/
public function render_epanel_confirm_rollback_modal() {
$action = $this->_get_ajax_action();
$url = add_query_arg( array(
'action' => $action,
'nonce' => wp_create_nonce( $action ),
), admin_url( 'admin-ajax.php' ) );
?>
<div class="et-core-modal-overlay et-core-form et-core-version-rollback-modal">
<div class="et-core-modal">
<div class="et-core-modal-header">
<h3 class="et-core-modal-title">
<?php esc_html_e( 'Version Rollback', 'et-core' ); ?>
</h3>
<a href="#" class="et-core-modal-close" data-et-core-modal="close"></a>
</div>
<div id="et-core-version-rollback-modal-content">
<div class="et-core-modal-content">
<p>
<?php
echo et_get_safe_localization( sprintf(
__( 'You\'ll be rolled back to <strong>Version %1$s</strong> from the current <strong>Version %2$s</strong>.', 'et-core' ),
esc_html( $this->_get_previous_installed_version() ),
esc_html( $this->_get_latest_installed_version() )
) );
?>
</p>
<p>
<?php
echo et_get_safe_localization( sprintf(
__( 'Rolling back will reinstall the previous version of %1$s. You will be able to update to the latest version at any time. <a href="%2$s" target="_blank">Learn more here</a>.', 'et-core' ),
esc_html( $this->product_name ),
esc_url( $this->_get_update_documentation_url() )
) );
?>
</p>
<p>
<strong>
<?php esc_html_e( 'Make sure you have a full site backup before proceeding.', 'et-core' ); ?>
</strong>
</p>
</div>
<a class="et-core-modal-action et-core-version-rollback-confirm" href="<?php echo esc_url( $url ); ?>">
<?php esc_html_e( 'Rollback to the previous version', 'et-core' ); ?>
</a>
</div>
</div>
</div>
<?php
}
/**
* Render ePanel warning modal when a rollback has already been done.
*
* @since 3.10
*
* @return void
*/
public function render_epanel_already_rolled_back_modal() {
?>
<div class="et-core-modal-overlay et-core-form et-core-version-rollback-modal">
<div class="et-core-modal">
<div class="et-core-modal-header">
<h3 class="et-core-modal-title">
<?php esc_html_e( 'Version Rollback', 'et-core' ); ?>
</h3>
<a href="#" class="et-core-modal-close" data-et-core-modal="close"></a>
</div>
<div id="et-core-version-rollback-modal-content">
<div class="et-core-modal-content">
<p>
<?php
echo et_get_safe_localization( sprintf(
__( 'You\'re currently rolled back to <strong>Version %1$s</strong> from <strong>Version %2$s</strong>.', 'et-core' ),
esc_html( $this->_get_latest_installed_version() ),
esc_html( $this->_get_previous_installed_version() )
) );
?>
</p>
<p>
<?php
echo et_get_safe_localization( sprintf(
__( 'Update to the latest version to unlock the full power of %1$s. <a href="%2$s" target="_blank">Learn more here</a>.', 'et-core' ),
esc_html( $this->product_name ),
esc_url( $this->_get_update_documentation_url() )
) );
?>
</p>
</div>
<a class="et-core-modal-action" href="<?php echo esc_url( admin_url( 'update-core.php' ) ); ?>">
<?php esc_html_e( 'Update to the Latest Version', 'et-core' ); ?>
</a>
</div>
</div>
</div>
<?php
}
/**
* Store latest and previous installed version.
*
* @since 3.10
*
* @return void;
*/
public function store_previous_version_number() {
$previous_installed_version = $this->_get_previous_installed_version();
$latest_installed_version = $this->_get_latest_installed_version();
// Get the theme version since the files may have changed but
// we are still executing old code from memory.
$theme_version = et_get_theme_version();
if ( $latest_installed_version === $theme_version ) {
return;
}
if ( empty( $latest_installed_version ) ) {
$latest_installed_version = $theme_version;
}
if ( version_compare( $theme_version, $latest_installed_version, '!=') ) {
$previous_installed_version = $latest_installed_version;
$latest_installed_version = $theme_version;
}
/**
* Fires after new version number is updated.
*
* @since 4.10.0
*/
do_action( 'et_store_before_new_version_update' );
$this->_set_previous_installed_version( $previous_installed_version );
$this->_set_latest_installed_version( $latest_installed_version );
/**
* Fires after new version number is updated.
*
* @since 4.10.0
*/
do_action( 'et_store_after_new_version_update' );
}
}
endif;

View File

@ -0,0 +1,248 @@
<?php
if ( ! class_exists( 'ET_Core_API_ElegantThemes' ) ):
/**
* Handles communication with the main ET API.
*
* @since 3.10
*
* @private
*
* @package ET\Core\API
*/
class ET_Core_API_ElegantThemes {
/**
* Base API URL.
*
* @var string
*/
protected $api_url = 'https://www.elegantthemes.com/api/';
/**
* API username.
*
* @var string
*/
protected $username = '';
/**
* API key.
*
* @var string
*/
protected $api_key = '';
/**
* ET_Core_API_Client constructor.
*
* @since 3.10
*
* @param string $username
* @param string $api_key
* @param string $api
*/
public function __construct( $username, $api_key ) {
$this->username = sanitize_text_field( $username );
$this->api_key = sanitize_text_field( $api_key );
}
/**
* Decorate a payload array with common data.
*
* @since 3.10
*
* @param array $payload
*
* @return array
*/
protected function _get_decorated_payload( $payload ) {
if ( ! isset( $payload['username'] ) ) {
$payload['username'] = $this->username;
}
if ( ! isset( $payload['api_key'] ) ) {
$payload['api_key'] = $this->api_key;
}
return $payload;
}
/**
* Decorate request options array with common options.
*
* @since 3.10
*
* @param array $options
*
* @return array
*/
protected function _get_decorated_request_options( $options = array() ) {
global $wp_version;
$options = array_merge( array(
'timeout' => 30,
'user-agent' => 'WordPress/' . $wp_version . '; ' . home_url( '/' ),
), $options );
return $options;
}
/**
* Parse a response from the API.
*
* @since 3.10
*
* @param array|WP_Error $response
*
* @return object|WP_Error
*/
protected function _parse_response( $response ) {
if ( is_wp_error( $response ) ) {
return $response;
}
$response_body = trim( wp_remote_retrieve_body( $response ) );
if ( '' === $response_body ) {
return new WP_Error( 'et_unknown', esc_html__( 'An unknown error has occurred. Please try again later.', 'et-core' ) );
}
$credentials_errors = array(
'Username is not set.',
'Subscription is not active',
);
if ( in_array( $response_body, $credentials_errors ) ) {
return new WP_Error( 'et_invalid_api_credentials', esc_html__( 'Invalid Username and/or API Key provided.', 'et-core' ) );
}
return maybe_unserialize( $response_body );
}
/**
* Get the full API endpoint.
*
* @since 3.10
*
* @param string $endpoint "api" or "api_downloads"
*
* @return string
*/
protected function _get_endpoint( $endpoint = 'api' ) {
$allowed_endpoints = array( 'api', 'api_downloads' );
$suffix = '.php';
if ( ! in_array( $endpoint, $allowed_endpoints ) ) {
$endpoint = $allowed_endpoints[0];
}
return esc_url_raw( $this->api_url . $endpoint . $suffix );
}
/**
* Submit a GET request to the API.
*
* @since 3.10
*
* @param array $payload
* @param string $endpoint
*
* @return object|WP_Error
*/
protected function _get( $payload, $endpoint = 'api' ) {
$payload = $this->_get_decorated_payload( $payload );
$options = $this->_get_decorated_request_options();
$url = esc_url_raw( add_query_arg( $payload, $this->_get_endpoint( $endpoint ) ) );
$response = wp_remote_get( $url, $options );
return $this->_parse_response( $response );
}
/**
* Submit a POST request to the API.
*
* @since 3.10
*
* @param array $payload
* @param string $endpoint
*
* @return object|WP_Error
*/
protected function _post( $payload, $endpoint = 'api' ) {
$payload = $this->_get_decorated_payload( $payload );
$options = $this->_get_decorated_request_options( array(
'body' => $payload,
) );
$response = wp_remote_post( $this->_get_endpoint( $endpoint ), $options );
return $this->_parse_response( $response );
}
/**
* Check if a product is available.
*
* @since 3.10
*
* @param string $product_name
* @param string $version
*
* @return bool|WP_Error
*/
public function is_product_available( $product_name, $version ) {
$response = $this->_get( array(
'api_update' => 1,
'action' => 'check_version_status',
'product' => $product_name,
'version' => $version,
) );
if ( is_wp_error( $response ) ) {
return $response;
}
if ( ! is_array( $response ) ) {
return new WP_Error( 'et_unknown', esc_html__( 'An unexpected response was received from the version server. Please try again later.', 'et-core' ) );
}
switch ( $response['status'] ) {
case 'not_available':
return new WP_Error( 'et_version_rollback_not_available', sprintf(
esc_html__( 'The previously used version of %1$s does not support version rollback.', 'et-core' ),
esc_html( $product_name )
) );
case 'blocklisted':
return new WP_Error( 'et_version_rollback_blocklisted', et_get_safe_localization( sprintf(
__( 'For privacy and security reasons, you cannot rollback to <strong>Version %1$s</strong>.', 'et-core' ),
esc_html( $version )
) ) );
case 'available':
return true;
}
return new WP_Error( 'et_unknown', esc_html__( 'An unknown error has occurred. Please try again later.', 'et-core' ) );
}
/**
* Get a product download url for a specific version, if available.
*
* @since 3.10
*
* @param string $product_name
* @param string $version
*
* @return string|WP_Error
*/
public function get_download_url( $product_name, $version ) {
$payload = $this->_get_decorated_payload( array(
'api_update' => 1,
'theme' => $product_name,
'version' => $version,
) );
return esc_url_raw( add_query_arg( $payload, $this->_get_endpoint( 'api_downloads' ) ) );
}
}
endif;

View File

@ -0,0 +1,295 @@
<?php
/**
* Wrapper for the ET_Core_OAuth library.
*
* @since 1.1.0
* @package ET\Core\API
*
* @property string consumer_key
* @property string consumer_secret
* @property string access_token
* @property string access_token_secret
*/
class ET_Core_API_OAuthHelper {
/**
* URL for access token requests.
*
* @since 1.1.0
* @var string
*/
public $ACCESS_TOKEN_URL;
/**
* URL for authorizing applications.
*
* @since 1.1.0
* @var string
*/
public $AUTHORIZATION_URL;
/**
* URL for request token requests.
*
* @since 1.1.0
* @var string
*/
public $REQUEST_TOKEN_URL;
/**
* Instance URL, used by Salesforce.
*
* @since 4.9.0
* @var string
*/
public $INSTANCE_URL; // @phpcs:ignore ET.Sniffs.ValidVariableName.PropertyNotSnakeCase -- Use uppercase to be consistent with existing code.
/**
* The OAuth2 bearer for this instance. This is used as the value of the HTTP
* `Authorization` header. The format is: `Bearer {$access_token}`.
*
* @since 1.1.0
* @var string
*/
private $bearer = null;
/**
* The OAuth Consumer object for this instance.
*
* @since 1.1.0
* @var ET_Core_LIB_OAuthConsumer
*/
public $consumer = null;
/**
* The OAuth Token object for this instance.
*
* @since 1.1.0
* @var ET_Core_LIB_OAuthToken
*/
public $token = null;
/**
* ET_Core_API_OAuth_Helper constructor.
*
* @since 1.1.0
*
* @param array $data {
* The consumer key/secret and access token/secret.
*
* @type string $consumer_key Consumer key. Required.
* @type string $consumer_secret Consumer secret. Required.
* @type string $access_token Previously obtained access token. Optional.
* @type string $access_secret Previously obtained access secret. Optional.
* }
* @param array $urls {
* The service's OAuth endpoints.
*
* @type string $request_token_url URL for request tokens. Required.
* @type string $authorization_url URL for authorizations. Optional.
* @type string $access_token_url URL for access tokens. Required.
* }
*/
public function __construct( array $data, array $urls, $owner ) {
$this->_initialize( $data, $urls );
$this->sha1_method = new ET_Core_LIB_OAuthHMACSHA1();
$this->consumer = new ET_Core_LIB_OAuthConsumer( $this->consumer_key, $this->consumer_secret );
if ( '' !== $this->access_token && '' !== $this->access_token_secret ) {
$this->token = new ET_Core_LIB_OAuthToken( $this->access_token, $this->access_token_secret );
} else if ( empty( $this->access_token ) && ! empty( $this->access_token_secret ) ) {
$this->bearer = "Bearer {$this->access_token_secret}";
}
}
/**
* @internal
*
* @param array $data {@see self::__construct()}
* @param array $urls {@see self::__construct()}
*/
private function _initialize( $data, $urls ) {
$this->consumer_key = isset( $data['consumer_key'] ) ? $data['consumer_key'] : '';
$this->consumer_secret = isset( $data['consumer_secret'] ) ? $data['consumer_secret'] : '';
$this->access_token = isset( $data['access_key'] ) ? $data['access_key'] : '';
$this->access_token_secret = isset( $data['access_secret'] ) ? $data['access_secret'] : '';
$this->REQUEST_TOKEN_URL = $urls['request_token_url'];
$this->AUTHORIZATION_URL = $urls['authorization_url'];
$this->ACCESS_TOKEN_URL = $urls['access_token_url'];
$this->REDIRECT_URL = isset( $urls['redirect_url'] ) ? $urls['redirect_url'] : '';
}
protected function _get_oauth2_parameters( $args ) {
et_core_nonce_verified_previously();
return wp_parse_args( $args, array(
'grant_type' => 'authorization_code',
'code' => sanitize_text_field( $_GET['code'] ),
'client_id' => $this->consumer_key,
'client_secret' => $this->consumer_secret,
'redirect_uri' => $this->REDIRECT_URL,
) );
}
/**
* @param \ET_Core_HTTPRequest $request
*
* @return \ET_Core_HTTPRequest
*/
protected function _prepare_oauth_request( $request ) {
$parameters = array();
if ( is_array( $request->BODY ) && $request->BODY && ! $request->JSON_BODY ) {
$parameters = $request->BODY;
}
$oauth_request = ET_Core_LIB_OAuthRequest::from_consumer_and_token(
$this->consumer,
$this->token,
$request->METHOD,
$request->URL,
$parameters
);
$oauth_request->sign_request( $this->sha1_method, $this->consumer, $this->token );
if ( 'GET' === $request->METHOD ) {
$request->URL = $oauth_request->to_url();
} else if ( 'POST' === $request->METHOD ) {
$request->URL = $request->JSON_BODY ? $oauth_request->to_url() : $oauth_request->get_normalized_http_url();
$request->BODY = $request->JSON_BODY ? json_encode( $request->BODY ) : $oauth_request->to_post_data();
}
return $request;
}
/**
* @param \ET_Core_HTTPRequest $request
*
* @return \ET_Core_HTTPRequest
*/
protected function _prepare_oauth2_request( $request ) {
if ( null !== $this->bearer ) {
$request->HEADERS['Authorization'] = $this->bearer;
}
if ( $request->JSON_BODY ) {
return $request;
}
if ( is_array( $request->BODY ) && ! array_key_exists( 'code', $request->BODY ) ) {
$request->URL = add_query_arg( $request->BODY, $request->URL );
$request->BODY = null;
}
return $request;
}
/**
* Finish the OAuth2 authorization process if needed.
*/
public static function finish_oauth2_authorization() {
et_core_nonce_verified_previously();
if ( ! isset( $_GET['state'] ) || 0 !== strpos( $_GET['state'], 'ET_Core' ) ) {
return;
}
list( $_, $name, $account, $nonce ) = explode( '|', sanitize_text_field( rawurldecode( $_GET['state'] ) ) );
if ( ! $name || ! $account || ! $nonce ) {
return;
}
$_GET['nonce'] = $nonce;
et_core_security_check( 'manage_options', 'et_core_api_service_oauth2', 'nonce', '_GET' );
$providers = et_core_api_email_providers();
if ( ! $providers->account_exists( $name, $account ) ) {
et_core_die();
}
if ( ! $provider = $providers->get( $name, $account, 'ET_Core' ) ) {
et_core_die();
}
$result = $provider->fetch_subscriber_lists();
// Display the authorization results
echo et_core_esc_previously( ET_Bloom::generate_modal_warning( $result ) );
}
/**
* Prepare a request for an access token.
*
* @param array $args {
* For OAuth 1.0 & 1.0a:
*
* @type string $verifier OAuth verifier token. Optional.
*
* For OAuth 2.0:
*
* @type string $code The code returned when the user was redirected back to their dashboard.
* @type string $grant_type The desired grant type, as per the OAuth 2.0 spec.
* @type string $redirect_uri The redirect URL from the original authorization request.
* }
*
* @return ET_Core_HTTPRequest
*/
public function prepare_access_token_request( $args = array() ) {
et_core_nonce_verified_previously();
$oauth2 = ! empty( $_GET['code'] );
$request = new ET_Core_HTTPRequest( $this->ACCESS_TOKEN_URL, 'POST', '', true );
$request->BODY = $oauth2 ? $this->_get_oauth2_parameters( $args ) : $args;
return $this->prepare_oauth_request( $request, $oauth2 );
}
/**
* Prepare an OAuth 1.0a or 2.0 request.
*
* @param ET_Core_HTTPRequest $request
* @param bool $oauth2
*
* @return \ET_Core_HTTPRequest
*/
function prepare_oauth_request( $request, $oauth2 = false ) {
return $oauth2 ? $this->_prepare_oauth2_request( $request ) : $this->_prepare_oauth_request( $request );
}
/**
* Process a response to an OAuth access token request and retrieve the access token if auth was successful.
*
* @param \ET_Core_HTTPResponse $response
*/
public function process_authentication_response( $response, $json = true ) {
if ( $response->ERROR ) {
return;
}
$response = $json ? $response->DATA : wp_parse_args( $response->DATA );
// Salesforce returns an `instance_url` data in the auth response.
if ( isset( $response['instance_url'] ) ) {
$this->INSTANCE_URL = esc_url( $response['instance_url'] ); // @phpcs:ignore ET.Sniffs.ValidVariableName.UsedPropertyNotSnakeCase -- Use uppercase to be consistent with existing code.
}
if ( isset( $response['oauth_token'], $response['oauth_token_secret'] ) ) {
// OAuth 1.0a
$token = sanitize_text_field( $response['oauth_token'] );
$secret = sanitize_text_field( $response['oauth_token_secret'] );
$this->token = new ET_Core_LIB_OAuthToken( $token, $secret );
} elseif ( isset( $response['access_token'] ) ) {
// OAuth 2.0
$this->token = new ET_Core_LIB_OAuthToken( '', sanitize_text_field( $response['access_token'] ) );
if ( isset( $response['refresh_token'] ) ) {
$this->token->refresh_token = sanitize_text_field( $response['refresh_token'] );
}
}
}
}

View File

@ -0,0 +1,17 @@
## Core API Component Group
This directory contains components that facilitate interacting with 3rd-party REST APIs.
### What's Inside
|File|Name|Description|
|:--------|:----------|:----------|
|**email**|**ET_Core_API_Email**|Email marketing service providers|
|**social**|**ET_Core_API_Social**|Social networks|
|Service.php|ET_Core_API_Service|Base class for all API components|
|OAuthHelper.php|ET_Core_API_OAuthHelper|Handles OAuth authorization flow|
> ***Note:*** Component groups are in **bold**.
### Class Hierarchy
<p align="center">
<img src="https://www.dropbox.com/s/lvwes19noua3jtu/2016-11-11_07-29-13-PM.png?dl=1" />
</p>

View File

@ -0,0 +1,546 @@
<?php
/**
* High level, generic, wrapper for interacting with a external, 3rd-party API.
*
* @since 1.1.0
*
* @package ET\Core\API
*/
abstract class ET_Core_API_Service {
/**
* @var ET_Core_Data_Utils
*/
protected static $_;
/**
* URL to request an OAuth access token.
*
* @since 1.1.0
* @var string
*/
public $ACCESS_TOKEN_URL;
/**
* URL to authorize an application using OAuth.
*
* @since 1.1.0
* @var string
*/
public $AUTHORIZATION_URL;
/**
* General failure message (translated & escaped).
*
* @since 1.1.0
* @var string
*/
public $FAILURE_MESSAGE;
/**
* URL to request an OAuth request token.
*
* @since 1.1.0
* @var string
*/
public $REQUEST_TOKEN_URL;
/**
* Callback URL for OAuth Authorization.
*
* @since 1.1.0
* @var string
*/
public $REDIRECT_URL;
/**
* The base url for the service.
*
* @since 1.1.0
* @var string
*/
public $BASE_URL;
/**
* Instance of the OAuth wrapper class initialized for this service.
*
* @since 1.1.0
* @var ET_Core_API_OAuthHelper
*/
public $OAuth_Helper;
/**
* The form fields (shown in the dashboard) for the service account.
*
* @since 1.1.0
* @var array
*/
public $account_fields;
/**
* Each service can have multiple sets of credentials (accounts). This identifies which
* account an instance of this class will provide access to.
*
* @since 1.1.0
* @var string
*/
public $account_name;
/**
* Custom HTTP headers that should be added to all requests made to this service's API.
*
* @since 1.1.0
* @var array
*/
public $custom_headers;
/**
* The service's data. Typically this will be IDs, tokens, secrets, etc used for API authentication.
*
* @since 1.1.0
* @var string[]
*/
public $data;
/**
* The mapping of the key names we use to store the service's data to the key names used by the service's API.
*
* @since 1.1.0
* @var string[]
*/
public $data_keys;
/**
* An instance of our HTTP Interface (utility class).
*
* @since 1.1.0
* @var ET_Core_HTTPInterface
*/
public $http;
/**
* If service uses HTTP Basic Auth, an array with details needed to generate the auth header, false otherwise.
*
* @since 1.1.0
* @var bool|array {
* Details needed to generate the HTTP Auth header.
*
* @type string $username The data key name who's value should be used as the username, or a value to use instead.
* @type string $password The data key name who's value should be used as the password, or a value to use instead.
* }
*/
public $http_auth = false;
/**
* Maximum number of accounts user is allowed to add for the service.
*
* @since 4.0.7
* @var int
*/
public $max_accounts;
/**
* The service's proper name (will be shown in the UI).
*
* @since 1.1.0
* @var string
*/
public $name;
/**
* The OAuth version (if the service uses OAuth).
*
* @since 1.1.0
* @var string
*/
public $oauth_version;
/**
* The OAuth verifier key (if the service uses OAuth along with verifier keys).
*
* @since 1.1.0
* @var string
*/
public $oauth_verifier;
/**
* The name and version of the theme/plugin that created this class instance.
* Should be formatted like this: `Divi/3.0.23`.
*
* @since 1.1.0
* @var string
*/
public $owner;
/**
* Convenience accessor for {@link self::http->request}
*
* @since 1.1.0
* @var \ET_Core_HTTPRequest?
*/
public $request;
/**
* Convenience accessor for {@link self::http->response}
*
* @since 1.1.0
* @var \ET_Core_HTTPResponse?
*/
public $response;
/**
* For services that return JSON responses, this is the top-level/root key for the returned data.
*
* @since 1.1.0
* @var string
*/
public $response_data_key;
/**
* The service type (email, social, etc).
*
* @since 1.1.0
* @var string
*/
public $service_type;
/**
* The slug for this service (not shown in the UI).
*
* @since 1.1.0
* @var string
*/
public $slug;
/**
* Whether or not the service uses OAuth.
*
* @since 1.1.0
* @var bool
*/
public $uses_oauth;
/**
* ET_Core_API_Service constructor.
*
* @since 1.1.0
*
* @param string $owner {@see self::owner}
* @param string $account_name The name of the service account that the instance will provide access to.
* @param string $api_key The api key for the account. Optional (can be set after instantiation).
*/
public function __construct( $owner = 'ET_Core', $account_name = '', $api_key = '' ) {
$this->account_name = str_replace( '.', '', sanitize_text_field( $account_name ) );
$this->owner = sanitize_text_field( $owner );
$this->account_fields = $this->get_account_fields();
$this->data = $this->_get_data();
$this->data_keys = $this->get_data_keymap();
$this->oauth_verifier = '';
$this->http = new ET_Core_HTTPInterface( $this->owner );
self::$_ = $this->data_utils = new ET_Core_Data_Utils();
$this->FAILURE_MESSAGE = esc_html__( 'API request failed, please try again.', 'et_core' );
$this->API_KEY_REQUIRED = esc_html__( 'API request failed. API Key is required.', 'et_core' );
if ( '' !== $api_key ) {
$this->data['api_key'] = sanitize_text_field( $api_key );
$this->save_data();
}
if ( $this->uses_oauth && $this->is_authenticated() ) {
$this->_initialize_oauth_helper();
}
}
/**
* Generates a HTTP Basic Auth header and adds it to the current request object. Uses the value
* of {@link self::http_auth} to determine the correct values to use for the username and password.
*/
protected function _add_http_auth_header_to_request() {
$username_key = $this->http_auth['username'];
$password_key = $this->http_auth['password'];
$username = isset( $this->data[ $username_key ] ) ? $this->data[ $username_key ] : $username_key;
$password = isset( $this->data[ $password_key ] ) ? $this->data[ $password_key ] : $password_key;
$this->request->HEADERS['Authorization'] = 'Basic ' . base64_encode( "{$username}:{$password}" );
}
/**
* Exchange a request/verifier token for an access token. This is the last step in the OAuth authorization process.
* If successful, the access token will be saved and used for future calls to the API.
*
* @return bool Whether or not authentication was successful.
*/
private function _do_oauth_access_token_request() {
if ( ! $this->_initialize_oauth_helper() ) {
return false;
}
$args = array();
if ( '' !== $this->oauth_verifier ) {
$args['oauth_verifier'] = $this->oauth_verifier;
}
$this->request = $this->http->request = $this->OAuth_Helper->prepare_access_token_request( $args );
$this->request->HEADERS['Content-Type'] = 'application/x-www-form-urlencoded';
$this->http->make_remote_request();
$this->response = $this->http->response;
if ( $this->response->ERROR ) {
return false;
}
$this->OAuth_Helper->process_authentication_response( $this->response, $this->http->expects_json );
if ( null === $this->OAuth_Helper->token ) {
return false;
}
$this->data['access_key'] = $this->OAuth_Helper->token->key;
$this->data['access_secret'] = $this->OAuth_Helper->token->secret;
$this->data['is_authorized'] = true;
if ( ! empty( $this->OAuth_Helper->token->refresh_token ) ) {
$this->data['refresh_token'] = $this->OAuth_Helper->token->refresh_token;
}
// If there an `instance_url` returned from the auth response, save it.
// Salesforce API should use this URL instead of the `login_url` provided by user.
if ( ! empty( $this->OAuth_Helper->INSTANCE_URL ) ) { // @phpcs:ignore ET.Sniffs.ValidVariableName.UsedPropertyNotSnakeCase -- No need to change the prop name.
$this->data['instance_url'] = $this->OAuth_Helper->INSTANCE_URL; // @phpcs:ignore ET.Sniffs.ValidVariableName.UsedPropertyNotSnakeCase -- No need to change the prop name.
}
return true;
}
/**
* The service's private data.
*
* @see self::$_data
* @internal
* @since 1.1.0
*
* @return array
*/
protected function _get_data() {
return (array) get_option( "et_core_api_{$this->service_type}_options" );
}
/**
* Initializes {@link self::OAuth_Helper}
*
* @return bool `true` if successful, `false` otherwise.
*/
protected function _initialize_oauth_helper() {
if ( null !== $this->OAuth_Helper ) {
return true;
}
$urls = array(
'access_token_url' => $this->ACCESS_TOKEN_URL,
'request_token_url' => $this->REQUEST_TOKEN_URL,
'authorization_url' => $this->AUTHORIZATION_URL,
'redirect_url' => $this->REDIRECT_URL,
);
$this->OAuth_Helper = new ET_Core_API_OAuthHelper( $this->data, $urls, $this->owner );
return null !== $this->OAuth_Helper;
}
/**
* Returns the currently known custom fields for this service.
*
* @return array
*/
protected function _get_custom_fields() {
return empty( $this->data['custom_fields'] ) ? array() : $this->data['custom_fields'];
}
/**
* Initiate OAuth Authorization Flow
*
* @return array|bool
*/
public function authenticate() {
et_core_nonce_verified_previously();
if ( '1.0a' === $this->oauth_version || ( '2.0' === $this->oauth_version && ! empty( $_GET['code'] ) ) ) {
$authenticated = $this->_do_oauth_access_token_request();
if ( $authenticated ) {
$this->save_data();
return true;
}
} elseif ( '2.0' === $this->oauth_version ) {
$nonce = wp_create_nonce( 'et_core_api_service_oauth2' );
$args = array(
'client_id' => $this->data['api_key'],
'response_type' => 'code',
'state' => rawurlencode( "ET_Core|{$this->slug}|{$this->account_name}|{$nonce}" ),
'redirect_uri' => $this->REDIRECT_URL, // @phpcs:ignore -- No need to change the class property
);
$this->save_data();
return array( 'redirect_url' => add_query_arg( $args, $this->AUTHORIZATION_URL ) );
}
return false;
}
/**
* Remove the service account from the database. This cannot be undone. The instance
* will no longer be usable after a call to this method.
*
* @since 1.1.0
*/
abstract public function delete();
/**
* Get the form fields to show in the WP Dashboard for this service's accounts.
*
* @since 1.1.0
*
* @return array
*/
abstract public function get_account_fields();
/**
* Get an array that maps our data keys to those returned by the 3rd-party service's API.
*
* @since 1.1.0
*
* @param array $keymap A mapping of our data key addresses to those of the service, organized by type/category.
*
* @return array[] {
*
* @type array $key_type {
*
* @type string $our_key_address The corresponding key address on the service.
* ...
* }
* ...
* }
*/
abstract public function get_data_keymap( $keymap = array() );
/**
* Get error message for a response that has an ERROR status. If possible the provider's
* error message will be returned. Otherwise the HTTP error status description will be returned.
*
* @return string
*/
public function get_error_message() {
if ( ! empty( $this->data_keys['error'] ) ) {
$data = $this->transform_data_to_our_format( $this->response->DATA, 'error' );
return isset( $data['error_message'] ) ? $data['error_message'] : '';
}
return $this->response->ERROR_MESSAGE;
}
/**
* Whether or not the current account has been authenticated with the service's API.
*
* @return bool
*/
public function is_authenticated() {
return isset( $this->data['is_authorized'] ) && in_array( $this->data['is_authorized'], array( true, 'true' ) );
}
/**
* Makes a remote request using the current {@link self::http->request}. Automatically
* handles custom headers and OAuth when applicable.
*/
public function make_remote_request() {
if ( ! empty( $this->custom_headers ) ) {
$this->http->request->HEADERS = array_merge( $this->http->request->HEADERS, $this->custom_headers );
}
if ( $this->uses_oauth ) {
$oauth2 = '2.0' === $this->oauth_version;
$this->http->request = $this->OAuth_Helper->prepare_oauth_request( $this->http->request, $oauth2 );
} else if ( $this->http_auth ) {
$this->_add_http_auth_header_to_request();
}
$this->request = $this->http->request;
$this->http->make_remote_request();
$this->response = $this->http->response;
}
/**
* Convenience accessor for {@link self::http->prepare_request()}
*
* @param string $url
* @param string $method
* @param bool $is_auth
* @param mixed $body
* @param bool $json_body
* @param bool $ssl_verify
*/
public function prepare_request( $url, $method = 'GET', $is_auth = false, $body = null, $json_body = false, $ssl_verify = true ) {
$this->http->prepare_request( $url, $method, $is_auth, $body, $json_body, $ssl_verify );
$this->request = $this->http->request;
}
/**
* Save this service's data to the database.
*
* @return mixed
*/
abstract public function save_data();
/**
* Set the account name for the instance. Changing the accounts name affects the
* value of {@link self::data}.
*
* @param string $name
*/
abstract public function set_account_name( $name );
/**
* Transforms an array from our data format to that of the service provider.
*
* @param array $data The data to be transformed.
* @param string $key_type The type of data. This corresponds to the key_type in {@link self::data_keys}.
* @param array $exclude_keys Keys that should be excluded from the transformed data.
*
* @return array
*/
public function transform_data_to_our_format( $data, $key_type, $exclude_keys = array() ) {
if ( ! isset( $this->data_keys[ $key_type ] ) ) {
return array();
}
return self::$_->array_transform( $data, $this->data_keys[ $key_type ], '<-', $exclude_keys );
}
/**
* Transforms an array from the service provider's data format to our data format.
*
* @param array $data The data to be transformed.
* @param string $key_type The type of data. This corresponds to the key_type in {@link self::data_keys}.
* @param array $exclude_keys Keys that should be excluded from the transformed data.
*
* @return array
*/
public function transform_data_to_provider_format( $data, $key_type, $exclude_keys = array() ) {
if ( ! isset( $this->data_keys[ $key_type ] ) ) {
return array();
}
return self::$_->array_transform( $data, $this->data_keys[ $key_type ], '->', $exclude_keys );
}
}

View File

@ -0,0 +1,316 @@
<?php
/**
* Wrapper for ActiveCampaign's API.
*
* @since 1.1.0
*
* @package ET\Core\API\Email
*/
class ET_Core_API_Email_ActiveCampaign extends ET_Core_API_Email_Provider {
/**
* @inheritDoc
*/
public $name = 'ActiveCampaign';
/**
* @inheritDoc
*/
public $slug = 'activecampaign';
/**
* @inheritDoc
*/
public $uses_oauth = false;
/**
* @inheritDoc
*/
public $custom_fields_scope = 'account';
protected function _fetch_custom_fields( $list_id = '', $list = array() ) {
// These general / default fields are static (it can not be edited or deleted in the ActiveCampaign dashboard) and not returned by the rest API,
// so just add it here as the default custom fields for ActiveCampaign.
$fields = array(
'%phone%' => array(
'field_id' => '%phone%',
'hidden' => false,
'name' => 'Phone',
'type' => 'input',
),
'%customer_acct_name%' => array(
'field_id' => '%customer_acct_name%',
'hidden' => false,
'name' => 'Account',
'type' => 'input',
),
);
foreach ( $list['fields'] as $field ) {
$field = $this->transform_data_to_our_format( $field, 'custom_field' );
$field_id = $field['field_id'];
$type = $field['type'];
$field['type'] = self::$_->array_get( $this->data_keys, "custom_field_type.{$type}", 'text' );
if ( isset( $field['options'] ) ) {
$options = array();
foreach ( $field['options'] as $option ) {
$option = $this->transform_data_to_our_format( $option, 'custom_field_option' );
$id = $option['id'];
$options[ $id ] = $option['name'];
}
$field['options'] = $options;
}
$fields[ $field_id ] = $field;
}
return $fields;
}
protected function _process_custom_fields( $args ) {
if ( ! isset( $args['custom_fields'] ) ) {
return $args;
}
$fields = $args['custom_fields'];
unset( $args['custom_fields'] );
foreach ( $fields as $field_id => $value ) {
if ( is_array( $value ) && $value ) {
// This is a multiple choice field (eg. checkbox, radio, select)
$value = array_values( $value );
if ( 'checkbox' === $this->data['custom_fields'][ $field_id ]['type'] ) {
$value = implode( '||', $value );
$value = "||{$value}||";
} else {
$value = array_pop( $value );
}
}
// Check if the custom field is an ActiveCampaign general field,
// it should be posted as dedicated param instead of `field[{$field_id},0]` param.
if ( preg_match( '/%(.*)%/', $field_id, $matches ) ) {
self::$_->array_set( $args, $matches[1], $value );
} else {
self::$_->array_set( $args, "field[{$field_id},0]", $value );
}
}
return $args;
}
/**
* Returns the requests URL for the account assigned to this class instance.
*
* @return string
*/
protected function _get_requests_url() {
$base_url = untrailingslashit( $this->data['api_url'] );
return "{$base_url}/admin/api.php";
}
/**
* @inheritDoc
*/
public function get_account_fields() {
return array(
'api_key' => array(
'label' => esc_html__( 'API Key', 'et_core' ),
),
'api_url' => array(
'label' => esc_html__( 'API URL', 'et_core' ),
'apply_password_mask' => false,
),
'form_id' => array(
'label' => esc_html__( 'Form ID', 'et_core' ),
'not_required' => true,
'apply_password_mask' => false,
),
);
}
/**
* @inheritDoc
*/
public function get_data_keymap( $keymap = array() ) {
$keymap = array(
'list' => array(
'list_id' => 'id',
'name' => 'name',
'subscribers_count' => 'subscriber_count',
),
'subscriber' => array(
'email' => 'email',
'last_name' => 'last_name',
'name' => 'first_name',
'custom_fields' => 'custom_fields',
),
'error' => array(
'error_message' => 'result_message',
),
'custom_field' => array(
'field_id' => 'id',
'name' => 'title',
'type' => 'type',
'hidden' => '!visible',
'options' => 'options',
),
'custom_field_option' => array(
'id' => 'value',
'name' => 'name',
),
'custom_field_type' => array(
// Us <=> Them
'checkbox' => 'checkbox',
'radio' => 'radio',
'textarea' => 'textarea',
'hidden' => 'hidden',
// Us => Them
'select' => 'dropdown',
'input' => 'text',
// Them => Us
'dropdown' => 'select',
'text' => 'input',
),
);
return parent::get_data_keymap( $keymap );
}
/**
* @inheritDoc
*/
public function fetch_subscriber_lists() {
if ( empty( $this->data['api_key'] ) || empty( $this->data['api_url'] ) ) {
return $this->API_KEY_REQUIRED;
}
$query_args = array(
'api_key' => $this->data['api_key'],
'api_action' => 'list_list',
'api_output' => 'json',
'ids' => 'all',
'full' => '1',
'global_fields' => '1',
);
$request_url = add_query_arg( $query_args, $this->_get_requests_url() );
$request_url = esc_url_raw( $request_url, array( 'https' ) );
$this->prepare_request( $request_url );
$this->request->HEADERS['Content-Type'] = 'application/x-www-form-urlencoded';
parent::fetch_subscriber_lists();
if ( $this->response->ERROR ) {
return $this->get_error_message();
}
$lists = array();
foreach ( $this->response->DATA as $key => $list_data ) {
if ( ! is_numeric( $key ) ) {
continue;
}
if ( ! empty( $list_data ) ) {
$lists[] = $list_data;
}
}
$this->data['lists'] = $this->_process_subscriber_lists( $lists );
$this->data['custom_fields'] = $this->_fetch_custom_fields( '', array_shift( $this->response->DATA ) );
$this->data['is_authorized'] = 'true';
$this->save_data();
et_debug($this->data);
return 'success';
}
/**
* Get ActiveCampaign subscriber info by email.
*
* @param string $email
*
* @return array
*/
public function get_subscriber( $email ) {
$query_args = array(
'api_key' => $this->data['api_key'],
'api_action' => 'subscriber_view_email',
'api_output' => 'json',
'email' => $email,
);
// Build request URL. This action only accept GET method.
$request_url = add_query_arg( $query_args, $this->_get_requests_url() );
$request_url = esc_url_raw( $request_url, array( 'https' ) );
// Prepare and send the request.
$this->prepare_request( $request_url );
$this->request->HEADERS['Content-Type'] = 'application/x-www-form-urlencoded';
$this->make_remote_request();
// Ensure no error happen and it's included in one of the lists.
$list_id = self::$_->array_get( $this->response->DATA, 'listid', false );
$result_code = self::$_->array_get( $this->response->DATA, 'result_code', false );
if ( $this->response->ERROR || ! $list_id || ! $result_code ) {
return false;
}
return $this->response->DATA;
}
/**
* @inheritDoc
*/
public function subscribe( $args, $url= '' ) {
// Ensure to skip subscribe action if current email already subscribed.
$subscriber_data = $this->get_subscriber( $args['email'] );
$subscriber_lists = self::$_->array_get( $subscriber_data, 'lists', array() );
$subscriber_list = self::$_->array_get( $subscriber_lists, $args['list_id'], false );
if ( $subscriber_list ) {
return 'success';
}
$list_id_key = 'p[' . $args['list_id'] . ']';
$status_key = 'status[' . $args['list_id'] . ']';
$responders_key = 'instantresponders[' . $args['list_id'] . ']';
$list_id = $args['list_id'];
$args = $this->transform_data_to_provider_format( $args, 'subscriber', array( 'list_id' ) );
$args = $this->_process_custom_fields( $args );
$query_args = array(
'api_key' => $this->data['api_key'],
'api_action' => 'contact_sync',
'api_output' => 'json',
);
$url = esc_url_raw( add_query_arg( $query_args, $this->_get_requests_url() ) );
$args[ $list_id_key ] = $list_id;
$args[ $status_key ] = 1;
$args[ $responders_key ] = 1;
if ( ! empty( $this->data['form_id'] ) ) {
$args['form'] = (int) $this->data['form_id'];
}
$this->prepare_request( $url, 'POST', false, $args );
$this->request->HEADERS['Content-Type'] = 'application/x-www-form-urlencoded';
return parent::subscribe( $args, $url );
}
}

View File

@ -0,0 +1,255 @@
<?php
/**
* Wrapper for Aweber's API.
*
* @since 1.1.0
*
* @package ET\Core\API\Email
*/
class ET_Core_API_Email_Aweber extends ET_Core_API_Email_Provider {
/**
* @inheritDoc
*/
public $ACCESS_TOKEN_URL = 'https://auth.aweber.com/1.0/oauth/access_token';
/**
* @inheritDoc
*/
public $AUTHORIZATION_URL = 'https://auth.aweber.com/1.0/oauth/authorize';
/**
* @inheritDoc
*/
public $REQUEST_TOKEN_URL = 'https://auth.aweber.com/1.0/oauth/request_token';
/**
* @inheritDoc
*/
public $BASE_URL = 'https://api.aweber.com/1.0';
/**
* @var string
*/
public $accounts_url;
/**
* @inheritDoc
*/
public $name = 'Aweber';
/**
* @inheritDoc
*/
public $name_field_only = true;
/**
* @inheritDoc
*/
public $slug = 'aweber';
/**
* @inheritDoc
*/
public $oauth_version = '1.0a';
/**
* @inheritDoc
*/
public $uses_oauth = true;
/**
* ET_Core_API_Email_Aweber constructor.
*
* @inheritDoc
*/
public function __construct( $owner, $account_name = '', $api_key = '' ) {
parent::__construct( $owner, $account_name, $api_key );
$this->accounts_url = "{$this->BASE_URL}/accounts";
}
protected function _fetch_custom_fields( $list_id = '', $list = array() ) {
$this->prepare_request( $list['custom_fields_collection_link'] );
return parent::_fetch_custom_fields( $list_id, $list );
}
protected function _get_lists_collection_url() {
$this->prepare_request( $this->accounts_url );
$this->make_remote_request();
$url = '';
if ( ! $this->response->ERROR && ! empty( $this->response->DATA['entries'][0]['lists_collection_link'] ) ) {
$url = $this->response->DATA['entries'][0]['lists_collection_link'];
}
return $url;
}
protected static function _parse_ID( $ID ) {
$values = explode( '|', $ID );
return ( count( $values ) === 6 ) ? $values : null;
}
protected function _process_custom_fields( $args ) {
if ( ! isset( $args['custom_fields'] ) ) {
return $args;
}
$fields = $args['custom_fields'];
unset( $args['custom_fields'] );
foreach ( $fields as $field_id => $value ) {
if ( is_array( $value ) && $value ) {
// This is a multiple choice field (eg. checkbox, radio, select)
$value = array_values( $value );
if ( count( $value ) > 1 ) {
$value = implode( ',', $value );
} else {
$value = array_pop( $value );
}
}
$list_id = $args['list_id'];
$field_name = self::$_->array_get( $this->data, "lists.{$list_id}.custom_fields.{$field_id}.name" );
self::$_->array_set( $args, "custom_fields.{$field_name}", $value );
}
return $args;
}
/**
* Uses the app's authorization code to get an access token
*/
public function authenticate() {
if ( empty( $this->data['api_key'] ) ) {
return false;
}
$key_parts = self::_parse_ID( $this->data['api_key'] );
if ( null === $key_parts ) {
return false;
}
list( $consumer_key, $consumer_secret, $request_token, $request_secret, $verifier ) = $key_parts;
if ( ! $verifier ) {
return false;
}
$this->data['consumer_key'] = $consumer_key;
$this->data['consumer_secret'] = $consumer_secret;
$this->data['access_key'] = $request_token;
$this->data['access_secret'] = $request_secret;
$this->oauth_verifier = $verifier;
// AWeber returns oauth access key in url query format :face_with_rolling_eyes:
$this->http->expects_json = false;
return parent::authenticate();
}
/**
* @inheritDoc
*/
public function get_account_fields() {
return array(
'api_key' => array(
'label' => esc_html__( 'Authorization Code', 'et_core' ),
),
);
}
/**
* @inheritDoc
*/
public function get_error_message() {
$error_message = parent::get_error_message();
return preg_replace('/https.*/m', '', $error_message );
}
/**
* @inheritDoc
*/
public function get_data_keymap( $keymap = array() ) {
$keymap = array(
'list' => array(
'list_id' => 'id',
'name' => 'name',
'subscribers_count' => 'total_subscribers',
),
'subscriber' => array(
'name' => 'name',
'email' => 'email',
'ad_tracking' => 'ad_tracking',
'custom_fields' => 'custom_fields',
),
'error' => array(
'error_message' => 'error.message',
),
'custom_field' => array(
'field_id' => 'id',
'name' => 'name',
),
);
return parent::get_data_keymap( $keymap );
}
/**
* @inheritDoc
*/
public function fetch_subscriber_lists() {
$needs_to_authenticate = ! $this->is_authenticated() || ! $this->_initialize_oauth_helper();
if ( $needs_to_authenticate && ! $this->authenticate() ) {
if ( empty( $this->response->DATA ) ) {
return '';
}
$this->response->DATA = json_decode( $this->response->DATA, true );
return $this->get_error_message();
}
$this->http->expects_json = true;
$this->LISTS_URL = $this->_get_lists_collection_url();
if ( empty( $this->LISTS_URL ) ) {
return '';
}
$this->response_data_key = 'entries';
return parent::fetch_subscriber_lists();
}
/**
* @inheritDoc
*/
public function subscribe( $args, $url = '' ) {
$lists_url = $this->_get_lists_collection_url();
$url = "{$lists_url}/{$args['list_id']}/subscribers";
$ip_address = 'true' === self::$_->array_get( $args, 'ip_address', 'true' );
$params = $this->_process_custom_fields( $args );
$params = $this->transform_data_to_provider_format( $params, 'subscriber' );
$params = array_merge( $params, array(
'ws.op' => 'create',
'ip_address' => $ip_address ? et_core_get_ip_address() : '0.0.0.0',
'misc_notes' => $this->SUBSCRIBED_VIA,
) );
// There is a bug in AWeber some characters not encoded properly on AWeber side when sending data in x-www-form-urlencoded format so use json instead
$this->prepare_request( $url, 'POST', false, $params, true );
$this->request->HEADERS['Content-Type'] = 'application/json';
return parent::subscribe( $params, $url );
}
}

View File

@ -0,0 +1,271 @@
<?php
/**
* Wrapper for Campaign Monitor's API.
*
* @since 1.1.0
*
* @package ET\Core\API\Email
*/
class ET_Core_API_Email_CampaignMonitor extends ET_Core_API_Email_Provider {
/**
* @inheritDoc
*/
public $BASE_URL = 'https://api.createsend.com/api/v3.1';
/**
* @inheritDoc
*/
public $http_auth = array(
'username' => 'api_key',
'password' => '-',
);
/**
* @inheritDoc
*/
public $name = 'CampaignMonitor';
/**
* @inheritDoc
*/
public $name_field_only = true;
/**
* @inheritDoc
*/
public $slug = 'campaign_monitor';
/**
* @inheritDoc
* @internal If true, oauth endpoints properties must also be defined.
*/
public $uses_oauth = false;
public function __construct( $owner, $account_name, $api_key = '' ) {
parent::__construct( $owner, $account_name, $api_key );
$this->http_auth['password'] = $owner;
}
protected function _fetch_custom_fields( $list_id = '', $list = array() ) {
$this->prepare_request( "{$this->BASE_URL}/lists/{$list_id}/customfields.json" );
$this->make_remote_request();
$result = array();
if ( $this->response->ERROR ) {
et_debug( $this->get_error_message() );
return $result;
}
foreach ( $this->response->DATA as &$custom_field ) {
$custom_field = $this->transform_data_to_our_format( $custom_field, 'custom_field' );
$custom_field['field_id'] = ltrim( rtrim( $custom_field['field_id'], ']' ), '[' );
}
$fields = array();
foreach ( $this->response->DATA as $field ) {
$field_id = $field['field_id'];
$type = self::$_->array_get( $field, 'type', 'any' );
$field['type'] = self::$_->array_get( $this->data_keys, "custom_field_type.{$type}", 'any' );
$fields[ $field_id ] = $field;
}
return $fields;
}
protected function _get_clients() {
$url = "{$this->BASE_URL}/clients.json";
$this->prepare_request( $url );
$this->make_remote_request();
if ( $this->response->ERROR ) {
return $this->get_error_message();
}
return (array) $this->response->DATA;
}
protected function _get_subscriber_counts() {
$subscriber_lists = $this->_process_subscriber_lists( $this->response->DATA );
$with_counts = array();
foreach ( $subscriber_lists as $subscriber_list ) {
$list_id = $subscriber_list['list_id'];
$with_counts[ $list_id ] = $subscriber_list;
$url = "{$this->BASE_URL}/lists/{$list_id}/stats.json";
$this->prepare_request( $url );
$this->make_remote_request();
if ( $this->response->ERROR ) {
continue;
}
if ( isset( $this->response->DATA['TotalActiveSubscribers'] ) ) {
$with_counts[ $list_id ]['subscribers_count'] = $this->response->DATA['TotalActiveSubscribers'];
} else {
$with_counts[ $list_id ]['subscribers_count'] = 0;
}
usleep( 500000 ); // 0.5 seconds
}
return $with_counts;
}
protected function _process_custom_fields( $args ) {
if ( ! isset( $args['custom_fields'] ) ) {
return $args;
}
$fields_unprocessed = $args['custom_fields'];
unset( $args['custom_fields'] );
$fields = array();
foreach ( $fields_unprocessed as $field_id => $value ) {
if ( is_array( $value ) && $value ) {
// This is a multiple choice field (eg. checkbox)
foreach ( $value as $selected_option ) {
$fields[] = array(
'Key' => $field_id,
'Value' => $selected_option,
);
}
} else {
$fields[] = array(
'Key' => $field_id,
'Value' => $value,
);
}
}
$args['CustomFields'] = $fields;
return $args;
}
/**
* @inheritDoc
*/
public function get_account_fields() {
return array(
'api_key' => array(
'label' => esc_html__( 'API Key', 'et_core' ),
),
);
}
/**
* @inheritDoc
*/
public function get_data_keymap( $keymap = array() ) {
$keymap = array(
'list' => array(
'list_id' => 'ListID',
'name' => 'Name',
'subscribers_count' => 'TotalActiveSubscribers',
),
'subscriber' => array(
'name' => 'Name',
'email' => 'EmailAddress',
'custom_fields' => 'custom_fields',
),
'error' => array(
'error_message' => 'Message',
),
'custom_field' => array(
'field_id' => 'Key',
'name' => 'FieldName',
'type' => 'DataType',
'options' => 'FieldOptions',
),
'custom_field_type' => array(
// Us => Them
'input' => 'Text',
'select' => 'MultiSelectOne',
'checkbox' => 'MultiSelectMany',
// Them => Us
'Text' => 'input',
'Number' => 'input',
'Date' => 'input',
'MultiSelectOne' => 'select',
'MultiSelectMany' => 'checkbox',
),
);
return parent::get_data_keymap( $keymap );
}
/**
* @inheritDoc
*/
public function fetch_subscriber_lists() {
if ( empty( $this->data['api_key'] ) ) {
return $this->API_KEY_REQUIRED;
}
$clients = $this->_get_clients();
$lists = array();
if ( ! is_array( $clients ) ) {
// Request failed with an error, return the error message.
return $clients;
}
foreach ( $clients as $client_info ) {
if ( empty( $client_info['ClientID'] ) ) {
continue;
}
$url = "{$this->BASE_URL}/clients/{$client_info['ClientID']}/lists.json";
$this->prepare_request( $url );
parent::fetch_subscriber_lists();
if ( $this->response->ERROR ) {
return $this->get_error_message();
}
if ( isset( $this->response->DATA ) ) {
$with_counts = $this->_get_subscriber_counts();
$lists = $lists + $with_counts;
$this->data['is_authorized'] = true;
$this->save_data();
}
}
if ( empty( $this->data['lists'] ) || ! empty( $lists ) ) {
$this->data['lists'] = $lists;
$this->save_data();
}
return $this->is_authenticated() ? 'success' : $this->FAILURE_MESSAGE;
}
/**
* @inheritDoc
*/
public function subscribe( $args, $url = '' ) {
$url = "{$this->BASE_URL}/subscribers/{$args['list_id']}.json";
$params = $this->transform_data_to_provider_format( $args, 'subscriber' );
$params = $this->_process_custom_fields( $params );
$params['CustomFields'][] = array( 'Key' => 'Note', 'Value' => $this->SUBSCRIBED_VIA );
$this->prepare_request( $url, 'POST', false, $params, true );
return parent::subscribe( $params, $url );
}
}

View File

@ -0,0 +1,255 @@
<?php
/**
* Wrapper for ConstantContact's API.
*
* @since 3.0.75
*
* @package ET\Core\API\Email
*/
class ET_Core_API_Email_ConstantContact extends ET_Core_API_Email_Provider {
/**
* @inheritDoc
*/
public $BASE_URL = 'https://api.constantcontact.com/v2';
/**
* @inheritDoc
*/
public $LISTS_URL = 'https://api.constantcontact.com/v2/lists';
/**
* @inheritDoc
*/
public $SUBSCRIBE_URL = 'https://api.constantcontact.com/v2/contacts';
/**
* @inheritDoc
*/
public $SUBSCRIBERS_URL = 'https://api.constantcontact.com/v2/contacts';
protected $_subscriber;
/**
* @inheritDoc
*/
public $custom_fields_scope = 'account';
/**
* @inheritDoc
*/
public $name = 'ConstantContact';
/**
* @inheritDoc
*/
public $slug = 'constant_contact';
/**
* ET_Core_API_Email_ConstantContact constructor.
*
* @inheritDoc
*/
public function __construct( $owner = '', $account_name = '', $api_key = '' ) {
parent::__construct( $owner, $account_name, $api_key );
$this->_maybe_set_custom_headers();
}
protected function _create_subscriber_data_array( $args ) {
return $this->transform_data_to_provider_format( $args, 'subscriber' );
}
protected function _fetch_custom_fields( $list_id = '', $list = array() ) {
$fields = array();
foreach ( range( 1, 15 ) as $i ) {
$fields["custom_field_{$i}"] = array(
'field_id' => "custom_field_{$i}",
'name' => "custom_field_{$i}",
'type' => 'any',
);
}
return $fields;
}
protected function _get_list_from_subscriber( $subscriber, $list_id ) {
if ( ! isset( $subscriber['lists'] ) ) {
return false;
}
foreach ( $subscriber['lists'] as &$list ) {
if ( $list['id'] === $list_id ) {
return $list;
}
}
return false;
}
protected function _maybe_set_custom_headers() {
if ( empty( $this->custom_headers ) && isset( $this->data['token'] ) ) {
$this->custom_headers = array(
'Authorization' => 'Bearer ' . sanitize_text_field( $this->data['token'] ),
);
}
}
protected function _process_custom_fields( $args ) {
if ( ! isset( $args['custom_fields'] ) ) {
return $args;
}
$fields = $args['custom_fields'];
$processed_fields = array();
unset( $args['custom_fields'], $this->_subscriber['custom_fields_unprocessed'] );
foreach ( $fields as $field_id => $value ) {
if ( is_array( $value ) && $value ) {
// This is a multiple choice field (eg. checkbox, radio, select)
$value = array_values( $value );
if ( count( $value ) > 1 ) {
$value = implode( ',', $value );
} else {
$value = array_pop( $value );
}
}
$processed_fields[] = array(
'name' => $field_id,
'value' => $value,
);
}
if ( isset( $this->_subscriber['custom_fields'] ) ) {
$processed_fields = array_merge( $processed_fields, $this->_subscriber['custom_fields'] );
}
$this->_subscriber['custom_fields'] = array_unique( $processed_fields, SORT_REGULAR );
return $args;
}
/**
* @inheritDoc
*/
public function get_account_fields() {
return array(
'api_key' => array(
'label' => esc_html__( 'API Key', 'et_core' ),
),
'token' => array(
'label' => esc_html__( 'Access Token', 'et_core' ),
),
);
}
/**
* @inheritDoc
*/
public function get_data_keymap( $keymap = array() ) {
$keymap = array(
'list' => array(
'list_id' => 'id',
'name' => 'name',
'subscribers_count' => 'contact_count',
),
'subscriber' => array(
'name' => 'first_name',
'last_name' => 'last_name',
'email' => 'email_addresses.[0].email_address',
'list_id' => 'lists.[0].id',
'custom_fields' => 'custom_fields_unprocessed',
),
'error' => array(
'error_message' => '[0].error_message',
),
'custom_field' => array(
'field_id' => 'name',
'name' => 'name',
),
);
return parent::get_data_keymap( $keymap );
}
public function get_subscriber( $email ) {
$url = add_query_arg( 'email', $email, $this->SUBSCRIBERS_URL );
$this->prepare_request( $url, 'GET', false );
$this->make_remote_request();
if ( $this->response->ERROR || ! isset( $this->response->DATA['results'] ) ) {
return array();
}
return $this->response->DATA['results'];
}
/**
* @inheritDoc
*/
public function fetch_subscriber_lists() {
if ( empty( $this->data['api_key'] ) || empty( $this->data['token'] ) ) {
return $this->API_KEY_REQUIRED;
}
$this->_maybe_set_custom_headers();
$this->response_data_key = false;
$this->LISTS_URL = add_query_arg( 'api_key', $this->data['api_key'], $this->LISTS_URL );
return parent::fetch_subscriber_lists();
}
/**
* @inheritDoc
*/
public function subscribe( $args, $url = '' ) {
$this->SUBSCRIBERS_URL = add_query_arg( 'api_key', $this->data['api_key'], $this->SUBSCRIBERS_URL );
$result = null;
$args['list_id'] = (string) $args['list_id'];
$subscriber = $this->get_subscriber( $args['email'] );
$subscriber = $subscriber ? $subscriber[0] : $subscriber;
$query_args = array( 'api_key' => $this->data['api_key'], 'action_by' => 'ACTION_BY_VISITOR' );
if ( $subscriber ) {
if ( $list = $this->_get_list_from_subscriber( $subscriber, $args['list_id'] ) ) {
$result = 'success';
} else {
$subscriber['lists'][] = array( 'id' => $args['list_id'] );
$this->_subscriber = &$subscriber;
$args = $this->_process_custom_fields( $args );
$url = add_query_arg( $query_args, "{$this->SUBSCRIBE_URL}/{$subscriber['id']}" );
$this->prepare_request( $url, 'PUT', false, $subscriber, true );
}
} else {
$url = add_query_arg( $query_args, $this->SUBSCRIBE_URL );
$subscriber = $this->_create_subscriber_data_array( $args );
$this->_subscriber = &$subscriber;
$args = $this->_process_custom_fields( $args );
$this->prepare_request( $url, 'POST', false, $subscriber, true );
}
if ( 'success' !== $result ) {
$result = parent::subscribe( $args, $this->SUBSCRIBE_URL );
}
return $result;
}
}

View File

@ -0,0 +1,224 @@
<?php
/**
* Wrapper for ConvertKit's API.
*
* @since 1.1.0
*
* @package ET\Core\API\Email
*/
class ET_Core_API_Email_ConvertKit extends ET_Core_API_Email_Provider {
/**
* @inheritDoc
*/
public $BASE_URL = 'https://api.convertkit.com/v3';
/**
* @inheritDoc
*/
public $custom_fields_scope = 'account';
/**
* @inheritDoc
*/
public $name = 'ConvertKit';
/**
* @inheritDoc
*/
public $name_field_only = true;
/**
* @inheritDoc
*/
public $slug = 'convertkit';
/**
* @inheritDoc
*/
public $uses_oauth = false;
/**
* ET_Core_API_Email_ConvertKit constructor.
*
* @inheritDoc
*/
public function __construct( $owner, $account_name = '', $api_key = '' ) {
parent::__construct( $owner, $account_name, $api_key );
// ConvertKit doesn't have "lists". They have "forms" so we use "forms" as if they were "lists".
$this->LISTS_URL = "{$this->BASE_URL}/forms";
}
protected function _fetch_custom_fields( $list_id = '', $list = array() ) {
$this->response_data_key = 'custom_fields';
$this->prepare_request( $this->_generate_url_for_request( "{$this->BASE_URL}/custom_fields" ) );
return parent::_fetch_custom_fields( $list_id, $list );
}
/**
* Generates the URL for adding subscribers.
*
* @param $list_id
*
* @since 1.1.0
*
* @return string
*/
protected function _get_subscribe_url( $list_id ) {
return "{$this->LISTS_URL}/{$list_id}/subscribe";
}
protected function _get_subscriber_counts( $forms ) {
$result = array();
foreach ( (array) $forms as $form_info ) {
$url = $this->_generate_url_for_request( "{$this->LISTS_URL}/{$form_info['id']}/subscriptions", true );
$this->prepare_request( $url );
$this->make_remote_request();
if ( $this->response->ERROR || ! isset( $this->response->DATA['total_subscriptions'] ) ) {
continue;
}
$form_info['total_subscriptions'] = $this->response->DATA['total_subscriptions'];
$result[] = $form_info;
}
return $result;
}
/**
* Adds default args for all API requests to given url.
*
* @since 1.1.0
*
* @param string $url
* @param bool $with_secret
*
* @return string
*/
protected function _generate_url_for_request( $url, $with_secret = false ) {
$key = $with_secret ? $this->data['api_secret'] : $this->data['api_key'];
$key_type = $with_secret ? 'api_secret' : 'api_key';
return esc_url_raw( add_query_arg( $key_type, $key, $url ) );
}
protected function _process_custom_fields( $args ) {
if ( ! isset( $args['custom_fields'] ) ) {
return $args;
}
$fields = $args['custom_fields'];
unset( $args['custom_fields'] );
foreach ( $fields as $field_id => $value ) {
if ( is_array( $value ) && $value ) {
// This is a multiple choice field (eg. checkbox, radio, select)
$value = array_values( $value );
if ( count( $value ) > 1 ) {
$value = implode( ',', $value );
} else {
$value = array_pop( $value );
}
}
self::$_->array_set( $args, "fields.{$field_id}", $value );
}
return $args;
}
/**
* @inheritDoc
*/
public function get_account_fields() {
return array(
'api_key' => array(
'label' => esc_html__( 'API Key', 'et_core' ),
),
'api_secret' => array(
'label' => esc_html__( 'API Secret', 'et_core' ),
),
);
}
/**
* @inheritDoc
*/
public function get_data_keymap( $keymap = array() ) {
$keymap = array(
'list' => array(
'list_id' => 'id',
'name' => 'name',
'subscribers_count' => 'total_subscriptions',
),
'subscriber' => array(
'email' => 'email',
'name' => 'first_name',
'custom_fields' => 'custom_fields',
),
'error' => array(
'error_message' => 'message',
),
'custom_field' => array(
'field_id' => 'key',
'name' => 'label',
),
);
return parent::get_data_keymap( $keymap );
}
/**
* @inheritDoc
*/
public function fetch_subscriber_lists() {
if ( empty( $this->data['api_key'] ) ) {
return $this->API_KEY_REQUIRED;
}
$url = $this->_generate_url_for_request( $this->LISTS_URL );
$result = 'success';
$this->response_data_key = '';
$this->prepare_request( $url );
parent::fetch_subscriber_lists();
if ( ! $this->response->ERROR && ! empty( $this->response->DATA['forms'] ) ) {
$with_subscriber_counts = $this->_get_subscriber_counts( $this->response->DATA['forms'] );
$this->data['lists'] = $this->_process_subscriber_lists( $with_subscriber_counts );
$this->data['is_authorized'] = 'true';
$this->data['custom_fields'] = $this->_fetch_custom_fields( '', array_shift( $this->response->DATA['forms'] ) );
$this->save_data();
} else if ( $this->response->ERROR ) {
$result = $this->get_error_message();
}
return $result;
}
/**
* @inheritDoc
*/
public function subscribe( $args, $url = '' ) {
$url = $this->_generate_url_for_request( $this->_get_subscribe_url( $args['list_id'] ) );
$params = $this->transform_data_to_provider_format( $args, 'subscriber' );
$params = $this->_process_custom_fields( $params );
$params['fields']['notes'] = $this->SUBSCRIBED_VIA;
$this->prepare_request( $url, 'POST', false, $params );
return parent::subscribe( $params, $url );
}
}

View File

@ -0,0 +1,167 @@
<?php
/**
* Wrapper for Emma's API.
*
* @since 1.1.0
*
* @package ET\Core\API\Email
*/
class ET_Core_API_Email_Emma extends ET_Core_API_Email_Provider {
/**
* @inheritDoc
*/
public $BASE_URL = 'https://api.e2ma.net';
/**
* @inheritDoc
*/
public $custom_fields_scope = 'account';
/**
* @inheritDoc
*/
public $http_auth = array(
'username' => 'api_key',
'password' => 'private_api_key',
);
/**
* @inheritDoc
*/
public $name = 'Emma';
/**
* @inheritDoc
*/
public $slug = 'emma';
protected function _fetch_custom_fields( $list_id = '', $list = array() ) {
$this->response_data_key = false;
$this->prepare_request( $this->_get_custom_fields_url() );
return parent::_fetch_custom_fields( $list_id, $list );
}
protected function _get_custom_fields_url() {
return "https://api.e2ma.net/{$this->data['user_id']}/fields?group_types=all";
}
protected function _get_lists_url() {
return "https://api.e2ma.net/{$this->data['user_id']}/groups?group_types=all";
}
protected function _get_subscribe_url() {
return "https://api.e2ma.net/{$this->data['user_id']}/members/signup";
}
protected function _process_custom_fields( $args ) {
if ( ! isset( $args['custom_fields'] ) ) {
return $args;
}
$fields = $args['custom_fields'];
unset( $args['custom_fields'] );
foreach ( $fields as $field_id => $value ) {
if ( is_array( $value ) && $value ) {
// This is a multiple choice field (eg. checkbox, radio, select)
$value = array_values( $value );
}
self::$_->array_set( $args, "fields.{$field_id}", $value );
}
return $args;
}
/**
* @inheritDoc
*/
public function get_account_fields() {
return array(
'api_key' => array(
'label' => esc_html__( 'Public API Key', 'et_core' ),
),
'private_api_key' => array(
'label' => esc_html__( 'Private API Key', 'et_core' ),
),
'user_id' => array(
'label' => esc_html__( 'Account ID', 'et_core' ),
),
);
}
/**
* @inheritDoc
*/
public function get_data_keymap( $keymap = array() ) {
$keymap = array(
'list' => array(
'name' => 'group_name',
'list_id' => 'member_group_id',
'subscribers_count' => 'active_count',
),
'subscriber' => array(
'email' => 'email',
'name' => 'fields.first_name',
'last_name' => 'fields.last_name',
'list_id' => '@group_ids',
'custom_fields' => 'custom_fields',
),
'custom_field' => array(
'field_id' => 'shortcut_name',
'name' => 'display_name',
'options' => 'options',
'type' => 'widget_type',
),
'custom_field_type' => array(
// Us <=> Them
'radio' => 'radio',
// Us => Them
'select' => 'select one',
'text' => 'long',
'checkbox' => 'check_multiple',
// Them => Us
'text' => 'input',
'long' => 'text',
'check_multiple' => 'checkbox',
'select one' => 'select',
),
);
return parent::get_data_keymap( $keymap );
}
/**
* @inheritDoc
*/
public function fetch_subscriber_lists() {
if ( empty( $this->data['api_key'] ) ) {
return $this->API_KEY_REQUIRED;
}
$this->LISTS_URL = $this->_get_lists_url();
$this->response_data_key = false;
return parent::fetch_subscriber_lists();
}
/**
* @inheritDoc
*/
public function subscribe( $args, $url = '' ) {
$url = $this->_get_subscribe_url();
$this->response_data_key = 'member_id';
$args = $this->transform_data_to_provider_format( $args, 'subscriber' );
$args = $this->_process_custom_fields( $args );
$this->prepare_request( $url, 'POST', false, $args, true );
return parent::subscribe( $args, $url );
}
}

View File

@ -0,0 +1,172 @@
<?php
/**
* Wrapper for Feedblitz's API.
*
* @since 1.1.0
*
* @package ET\Core\API\Email
*/
class ET_Core_API_Email_Feedblitz extends ET_Core_API_Email_Provider {
/**
* @inheritDoc
*/
public $BASE_URL = 'https://www.feedblitz.com';
/**
* @inheritDoc
*/
public $LISTS_URL = 'https://www.feedblitz.com/f.api/syndications';
/**
* @inheritDoc
*/
public $SUBSCRIBE_URL = 'https://www.feedblitz.com/f';
/**
* @inheritDoc
*/
public $custom_fields = 'dynamic';
/**
* @inheritDoc
*/
public $custom_fields_scope = 'account';
/**
* @inheritDoc
*/
public $name = 'Feedblitz';
/**
* @inheritDoc
*/
public $slug = 'feedblitz';
protected function _process_custom_fields( $args ) {
if ( ! isset( $args['custom_fields'] ) ) {
return $args;
}
$fields = $args['custom_fields'];
unset( $args['custom_fields'] );
foreach ( $fields as $field_id => $value ) {
if ( is_array( $value ) && $value ) {
// This is a multiple choice field (eg. checkbox, radio, select)
$value = array_values( $value );
if ( count( $value ) > 1 ) {
$value = implode( ',', $value );
} else {
$value = array_pop( $value );
}
}
self::$_->array_set( $args, $field_id, rawurlencode( $value ) );
}
return $args;
}
/**
* @inheritDoc
*/
public function get_account_fields() {
return array(
'api_key' => array(
'label' => esc_html__( 'API Key', 'et_core' ),
),
);
}
/**
* @inheritDoc
*/
public function get_data_keymap( $keymap = array() ) {
$keymap = array(
'list' => array(
'list_id' => 'id',
'name' => 'name',
'subscribers_count' => 'subscribersummary.subscribers',
),
'subscriber' => array(
'list_id' => 'listid',
'email' => 'email',
'name' => 'FirstName',
'last_name' => 'LastName',
'custom_fields' => 'custom_fields',
),
'error' => array(
'error_message' => 'rsp.err.@attributes.msg',
)
);
return parent::get_data_keymap( $keymap );
}
/**
* @inheritDoc
*/
public function fetch_subscriber_lists() {
if ( empty( $this->data['api_key'] ) ) {
return $this->API_KEY_REQUIRED;
}
$this->http->expects_json = false;
$this->response_data_key = false;
$this->LISTS_URL = add_query_arg( 'key', $this->data['api_key'], $this->LISTS_URL );
parent::fetch_subscriber_lists();
$response = $this->data_utils->process_xmlrpc_response( $this->response->DATA, true );
$response = $this->data_utils->xml_to_array( $response );
if ( $this->response->ERROR || ! empty( $response['rsp']['err']['@attributes']['msg'] ) ) {
return $this->get_error_message();
}
$this->data['lists'] = $this->_process_subscriber_lists( $response['syndications']['syndication'] );
$this->data['is_authorized'] = true;
$this->save_data();
return 'success';
}
/**
* @inheritDoc
*/
public function subscribe( $args, $url = '' ) {
$query_args = array(
'email' => rawurlencode( $args['email'] ),
'name' => empty( $args['name'] ) ? '' : rawurlencode( $args['name'] ),
'last_name' => empty( $args['last_name'] ) ? '' : rawurlencode( $args['last_name'] ),
'custom_fields' => $args['custom_fields'],
'list_id' => $args['list_id'],
);
$query = $this->transform_data_to_provider_format( $query_args, 'subscriber' );
$query = $this->_process_custom_fields( $query );
$query['key'] = rawurlencode( $this->data['api_key'] );
$url = add_query_arg( $query, "{$this->SUBSCRIBE_URL}?SimpleApiSubscribe" );
$this->prepare_request( $url, 'GET', false, null, false, false );
$this->make_remote_request();
$response = $this->data_utils->process_xmlrpc_response( $this->response->DATA, true );
$response = $this->data_utils->xml_to_array( $response );
if ( $this->response->ERROR || ! empty( $response['rsp']['err']['@attributes']['msg'] ) ) {
return $this->get_error_message();
}
if ( ! empty( $response['rsp']['success']['@attributes']['msg'] ) ) {
return $response['rsp']['success']['@attributes']['msg'];
}
return 'success';
}
}

View File

@ -0,0 +1,436 @@
<?php
class ET_Core_API_Email_Fields {
/**
* @var ET_Core_Data_Utils
*/
protected static $_;
protected static $_any_custom_field_type_support;
protected static $_predefined_custom_field_support;
/**
* @var ET_Core_API_Email_Providers
*/
protected static $_providers;
public static $owner;
protected static function _custom_field_definitions() {
$readonly_dependency = 'builder' === self::$owner ? 'parentModule:provider' : 'email_provider';
$custom_fields_support = array_keys( self::$_providers->names_by_slug( 'all', 'custom_fields' ) );
$custom_fields_data = self::$_providers->custom_fields_data();
$fields = array();
if ( 'bloom' === self::$owner ) {
$fields = array(
'use_custom_fields' => array(
'label' => esc_html__( 'Use Custom Fields', 'et_core' ),
'type' => 'yes_no_button',
'options' => array(
'on' => esc_html__( 'Yes', 'et_core' ),
'off' => esc_html__( 'No', 'et_core' ),
),
'default' => 'off',
'show_if' => array(
'email_provider' => $custom_fields_support,
),
'allow_dynamic' => array_keys( self::$_providers->names_by_slug( 'all', 'dynamic_custom_fields' ) ),
'description' => esc_html__( 'Enable this option to use custom fields in your opt-in form.', 'et_core' ),
),
'custom_fields' => array(
'type' => 'sortable_list',
'label' => '',
'default' => '[]',
),
'editing_field' => array(
'type' => 'skip',
'default' => 'off',
),
);
}
foreach ( $custom_fields_data as $provider => $accounts ) {
foreach ( $accounts as $account => $lists ) {
$account_name = str_replace( ' ', '', strtolower( $account ) );
$key = "predefined_field_{$provider}_{$account_name}";
if ( isset( $lists['custom_fields'] ) ) {
// Custom fields are per account
$fields[ $key ] = self::_predefined_custom_field_select( $lists['custom_fields'], $provider, $account );
continue;
}
foreach ( $lists as $list_id => $custom_fields ) {
// Custom fields are per list
$key = "predefined_field_{$provider}_{$account_name}_{$list_id}";
$fields[ $key ] = self::_predefined_custom_field_select( $custom_fields, $provider, $account, $list_id );
}
}
}
$labels = array(
'link_url' => esc_html__( 'Link URL', 'et_core' ),
'link_text' => esc_html__( 'Link Text', 'et_core' ),
'link_cancel' => esc_html__( 'Discard Changes', 'et_core' ),
'link_save' => esc_html__( 'Save Changes', 'et_core' ),
'link_settings' => esc_html__( 'Option Link', 'et_core' ),
);
$fields = array_merge( $fields, array(
'predefined_field' => array(
'type' => 'skip',
'default' => 'none',
),
'field_id' => array(
'label' => esc_html__( 'ID', 'et_core' ),
'type' => 'text',
'default' => '', // <--- Intentional
'description' => esc_html__( 'Define a unique ID for this field. You should use only English characters without special characters or spaces.', 'et_core' ),
'toggle_slug' => 'main_content',
'readonly_if' => array(
$readonly_dependency => self::$_predefined_custom_field_support,
),
),
'field_title' => array(
'label' => esc_html__( 'Name', 'et_core' ),
'type' => 'text',
'description' => esc_html__( 'Set the label that will appear above this field in the opt-in form.', 'et_core' ),
'toggle_slug' => 'main_content',
'readonly_if' => array(
$readonly_dependency => self::$_predefined_custom_field_support,
),
'readonly_if_not' => array(
$readonly_dependency => array( 'getresponse', 'sendinblue', 'constant_contact', 'fluentcrm' ),
),
),
'field_type' => array(
'label' => esc_html__( 'Type', 'et_core' ),
'type' => 'select',
'default' => 'none',
'option_category' => 'basic_option',
'options' => array(
'none' => esc_html__( 'Choose a field type...', 'et_core' ),
'input' => esc_html__( 'Input Field', 'et_core' ),
'email' => esc_html__( 'Email Field', 'et_core' ),
'text' => esc_html__( 'Textarea', 'et_core' ),
'checkbox' => esc_html__( 'Checkboxes', 'et_core' ),
'radio' => esc_html__( 'Radio Buttons', 'et_core' ),
'select' => esc_html__( 'Select Dropdown', 'et_core' ),
),
'description' => esc_html__( 'Choose the type of field', 'et_core' ),
'toggle_slug' => 'field_options',
'readonly_if' => array(
$readonly_dependency => self::$_predefined_custom_field_support,
),
'readonly_if_not' => array(
$readonly_dependency => self::$_any_custom_field_type_support,
),
),
'checkbox_checked' => array(
'label' => esc_html__( 'Checked By Default', 'et_core' ),
'description' => esc_html__( 'If enabled, the check mark will be automatically selected for the visitor. They can still deselect it.', 'et_core' ),
'type' => 'hidden',
'option_category' => 'layout',
'default' => 'off',
'toggle_slug' => 'field_options',
'show_if' => array(
'field_type' => 'checkbox',
),
),
'checkbox_options' => array(
'label' => esc_html__( 'Options', 'et_core' ),
'type' => 'sortable_list',
'checkbox' => true,
'option_category' => 'basic_option',
'toggle_slug' => 'field_options',
'readonly_if' => array(
$readonly_dependency => self::$_predefined_custom_field_support,
),
'readonly_if_not' => array(
$readonly_dependency => self::$_any_custom_field_type_support,
),
'show_if' => array(
'field_type' => 'checkbox',
),
'right_actions' => 'move|link|copy|delete',
'right_actions_readonly' => 'move|link',
'labels' => $labels,
),
'radio_options' => array(
'label' => esc_html__( 'Options', 'et_core' ),
'type' => 'sortable_list',
'radio' => true,
'option_category' => 'basic_option',
'toggle_slug' => 'field_options',
'readonly_if' => array(
$readonly_dependency => self::$_predefined_custom_field_support,
),
'readonly_if_not' => array(
$readonly_dependency => self::$_any_custom_field_type_support,
),
'show_if' => array(
'field_type' => 'radio',
),
'right_actions' => 'move|link|copy|delete',
'right_actions_readonly' => 'move|link',
'labels' => $labels,
),
'select_options' => array(
'label' => esc_html__( 'Options', 'et_core' ),
'type' => 'sortable_list',
'option_category' => 'basic_option',
'toggle_slug' => 'field_options',
'readonly_if' => array(
$readonly_dependency => self::$_predefined_custom_field_support,
),
'readonly_if_not' => array(
$readonly_dependency => self::$_any_custom_field_type_support,
),
'show_if' => array(
'field_type' => 'select',
),
'right_actions' => 'move|copy|delete',
'right_actions_readonly' => 'move',
),
'min_length' => array(
'label' => esc_html__( 'Minimum Length', 'et_core' ),
'description' => esc_html__( 'Leave at 0 to remove restriction', 'et_core' ),
'type' => 'range',
'default' => '0',
'unitless' => true,
'range_settings' => array(
'min' => '0',
'max' => '255',
'step' => '1',
),
'option_category' => 'basic_option',
'toggle_slug' => 'field_options',
'show_if' => array(
'field_type' => 'input',
),
),
'max_length' => array(
'label' => esc_html__( 'Maximum Length', 'et_core' ),
'description' => esc_html__( 'Leave at 0 to remove restriction', 'et_core' ),
'type' => 'range',
'default' => '0',
'unitless' => true,
'range_settings' => array(
'min' => '0',
'max' => '255',
'step' => '1',
),
'option_category' => 'basic_option',
'toggle_slug' => 'field_options',
'show_if' => array(
'field_type' => 'input',
),
),
'allowed_symbols' => array(
'label' => esc_html__( 'Allowed Symbols', 'et_core' ),
'description' => esc_html__( 'You can validate certain types of information by disallowing certain symbols. Symbols added here will prevent the form from being submitted if added by the user.', 'et_core' ),
'type' => 'select',
'default' => 'all',
'options' => array(
'all' => esc_html__( 'All', 'et_core' ),
'letters' => esc_html__( 'Letters Only (A-Z)', 'et_core' ),
'numbers' => esc_html__( 'Numbers Only (0-9)', 'et_core' ),
'alphanumeric' => esc_html__( 'Alphanumeric Only (A-Z, 0-9)', 'et_core' ),
),
'option_category' => 'basic_option',
'toggle_slug' => 'field_options',
'show_if' => array(
'field_type' => 'input',
),
),
'required_mark' => array(
'label' => esc_html__( 'Required Field', 'et_core' ),
'type' => 'yes_no_button',
'option_category' => 'configuration',
'default' => 'on',
'options' => array(
'on' => esc_html__( 'Yes', 'et_core' ),
'off' => esc_html__( 'No', 'et_core' ),
),
'description' => esc_html__( 'Define whether the field should be required or optional', 'et_core' ),
'toggle_slug' => 'field_options',
'show_if_not' => array(
'email_provider' => 'getresponse',
),
),
'hidden' => array(
'label' => esc_html__( 'Hidden Field', 'et_core' ),
'type' => 'yes_no_button',
'option_category' => 'configuration',
'default' => 'off',
'options' => array(
'on' => esc_html__( 'Yes', 'et_core' ),
'off' => esc_html__( 'No', 'et_core' ),
),
'description' => esc_html__( 'Define whether or not the field should be visible.', 'et_core' ),
'toggle_slug' => 'field_options',
'readonly_if' => array(
$readonly_dependency => self::$_predefined_custom_field_support,
),
'readonly_if_not' => array(
$readonly_dependency => self::$_any_custom_field_type_support,
),
),
'fullwidth_field' => array(
'label' => esc_html__( 'Make Fullwidth', 'et_core' ),
'type' => 'yes_no_button',
'option_category' => 'layout',
'options' => array(
'on' => esc_html__( 'Yes', 'et_core' ),
'off' => esc_html__( 'No', 'et_core' ),
),
'tab_slug' => 'advanced',
'toggle_slug' => 'layout',
'description' => esc_html__( 'If enabled, the field will take 100% of the width of the form, otherwise it will take 50%', 'et_core' ),
'default' => array( 'parent:layout', array(
'left_right' => 'on',
'right_left' => 'on',
'top_bottom' => 'off',
'bottom_top' => 'off',
)),
),
) );
if ( 'builder' === self::$owner ) {
$fields = array_merge( $fields, array(
'field_background_color' => array(
'label' => esc_html__( 'Field Background Color', 'et_core' ),
'description' => esc_html__( "Pick a color to fill the module's input fields.", 'et_core' ),
'type' => 'color-alpha',
'custom_color' => true,
'toggle_slug' => 'form_field',
'tab_slug' => 'advanced',
),
'conditional_logic' => array(
'label' => esc_html__( 'Enable', 'et_core' ),
'type' => 'yes_no_button',
'option_category' => 'layout',
'default' => 'off',
'options' => array(
'on' => esc_html__( 'Yes', 'et_core' ),
'off' => esc_html__( 'No', 'et_core' ),
),
'description' => et_get_safe_localization( __( "Enabling conditional logic makes this field only visible when any or all of the rules below are fulfilled<br><strong>Note:</strong> Only fields with an unique and non-empty field ID can be used", 'et_core' ) ),
'toggle_slug' => 'conditional_logic',
),
'conditional_logic_relation' => array(
'label' => esc_html__( 'Relation', 'et_core' ),
'type' => 'yes_no_button',
'option_category' => 'layout',
'options' => array(
'on' => esc_html__( 'All', 'et_core' ),
'off' => esc_html__( 'Any', 'et_core' ),
),
'default' => 'off',
'button_options' => array(
'button_type' => 'equal',
),
'description' => esc_html__( 'Choose whether any or all of the rules should be fulfilled', 'et_core' ),
'toggle_slug' => 'conditional_logic',
'show_if' => array(
'conditional_logic' => 'on',
),
),
'conditional_logic_rules' => array(
'label' => esc_html__( 'Rules', 'et_core' ),
'description' => esc_html__( 'Conditional logic rules can be used to hide and display different input fields conditionally based on how the visitor has interacted with different inputs.', 'et_core' ),
'type' => 'conditional_logic',
'option_category' => 'layout',
'depends_show_if' => 'on',
'toggle_slug' => 'conditional_logic',
'show_if' => array(
'conditional_logic' => 'on',
),
),
) );
}
if ( 'bloom' === self::$owner ) {
foreach ( $fields as $field_slug => &$field ) {
if ( 'use_custom_fields' !== $field_slug ) {
self::$_->array_set( $field, 'show_if.use_custom_fields', 'on' );
}
}
}
return $fields;
}
protected static function _predefined_custom_field_select( $custom_fields, $provider, $account_name, $list_id = '' ) {
$is_builder = 'builder' === self::$owner;
$is_bloom = 'bloom' === self::$owner;
$dependency = $is_builder ? 'parentModule:provider' : 'email_provider';
$show_if = array(
$dependency => $provider,
);
if ( $is_bloom ) {
$show_if['account_name'] = $account_name;
}
if ( $list_id && $is_builder ) {
$dependency = "parentModule:{$provider}_list";
$show_if[ $dependency ] = "{$account_name}|{$list_id}";
} else if ( $list_id && $is_bloom ) {
$show_if['email_list'] = (string) $list_id;
}
$options = array( 'none' => esc_html__( 'Choose a field...', 'et_core' ) );
foreach ( $custom_fields as $field ) {
$field_id = isset( $field['group_id'] ) ? $field['group_id'] : $field['field_id'];
$options[ $field_id ] = $field['name'];
}
return array(
'label' => esc_html__( 'Field', 'et_core' ),
'type' => 'select',
'options' => $options,
'order' => array_keys( $options ),
'show_if' => $show_if,
'toggle_slug' => 'main_content',
'default' => 'none',
'description' => esc_html__( 'Choose a custom field. Custom fields must be defined in your email provider account.', 'et_core' ),
);
}
/**
* Get field definitions
*
* @since 3.10
*
* @param string $type Accepts 'custom_field'
* @param string $for Accepts 'builder', 'bloom'
*
* @return array
*/
public static function get_definitions( $for, $type = 'custom_field' ) {
self::$owner = $for;
$fields = array();
if ( 'custom_field' === $type ) {
$fields = self::_custom_field_definitions();
}
return $fields;
}
public static function initialize() {
self::$_ = ET_Core_Data_Utils::instance();
self::$_providers = ET_Core_API_Email_Providers::instance();
self::$_predefined_custom_field_support = array_keys( self::$_providers->names_by_slug( 'all', 'predefined_custom_fields' ) );
self::$_any_custom_field_type_support = self::$_providers->names_by_slug( 'all', 'any_custom_field_type' );
}
}
ET_Core_API_Email_Fields::initialize();

View File

@ -0,0 +1,338 @@
<?php
/**
* ET_Core_API_Email_FluentCRM class file.
*
* @class ET_Core_API_Email_FluentCRM
* @package ET\Core\API\Email
*/
// phpcs:disable Squiz.Commenting.VariableComment.MissingVar -- All the class level variables here inherit parent inline documentation. Please check ET_Core_API_Email_Provider or ET_Core_API_Service class.
// phpcs:disable Squiz.Commenting.FunctionComment.MissingParamTag -- Almost all the methods here inherit parent inline documentation. Please check ET_Core_API_Email_Provider or ET_Core_API_Service class.
/**
* Wrapper for FluentCRM's API.
*
* @since 4.9.1
*
* @package ET\Core\API\Email
*/
class ET_Core_API_Email_FluentCRM extends ET_Core_API_Email_Provider {
/**
* Warning message text if the required plugin doesn't exist.
*
* @var boolean
*/
public static $PLUGIN_REQUIRED; // phpcs:ignore ET.Sniffs.ValidVariableName.PropertyNotSnakeCase -- Widely used on all email provider classes.
/**
* {@inheritdoc}
*/
public $name = 'FluentCRM';
/**
* {@inheritdoc}
*/
public $slug = 'fluentcrm';
/**
* {@inheritdoc}
*/
public $custom_fields = 'predefined';
/**
* {@inheritdoc}
*/
public $custom_fields_scope = 'account';
/**
* ET_Core_API_Email_FluentCRM constructor.
*/
public function __construct( $owner = '', $account_name = '', $api_key = '' ) {
parent::__construct( $owner, $account_name, $api_key );
if ( null === self::$PLUGIN_REQUIRED ) { // phpcs:ignore ET.Sniffs.ValidVariableName.UsedPropertyNotSnakeCase -- Widely used on all email provider classes.
self::$PLUGIN_REQUIRED = esc_html__( 'FluentCRM plugin is either not installed or not activated.', 'et_core' ); // phpcs:ignore ET.Sniffs.ValidVariableName.UsedPropertyNotSnakeCase -- Widely used on all email provider classes.
}
}
/**
* {@inheritdoc}
*/
public function get_data_keymap( $keymap = array() ) {
$keymap = array(
'list' => array(
'list_id' => 'id',
'name' => 'title',
),
'subscriber' => array(
'email' => 'email',
'last_name' => 'last_name',
'name' => 'first_name',
'custom_fields' => 'custom_fields',
),
'custom_field' => array(
'field_id' => 'slug',
'name' => 'label',
'type' => 'type',
'hidden' => 'hidden',
'options' => 'options',
),
'custom_field_option' => array(
'id' => 'value',
'name' => 'value',
),
'custom_field_type' => array(
// Us <=> Them.
'radio' => 'radio',
'checkbox' => 'checkbox',
// Us => Them.
'input' => 'text',
'select' => 'select-one',
// Them => Us.
'text' => 'input',
'number' => 'input',
'date' => 'input',
'date_time' => 'input',
'select-one' => 'select',
'select-multi' => 'checkbox',
),
);
return parent::get_data_keymap( $keymap );
}
/**
* {@inheritdoc}
*
* FluentCRM is self hosted and all the fields can be managed on the plugin itself.
*/
protected function _fetch_custom_fields( $list_id = '', $list = array() ) {
static $processed = null;
if ( is_null( $processed ) ) {
// A. Default custom fields.
$processed = array(
'address_line_1' => array(
'field_id' => 'address_line_1',
'name' => __( 'Address Line 1', 'et_builder' ),
'type' => 'input',
'hidden' => false,
),
'address_line_2' => array(
'field_id' => 'address_line_2',
'name' => __( 'Address Line 2', 'et_builder' ),
'type' => 'input',
'hidden' => false,
),
'postal_code' => array(
'field_id' => 'postal_code',
'name' => __( 'Postal Code', 'et_builder' ),
'type' => 'input',
'hidden' => false,
),
'city' => array(
'field_id' => 'city',
'name' => __( 'City', 'et_builder' ),
'type' => 'input',
'hidden' => false,
),
'state' => array(
'field_id' => 'state',
'name' => __( 'State', 'et_builder' ),
'type' => 'input',
'hidden' => false,
),
'country' => array(
'field_id' => 'country',
'name' => __( 'Country', 'et_builder' ),
'type' => 'input',
'hidden' => false,
),
'phone' => array(
'field_id' => 'phone',
'name' => __( 'Phone', 'et_builder' ),
'type' => 'input',
'hidden' => false,
),
'status' => array(
'field_id' => 'status',
'name' => __( 'Enable Double Optin', 'et_builder' ),
'type' => 'input',
'required_mark' => 'off',
'default' => 'pending',
'hidden' => true,
),
);
// B. Contact custom fields.
$contact_custom_fields = fluentcrm_get_option( 'contact_custom_fields', array() );
if ( ! empty( $contact_custom_fields ) ) {
foreach ( $contact_custom_fields as $field ) {
// 1. Transform field data to fit our format.
$field = $this->transform_data_to_our_format( $field, 'custom_field' );
$field_id = $field['field_id'];
$type = $field['type'];
// 2. Transform field type to fit our supported field type.
$field['type'] = self::$_->array_get( $this->data_keys, "custom_field_type.{$type}", 'any' );
if ( 'select-multi' === $type ) {
$field['type_origin'] = $type;
}
// 3. Transform `options` data to fit our format (`id` => `name`).
if ( ! empty( $field['options'] ) ) {
$options = array();
foreach ( $field['options'] as $option ) {
$option = array( 'value' => $option );
$option = $this->transform_data_to_our_format( $option, 'custom_field_option' );
$options[ $option['id'] ] = $option['name'];
}
$field['options'] = $options;
}
$processed[ $field_id ] = $field;
}
}
}
return $processed;
}
/**
* {@inheritdoc}
*
* FluentCRM is self hosted and all the fields can be managed on the plugin itself.
*/
public function get_account_fields() {
return array();
}
/**
* {@inheritdoc}
*
* FluentCRM is self hosted and all the lists can be managed on the plugin itself.
*/
public function fetch_subscriber_lists() {
if ( ! defined( 'FLUENTCRM' ) ) {
return self::$PLUGIN_REQUIRED; // phpcs:ignore ET.Sniffs.ValidVariableName.UsedPropertyNotSnakeCase -- Widely used on all email provider classes.
}
$list_tags = array();
// Lists.
$lists = FluentCrmApi( 'lists' )->all();
foreach ( $lists as $list ) {
$list_tags[ 'list_' . $list->id ] = array(
'list_id' => 'list_' . $list->id,
'name' => 'List: ' . $list->title,
'subscribers_count' => 0,
);
}
// Tags.
$tags = FluentCrmApi( 'tags' )->all();
foreach ( $tags as $tag ) {
$list_tags[ 'tags_' . $tag->id ] = array(
'list_id' => 'tags_' . $tag->id,
'name' => 'Tag: ' . $tag->title,
'subscribers_count' => 0,
);
}
// Combine both of lists and tags because there is no other way to display both
// with different fields.
$this->data['lists'] = $list_tags;
$this->data['custom_fields'] = $this->_fetch_custom_fields( '', array() );
$this->data['is_authorized'] = true;
$this->save_data();
return 'success';
}
/**
* {@inheritdoc}
*/
protected function _process_custom_fields( $args ) {
if ( ! empty( $args['custom_fields'] ) ) {
$custom_fields = $args['custom_fields'];
foreach ( $custom_fields as $field_id => $field_value ) {
if ( $field_value ) {
$field_type = self::$_->array_get( $this->data['custom_fields'][ $field_id ], 'type' );
$field_type_origin = self::$_->array_get( $this->data['custom_fields'][ $field_id ], 'type_origin' );
// Transform `checkbox` value into string separated by comma i.e. 'val1,val2'.
// However, if the field type origin is `select-multi`, we have to use array
// values instead.
if ( 'checkbox' === $field_type ) {
$field_value = 'select-multi' === $field_type_origin ? array_values( $field_value ) : implode( ',', $field_value );
}
$args[ $field_id ] = $field_value;
}
}
if ( isset( $custom_fields['status'] ) ) {
$args['status'] = 'pending';
}
unset( $args['custom_fields'] );
}
return $args;
}
/**
* {@inheritdoc}
*/
public function subscribe( $args, $url = '' ) {
if ( ! defined( 'FLUENTCRM' ) ) {
ET_Core_Logger::error( self::$PLUGIN_REQUIRED ); // phpcs:ignore ET.Sniffs.ValidVariableName.UsedPropertyNotSnakeCase -- Widely used on all email provider classes.
return esc_html__( 'An error occurred. Please try again later.', 'et_core' );
}
$contact = $this->transform_data_to_provider_format( $args, 'subscriber' );
$contact = $this->_process_custom_fields( $contact );
// IP Address.
$ip_address = 'true' === self::$_->array_get( $args, 'ip_address', 'true' ) ? et_core_get_ip_address() : '';
$contact['ip_address'] = $ip_address;
// Name.
if ( empty( $contact['last_name'] ) ) {
$contact['full_name'] = $contact['first_name'];
unset( $contact['first_name'] );
unset( $contact['last_name'] );
}
// List or Tag.
$list_or_tag_id = $args['list_id'];
if ( false === strpos( $list_or_tag_id, 'tags_' ) ) {
$contact['lists'] = array( intval( str_replace( 'list_', '', $list_or_tag_id ) ) );
} else {
$contact['tags'] = array( intval( str_replace( 'tags_', '', $list_or_tag_id ) ) );
}
$contact = array_filter( $contact );
// Email.
if ( empty( $contact['email'] ) || ! is_email( $contact['email'] ) ) {
return esc_html__( 'Email Validation has been failed.', 'et_core' );
}
FluentCrmApi( 'contacts' )->createOrUpdate( $contact );
return 'success';
}
}

View File

@ -0,0 +1,184 @@
<?php
/**
* Wrapper for GetResponse's API.
*
* @since 1.1.0
*
* @package ET\Core\API\Email
*/
class ET_Core_API_Email_GetResponse extends ET_Core_API_Email_Provider {
/**
* @inheritDoc
*/
public $BASE_URL = 'https://api.getresponse.com/v3';
/**
* @inheritDoc
*/
public $FIELDS_URL = 'https://api.getresponse.com/v3/custom-fields';
/**
* @inheritDoc
*/
public $LISTS_URL = 'https://api.getresponse.com/v3/campaigns';
/**
* @inheritDoc
*/
public $SUBSCRIBE_URL = 'https://api.getresponse.com/v3/contacts';
/**
* @inheritDoc
*/
public $name = 'GetResponse';
/**
* @inheritDoc
*/
public $name_field_only = true;
/**
* @inheritDoc
*/
public $slug = 'getresponse';
/**
* @inheritDoc
* @internal If true, oauth endpoints properties must also be defined.
*/
public $uses_oauth = false;
public function __construct( $owner = '', $account_name = '', $api_key = '' ) {
parent::__construct( $owner, $account_name, $api_key );
$this->_maybe_set_custom_headers();
}
protected function _maybe_set_custom_headers() {
if ( empty( $this->custom_headers ) && isset( $this->data['api_key'] ) ) {
$this->custom_headers = array( 'X-Auth-Token' => "api-key {$this->data['api_key']}" );
}
}
protected function _process_custom_fields( $args ) {
if ( ! isset( $args['custom_fields'] ) ) {
return $args;
}
$fields_unprocessed = $args['custom_fields'];
$fields = array();
unset( $args['custom_fields'] );
foreach ( $fields_unprocessed as $field_id => $value ) {
if ( is_array( $value ) && $value ) {
// This is a multiple choice field (eg. checkbox, radio, select)
$value = array_values( $value );
} else {
$value = array( $value );
}
$fields[] = array(
'customFieldId' => $field_id,
'value' => $value,
);
}
$args['customFieldValues'] = $fields;
return $args;
}
/**
* @inheritDoc
*/
public function get_account_fields() {
return array(
'api_key' => array(
'label' => esc_html__( 'API Key', 'et_core' ),
),
);
}
/**
* @inheritDoc
*/
public function get_data_keymap( $keymap = array() ) {
$keymap = array(
'list' => array(
'name' => 'name',
'list_id' => 'campaignId',
'subscribers_count' => 'totalSubscribers',
),
'subscriber' => array(
'name' => 'name',
'email' => 'email',
'list_id' => 'campaign.campaignId',
'ip_address' => 'ipAddress',
'custom_fields' => 'custom_fields',
),
'error' => array(
'error_message' => 'message',
),
'custom_field' => array(
'field_id' => 'customFieldId',
'name' => 'name',
'type' => 'fieldType',
'options' => 'values',
'hidden' => 'hidden',
),
'custom_field_type' => array(
// Us <=> Them
'textarea' => 'textarea',
'radio' => 'radio',
'checkbox' => 'checkbox',
// Us => Them
'input' => 'text',
'select' => 'single_select',
// Them => Us
'text' => 'input',
'single_select' => 'select',
),
);
return parent::get_data_keymap( $keymap );
}
/**
* @inheritDoc
*/
public function fetch_subscriber_lists() {
if ( empty( $this->data['api_key'] ) ) {
return $this->API_KEY_REQUIRED;
}
$this->_maybe_set_custom_headers();
$this->response_data_key = false;
return parent::fetch_subscriber_lists();
}
/**
* @inheritDoc
*/
public function subscribe( $args, $url = '' ) {
$ip_address = 'true' === self::$_->array_get( $args, 'ip_address', 'true' ) ? et_core_get_ip_address() : '0.0.0.0';
$args['ip_address'] = $ip_address;
$args = $this->transform_data_to_provider_format( $args, 'subscriber' );
$args = $this->_process_custom_fields( $args );
$args['note'] = $this->SUBSCRIBED_VIA;
$args['dayOfCycle'] = 0;
if ( empty( $args['name'] ) ) {
unset( $args['name'] );
}
$this->prepare_request( $this->SUBSCRIBE_URL, 'POST', false, $args );
return parent::subscribe( $args, $this->SUBSCRIBE_URL );
}
}

View File

@ -0,0 +1,273 @@
<?php
/**
* Wrapper for HubSpot's API.
*
* @since 3.0.72
*
* @package ET\Core\API\Email
*/
class ET_Core_API_Email_HubSpot extends ET_Core_API_Email_Provider {
/**
* @inheritDoc
*/
public $BASE_URL = 'https://api.hubapi.com/contacts/v1';
/**
* @inheritDoc
*/
public $FIELDS_URL = 'https://api.hubapi.com/properties/v1/contacts/properties';
/**
* @inheritDoc
*/
public $LISTS_URL = 'https://api.hubapi.com/contacts/v1/lists/static';
/**
* @inheritDoc
*/
public $SUBSCRIBE_URL = 'https://api.hubapi.com/contacts/v1/contact/createOrUpdate/email/@email@';
/**
* @inheritDoc
*/
public $custom_fields_scope = 'account';
/**
* @inheritDoc
*/
public $name = 'HubSpot';
/**
* @inheritDoc
*/
public $slug = 'hubspot';
protected function _fetch_custom_fields( $list_id = '', $list = array() ) {
$this->response_data_key = false;
$fields_unprocessed = parent::_fetch_custom_fields( $list_id, $list );
$fields = array();
foreach ( $fields_unprocessed as $field ) {
$field_id = $field['field_id'];
if ( ! isset( $field['options'] ) ) {
$fields[ $field_id ] = $field;
continue;
}
$options = array();
foreach ( $field['options'] as $option ) {
$option = $this->transform_data_to_our_format( $option, 'custom_field_option' );
$id = $option['id'];
$options[ $id ] = $option['name'];
}
$field['options'] = $options;
$fields[ $field_id ] = $field;
}
return $fields;
}
protected function _get_list_add_contact_url( $list_id ) {
$url = "{$this->BASE_URL}/lists/{$list_id}/add";
return add_query_arg( 'hapikey', $this->data['api_key'], $url );
}
protected function _maybe_set_urls( $email = '' ) {
if ( empty( $this->data['api_key'] ) ) {
return;
}
$this->FIELDS_URL = add_query_arg( 'hapikey', $this->data['api_key'], $this->FIELDS_URL );
$this->LISTS_URL = add_query_arg( 'hapikey', $this->data['api_key'], $this->LISTS_URL );
$this->SUBSCRIBE_URL = add_query_arg( 'hapikey', $this->data['api_key'], $this->SUBSCRIBE_URL );
if ( $email ) {
$this->SUBSCRIBE_URL = str_replace( '@email@', rawurlencode( $email ), $this->SUBSCRIBE_URL );
}
}
protected function _process_custom_fields( $args ) {
$args['properties'] = array();
if ( ! isset( $args['custom_fields'] ) ) {
return $args;
}
$fields = $args['custom_fields'];
$properties = array();
unset( $args['custom_fields'] );
$field_types = array(
'radio',
'booleancheckbox',
);
foreach ( $fields as $field_id => $value ) {
if ( is_array( $value ) && $value ) {
// This is a multiple choice field (eg. checkbox, radio, select)
$value = array_values( $value );
$value = implode( ';', $value );
} elseif ( in_array( $this->data['custom_fields'][ $field_id ]['type'], $field_types, true ) ) {
// Should use array key. Sometimes it's different than value and Hubspot expects the key
$radio_options = $this->data['custom_fields'][ $field_id ]['options'];
$value = array_search( $value, $radio_options );
}
$properties[] = array(
'property' => $field_id,
'value' => $value,
);
}
$args['properties'] = $properties;
return $args;
}
/**
* @inheritDoc
*/
public function get_account_fields() {
return array(
'api_key' => array(
'label' => esc_html__( 'API Key', 'et_core' ),
),
);
}
/**
* @inheritDoc
*/
public function get_data_keymap( $keymap = array() ) {
$keymap = array(
'list' => array(
'list_id' => 'listId',
'name' => 'name',
'subscribers_count' => 'metaData.size',
),
'subscriber' => array(
'email' => 'email',
'name' => 'firstname',
'last_name' => 'lastname',
'custom_fields' => 'custom_fields',
),
'error' => array(
'error_message' => 'message',
),
'custom_field' => array(
'field_id' => 'name',
'name' => 'label',
'type' => 'fieldType',
'options' => 'options',
'hidden' => 'hidden',
),
'custom_field_option' => array(
'id' => 'value',
'name' => 'label',
),
'custom_field_type' => array(
// Us <=> Them
'select' => 'select',
'radio' => 'radio',
'checkbox' => 'checkbox',
// Us => Them
'input' => 'text',
'textarea' => 'text',
// Them => Us
'text' => 'input',
'booleancheckbox' => 'booleancheckbox',
),
);
return parent::get_data_keymap( $keymap );
}
/**
* @inheritDoc
*/
public function fetch_subscriber_lists() {
if ( empty( $this->data['api_key'] ) ) {
return $this->API_KEY_REQUIRED;
}
$this->_maybe_set_urls();
/**
* The maximum number of subscriber lists to request from Hubspot's API at a time.
*
* @since 3.0.75
*
* @param int $max_lists Value must be <= 250.
*/
$max_lists = (int) apply_filters( 'et_core_api_email_hubspot_max_lists', 250 );
$this->LISTS_URL = add_query_arg( 'count', $max_lists, $this->LISTS_URL );
$this->response_data_key = 'lists';
return parent::fetch_subscriber_lists();
}
/**
* @inheritDoc
*/
public function subscribe( $args, $url = '' ) {
if ( empty( $this->data['api_key'] ) ) {
return $this->API_KEY_REQUIRED;
}
$this->_maybe_set_urls( $args['email'] );
$args = $this->_process_custom_fields( $args );
$data = array(
'properties' => array(
array(
'property' => 'email',
'value' => et_core_sanitized_previously( $args['email'] ),
),
array(
'property' => 'firstname',
'value' => et_core_sanitized_previously( $args['name'] ),
),
array(
'property' => 'lastname',
'value' => et_core_sanitized_previously( $args['last_name'] ),
),
),
);
$data['properties'] = array_merge( $data['properties'], $args['properties'] );
$this->prepare_request( $this->SUBSCRIBE_URL, 'POST', false, $data, true );
$this->make_remote_request();
if ( $this->response->ERROR ) {
return $this->get_error_message();
}
$url = $this->_get_list_add_contact_url( $args['list_id'] );
$data = array(
'emails' => array( $args['email'] ),
);
$this->prepare_request( $url, 'POST', false, $data, true );
$this->make_remote_request();
if ( $this->response->ERROR ) {
return $this->get_error_message();
}
return 'success';
}
}

View File

@ -0,0 +1,439 @@
<?php
/**
* Wrapper for Infusionsoft's API.
*
* @since 1.1.0
*
* @package ET\Core\API\Email
*/
class ET_Core_API_Email_Infusionsoft extends ET_Core_API_Email_Provider {
private static $_data_keys = array(
'app_name' => 'client_id',
'api_key' => 'api_key',
);
/**
* @inheritDoc
*/
public $ACCESS_TOKEN_URL = 'https://api.infusionsoft.com/token';
/**
* @inheritDoc
*/
public $AUTHORIZATION_URL = 'https://signin.infusionsoft.com/app/oauth/authorize';
/**
* @inheritDoc
*/
public $BASE_URL = '';
/**
* @inheritDoc
* Use this variable to hold the pattern and update $BASE_URL dynamically when needed
*/
public $BASE_URL_PATTERN = 'https://@app_name@.infusionsoft.com/api/xmlrpc';
/**
* @inheritDoc
*/
public $custom_fields_scope = 'account';
/**
* @inheritDoc
*/
public $name = 'Infusionsoft';
/**
* @inheritDoc
*/
public $oauth_version = '2.0';
/**
* @inheritDoc
*/
public $slug = 'infusionsoft';
/**
* @inheritDoc
* @internal If true, oauth endpoints properties must also be defined.
*/
public $uses_oauth = false;
public function __construct( $owner = '', $account_name = '', $api_key = '' ) {
parent::__construct( $owner, $account_name, $api_key );
$this->http->expects_json = false;
}
protected function _add_contact_to_list( $contact_id, $list_id ) {
$params = array( $this->data[ self::$_data_keys['api_key'] ], (int) $contact_id, (int) $list_id );
$data = self::$_->prepare_xmlrpc_method_call( 'ContactService.addToGroup', $params );
$this->_do_request( $data );
if ( $this->response->ERROR ) {
return false;
}
return self::$_->process_xmlrpc_response( $this->response->DATA );
}
protected function _create_contact( $contact_details ) {
$params = array( $this->data[ self::$_data_keys['api_key'] ], $contact_details );
$data = self::$_->prepare_xmlrpc_method_call( 'ContactService.add', $params );
$this->_do_request( $data );
if ( $this->response->ERROR ) {
return false;
}
$result = self::$_->process_xmlrpc_response( $this->response->DATA );
return $result;
}
protected function _do_request( $data ) {
$this->prepare_request( $this->_get_base_url(), 'POST', false, $data );
$this->request->HEADERS = array( 'Content-Type' => 'application/xml', 'Accept-Charset' => 'UTF-8' );
$this->make_remote_request();
}
protected function _fetch_custom_fields( $list_id = '', $list = array() ) {
$params = array( $this->data[ self::$_data_keys['api_key'] ], 'DataFormField', 100, 0, array( 'FormId' => -1 ), array( 'Name', 'Label', 'DataType', 'Values' ) );
$data = self::$_->prepare_xmlrpc_method_call( 'DataService.query', $params );
$this->_do_request( $data );
if ( $this->response->ERROR ) {
return array();
}
$data = self::$_->process_xmlrpc_response( $this->response->DATA );
foreach ( $data as &$custom_field ) {
$custom_field = (array) $custom_field;
$custom_field = $this->transform_data_to_our_format( $custom_field, 'custom_field' );
}
$fields = array();
foreach ( $data as $field ) {
$field_id = $field['field_id'];
$type = self::$_->array_get( $field, 'type', 'any' );
$field['type'] = self::$_->array_get( $this->data_keys, "custom_field_type.{$type}", 'any' );
if ( isset( $field['options'] ) ) {
$field['options'] = explode( "\n", $field['options'] );
$field['options'] = array_filter( $field['options'] );
}
$fields[ $field_id ] = $field;
}
return $fields;
}
protected function _get_base_url() {
$this->BASE_URL = str_replace( '@app_name@', $this->data[ self::$_data_keys['app_name'] ], $this->BASE_URL_PATTERN );
return $this->BASE_URL;
}
protected function _get_contact_by_email( $email ) {
$params = array( $this->data[ self::$_data_keys['api_key'] ], 'Contact', 1, 0, array( 'Email' => $email ), array( 'Id', 'Groups' ) );
$data = self::$_->prepare_xmlrpc_method_call( 'DataService.query', $params );
$this->_do_request( $data );
if ( $this->response->ERROR ) {
return false;
}
return self::$_->process_xmlrpc_response( $this->response->DATA );
}
protected function _optin_email_address( $email ) {
$params = array( $this->data[ self::$_data_keys['api_key'] ], $email, $this->SUBSCRIBED_VIA );
$data = self::$_->prepare_xmlrpc_method_call('APIEmailService.optIn', $params );
$this->_do_request( $data );
if ( $this->response->ERROR ) {
return false;
}
return self::$_->process_xmlrpc_response( $this->response->DATA );
}
protected function _process_custom_fields( $args ) {
if ( ! isset( $args['custom_fields'] ) ) {
return array();
}
$fields = array();
foreach ( $args['custom_fields'] as $field_id => $value ) {
if ( is_array( $value ) && $value ) {
// This is a multiple choice field (eg. checkbox, radio, select)
$value = array_values( $value );
$value = implode( ',', $value );
}
$fields[ "_{$field_id}" ] = $value;
}
return $fields;
}
public function retrieve_subscribers_count() {
$existing_lists = $this->data['lists'];
if ( empty( $existing_lists ) ) {
return;
}
foreach( $existing_lists as $list_id => $list_data ) {
$params = array( $this->data[ self::$_data_keys['api_key'] ], 'Contact', array( 'Groups' => "%{$list_id}%" ) );
$data = self::$_->prepare_xmlrpc_method_call( 'DataService.count', $params );
$this->_do_request( $data );
if ( $this->response->ERROR ) {
continue;
}
$subscribers_count = self::$_->process_xmlrpc_response( $this->response->DATA );
if ( empty( $subscribers_count ) || self::$_->is_xmlrpc_error( $subscribers_count ) ) {
$subscribers_count = 0;
}
$existing_lists[ $list_id ]['subscribers_count'] = $subscribers_count;
$this->data['lists'] = $existing_lists;
$this->save_data();
}
}
/**
* @inheritDoc
*/
protected function _process_subscriber_lists( $lists ) {
$result = array();
if ( empty( $lists ) || ! is_array( $lists ) ) {
return $result;
}
foreach( $lists as $list ) {
$list_id = (string) $list->Id;
$list_name = (string) $list->GroupName;
$result[ $list_id ]['list_id'] = $list_id;
$result[ $list_id ]['name'] = $list_name;
$result[ $list_id ]['subscribers_count'] = 0;
}
return $result;
}
/**
* @inheritDoc
*/
public function get_account_fields() {
return array(
self::$_data_keys['api_key'] => array(
'label' => esc_html__( 'API Key', 'et_core' ),
),
self::$_data_keys['app_name'] => array(
'label' => esc_html__( 'App Name', 'et_core' ),
),
);
}
/**
* @inheritDoc
*/
public function get_data_keymap( $keymap = array() ) {
$keymap = array(
'subscriber' => array(
'name' => 'FirstName',
'last_name' => 'LastName',
'email' => 'Email',
'custom_fields' => 'custom_fields',
),
'custom_field' => array(
'field_id' => 'Name',
'name' => 'Label',
'type' => 'DataType',
'options' => 'Values',
),
'custom_field_type' => array(
// Us => Them
'input' => 15,
'textarea' => 16,
'checkbox' => 17,
'radio' => 20,
'select' => 21,
// Them => Us
15 => 'input',
16 => 'textarea',
17 => 'checkbox',
20 => 'radio',
21 => 'select',
),
);
return parent::get_data_keymap( $keymap );
}
/**
* @inheritDoc
*/
public function fetch_subscriber_lists() {
if ( empty( $this->data[ self::$_data_keys['api_key'] ] ) || empty( $this->data[ self::$_data_keys['app_name'] ] ) ) {
return $this->API_KEY_REQUIRED;
}
$this->response_data_key = false;
$params_count = array( $this->data[ self::$_data_keys['api_key'] ], 'ContactGroup', array( 'Id' => '%' ) );
$data_count = self::$_->prepare_xmlrpc_method_call( 'DataService.count', $params_count );
$this->_do_request( $data_count );
if ( $this->response->ERROR ) {
return $this->response->ERROR_MESSAGE;
}
$records_count = (int) self::$_->process_xmlrpc_response( $this->http->response->DATA );
if ( 0 === $records_count ) {
return 'success';
}
// determine how many requests we need to retrieve all lists
$number_of_additional_requests = floor( $records_count / 1000 );
$list_data = array();
for ( $i = 0; $i <= $number_of_additional_requests; $i++ ) {
$params = array( $this->data[ self::$_data_keys['api_key'] ], 'ContactGroup', 1000, $i, array( 'Id' => '%' ), array( 'Id', 'GroupName' ) );
$data = self::$_->prepare_xmlrpc_method_call( 'DataService.query', $params );
$this->_do_request( $data );
if ( $this->http->response->ERROR ) {
return $this->http->response->ERROR_MESSAGE;
}
$response = self::$_->process_xmlrpc_response( $this->http->response->DATA );
if ( self::$_->is_xmlrpc_error( $response ) ) {
return $response->faultString;
}
$list_data = array_merge( $list_data, $response );
}
$lists = $this->_process_subscriber_lists( $list_data );
if ( false === $lists ) {
return $this->response->ERROR_MESSAGE;
} else if ( self::$_->is_xmlrpc_error( $lists ) ) {
return $lists->faultString;
}
$result = 'success';
$this->data['lists'] = $lists;
$this->data['custom_fields'] = $this->_fetch_custom_fields();
$this->data['is_authorized'] = true;
// retrieve counts right away if it can be done in reasonable time ( in 20 seconds )
if ( 20 >= count( $lists ) ) {
$this->retrieve_subscribers_count();
} else {
// estimate the time for all lists update assuming that one list can be updated in 1 second
$estimated_time = ceil( count( $lists ) / 60 );
$result = array(
'need_counts_update' => true,
'message' => sprintf(
esc_html__( 'Successfully authorized. Subscribers count will be updated in background, please check back in %1$s %2$s', 'et_core' ),
$estimated_time,
1 === (int) $estimated_time ? esc_html__( 'minute', 'et_core' ) : esc_html__( 'minutes', 'et_core' )
),
);
}
$this->save_data();
return $result;
}
/**
* @inheritDoc
*/
public function subscribe( $args, $url = '' ) {
if ( empty( $this->data[ self::$_data_keys['api_key'] ] ) || empty( $this->data[ self::$_data_keys['app_name'] ] ) ) {
return $this->API_KEY_REQUIRED;
}
$message = '';
$search_result = $this->_get_contact_by_email( $args['email'] );
if ( false === $search_result ) {
return $this->response->ERROR_MESSAGE;
} else if ( self::$_->is_xmlrpc_error( $search_result ) ) {
return $search_result->faultString;
}
if ( ! empty( $search_result ) ) {
$message = esc_html__( 'Already subscribed', 'bloom' );
if ( false === strpos( $search_result[0]->Groups, $args['list_id'] ) ) {
$result = $this->_add_contact_to_list( $search_result[0]->Id, $args['list_id'] );
$message = 'success';
if ( false === $result ) {
return $this->response->ERROR_MESSAGE;
} else if ( self::$_->is_xmlrpc_error( $result ) ) {
return $result->faultString;
}
}
} else {
$custom_fields = $this->_process_custom_fields( $args );
$contact_details = array(
'FirstName' => $args['name'],
'LastName' => $args['last_name'],
'Email' => $args['email'],
);
$new_contact_id = $this->_create_contact( array_merge( $contact_details, $custom_fields ) );
if ( false === $new_contact_id ) {
return $this->response->ERROR_MESSAGE;
} else if ( self::$_->is_xmlrpc_error( $new_contact_id ) ) {
return $new_contact_id->faultString;
}
$result = $this->_add_contact_to_list( $new_contact_id, $args['list_id'] );
if ( false === $result ) {
return $this->response->ERROR_MESSAGE;
} else if ( self::$_->is_xmlrpc_error( $result ) ) {
return $search_result->faultString;
}
if ( $this->_optin_email_address( $args['email'] ) ) {
$message = 'success';
}
}
return ( '' !== $message ) ? $message : $this->FAILURE_MESSAGE;
}
}

View File

@ -0,0 +1,164 @@
<?php
/**
* Wrapper for MadMimi's API.
*
* @since 1.1.0
*
* @package ET\Core\API\Email
*/
class ET_Core_API_Email_MadMimi extends ET_Core_API_Email_Provider {
/**
* @inheritDoc
*/
public $BASE_URL = 'https://api.madmimi.com';
/**
* @inheritDoc
*/
public $LISTS_URL = 'https://api.madmimi.com/audience_lists/lists.json';
/**
* @inheritDoc
*/
public $SUBSCRIBE_URL = 'https://api.madmimi.com/audience_lists/@list_id@/add';
/**
* @inheritDoc
*/
public $custom_fields = 'dynamic';
/**
* @inheritDoc
*/
public $name = 'MadMimi';
/**
* @inheritDoc
*/
public $slug = 'madmimi';
public function __construct( $owner, $account_name, $api_key = '' ) {
parent::__construct( $owner, $account_name, $api_key );
$this->_maybe_set_urls();
}
protected function _maybe_set_urls( $list_id = '' ) {
if ( ! empty( $this->data['api_key'] ) && ! empty( $this->data['username'] ) ) {
$args = array(
'username' => rawurlencode( $this->data['username'] ),
'api_key' => $this->data['api_key'],
);
$this->LISTS_URL = add_query_arg( $args, $this->LISTS_URL );
$this->SUBSCRIBE_URL = add_query_arg( $args, $this->SUBSCRIBE_URL );
if ( $list_id ) {
$this->SUBSCRIBE_URL = str_replace( '@list_id@', rawurlencode( $list_id ), $this->SUBSCRIBE_URL );
}
}
}
protected function _process_custom_fields( $args ) {
if ( ! isset( $args['custom_fields'] ) ) {
return $args;
}
$fields = $args['custom_fields'];
unset( $args['custom_fields'] );
foreach ( $fields as $field_id => $value ) {
if ( is_array( $value ) && $value ) {
// This is a multiple choice field (eg. checkbox, radio, select)
$value = array_values( $value );
if ( count( $value ) > 1 ) {
$value = implode( ',', $value );
} else {
$value = array_pop( $value );
}
}
self::$_->array_set( $args, $field_id, $value );
}
return $args;
}
/**
* @inheritDoc
*/
public function get_account_fields() {
return array(
'username' => array(
'label' => esc_html__( 'Username', 'et_core' ),
),
'api_key' => array(
'label' => esc_html__( 'API Key', 'et_core' ),
),
);
}
/**
* @inheritDoc
*/
public function get_data_keymap( $keymap = array() ) {
$keymap = array(
'list' => array(
'list_id' => 'id',
'name' => 'name',
'subscribers_count' => 'list_size',
),
'subscriber' => array(
'name' => 'first_name',
'last_name' => 'last_name',
'email' => 'email',
'custom_fields' => 'custom_fields',
),
);
return parent::get_data_keymap( $keymap );
}
/**
* @inheritDoc
*/
public function fetch_subscriber_lists() {
if ( empty( $this->data['api_key'] ) || empty( $this->data['username'] ) ) {
return $this->API_KEY_REQUIRED;
}
$this->_maybe_set_urls();
$this->response_data_key = false;
return parent::fetch_subscriber_lists();
}
/**
* @inheritDoc
*/
public function subscribe( $args, $url = '' ) {
if ( empty( $this->data['api_key'] ) || empty( $this->data['username'] ) ) {
return $this->API_KEY_REQUIRED;
}
$this->_maybe_set_urls( $args['list_id'] );
$ip_address = 'true' === self::$_->array_get( $args, 'ip_address', 'true' ) ? et_core_get_ip_address() : '0.0.0.0';
$args = $this->transform_data_to_provider_format( $args, 'subscriber' );
$args = $this->_process_custom_fields( $args );
$args['ip_address'] = $ip_address;
$args['subscribed_via'] = $this->SUBSCRIBED_VIA;
$this->SUBSCRIBE_URL = add_query_arg( $args, $this->SUBSCRIBE_URL );
$this->prepare_request( $this->SUBSCRIBE_URL, 'POST', false );
return parent::subscribe( $args, $url );
}
}

View File

@ -0,0 +1,345 @@
<?php
/**
* Wrapper for MailChimp's API.
*
* @since 1.1.0
*
* @package ET\Core\API\Email
*/
class ET_Core_API_Email_MailChimp extends ET_Core_API_Email_Provider {
/**
* @inheritDoc
*/
public $BASE_URL = '';
/**
* Use this variable to hold the pattern and update $BASE_URL dynamically when needed
*/
public $BASE_URL_PATTERN = 'https://@datacenter@.api.mailchimp.com/3.0';
/**
* @inheritDoc
*/
public $http_auth = array(
'username' => '-',
'password' => 'api_key',
);
/**
* @inheritDoc
*/
public $name = 'MailChimp';
/**
* @inheritDoc
*/
public $slug = 'mailchimp';
public function __construct( $owner, $account_name, $api_key = '' ) {
parent::__construct( $owner, $account_name, $api_key );
if ( ! empty( $this->data['api_key'] ) ) {
$this->_set_base_url();
}
$this->http_auth['username'] = $owner;
}
protected function _add_note_to_subscriber( $email, $url ) {
$email = md5( $email );
$this->prepare_request( "{$url}/$email/notes", 'POST' );
$this->request->BODY = json_encode( array( 'note' => $this->SUBSCRIBED_VIA ) );
$this->make_remote_request();
}
protected function _fetch_custom_fields( $list_id = '', $list = array() ) {
$this->response_data_key = 'merge_fields';
$this->prepare_request( "{$this->BASE_URL}/lists/{$list_id}/merge-fields?count={$this->COUNT}" );
$fields = parent::_fetch_custom_fields( $list_id, $list );
foreach ( $fields as $id => $field ) {
if ( in_array( $id, array( 1, 2 ) ) ) {
unset( $fields[ $id ] );
}
}
// MailChimp is weird in that they treat checkbox fields as an entirely different concept in their API (Groups)
// We'll grab the groups and treat them as checkbox fields in our UI.
$groups = $this->_fetch_subscriber_list_groups( $list_id );
return $fields + $groups;
}
protected function _fetch_subscriber_list_group_options( $list_id, $group_id ) {
$this->prepare_request( "{$this->BASE_URL}/lists/{$list_id}/interest-categories/{$group_id}/interests?count={$this->COUNT}" );
$this->make_remote_request();
if ( $this->response->ERROR ) {
et_debug( $this->get_error_message() );
return array();
}
$data = $this->response->DATA['interests'];
$options = array();
foreach ( $data as $option ) {
$option = $this->transform_data_to_our_format( $option, 'group_option' );
$id = $option['id'];
$options[ $id ] = $option['name'];
}
return $options;
}
protected function _fetch_subscriber_list_groups( $list_id ) {
$this->response_data_key = 'categories';
$this->prepare_request( "{$this->BASE_URL}/lists/{$list_id}/interest-categories?count={$this->COUNT}" );
$this->make_remote_request();
$groups = array();
if ( false !== $this->response_data_key && empty( $this->response_data_key ) ) {
// Let child class handle parsing the response data themselves.
return $groups;
}
if ( $this->response->ERROR ) {
et_debug( $this->get_error_message() );
return $groups;
}
$data = $this->response->DATA[ $this->response_data_key ];
foreach ( $data as $group ) {
$group = $this->transform_data_to_our_format( $group, 'group' );
$field_id = $group['field_id'];
$type = $group['type'];
if ( 'hidden' === $type ) {
// MailChimp only allows groups of type: 'checkbox' to be hidden.
$group['type'] = 'checkbox';
$group['hidden'] = true;
}
$group['is_group'] = true;
$group['options'] = $this->_fetch_subscriber_list_group_options( $list_id, $field_id );
$group['type'] = self::$_->array_get( $this->data_keys, "custom_field_type.{$type}", 'text' );
$groups[ $field_id ] = $group;
}
return $groups;
}
protected function _process_custom_fields( $args ) {
if ( ! isset( $args['custom_fields'] ) ) {
return $args;
}
$fields = $args['custom_fields'];
$list_id = self::$_->array_get( $args, 'list_id', '' );
unset( $args['custom_fields'] );
unset( $args['list_id'] );
$custom_fields_data = self::$_->array_get( $this->data, "lists.{$list_id}.custom_fields", array() );
foreach ( $fields as $field_id => $value ) {
$is_group = self::$_->array_get( $custom_fields_data, "{$field_id}.is_group", false );
if ( is_array( $value ) && $value ) {
foreach ( $value as $id => $field_value ) {
if ( $is_group ) {
// If it is a group custom field, set as `interests` and don't process the `merge_fields`
self::$_->array_set( $args, "interests.{$id}", true );
$field_id = false;
} else {
$value = $field_value;
}
}
}
if ( false === $field_id ) {
continue;
}
// In previous version of Mailchimp implementation we only supported default field tag, but it can be customized and our code fails.
// Added `field_tag` attribute which is actual field tag. Fallback to default field tag if `field_tag` doesn't exist for backward compatibility.
$custom_field_tag = self::$_->array_get( $custom_fields_data, "{$field_id}.field_tag", "MMERGE{$field_id}" );
// Need to strips existing slash chars.
self::$_->array_set( $args, "merge_fields.{$custom_field_tag}", stripslashes( $value ) );
}
return $args;
}
protected function _set_base_url() {
$api_key_pieces = explode( '-', $this->data['api_key'] );
$datacenter = empty( $api_key_pieces[1] ) ? '' : $api_key_pieces[1];
$this->BASE_URL = str_replace( '@datacenter@', $datacenter, $this->BASE_URL_PATTERN );
}
/**
* @inheritDoc
*/
public function fetch_subscriber_lists() {
if ( empty( $this->data['api_key'] ) ) {
return $this->API_KEY_REQUIRED;
}
$this->_set_base_url();
/**
* The maximum number of subscriber lists to request from MailChimp's API.
*
* @since 2.0.0
*
* @param int $max_lists
*/
$max_lists = (int) apply_filters( 'et_core_api_email_mailchimp_max_lists', 250 );
$url = "{$this->BASE_URL}/lists?count={$max_lists}&fields=lists.name,lists.id,lists.stats,lists.double_optin";
$this->prepare_request( $url );
$this->response_data_key = 'lists';
$result = parent::fetch_subscriber_lists();
return $result;
}
/**
* @inheritDoc
*/
public function get_account_fields() {
return array(
'api_key' => array(
'label' => esc_html__( 'API Key', 'et_core' ),
),
);
}
/**
* @inheritDoc
*/
public function get_data_keymap( $keymap = array() ) {
$keymap = array(
'list' => array(
'list_id' => 'id',
'name' => 'name',
'double_optin' => 'double_optin',
'subscribers_count' => 'stats.member_count',
),
'subscriber' => array(
'email' => 'email_address',
'name' => 'merge_fields.FNAME',
'last_name' => 'merge_fields.LNAME',
'custom_fields' => 'custom_fields',
),
'error' => array(
'error_message' => 'detail',
),
'custom_field' => array(
'field_id' => 'merge_id',
'field_tag' => 'tag',
'name' => 'name',
'type' => 'type',
'hidden' => '!public',
'options' => 'options.choices',
),
'custom_field_type' => array(
// Us <=> Them
'radio' => 'radio',
// Us => Them
'input' => 'text',
'select' => 'dropdown',
'checkbox' => 'checkboxes',
// Them => Us
'text' => 'input',
'dropdown' => 'select',
'checkboxes' => 'checkbox',
),
'group' => array(
'field_id' => 'id',
'name' => 'title',
'type' => 'type',
),
'group_option' => array(
'id' => 'id',
'name' => 'name',
),
);
return parent::get_data_keymap( $keymap );
}
public function get_subscriber( $list_id, $email ) {
$hash = md5( strtolower( $email ) );
$this->prepare_request( implode( '/', array( $this->BASE_URL, 'lists', $list_id, 'members', $hash ) ) );
$this->make_remote_request();
return $this->response->STATUS_CODE !== 200 ? null : $this->response->DATA;
}
/**
* @inheritDoc
*/
public function subscribe( $args, $url = '' ) {
$list_id = $args['list_id'];
$args = $this->transform_data_to_provider_format( $args, 'subscriber' );
$url = "{$this->BASE_URL}/lists/{$list_id}/members";
$email = $args['email_address'];
$err = esc_html__( 'An error occurred, please try later.', 'et_core' );
$dbl_optin = self::$_->array_get( $this->data, "lists.{$list_id}.double_optin", true );
$ip_address = 'true' === self::$_->array_get( $args, 'ip_address', 'true' ) ? et_core_get_ip_address() : '0.0.0.0';
$args['ip_signup'] = $ip_address;
$args['status'] = $dbl_optin ? 'pending' : 'subscribed';
$args['list_id'] = $list_id;
$args = $this->_process_custom_fields( $args );
$this->prepare_request( $url, 'POST', false, $args, true );
$result = parent::subscribe( $args, $url );
if ( false !== stripos( $result, 'already a list member' ) ) {
$result = $err;
if ( $user = $this->get_subscriber( $list_id, $email ) ) {
if ( 'subscribed' === $user['status'] ) {
$result = 'success';
} else {
$this->prepare_request( implode( '/', array( $url, $user['id'] ) ), 'PUT', false, $args, true );
$result = parent::subscribe( $args, $url );
}
}
}
if ( 'success' === $result ) {
$this->_add_note_to_subscriber( $email, $url );
} else if ( false !== stripos( $result, 'has signed up to a lot of lists ' ) ) {
// return message which can be translated. Generic Mailchimp messages are not translatable.
$result = esc_html__( 'You have signed up to a lot of lists very recently, please try again later', 'et_core' );
} else {
$result = $err;
}
return $result;
}
}

View File

@ -0,0 +1,112 @@
<?php
/**
* Wrapper for MailPoet's API.
*
* @since 3.0.76
*
* @package ET\Core\API\Email
*/
class ET_Core_API_Email_MailPoet extends ET_Core_API_Email_Provider {
/**
* @var ET_Core_API_Email_Provider
*/
private $_MP;
public static $PLUGIN_REQUIRED;
/**
* @inheritDoc
*/
public $custom_fields_scope = 'account';
/**
* @inheritDoc
*/
public $name = 'MailPoet';
/**
* @inheritDoc
*/
public $slug = 'mailpoet';
public function __construct( $owner = '', $account_name = '', $api_key = '' ) {
parent::__construct( $owner, $account_name, $api_key );
if ( null === self::$PLUGIN_REQUIRED ) {
self::$PLUGIN_REQUIRED = esc_html__( 'MailPoet plugin is either not installed or not activated.', 'et_core' );
}
$has_php53 = version_compare( PHP_VERSION, '5.3', '>=' );
if ( $has_php53 && class_exists( '\MailPoet\API\API' ) ) {
require_once( ET_CORE_PATH . 'components/api/email/_MailPoet3.php' );
$this->_init_provider_class( '3', $owner, $account_name, $api_key );
} else if ( class_exists( 'WYSIJA' ) ) {
require_once( ET_CORE_PATH . 'components/api/email/_MailPoet2.php' );
$this->_init_provider_class( '2', $owner, $account_name, $api_key );
}
}
/**
* Initiate provider class based on the version number.
*
* @param string $version Version number.
* @param string $owner Owner.
* @param string $account_name Account name.
* @param string $api_key API key.
*/
protected function _init_provider_class( $version, $owner, $account_name, $api_key ) {
if ( '3' === $version ) {
$this->_MP = new ET_Core_API_Email_MailPoet3( $owner, $account_name, $api_key );
} else {
$this->_MP = new ET_Core_API_Email_MailPoet2( $owner, $account_name, $api_key );
$this->custom_fields = false;
}
}
/**
* @inheritDoc
*/
public function get_account_fields() {
return array();
}
/**
* @inheritDoc
*/
public function get_data_keymap( $keymap = array() ) {
if ( $this->_MP ) {
return $this->_MP->get_data_keymap( $keymap );
}
return parent::get_data_keymap( $keymap );
}
/**
* @inheritDoc
*/
public function fetch_subscriber_lists() {
$lists_data = $this->_MP ? $this->_MP->fetch_subscriber_lists() : self::$PLUGIN_REQUIRED;
// Update data in Main MailPoet class, so correct lists data can be accessed
if ( isset( $lists_data['success'] ) ) {
$this->data = $lists_data['success'];
$this->save_data();
return 'success';
}
return $lists_data;
}
/**
* @inheritDoc
*/
public function subscribe( $args, $url = '' ) {
return $this->_MP ? $this->_MP->subscribe( $args, $url ) : self::$PLUGIN_REQUIRED;
}
}

View File

@ -0,0 +1,151 @@
<?php
/**
* Wrapper for MailerLite's API.
*
* @since 1.1.0
*
* @package ET\Core\API\Email
*/
class ET_Core_API_Email_MailerLite extends ET_Core_API_Email_Provider {
/**
* @inheritDoc
*/
public $BASE_URL = 'https://api.mailerlite.com/api/v2';
/**
* @inheritDoc
*/
public $FIELDS_URL = 'https://api.mailerlite.com/api/v2/fields';
/**
* @inheritDoc
*/
public $LISTS_URL = 'https://api.mailerlite.com/api/v2/groups';
/**
* @inheritDoc
*/
public $custom_fields_scope = 'account';
/**
* @inheritDoc
*/
public $name = 'MailerLite';
/**
* @inheritDoc
*/
public $slug = 'mailerlite';
/**
* @inheritDoc
* @internal If true, oauth endpoints properties must also be defined.
*/
public $uses_oauth = false;
public function __construct( $owner = '', $account_name = '', $api_key = '' ) {
parent::__construct( $owner, $account_name, $api_key );
$this->_maybe_set_custom_headers();
}
protected function _maybe_set_custom_headers() {
if ( empty( $this->custom_headers ) && isset( $this->data['api_key'] ) ) {
$this->custom_headers = array( 'X-MailerLite-ApiKey' => "{$this->data['api_key']}" );
}
}
protected function _process_custom_fields( $args ) {
if ( ! isset( $args['custom_fields'] ) ) {
return $args;
}
$fields = $args['custom_fields'];
unset( $args['custom_fields'] );
foreach ( $fields as $field_id => $value ) {
if ( is_array( $value ) && $value ) {
// This is a multiple choice field (eg. checkbox, radio, select)
$value = array_values( $value );
if ( count( $value ) > 1 ) {
$value = implode( ',', $value );
} else {
$value = array_pop( $value );
}
}
self::$_->array_set( $args, "fields.{$field_id}", $value );
}
return $args;
}
/**
* @inheritDoc
*/
public function get_account_fields() {
return array(
'api_key' => array(
'label' => esc_html__( 'API Key', 'et_core' ),
),
);
}
/**
* @inheritDoc
*/
public function get_data_keymap( $keymap = array() ) {
$keymap = array(
'list' => array(
'list_id' => 'id',
'name' => 'name',
'subscribers_count' => 'active',
),
'subscriber' => array(
'name' => 'fields.name',
'last_name' => 'fields.last_name',
'email' => 'email',
'custom_fields' => 'custom_fields',
'resubscribe' => 'resubscribe',
),
'error' => array(
'error_message' => 'error.message',
),
'custom_field' => array(
'field_id' => 'key',
'name' => 'title',
),
);
return parent::get_data_keymap( $keymap );
}
/**
* @inheritDoc
*/
public function fetch_subscriber_lists() {
if ( empty( $this->data['api_key'] ) ) {
return $this->API_KEY_REQUIRED;
}
$this->_maybe_set_custom_headers();
$this->response_data_key = false;
return parent::fetch_subscriber_lists();
}
/**
* @inheritDoc
*/
public function subscribe( $args, $url = '' ) {
$args['resubscribe'] = 1;
$url = "{$this->LISTS_URL}/{$args['list_id']}/subscribers";
return parent::subscribe( $args, $url );
}
}

View File

@ -0,0 +1,211 @@
<?php
/**
* Wrapper for integration with Mailster plugin.
*
* @license
* Copyright © 2017 Elegant Themes, Inc.
* Copyright © 2017 Xaver Birsak
*
* @since 1.1.0
* @package ET\Core\API\Email
*/
class ET_Core_API_Email_Mailster extends ET_Core_API_Email_Provider {
/**
* @inheritDoc
*/
public $name = 'Mailster';
/**
* @inheritDoc
*/
public $slug = 'mailster';
/**
* @inheritDoc
*/
public $uses_oauth = false;
/**
* Creates a referrer string that includes the name of the opt-in used to subscribe.
*
* @param array $args The args array that was passed to {@link self::subscribe()}
*
* @return string
*/
protected function _get_referrer( $args ) {
$optin_name = '';
$owner = ucfirst( $this->owner );
if ( 'bloom' === $this->owner && isset( $args['optin_id'] ) ) {
$optin_form = ET_Bloom::get_this()->dashboard_options[ $args['optin_id'] ];
$optin_name = $optin_form['optin_name'];
}
return sprintf( '%1$s %2$s "%3$s" on %4$s',
esc_html( $owner ),
esc_html__( 'Opt-in', 'et_core' ),
esc_html( $optin_name ),
wp_get_referer()
);
}
protected function _fetch_custom_fields( $list_id = '', $list = array() ) {
static $fields = null;
if ( is_null( $fields ) ) {
$customfields = mailster()->get_custom_fields();
if ( empty( $customfields ) ) {
return $fields;
}
$field_types = self::$_->array_get( $this->data_keys, 'custom_field_type' );
foreach ( $customfields as $field_id => $field ) {
$field = $this->transform_data_to_our_format( $field, 'custom_field' );
$type = self::$_->array_get( $field, 'type', 'any' );
$field['field_id'] = $field_id;
if ( $field_types && ! isset( $field_types[ $type ] ) ) {
// Unsupported field type. Make it 'text' instead.
$type = 'textfield';
}
$field['type'] = self::$_->array_get( $this->data_keys, "custom_field_type.{$type}", 'input' );
$fields[ $field_id ] = $field;
}
}
return $fields;
}
protected function _process_custom_fields( $args ) {
if ( ! isset( $args['custom_fields'] ) ) {
return $args;
}
$fields = $args['custom_fields'];
unset( $args['custom_fields'] );
$registered_custom_fields = self::$_->array_get( $this->data, 'custom_fields', array() );
foreach ( $fields as $field_id => $value ) {
$field_type = isset( $registered_custom_fields[ $field_id ] ) && isset( $registered_custom_fields[ $field_id ]['type'] ) ? $registered_custom_fields[ $field_id ]['type'] : false;
// Mailster doesn't support multiple checkboxes and if it appears here that means the checkbox is checked
if ( 'checkbox' === $field_type ) {
$value = 1;
}
if ( is_array( $value ) && $value ) {
// This is a multiple choice field (eg. radio, select)
$value = array_values( $value );
if ( count( $value ) > 1 ) {
$value = implode( ',', $value );
} else {
$value = array_pop( $value );
}
}
self::$_->array_set( $args, $field_id, $value );
}
return $args;
}
/**
* @inheritDoc
*/
public function get_account_fields() {
return array();
}
/**
* @inheritDoc
*/
public function get_data_keymap( $keymap = array() ) {
$keymap = array(
'list' => array(
'list_id' => 'ID',
'name' => 'name',
'subscribers_count' => 'subscribers',
),
'subscriber' => array(
'dbl_optin' => 'status',
'email' => 'email',
'last_name' => 'lastname',
'name' => 'firstname',
'custom_fields' => 'custom_fields',
),
'custom_field' => array(
'name' => 'name',
'type' => 'type',
'options' => 'values',
'hidden' => 'hidden',
),
'custom_field_type' => array(
// Us => Them
'textarea' => 'textarea',
'radio' => 'radio',
'checkbox' => 'checkbox',
'input' => 'textfield',
'select' => 'dropdown',
// Them => Us
'textfield' => 'input',
'dropdown' => 'select',
),
);
return parent::get_data_keymap( $keymap );
}
public function fetch_subscriber_lists() {
if ( ! function_exists( 'mailster' ) ) {
return esc_html__( 'Mailster Newsletter Plugin is not enabled!', 'et_core' );
}
$lists = mailster( 'lists' )->get( null, null, true );
$error_message = esc_html__( 'No lists were found. Please create a Mailster list first!', 'et_core' );
if ( $lists ) {
$error_message = 'success';
$this->data['lists'] = $this->_process_subscriber_lists( $lists );
$this->data['is_authorized'] = true;
$this->data['custom_fields'] = $this->_fetch_custom_fields();
$this->save_data();
}
return $error_message;
}
/**
* @inheritDoc
*/
public function subscribe( $args, $url = '' ) {
$error = esc_html__( 'An error occurred. Please try again later.', 'et_core' );
if ( ! function_exists( 'mailster' ) ) {
return $error;
}
$params = $this->transform_data_to_provider_format( $args, 'subscriber', array( 'dbl_optin' ) );
$params = $this->_process_custom_fields( $params );
$extra_params = array(
'status' => 'disable' === $args['dbl_optin'] ? 1 : 0,
'referrer' => $this->_get_referrer( $args ),
);
$params = array_merge( $params, $extra_params );
$subscriber_id = mailster( 'subscribers' )->merge( $params );
if ( is_wp_error( $subscriber_id ) ) {
$result = htmlspecialchars_decode( $subscriber_id->get_error_message() );
} else if ( mailster( 'subscribers' )->assign_lists( $subscriber_id, $args['list_id'], false ) ) {
$result = 'success';
} else {
$result = $error;
}
return $result;
}
}

View File

@ -0,0 +1,311 @@
<?php
/**
* Wrapper for Ontraport's API.
*
* @since 1.1.0
*
* @package ET\Core\API\Email
*/
class ET_Core_API_Email_Ontraport extends ET_Core_API_Email_Provider {
/**
* @inheritDoc
*/
public $FIELDS_URL = 'https://api.ontraport.com/1/Contacts/meta';
/**
* @inheritDoc
*/
public $LISTS_URL = 'https://api.ontraport.com/1/objects?objectID=5';
/**
* @inheritDoc
*/
public $SUBSCRIBE_URL = 'https://api.ontraport.com/1';
/**
* @inheritDoc
*/
public $custom_fields_scope = 'account';
/**
* @inheritDoc
*/
public $name = 'Ontraport';
/**
* @inheritDoc
*/
public $slug = 'ontraport';
/**
* @inheritDoc
* @internal If true, oauth endpoints properties must also be defined.
*/
public $uses_oauth = false;
public function __construct( $owner = '', $account_name = '', $api_key = '' ) {
parent::__construct( $owner, $account_name, $api_key );
$this->_maybe_set_custom_headers();
}
protected function _fetch_custom_fields( $list_id = '', $list = array() ) {
static $fields = null;
if ( is_null( $fields ) ) {
$this->response_data_key = null;
parent::_fetch_custom_fields( $list_id, $list );
if ( $this->response->ERROR ) {
et_debug( $this->get_error_message() );
return array();
}
$fields = array();
$fields_unprocessed = self::$_->array_get( $this->response->DATA, 'data.[0].fields', $fields );
foreach ( $fields_unprocessed as $field_id => $field ) {
if ( in_array( $field_id, array( 'firstname', 'lastname', 'email' ) ) ) {
continue;
}
$type = $field['type'];
$field['field_id'] = $field_id;
$field['type'] = self::$_->array_get( $this->data_keys, "custom_field_type.{$type}", 'text' );
$fields[ $field_id ] = $this->transform_data_to_our_format( $field, 'custom_field' );
}
}
return $fields;
}
protected function _get_subscriber_list_type( $list_id ) {
$sequence_key = 'seq:' . $list_id;
$campaign_key = 'camp:' . $list_id;
if ( isset( $this->data['lists'][ $campaign_key ] ) ) {
return 'Campaign';
}
if ( isset( $this->data['lists'][ $sequence_key ] ) ) {
return 'Sequence';
}
return 'Campaign';
}
protected function _maybe_set_custom_headers() {
if ( empty( $this->custom_headers ) && isset( $this->data['api_key'] ) && isset( $this->data['client_id'] ) ) {
$this->custom_headers = array(
'Api-Appid' => sanitize_text_field( $this->data['client_id'] ),
'Api-Key' => sanitize_text_field( $this->data['api_key'] ),
);
}
}
protected function _prefix_subscriber_lists( $name_prefix, $id_prefix ) {
$lists = array();
foreach ( $this->data['lists'] as $list_id => $list ) {
$key = $id_prefix . $list_id;
if ( ! $list['name'] ) {
$list['name'] = $list_id;
}
$lists[ $key ] = $list;
$lists[ $key ]['name'] = $name_prefix . $list['name'];
$lists[ $key ]['list_id'] = $key;
}
$this->data['lists'] = $lists;
$this->save_data();
}
protected function _process_custom_fields( $args ) {
if ( ! isset( $args['custom_fields'] ) ) {
return $args;
}
$fields = $args['custom_fields'];
unset( $args['custom_fields'] );
foreach ( $fields as $field_id => $value ) {
if ( is_array( $value ) && $value ) {
// This is a multiple choice field (eg. checkbox, radio, select)
$value = array_keys( $value );
if ( 'checkbox' === $this->data['custom_fields'][ $field_id ]['type'] ) {
// Determine if checkbox is a single checkbox or a list.
// In case of single checkbox pass `1` as a value
if ( ! empty( $this->data['custom_fields'][ $field_id ]['options'] ) ) {
$value = implode( '*/*', $value );
$value = "*/*{$value}*/*";
} else {
$value = '1';
}
} else {
$value = array_pop( $value );
}
}
self::$_->array_set( $args, $field_id, $value );
}
return $args;
}
/**
* @inheritDoc
*/
public function get_account_fields() {
return array(
'api_key' => array(
'label' => esc_html__( 'API Key', 'et_core' ),
),
'client_id' => array(
'label' => esc_html__( 'APP ID', 'et_core' ),
),
);
}
/**
* @inheritDoc
*/
public function get_data_keymap( $keymap = array() ) {
$keymap = array(
'list' => array(
'name' => 'name',
'list_id' => 'drip_id',
'subscribers_count' => 'subscriber_count',
),
'subscriber' => array(
'name' => 'firstname',
'last_name' => 'lastname',
'email' => 'email',
'custom_fields' => 'custom_fields',
),
'custom_field' => array(
'field_id' => 'field_id',
'type' => 'type',
'name' => 'alias',
'options' => 'options',
),
'custom_field_type' => array(
// Us => Them
'input' => 'text',
'textarea' => 'textlong',
'checkbox' => 'list',
'select' => 'drop',
// Them => Us
'text' => 'input',
'textlong' => 'textarea',
'list' => 'checkbox',
'check' => 'checkbox',
'drop' => 'select',
),
);
return parent::get_data_keymap( $keymap );
}
/**
* @inheritDoc
*/
public function fetch_subscriber_lists() {
if ( empty( $this->data['api_key'] ) || empty( $this->data['client_id'] ) ) {
return $this->API_KEY_REQUIRED;
}
$this->_maybe_set_custom_headers();
$this->response_data_key = 'data';
parent::fetch_subscriber_lists();
$this->_prefix_subscriber_lists( 'Sequence: ', 'seq:' );
$sequences = $this->data['lists'];
$url = 'https://api.ontraport.com/1/CampaignBuilderItems';
$url = add_query_arg( 'listFields', 'id,name,subs', $url );
$this->data_keys['list']['list_id'] = 'id';
$this->data_keys['list']['subscribers_count'] = 'subs';
$this->prepare_request( $url );
$this->response_data_key = 'data';
$result = parent::fetch_subscriber_lists();
$this->_prefix_subscriber_lists( 'Campaign: ', 'camp:' );
$this->data['lists'] = array_merge( $this->data['lists'], $sequences );
$this->save_data();
return $result;
}
public function get_subscriber( $email ) {
$args = array(
'objectID' => '0',
'email' => rawurlencode( $email ),
);
$url = add_query_arg( $args, $this->SUBSCRIBE_URL . '/object/getByEmail' );
$this->prepare_request( $url );
$this->make_remote_request();
return self::$_->array_get( $this->response->DATA, 'data.id', false );
}
/**
* @inheritDoc
*/
public function subscribe( $args, $url = '' ) {
if ( empty( $this->data['api_key'] ) || empty( $this->data['client_id'] ) ) {
return $this->API_KEY_REQUIRED;
}
$list_id = $args['list_id'];
$args = $this->transform_data_to_provider_format( $args, 'subscriber' );
$args = $this->_process_custom_fields( $args );
$args['objectID'] = 0;
$url = $this->SUBSCRIBE_URL . '/Contacts/saveorupdate';
// Create or update contact
$this->prepare_request( $url, 'POST', false, $args );
$this->make_remote_request();
if ( $this->response->ERROR ) {
return $this->get_error_message();
}
$list_id_parts = explode( ':', $list_id );
$list_id = array_pop( $list_id_parts );
$data = $this->response->DATA['data'];
// Subscribe contact to list
$url = $this->SUBSCRIBE_URL . '/objects/subscribe';
$args = array(
'ids' => self::$_->array_get( $data, 'id', $data['attrs']['id'] ),
'add_list' => $list_id,
'sub_type' => $this->_get_subscriber_list_type( $list_id ),
'objectID' => 0,
);
$this->prepare_request( $url, 'PUT', false, $args );
return parent::subscribe( $args, $url );
}
}

View File

@ -0,0 +1,426 @@
<?php
/**
* High-level wrapper for interacting with the external API's offered by 3rd-party mailing list providers.
*
* @since 1.1.0
*
* @package ET\Core\API
*/
abstract class ET_Core_API_Email_Provider extends ET_Core_API_Service {
/**
* The URL from which custom fields for a list on this account can be retrieved.
*
* @var string
*/
public $FIELDS_URL;
/**
* The URL from which groups/tags for a list on this account can be retrieved.
*
* @var string
*/
public $GROUPS_URL;
/**
* The number of records to return from API (per request).
*
* @var int
*/
public $COUNT;
/**
* The URL from which subscriber lists for this account can be retrieved.
*
* @var string
*/
public $LISTS_URL;
/**
* The URL to which new subscribers can be posted.
*
* @var string
*/
public $SUBSCRIBE_URL;
/**
* The URL from which subscribers for this account can be retrieved.
*
* @var string
*/
public $SUBSCRIBERS_URL;
/**
* "Subscribed via..." translated string.
*
* @var string
*/
public $SUBSCRIBED_VIA;
/**
* Type of support for custom fields offered by provider.
*
* @since 3.17.2
*
* @var bool|string Accepts `dynamic`, `predefined`, `false`. Default `predefined`.
*/
public $custom_fields = 'predefined';
/**
* Type of support for custom fields offered by provider.
*
* @since 3.17.2
*
* @var string Accepts `list`, `account`.
*/
public $custom_fields_scope = 'list';
/**
* Whether or not only a single name field is supported instead of first/last name fields.
*
* @var string
*/
public $name_field_only = false;
/**
* ET_Core_API_Email_Provider constructor.
*
* @inheritDoc
*/
public function __construct( $owner = '', $account_name = '', $api_key = '' ) {
$this->service_type = 'email';
parent::__construct( $owner, $account_name, $api_key );
if ( 'builder' === $this->owner ) {
$owner = 'Divi Builder';
} else {
$owner = ucfirst( $this->owner );
}
$this->SUBSCRIBED_VIA = sprintf( '%1$s %2$s.', esc_html__( 'Subscribed via', 'et_core' ), $owner );
/**
* Filters the max number of results returned from email API provider per request.
*
* @since 3.17.2
*
* @param int $max_results_count
*/
$this->COUNT = apply_filters( 'et_core_api_email_max_results_count', 250 );
}
/**
* Get custom fields for a subscriber list.
*
* @param int|string $list_id
* @param array $list
*
* @return array
*/
protected function _fetch_custom_fields( $list_id = '', $list = array() ) {
if ( 'dynamic' === $this->custom_fields ) {
return array();
}
if ( null === $this->request || $this->request->COMPLETE ) {
$this->prepare_request( $this->FIELDS_URL );
}
$this->make_remote_request();
$result = array();
if ( false !== $this->response_data_key && empty( $this->response_data_key ) ) {
// Let child class handle parsing the response data themselves.
return $result;
}
if ( $this->response->ERROR ) {
et_debug( $this->get_error_message() );
return $result;
}
if ( false === $this->response_data_key ) {
// The data returned by the service is not nested.
$data = $this->response->DATA;
} else {
// The data returned by the service is nested under a single key.
$data = $this->response->DATA[ $this->response_data_key ];
}
foreach ( $data as &$custom_field ) {
$custom_field = $this->transform_data_to_our_format( $custom_field, 'custom_field' );
}
$fields = array();
$field_types = self::$_->array_get( $this->data_keys, 'custom_field_type' );
foreach ( $data as $field ) {
$field_id = $field['field_id'];
$type = self::$_->array_get( $field, 'type', 'any' );
if ( $field_types && ! isset( $field_types[ $type ] ) ) {
// Unsupported field type. Make it 'text' instead.
$type = 'text';
}
if ( isset( $field['hidden'] ) && is_string( $field['hidden'] ) ) {
$field['hidden'] = 'false' === $field['hidden'] ? false : true;
}
$field['type'] = self::$_->array_get( $this->data_keys, "custom_field_type.{$type}", 'any' );
$fields[ $field_id ] = $field;
}
return $fields;
}
/**
* @inheritDoc
*/
protected function _get_data() {
$options = parent::_get_data();
// return empty array in case of empty name
if ( '' === $this->account_name || ! is_string( $this->account_name ) ) {
return array();
}
$provider = sanitize_text_field( $this->slug );
$account = sanitize_text_field( $this->account_name );
if ( ! isset( $options['accounts'][ $provider ][ $account ] ) ) {
$options['accounts'][ $provider ][ $account ] = array();
update_option( "et_core_api_email_options", $options );
}
return $options['accounts'][ $provider ][ $account ];
}
protected function _process_custom_fields( $args ) {
return $args;
}
/**
* Processes subscriber lists data from the provider's API and returns only the data we're interested in.
*
* @since 1.1.0
*
* @param array $lists Subscriber lists data to process.
*
* @return array
*/
protected function _process_subscriber_lists( $lists ) {
$id_key = $this->data_keys['list']['list_id'];
$result = array();
foreach ( (array) $lists as $list ) {
if ( ! is_array( $list ) ) {
$list = (array) $list;
}
if ( ! isset( $list[ $id_key ] ) ) {
continue;
}
$id = $list[ $id_key ];
$result[ $id ] = $this->transform_data_to_our_format( $list, 'list' );
if ( ! array_key_exists( 'subscribers_count', $result[ $id ] ) ) {
$result[ $id ]['subscribers_count'] = 0;
}
$get_custom_fields = $this->custom_fields && 'list' === $this->custom_fields_scope;
if ( $get_custom_fields && $custom_fields = $this->_fetch_custom_fields( $id, $list ) ) {
$result[ $id ]['custom_fields'] = $custom_fields;
}
}
return $result;
}
/**
* Returns whether or not an account exists in the database.
*
* @param string $provider
* @param string $account_name
*
* @return bool
*/
public static function account_exists( $provider, $account_name ) {
$all_accounts = self::get_accounts();
return isset( $all_accounts[ $provider ][ $account_name ] );
}
/**
* @inheritDoc
*/
public function delete() {
self::remove_account( $this->slug, $this->account_name );
$this->account_name = '';
$this->_get_data();
}
/**
* Retrieves the email accounts data from the database.
*
* @return array
*/
public static function get_accounts() {
$options = (array) get_option( 'et_core_api_email_options' );
return isset( $options['accounts'] ) ? $options['accounts'] : array();
}
/**
* @inheritDoc
*/
public function get_data_keymap( $keymap = array() ) {
return $keymap;
}
/**
* Retrieves the subscriber lists for the account assigned to the current instance.
*
* @return string 'success' if successful, an error message otherwise.
*/
public function fetch_subscriber_lists() {
if ( null === $this->request || $this->request->COMPLETE ) {
$this->prepare_request( $this->LISTS_URL );
}
$this->make_remote_request();
$result = 'success';
if ( false !== $this->response_data_key && empty( $this->response_data_key ) ) {
// Let child class handle parsing the response data themselves.
return '';
}
if ( $this->response->ERROR ) {
return $this->get_error_message();
}
if ( false === $this->response_data_key ) {
// The data returned by the service is not nested.
$data = $this->response->DATA;
} else {
// The data returned by the service is nested under a single key.
$data = $this->response->DATA[ $this->response_data_key ];
}
if ( ! empty( $data ) ) {
$this->data['lists'] = $this->_process_subscriber_lists( $data );
$this->data['is_authorized'] = true;
$list = is_array( $data ) ? array_shift( $data ) : array();
if ( $this->custom_fields && 'account' === $this->custom_fields_scope ) {
$this->data['custom_fields'] = $this->_fetch_custom_fields( '', $list );
}
$this->save_data();
}
return $result;
}
/**
* Remove an account
*
* @param $provider
* @param $account_name
*/
public static function remove_account( $provider, $account_name ) {
$options = (array) get_option( 'et_core_api_email_options' );
unset( $options['accounts'][ $provider ][ $account_name ] );
update_option( 'et_core_api_email_options', $options );
}
/**
* @inheritDoc
*/
public function save_data() {
self::update_account( $this->slug, $this->account_name, $this->data );
}
/**
* @inheritDoc
*/
public function set_account_name( $name ) {
$this->account_name = $name;
$this->data = $this->_get_data();
}
/**
* Makes an HTTP POST request to add a subscriber to a list.
*
* @param string[] $args Data for the POST request.
* @param string $url The URL for the POST request. Optional when called on child classes.
*
* @return string 'success' if successful, an error message otherwise.
*/
public function subscribe( $args, $url = '' ) {
if ( null === $this->request || $this->request->COMPLETE ) {
if ( ! in_array( 'ip_address', $args ) || 'true' === $args['ip_address'] ) {
$args['ip_address'] = et_core_get_ip_address();
} else if ( 'false' === $args['ip_address'] ) {
$args['ip_address'] = '0.0.0.0';
}
$args = $this->transform_data_to_provider_format( $args, 'subscriber' );
if ( $this->custom_fields ) {
$args = $this->_process_custom_fields( $args );
}
$this->prepare_request( $url, 'POST', false, $args );
} else if ( $this->request->JSON_BODY && ! is_string( $this->request->BODY ) && ! $this->uses_oauth ) {
$this->request->BODY = json_encode( $this->request->BODY );
} else if ( is_array( $this->request->BODY ) ) {
$this->request->BODY = array_merge( $this->request->BODY, $args );
} else if ( ! $this->request->JSON_BODY ) {
$this->request->BODY = $args;
}
$this->make_remote_request();
return $this->response->ERROR ? $this->get_error_message() : 'success';
}
/**
* Updates the data for a provider account.
*
* @param string $provider The provider's slug.
* @param string $account The account name.
* @param array $data The new data for the account.
*/
public static function update_account( $provider, $account, $data ) {
$options = (array) get_option( 'et_core_api_email_options' );
$existing_data = array();
if ( empty( $account ) || empty( $provider ) ) {
return;
}
$provider = sanitize_text_field( $provider );
$account = sanitize_text_field( $account );
if ( isset( $options['accounts'][ $provider ][ $account ] ) ) {
$existing_data = $options['accounts'][ $provider ][ $account ];
}
$options['accounts'][ $provider ][ $account ] = array_merge( $existing_data, $data );
update_option( 'et_core_api_email_options', $options );
}
}

View File

@ -0,0 +1,300 @@
<?php
/**
* Manages email provider class instances.
*/
class ET_Core_API_Email_Providers {
private static $_instance;
/**
* @var ET_Core_Data_Utils
*/
protected static $_;
protected static $_any_custom_field_type;
protected static $_custom_fields_support;
protected static $_fields;
protected static $_metadata;
protected static $_names;
protected static $_names_by_slug;
protected static $_name_field_only = array();
protected static $_slugs;
public static $providers = array();
public function __construct() {
if ( null === self::$_metadata ) {
$this->_initialize();
}
}
protected function _initialize() {
self::$_ = ET_Core_Data_Utils::instance();
self::$_metadata = et_core_get_components_metadata();
$third_party_providers = et_core_get_third_party_components( 'api/email' );
$load_fields = is_admin() || et_core_is_saving_builder_modules_cache() || et_core_is_fb_enabled() || isset( $_GET['et_fb'] ); // phpcs:ignore WordPress.Security.NonceVerification.NoNonceVerification
$all_names = array(
'official' => self::$_metadata['groups']['api/email']['members'],
'third-party' => array_keys( $third_party_providers ),
);
$_names_by_slug = array();
$_custom_fields_support = array( 'dynamic' => array(), 'predefined' => array(), 'none' => array() );
$_any_custom_field_type = array();
foreach ( $all_names as $provider_type => $provider_names ) {
$_names_by_slug[ $provider_type ] = array();
foreach ( $provider_names as $provider_name ) {
if ( 'Fields' === $provider_name || self::$_->includes( $provider_name, 'Provider' ) ) {
continue;
}
if ( 'official' === $provider_type ) {
$class_name = self::$_metadata[ $provider_name ];
$provider_slug = self::$_metadata[ $class_name ]['slug'];
$provider = $load_fields ? new $class_name( 'ET_Core', '' ) : null;
} else {
$provider = $third_party_providers[ $provider_name ];
$provider_slug = is_object( $provider ) ? $provider->slug : '';
}
if ( ! $provider_slug ) {
continue;
}
$_names_by_slug[ $provider_type ][ $provider_slug ] = $provider_name;
if ( $load_fields && is_object( $provider ) ) {
self::$_fields[ $provider_slug ] = $provider->get_account_fields();
if ( $scope = $provider->custom_fields ) {
$_custom_fields_support[ $scope ][ $provider_slug ] = $provider_name;
if ( ! self::$_->array_get( $provider->data_keys, 'custom_field_type' ) ) {
$_any_custom_field_type[] = $provider_slug;
}
} else {
$_custom_fields_support['none'][ $provider_slug ] = $provider_name;
}
}
}
}
/**
* Filters the enabled email providers.
*
* @param array[] {
*
* @type string[] $provider_type {
*
* @type string $slug Provider name
* }
* }
*/
self::$_names_by_slug = apply_filters( 'et_core_api_email_enabled_providers', $_names_by_slug );
foreach ( array_keys( $all_names ) as $provider_type ) {
self::$_names[ $provider_type ] = array_values( self::$_names_by_slug[ $provider_type ] );
self::$_slugs[ $provider_type ] = array_keys( self::$_names_by_slug[ $provider_type ] );
}
self::$_name_field_only = self::$_metadata['groups']['api/email']['name_field_only'];
self::$_custom_fields_support = $_custom_fields_support;
self::$_any_custom_field_type = $_any_custom_field_type;
}
/**
* Returns the email provider accounts array from core.
*
* @return array|mixed
*/
public function accounts() {
return ET_Core_API_Email_Provider::get_accounts();
}
/**
* @see {@link \ET_Core_API_Email_Provider::account_exists()}
*/
public function account_exists( $provider, $account_name ) {
return ET_Core_API_Email_Provider::account_exists( $provider, $account_name );
}
public function account_fields( $provider = 'all' ) {
if ( 'all' !== $provider ) {
return isset( self::$_fields[ $provider ] ) ? self::$_fields[ $provider ] : array();
}
return self::$_fields;
}
public function custom_fields_data() {
$enabled_providers = self::slugs();
$custom_fields_data = array();
foreach ( $this->accounts() as $provider_slug => $accounts ) {
if ( ! in_array( $provider_slug, $enabled_providers ) ) {
continue;
}
foreach ( $accounts as $account_name => $account_details ) {
if ( empty( $account_details['lists'] ) ) {
continue;
}
if ( ! empty( $account_details['custom_fields'] ) ) {
$custom_fields_data[$provider_slug][$account_name]['custom_fields'] = $account_details['custom_fields'];
continue;
}
foreach ( (array) $account_details['lists'] as $list_id => $list_details ) {
if ( ! empty( $list_details['custom_fields'] ) ) {
$custom_fields_data[$provider_slug][$account_name][$list_id] = $list_details['custom_fields'];
}
}
}
}
return $custom_fields_data;
}
/**
* Get class instance for a provider. Instance will be created if necessary.
*
* @param string $name_or_slug The provider's name or slug.
* @param string $account_name The identifier for the desired account with the provider.
* @param string $owner The owner for the instance.
*
* @return bool|ET_Core_API_Email_Provider The provider instance or `false` if not found.
*/
public function get( $name_or_slug, $account_name, $owner = 'ET_Core' ) {
$name_or_slug = str_replace( ' ', '', $name_or_slug );
$is_official = isset( self::$_metadata[ $name_or_slug ] );
if ( ! $is_official && ! $this->is_third_party( $name_or_slug ) ) {
return false;
}
if ( ! in_array( $name_or_slug, array_merge( self::names(), self::slugs() ) ) ) {
return false;
}
// Make sure we have the component name
if ( $is_official ) {
$class_name = self::$_metadata[ $name_or_slug ];
$name = self::$_metadata[ $class_name ]['name'];
} else {
$components = et_core_get_third_party_components( 'api/email' );
if ( ! $name = array_search( $name_or_slug, self::$_names_by_slug['third-party'] ) ) {
$name = $name_or_slug;
}
}
if ( ! isset( self::$providers[ $name ][ $owner ] ) ) {
self::$providers[ $name ][ $owner ] = $is_official
? new $class_name( $owner, $account_name )
: $components[ $name ];
}
return self::$providers[ $name ][ $owner ];
}
public static function instance() {
if ( null === self::$_instance ) {
self::$_instance = new self;
}
return self::$_instance;
}
public function is_third_party( $name_or_slug ) {
$is_third_party = in_array( $name_or_slug, self::$_names['third-party'] );
return $is_third_party ? $is_third_party : in_array( $name_or_slug, self::$_slugs['third-party'] );
}
/**
* Returns the names of available providers. List can optionally be filtered.
*
* @param string $type The component type to include ('official'|'third-party'|'all'). Default is 'all'.
*
* @return array
*/
public function names( $type = 'all' ) {
if ( 'all' === $type ) {
$names = array_merge( self::$_names['third-party'], self::$_names['official'] );
} else {
$names = self::$_names[ $type ];
}
return $names;
}
/**
* Returns an array mapping the slugs of available providers to their names. List can optionally be filtered.
*
* @param string $type The component type to include ('official'|'third-party'|'all'). Default is 'all'.
* @param string $filter Optionally filter the list by a condition.
* Accepts 'name_field_only', 'predefined_custom_fields', 'dynamic_custom_fields',
* 'no_custom_fields', 'any_custom_field_type', 'custom_fields'.
*
* @return array
*/
public function names_by_slug( $type = 'all', $filter = '' ) {
if ( 'all' === $type ) {
$names_by_slug = array_merge( self::$_names_by_slug['third-party'], self::$_names_by_slug['official'] );
} else {
$names_by_slug = self::$_names_by_slug[ $type ];
}
if ( 'name_field_only' === $filter ) {
$names_by_slug = self::$_name_field_only;
} else if ( 'predefined_custom_fields' === $filter ) {
$names_by_slug = self::$_custom_fields_support['predefined'];
} else if ( 'dynamic_custom_fields' === $filter ) {
$names_by_slug = self::$_custom_fields_support['dynamic'];
} else if ( 'no_custom_fields' === $filter ) {
$names_by_slug = self::$_custom_fields_support['none'];
} else if ( 'any_custom_field_type' === $filter ) {
$names_by_slug = self::$_any_custom_field_type;
} else if ( 'custom_fields' === $filter ) {
$names_by_slug = array_merge( self::$_custom_fields_support['predefined'], self::$_custom_fields_support['dynamic'] );
}
return $names_by_slug;
}
/**
* @see {@link \ET_Core_API_Email_Provider::remove_account()}
*/
public function remove_account( $provider, $account_name ) {
ET_Core_API_Email_Provider::remove_account( $provider, $account_name );
}
/**
* Returns the slugs of available providers. List can optionally be filtered.
*
* @param string $type The component type to include ('official'|'third-party'|'all'). Default is 'all'.
*
* @return array
*/
public function slugs( $type = 'all' ) {
if ( 'all' === $type ) {
$names = array_merge( self::$_slugs['third-party'], self::$_slugs['official'] );
} else {
$names = self::$_slugs[ $type ];
}
return $names;
}
/**
* @see {@link \ET_Core_API_Email_Provider::update_account()}
*/
public function update_account( $provider, $account, $data ) {
ET_Core_API_Email_Provider::update_account( $provider, $account, $data );
}
}

View File

@ -0,0 +1,383 @@
<?php
/**
* Wrapper for SalesForce's API.
*
* @since 1.1.0
*
* @package ET\Core\API\Email
*/
class ET_Core_API_Email_SalesForce extends ET_Core_API_Email_Provider {
/**
* @inheritDoc
*/
public $ACCESS_TOKEN_URL = 'https://login.salesforce.com/services/oauth2/token';
/**
* @inheritDoc
*/
public $AUTHORIZATION_URL = 'https://login.salesforce.com/services/oauth2/authorize';
/**
* @inheritDoc
*/
public $BASE_URL = '';
/**
* @inheritDoc
*/
public $custom_fields_scope = 'account';
/**
* @inheritDoc
*/
public $name = 'SalesForce';
/**
* @inheritDoc
*/
public $slug = 'salesforce';
/**
* @inheritDoc
*/
public $oauth_version = '2.0';
/**
* @inheritDoc
*/
public $uses_oauth = true;
/**
* ET_Core_API_SalesForce constructor.
*
* @inheritDoc
*/
public function __construct( $owner, $account_name = '' ) {
parent::__construct( $owner, $account_name );
if ( 'builder' === $owner ) {
$this->REDIRECT_URL = add_query_arg( 'et-core-api-email-auth', 1, home_url( '', 'https' ) ); // @phpcs:ignore ET.Sniffs.ValidVariableName.UsedPropertyNotSnakeCase -- No need to change prop name.
} else {
$this->REDIRECT_URL = admin_url( 'admin.php?page=et_bloom_options', 'https' ); // @phpcs:ignore ET.Sniffs.ValidVariableName.UsedPropertyNotSnakeCase -- No need to change prop name.
}
$this->_set_base_url();
}
protected function _fetch_custom_fields( $list_id = '', $list = array() ) {
static $fields = null;
$this->response_data_key = 'fields';
$this->prepare_request( "{$this->BASE_URL}/services/data/v39.0/sobjects/Lead/describe" );
if ( is_null( $fields ) ) {
$fields = parent::_fetch_custom_fields( $list_id, $list );
foreach ( $fields as $index => $field ) {
if ( ! isset( $field['custom'] ) || ! $field['custom'] ) {
unset( $fields[ $index ] );
}
}
}
return $fields;
}
/**
* @return string
*/
protected function _fetch_subscriber_lists() {
$query = urlencode( 'SELECT Id, Name, NumberOfLeads from Campaign LIMIT 100' );
$url = "{$this->BASE_URL}/services/data/v39.0/query?q={$query}";
$this->response_data_key = 'records';
$this->prepare_request( $url );
return parent::fetch_subscriber_lists();
}
protected function _process_custom_fields( $args ) {
if ( ! isset( $args['custom_fields'] ) ) {
return $args;
}
$fields = $args['custom_fields'];
unset( $args['custom_fields'] );
foreach ( $fields as $field_id => $value ) {
if ( is_array( $value ) && $value ) {
// This is a multiple choice field (eg. checkbox, radio, select)
$value = array_values( $value );
if ( 'checkbox' === $this->data['custom_fields'][ $field_id ]['type'] ) {
$value = implode( ';', $value );
} else {
$value = array_pop( $value );
}
}
self::$_->array_set( $args, "custom_fields.{$field_id}", $value );
}
return $args;
}
public function _set_base_url() {
// If we already have the `instance_url`, use it as the base API url.
if ( isset( $this->data['instance_url'] ) && ! empty( $this->data['instance_url'] ) ) {
$this->BASE_URL = untrailingslashit( $this->data['instance_url'] ); // @phpcs:ignore ET.Sniffs.ValidVariableName.UsedPropertyNotSnakeCase -- No need to change prop name.
} else {
$this->BASE_URL = empty( $this->data['login_url'] ) ? '' : untrailingslashit( $this->data['login_url'] ); // @phpcs:ignore ET.Sniffs.ValidVariableName.UsedPropertyNotSnakeCase -- No need to change prop name.
}
}
public function authenticate() {
$this->data['consumer_secret'] = $this->data['client_secret'];
$this->data['consumer_key'] = $this->data['api_key'];
return parent::authenticate();
}
/**
* @inheritDoc
*/
public function get_account_fields() {
return array(
// SalesForce supports OAuth for SSL websites so generate different fields in this case
'login_url' => array(
'label' => esc_html__( 'Instance URL', 'et_core' ),
'required' => 'https',
'show_if' => array( 'function.protocol' => 'https' ),
),
'api_key' => array(
'label' => esc_html__( 'Consumer Key', 'et_core' ),
'required' => 'https',
'show_if' => array( 'function.protocol' => 'https' ),
),
'client_secret' => array(
'label' => esc_html__( 'Consumer Secret', 'et_core' ),
'required' => 'https',
'show_if' => array( 'function.protocol' => 'https' ),
),
// This has to be the last field because is the only one shown in both cases and
// CANCEL / SUBMIT buttons will be attached to it.
'organization_id' => array(
'label' => esc_html__( 'Organization ID', 'et_core' ),
'required' => 'http',
),
);
}
/**
* @inheritDoc
*/
public function fetch_subscriber_lists() {
$this->_set_base_url();
// SalesForce supports 2 types of authentication: Simple and OAuth2
if ( isset( $this->data['api_key'], $this->data['client_secret'] ) && ! empty( $this->data['api_key'] ) && ! empty( $this->data['client_secret'] ) ) {
// Fetch lists if user already authenticated.
if ( $this->is_authenticated() ) {
return $this->_fetch_subscriber_lists();
}
$authenticated = $this->authenticate();
// If the authenticating process returns an array with redirect url to complete OAuth authorization.
if ( is_array( $authenticated ) ) {
return $authenticated;
}
if ( true === $authenticated ) {
// Need to reinitialize the OAuthHelper with the new data, to set the authorization header in the next request.
$urls = array(
'access_token_url' => $this->ACCESS_TOKEN_URL, // @phpcs:ignore -- No need to change the class property
'request_token_url' => $this->REQUEST_TOKEN_URL, // @phpcs:ignore -- No need to change the class property
'authorization_url' => $this->AUTHORIZATION_URL, // @phpcs:ignore -- No need to change the class property
'redirect_url' => $this->REDIRECT_URL, // @phpcs:ignore -- No need to change the class property
);
$this->OAuth_Helper = new ET_Core_API_OAuthHelper( $this->data, $urls, $this->owner ); // @phpcs:ignore ET.Sniffs.ValidVariableName.UsedPropertyNotSnakeCase -- No need to change the prop name.
return $this->_fetch_subscriber_lists();
}
return false;
} elseif ( isset( $this->data['organization_id'] ) && '' !== $this->data['organization_id'] ) {
// Simple
$this->data['is_authorized'] = 'true';
$this->data['lists'] = array( array( 'list_id' => 0, 'name' => 'WebToLead', 'subscribers_count' => 0 ) );
$this->save_data();
// return 'success' immediately in case of simple authentication. Lists cannot be retrieved with this type.
return 'success';
} else {
return esc_html__( 'Organization ID cannot be empty', 'et_core' );
}
}
/**
* @inheritDoc
*/
public function get_data_keymap( $keymap = array() ) {
$keymap = array(
'list' => array(
'list_id' => 'Id',
'name' => 'Name',
'subscribers_count' => 'NumberOfLeads',
),
'subscriber' => array(
'name' => 'FirstName',
'last_name' => 'LastName',
'email' => 'Email',
'custom_fields' => 'custom_fields',
),
'custom_field' => array(
'field_id' => 'name',
'name' => 'label',
'type' => 'type',
'options' => 'valueSet',
),
'custom_field_type' => array(
// Us => Them
'input' => 'Text',
'textarea' => 'TextArea',
'checkbox' => 'MultiselectPicklist',
'select' => 'Picklist',
// Them => Us
'Text' => 'input',
'TextArea' => 'textarea',
'MultiselectPicklist' => 'checkbox',
'Picklist' => 'select',
),
);
return parent::get_data_keymap( $keymap );
}
public function get_subscriber( $email ) {
$query = urlencode( "SELECT Id from Lead where Email='{$email}' LIMIT 100" );
$url = "{$this->BASE_URL}/services/data/v39.0/query?q={$query}";
$this->response_data_key = 'records';
$this->prepare_request( $url );
$this->make_remote_request();
$response = $this->response;
if ( $response->ERROR || empty( $response->DATA['records'] ) ) {
return false;
}
return isset( $response->DATA['records'][0]['Id'] ) ? $response->DATA['records'][0]['Id'] : false;
}
/**
* @inheritDoc
*/
public function subscribe( $args, $url = '' ) {
if ( empty( $this->data['access_secret'] ) ) {
// Try to use simple web form
return $this->subscribe_salesforce_web( $args );
}
$error_message = esc_html__( 'An error occurred. Please try again.', 'et_core' );
$subscriber_id = $this->get_subscriber( $args['email'] );
if ( ! $subscriber_id ) {
$url = "{$this->BASE_URL}/services/data/v39.0/sobjects/Lead";
$content = $this->transform_data_to_provider_format( $args, 'subscriber' );
$content = $this->_process_custom_fields( $content );
$content['Company'] = 'Bloom';
if ( isset( $content['custom_fields'] ) && is_array( $content['custom_fields'] ) ) {
$content = array_merge( $content, $content['custom_fields'] );
unset( $content['custom_fields'] );
}
// The LastName is required by Salesforce, whereas it is possible for Optin Form to not have the last name field.
if ( ! isset( $content['LastName'] ) || empty( $content['LastName'] ) ) {
$content['LastName'] = '[not provided]';
}
$this->prepare_request( $url, 'POST', false, json_encode( $content ), true );
$this->response_data_key = false;
$result = parent::subscribe( $content, $url );
if ( 'success' !== $result || empty( $this->response->DATA['id'] ) ) {
return $error_message;
}
$subscriber_id = $this->response->DATA['id'];
}
$url = "{$this->BASE_URL}/services/data/v39.0/sobjects/CampaignMember";
$content = array(
'LeadId' => $subscriber_id,
'CampaignId' => $args['list_id'],
);
$this->prepare_request( $url, 'POST', false, json_encode( $content ), true );
$result = parent::subscribe( $content, $url );
if ( 'success' !== $result && ! empty( $this->response->DATA['errors'] ) ) {
return $this->response->DATA['errors'][0];
} else if ( 'success' !== $result ) {
return $error_message;
}
return 'success';
}
/**
* Post web-to-lead request to SalesForce
*
* @return string
*/
public function subscribe_salesforce_web( $args ) {
if ( ! isset( $this->data['organization_id'] ) || '' === $this->data['organization_id'] ) {
return esc_html__( 'Unknown Organization ID', 'et_core' );
}
// Define SalesForce web-to-lead endpoint
$url = 'https://webto.salesforce.com/servlet/servlet.WebToLead?encoding=UTF-8';
$args = $this->transform_data_to_provider_format( $args, 'subscriber' );
$args = $this->_process_custom_fields( $args );
// Prepare arguments for web-to-lead POST
$form_args = array(
'body' => array(
'oid' => sanitize_text_field( $this->data['organization_id'] ),
'retURL' => esc_url( home_url( '/' ) ),
'email' => sanitize_email( $args['Email'] ),
),
);
if ( '' !== $args['FirstName'] ) {
$form_args['body']['first_name'] = sanitize_text_field( $args['FirstName'] );
}
if ( '' !== $args['LastName'] ) {
$form_args['body']['last_name'] = sanitize_text_field( $args['LastName'] );
}
if ( isset( $args['custom_fields'] ) && is_array( $args['custom_fields'] ) ) {
$form_args = array_merge( $form_args, $args['custom_fields'] );
}
// Post to SalesForce web-to-lead endpoint
$request = wp_remote_post( $url, $form_args );
if ( ! is_wp_error( $request ) && 200 === wp_remote_retrieve_response_code( $request ) ) {
return 'success';
}
return esc_html__( 'An error occurred. Please try again.', 'et_core' );
}
}

View File

@ -0,0 +1,274 @@
<?php
/**
* Wrapper for SendinBlue's API.
*
* @since 1.1.0
*
* @package ET\Core\API\Email
*/
class ET_Core_API_Email_SendinBlue extends ET_Core_API_Email_Provider {
/**
* @inheritDoc
*/
public $BASE_URL = 'https://api.sendinblue.com/v3'; // @phpcs:ignore ET.Sniffs.ValidVariableName.PropertyNotSnakeCase -- Keep the variable name.
/**
* @inheritDoc
*/
public $FIELDS_URL = 'https://api.sendinblue.com/v3/contacts/attributes'; // @phpcs:ignore ET.Sniffs.ValidVariableName.PropertyNotSnakeCase -- Keep the variable name.
/**
* @inheritDoc
*/
public $LISTS_URL = 'https://api.sendinblue.com/v3/contacts/lists/'; // @phpcs:ignore ET.Sniffs.ValidVariableName.PropertyNotSnakeCase -- Keep the variable name.
/**
* The URL to which new subscribers can be posted.
*
* @var string
*/
public $SUBSCRIBE_URL = 'https://api.sendinblue.com/v3/contacts'; // @phpcs:ignore ET.Sniffs.ValidVariableName.PropertyNotSnakeCase -- Keep the variable name.
/**
* The URL to get the subscriber information.
* Only used by legacy mode (v2) to check whether we should create or update subscription.
*
* @var string
*/
public $USERS_URL = 'https://api.sendinblue.com/v2.0/user';
/**
* @inheritDoc
*/
public $custom_fields_scope = 'account';
/**
* @inheritDoc
*/
public $name = 'SendinBlue';
/**
* @inheritDoc
*/
public $slug = 'sendinblue';
/**
* @inheritDoc
*/
public $uses_oauth = false;
public function __construct( $owner, $account_name, $api_key = '' ) {
parent::__construct( $owner, $account_name, $api_key );
$this->_maybe_set_custom_headers();
}
protected function _maybe_set_custom_headers() {
if ( empty( $this->custom_headers ) && isset( $this->data['api_key'] ) ) {
$this->custom_headers = array( 'api-key' => $this->data['api_key'] );
}
}
protected function _process_custom_fields( $args ) {
if ( ! isset( $args['custom_fields'] ) ) {
return $args;
}
$fields = $args['custom_fields'];
unset( $args['custom_fields'] );
foreach ( $fields as $field_id => $value ) {
if ( is_array( $value ) && $value ) {
// This is a multiple choice field (eg. checkbox, radio, select)
$value = array_values( $value );
if ( count( $value ) > 1 ) {
$value = implode( ',', $value );
} else {
$value = array_pop( $value );
}
}
self::$_->array_set( $args, "attributes.{$field_id}", $value );
}
return $args;
}
/**
* @inheritDoc
*/
public function get_account_fields() {
return array(
'api_key' => array(
'label' => esc_html__( 'API Key', 'et_core' ),
),
);
}
/**
* @inheritDoc
*/
public function get_data_keymap( $keymap = array() ) {
$keymap = array(
'list' => array(
'list_id' => 'id',
'name' => 'name',
'subscribers_count' => $this->_should_use_legacy_api() ? 'total_subscribers' : 'totalSubscribers',
),
'subscriber' => array(
'email' => 'email',
'name' => 'attributes.FIRSTNAME',
'last_name' => 'attributes.LASTNAME',
'list_id' => $this->_should_use_legacy_api() ? '@listid' : '@listIds',
'custom_fields' => 'custom_fields',
'updateEnabled' => 'updateEnabled',
),
'custom_field' => array(
'field_id' => 'name',
'name' => 'name',
),
);
return parent::get_data_keymap( $keymap );
}
public function get_subscriber( $email ) {
$this->prepare_request( "{$this->USERS_URL}/{$email}", 'GET' );
$this->make_remote_request();
if ( $this->response->ERROR || ! isset( $this->response->DATA['listid'] ) ) {
return false;
}
if ( isset( $this->response->DATA['code'] ) && 'success' !== $this->response->DATA['code'] ) {
return false;
}
return $this->response->DATA;
}
/**
* @inheritDoc
*/
public function fetch_subscriber_lists() {
if ( empty( $this->data['api_key'] ) ) {
return $this->API_KEY_REQUIRED;
}
if ( empty( $this->custom_headers ) ) {
$this->_maybe_set_custom_headers();
}
$use_legacy_api = $this->_should_use_legacy_api();
if ( $use_legacy_api ) {
$this->LISTS_URL = 'https://api.sendinblue.com/v2.0/list'; // @phpcs:ignore ET.Sniffs.ValidVariableName.UsedPropertyNotSnakeCase -- Keep the variable name.
$this->response_data_key = 'data';
$params = array(
'page' => 1,
'page_limit' => 2,
);
} else {
$this->response_data_key = 'lists';
}
/**
* The maximum number of subscriber lists to request from Sendinblue's API.
*
* @since 4.11.4
*
* @param int $max_lists
*/
$max_lists = (int) apply_filters( 'et_core_api_email_sendinblue_max_lists', 50 );
$url = "{$this->LISTS_URL}?limit={$max_lists}&offset=0&sort=desc";
$this->prepare_request( $url, 'GET', false, $params );
$this->request->data_format = 'body';
parent::fetch_subscriber_lists();
if ( $this->response->ERROR ) {
return $this->response->ERROR_MESSAGE;
}
if ( isset( $this->response->DATA['code'] ) && 'success' !== $this->response->DATA['code'] ) {
return $this->response->DATA['message'];
}
$result = 'success';
$this->data['is_authorized'] = 'true';
$list_data = $use_legacy_api ? $this->response->DATA['data']['lists'] : $this->response->DATA['lists'];
if ( ! empty( $list_data ) ) {
$this->data['lists'] = $this->_process_subscriber_lists( $list_data );
$this->save_data();
}
return $result;
}
/**
* Get custom fields for a subscriber list.
* Need to override the method in the child class to dynamically use the API endpoint
* and response_data_key based on the API version being used.
*
* @param int|string $list_id The list ID.
* @param array $list The lists array.
*
* @return array
*/
protected function _fetch_custom_fields( $list_id = '', $list = array() ) {
if ( $this->_should_use_legacy_api() ) {
$this->FIELDS_URL = 'https://api.sendinblue.com/v2.0/attribute/normal'; // @phpcs:ignore ET.Sniffs.ValidVariableName.UsedPropertyNotSnakeCase -- Keep the variable name.
$this->response_data_key = 'data';
} else {
$this->response_data_key = 'attributes';
}
return parent::_fetch_custom_fields( $list_id, $list );
}
/**
* @inheritDoc
*/
public function subscribe( $args, $url = '' ) {
$args['list_id'] = array( absint( $args['list_id'] ) ); // in V3 the list id has to be integer.
if ( $this->_should_use_legacy_api() ) {
$this->SUBSCRIBE_URL = 'https://api.sendinblue.com/v2.0/user/createdituser'; // @phpcs:ignore ET.Sniffs.ValidVariableName.UsedPropertyNotSnakeCase -- Keep the variable name.
$existing_user = $this->get_subscriber( $args['email'] );
if ( false !== $existing_user ) {
$args['list_id'] = array_unique( array_merge( $args['list_id'], $existing_user['listid'] ) );
}
} else {
$args['updateEnabled'] = true; // Update existing contact if exists.
// Process data and encode to json, the new API (v3) uses json encoded body params.
if ( ! in_array( 'ip_address', $args, true ) || 'true' === $args['ip_address'] ) {
$args['ip_address'] = et_core_get_ip_address();
} elseif ( 'false' === $args['ip_address'] ) {
$args['ip_address'] = '0.0.0.0';
}
$args = $this->transform_data_to_provider_format( $args, 'subscriber' );
if ( $this->custom_fields ) {
$args = $this->_process_custom_fields( $args );
}
$this->prepare_request( $this->SUBSCRIBE_URL, 'POST', false, $args, true ); // @phpcs:ignore ET.Sniffs.ValidVariableName.UsedPropertyNotSnakeCase -- Keep the variable name.
}
return parent::subscribe( $args, $this->SUBSCRIBE_URL );
}
/**
* Check if the api-key being used is legacy (v2).
*
* @return boolean
*/
protected function _should_use_legacy_api() {
$api_key = isset( $this->data['api_key'] ) ? $this->data['api_key'] : '';
return ! empty( $api_key ) && 'xkeysib-' !== substr( $api_key, 0, 8 );
}
}

View File

@ -0,0 +1,149 @@
<?php
/**
* Wrapper for MailPoet's API.
*
* @since 3.0.76
*
* @package ET\Core\API\Email
*/
class ET_Core_API_Email_MailPoet2 extends ET_Core_API_Email_Provider {
public static $PLUGIN_REQUIRED;
/**
* @inheritDoc
*/
public $name = 'MailPoet';
/**
* @inheritDoc
*/
public $slug = 'mailpoet';
public function __construct( $owner = '', $account_name = '', $api_key = '' ) {
parent::__construct( $owner, $account_name, $api_key );
if ( null === self::$PLUGIN_REQUIRED ) {
self::$PLUGIN_REQUIRED = esc_html__( 'MailPoet plugin is either not installed or not activated.', 'et_core' );
}
}
/**
* @inheritDoc
*/
public function get_account_fields() {
return array();
}
/**
* @inheritDoc
*/
public function get_data_keymap( $keymap = array() ) {
$keymap = array(
'list' => array(
'list_id' => 'id',
'name' => 'name',
),
'subscriber' => array(
'name' => 'first_name',
'last_name' => 'last_name',
'email' => 'email',
),
);
return parent::get_data_keymap( $keymap );
}
/**
* @inheritDoc
*/
public function fetch_subscriber_lists() {
if ( ! class_exists( 'WYSIJA' ) ) {
return self::$PLUGIN_REQUIRED;
}
$lists = array();
$list_model = WYSIJA::get( 'list', 'model' );
$all_lists_array = $list_model->get( array( 'name', 'list_id' ), array( 'is_enabled' => '1' ) );
foreach ( $all_lists_array as $list_details ) {
$lists[ $list_details['list_id'] ]['name'] = sanitize_text_field( $list_details['name'] );
$user_model = WYSIJA::get( 'user_list', 'model' );
$all_subscribers_array = $user_model->get( array( 'user_id' ), array( 'list_id' => $list_details['list_id'] ) );
$subscribers_count = count( $all_subscribers_array );
$lists[ $list_details['list_id'] ]['subscribers_count'] = sanitize_text_field( $subscribers_count );
}
$this->data['is_authorized'] = true;
if ( ! empty( $lists ) ) {
$this->data['lists'] = $lists;
}
$this->save_data();
return array( 'success' => $this->data );
}
/**
* @inheritDoc
*/
public function subscribe( $args, $url = '' ) {
if ( ! class_exists( 'WYSIJA' ) ) {
ET_Core_Logger::error( self::$PLUGIN_REQUIRED );
return esc_html__( 'An error occurred. Please try again later.', 'et_core' );
}
global $wpdb;
$wpdb->wysija_user_table = $wpdb->prefix . 'wysija_user';
$wpdb->wysija_user_lists_table = $wpdb->prefix . 'wysija_user_list';
// get the ID of subscriber if they're in the list already
$subscriber_id = $wpdb->get_var( $wpdb->prepare(
"SELECT user_id FROM {$wpdb->wysija_user_table} WHERE email = %s",
array(
et_core_sanitized_previously( $args['email'] ),
)
) );
$already_subscribed = 0;
// if current email is subscribed, then check whether it subscribed to the current list
if ( ! empty( $subscriber_id ) ) {
$already_subscribed = (int) $wpdb->get_var( $wpdb->prepare(
"SELECT COUNT(*) FROM {$wpdb->wysija_user_lists_table} WHERE user_id = %s AND list_id = %s",
array(
$subscriber_id,
et_core_sanitized_previously( $args['list_id'] ),
)
) );
}
unset( $wpdb->wysija_user_table );
unset( $wpdb->wysija_user_list_table );
// if email is not subscribed to current list, then subscribe.
if ( 0 === $already_subscribed ) {
$new_user = array(
'user' => array(
'email' => et_core_sanitized_previously( $args['email'] ),
'firstname' => et_core_sanitized_previously( $args['name'] ),
'lastname' => et_core_sanitized_previously( $args['last_name'] ),
),
'user_list' => array(
'list_ids' => array( et_core_sanitized_previously( $args['list_id'] ) ),
),
);
$mailpoet_class = WYSIJA::get( 'user', 'helper' );
$error_message = $mailpoet_class->addSubscriber( $new_user );
$error_message = is_int( $error_message ) ? 'success' : $error_message;
} else {
$error_message = esc_html__( 'Already Subscribed', 'bloom' );
}
return $error_message;
}
}

View File

@ -0,0 +1,188 @@
<?php
/**
* Wrapper for MailPoet's API.
*
* @since 3.0.76
*
* @package ET\Core\API\Email
*/
class ET_Core_API_Email_MailPoet3 extends ET_Core_API_Email_Provider {
public static $PLUGIN_REQUIRED;
/**
* @inheritDoc
*/
public $name = 'MailPoet';
/**
* @inheritDoc
*/
public $slug = 'mailpoet';
/**
* @inheritDoc
*/
public $custom_fields_scope = 'account';
public function __construct( $owner = '', $account_name = '', $api_key = '' ) {
parent::__construct( $owner, $account_name, $api_key );
if ( null === self::$PLUGIN_REQUIRED ) {
self::$PLUGIN_REQUIRED = esc_html__( 'MailPoet plugin is either not installed or not activated.', 'et_core' );
}
}
protected function _fetch_custom_fields( $list_id = '', $list = array() ) {
static $processed = null;
if ( is_null( $processed ) ) {
$fields = \MailPoet\API\API::MP( 'v1' )->getSubscriberFields();
$processed = array();
foreach ( $fields as $field ) {
$field_id = $field['id'];
$field_name = $field['name'];
if ( in_array( $field_id, array( 'email', 'first_name', 'last_name' ) ) ) {
continue;
}
$processed[ $field_id ] = array(
'field_id' => $field_id,
'name' => $field_name,
'type' => 'any',
);
}
}
return $processed;
}
protected function _process_custom_fields( $args ) {
if ( ! isset( $args['custom_fields'] ) ) {
return $args;
}
$fields = $args['custom_fields'];
unset( $args['custom_fields'] );
foreach ( $fields as $field_id => $value ) {
if ( is_array( $value ) && $value ) {
// This is a multiple choice field (eg. checkbox, radio, select)
$value = array_keys( $value );
if ( count( $value ) > 1 ) {
$value = implode( ',', $value );
} else {
$value = array_pop( $value );
}
}
self::$_->array_set( $args, $field_id, $value );
}
return $args;
}
/**
* @inheritDoc
*/
public function get_account_fields() {
return array();
}
/**
* @inheritDoc
*/
public function get_data_keymap( $keymap = array() ) {
$keymap = array(
'list' => array(
'list_id' => 'id',
'name' => 'name',
),
'subscriber' => array(
'name' => 'first_name',
'last_name' => 'last_name',
'email' => 'email',
'custom_fields' => 'custom_fields',
),
);
return parent::get_data_keymap( $keymap );
}
/**
* @inheritDoc
*/
public function fetch_subscriber_lists() {
if ( ! class_exists( '\MailPoet\API\API' ) ) {
return self::$PLUGIN_REQUIRED;
}
$data = \MailPoet\API\API::MP( 'v1' )->getLists();
if ( ! empty( $data ) ) {
$this->data['lists'] = $this->_process_subscriber_lists( $data );
$list = is_array( $data ) ? array_shift( $data ) : array();
$this->data['custom_fields'] = $this->_fetch_custom_fields( '', $list );
}
$this->data['is_authorized'] = true;
$this->save_data();
return array( 'success' => $this->data );
}
/**
* @inheritDoc
*/
public function subscribe( $args, $url = '' ) {
if ( ! class_exists( '\MailPoet\API\API' ) ) {
ET_Core_Logger::error( self::$PLUGIN_REQUIRED );
return esc_html__( 'An error occurred. Please try again later.', 'et_core' );
}
$subscriber = array();
$args = et_core_sanitized_previously( $args );
$subscriber_data = $this->transform_data_to_provider_format( $args, 'subscriber' );
$subscriber_data = $this->_process_custom_fields( $subscriber_data );
$subscriber_data = self::$_->array_flatten( $subscriber_data );
$result = 'success';
$lists = array( $args['list_id'] );
unset( $subscriber_data['custom_fields'] );
/**
* Check if the subscriber with this email already exists.
*/
$subscriber = \MailPoet\Models\Subscriber::findOne( $subscriber_data['email'] );
if ( $subscriber ) {
$subscriber = $subscriber->withCustomFields()->withSubscriptions()->asArray();
}
/**
* If subscriber is not found, add as a new subscriber. Otherwise, add existing subscriber to the lists.
*/
if ( empty( $subscriber ) ) {
try {
\MailPoet\API\API::MP( 'v1' )->addSubscriber( $subscriber_data, $lists );
} catch ( Exception $exception ) {
$result = $exception->getMessage();
}
} else {
try {
\MailPoet\API\API::MP( 'v1' )->subscribeToLists( $subscriber['id'], $lists );
} catch ( Exception $exception ) {
$result = $exception->getMessage();
}
}
return $result;
}
}

View File

@ -0,0 +1,82 @@
<?php
/**
* Wrapper for ProviderName's API.
*
* @since 1.1.0
*
* @package ET\Core\API\Email
*/
class ET_Core_API_Email_ProviderName extends ET_Core_API_Email_Provider {
/**
* @inheritDoc
*/
public $BASE_URL = '';
/**
* @inheritDoc
*/
public $name = 'ProviderName';
/**
* Whether or not only a single name field is supported instead of first/last name fields.
*
* @var string
*/
public static $name_field_only = false;
/**
* @inheritDoc
*/
public $slug = 'providername';
/**
* @inheritDoc
* @internal If true, oauth endpoints properties must also be defined.
*/
public $uses_oauth = false;
/**
* @inheritDoc
*/
public function get_account_fields() {
// Implement get_account_fields() method.
}
/**
* @inheritDoc
*/
public function get_data_keymap( $keymap = array(), $custom_fields_key = '' ) {
// Implement get_data_keys() method.
$keymap = array(
'list' => array(
'list_id' => '',
'name' => '',
'subscribers_count' => '',
),
'subscriber' => array(
'name' => '',
'email' => '',
'list_id' => '',
'ip_address' => '',
),
);
return parent::get_data_keymap( $keymap, $custom_fields_key );
}
/**
* @inheritDoc
*/
public function fetch_subscriber_lists() {
// Implement get_subscriber_lists() method.
}
/**
* @inheritDoc
*/
public function subscribe( $args, $url = '' ) {
// Implement subscribe() method.
}
}

View File

@ -0,0 +1,278 @@
<?php
/**
* Wrapper for iContact's API.
*
* @since 1.1.0
*
* @package ET\Core\API\Email
*/
class ET_Core_API_Email_iContact extends ET_Core_API_Email_Provider {
/**
* @inheritDoc
*/
public $BASE_URL = 'https://app.icontact.com/icp/a';
/**
* @inheritDoc
*/
public $custom_fields_scope = 'account';
/**
* @inheritDoc
*/
public $name = 'iContact';
/**
* @inheritDoc
*/
public $slug = 'icontact';
protected function _fetch_custom_fields( $list_id = '', $list = array() ) {
$this->prepare_request( $this->FIELDS_URL );
$this->make_remote_request();
$result = array();
if ( $this->response->ERROR ) {
et_debug( $this->get_error_message() );
return $result;
}
$data = $this->response->DATA['customfields'];
foreach ( $data as &$custom_field ) {
$custom_field = $this->transform_data_to_our_format( $custom_field, 'custom_field' );
}
$fields = array();
foreach ( $data as $field ) {
if ( 'text' !== $field['type'] ) {
continue;
}
$field_id = $field['field_id'];
$type = self::$_->array_get( $field, 'type', 'any' );
$field['type'] = self::$_->array_get( $this->data_keys, "custom_field_type.{$type}", 'any' );
$fields[ $field_id ] = $field;
}
return $fields;
}
protected function _set_custom_headers() {
if ( ! empty( $this->custom_headers ) ) {
return;
}
$this->custom_headers = array(
'Accept' => 'application/json',
'API-Version' => '2.2',
'API-AppId' => sanitize_text_field( $this->data['client_id'] ),
'API-Username' => sanitize_text_field( $this->data['username'] ),
'API-Password' => sanitize_text_field( $this->data['password'] ),
);
}
protected function _get_account_id() {
if ( ! empty( $this->data['account_id'] ) ) {
return $this->data['account_id'];
}
$this->prepare_request( 'https://app.icontact.com/icp/a' );
$this->make_remote_request();
if ( isset( $this->response->DATA['accounts'][0]['accountId'] ) ) {
$this->data['account_id'] = $this->response->DATA['accounts'][0]['accountId'];
}
return $this->data['account_id'];
}
protected function _get_subscriber( $args, $exclude = null ) {
$default_excludes = array( 'name', 'last_name' );
if ( is_array( $exclude ) ) {
$exclude = array_merge( $default_excludes, $exclude );
} else if ( $exclude ) {
$default_excludes[] = $exclude;
$exclude = $default_excludes;
}
$args = $this->transform_data_to_provider_format( $args, 'subscriber', $exclude );
$url = add_query_arg( $args, $this->SUBSCRIBERS_URL );
$this->prepare_request( $url );
$this->make_remote_request();
if ( $this->response->ERROR || ! $this->response->DATA['contacts'] ) {
return false;
}
return $this->response->DATA['contacts'][0];
}
protected function _get_folder_id() {
if ( ! empty( $this->data['folder_id'] ) ) {
return $this->data['folder_id'];
}
$this->prepare_request( "{$this->BASE_URL}/{$this->data['account_id']}/c" );
$this->make_remote_request();
if ( isset( $this->response->DATA['clientfolders'][0]['clientFolderId'] ) ) {
$this->data['folder_id'] = $this->response->DATA['clientfolders'][0]['clientFolderId'];
}
return $this->data['folder_id'];
}
protected function _process_custom_fields( $args ) {
if ( ! isset( $args['custom_fields'] ) ) {
return $args;
}
$fields = $args['custom_fields'];
unset( $args['custom_fields'] );
foreach ( $fields as $field_id => $value ) {
if ( is_array( $value ) && $value ) {
// This is a multiple choice field (eg. checkbox, radio, select)
$value = array_values( $value );
if ( count( $value ) > 1 ) {
$value = implode( ',', $value );
} else {
$value = array_pop( $value );
}
}
self::$_->array_set( $args, $field_id, $value );
}
return $args;
}
protected function _set_urls() {
$this->_set_custom_headers();
$account_id = $this->_get_account_id();
$folder_id = $this->_get_folder_id();
$this->BASE_URL = "{$this->BASE_URL}/{$account_id}/c/{$folder_id}";
$this->FIELDS_URL = "{$this->BASE_URL}/customfields";
$this->LISTS_URL = "{$this->BASE_URL}/lists";
$this->SUBSCRIBE_URL = "{$this->BASE_URL}/subscriptions";
$this->SUBSCRIBERS_URL = "{$this->BASE_URL}/contacts";
}
/**
* @inheritDoc
*/
public function get_account_fields() {
return array(
'client_id' => array(
'label' => esc_html__( 'App ID', 'et_core' ),
),
'username' => array(
'label' => esc_html__( 'Username', 'et_core' ),
),
'password' => array(
'label' => esc_html__( 'App Password', 'et_core' ),
),
);
}
/**
* @inheritDoc
*/
public function get_data_keymap( $keymap = array() ) {
$keymap = array(
'list' => array(
'list_id' => 'listId',
'name' => 'name',
'subscribers_count' => 'total',
),
'subscriber' => array(
'name' => 'firstName',
'last_name' => 'lastName',
'email' => 'email',
'list_id' => 'listId',
'custom_fields' => 'custom_fields',
),
'subscriptions' => array(
'list_id' => 'listId',
'message_id' => 'confirmationMessageId',
),
'custom_field' => array(
'field_id' => 'privateName',
'name' => 'publicName',
'type' => 'fieldType',
'hidden' => '!displayToUser',
),
);
return parent::get_data_keymap( $keymap );
}
/**
* @inheritDoc
*/
public function fetch_subscriber_lists() {
if ( empty( $this->data['client_id'] ) || empty( $this->data['username'] ) || empty( $this->data['password'] ) ) {
return $this->API_KEY_REQUIRED;
}
$this->_set_urls();
$this->response_data_key = 'lists';
return parent::fetch_subscriber_lists();
}
/**
* @inheritDoc
*/
public function subscribe( $args, $url = '' ) {
if ( empty( $this->data['client_id'] ) || empty( $this->data['username'] ) || empty( $this->data['password'] ) ) {
return $this->API_KEY_REQUIRED;
}
$this->_set_urls();
if ( $this->_get_subscriber( $args ) ) {
// Subscriber exists and is already subscribed to the list.
return 'success';
}
if ( ! $contact = $this->_get_subscriber( $args, 'list_id' ) ) {
// Create new contact
$body = $this->transform_data_to_provider_format( $args, 'subscriber' );
$body = $this->_process_custom_fields( $body );
$this->prepare_request( $this->SUBSCRIBERS_URL, 'POST', false, array( $body ), true );
$this->make_remote_request();
if ( $this->response->ERROR ) {
return $this->get_error_message();
}
$contact = $this->response->DATA['contacts'][0];
}
// Subscribe contact to list
$body = $this->transform_data_to_provider_format( $args, 'subscriptions' );
$body['contactId'] = $contact['contactId'];
$body['status'] = isset( $args['message_id'] ) ? 'pending' : 'normal';
$this->prepare_request( $this->SUBSCRIBE_URL, 'POST', false, array( $body ), true );
return parent::subscribe( $args, $this->SUBSCRIBE_URL );
}
}

View File

@ -0,0 +1,130 @@
<?php
if ( ! function_exists( 'et_core_api_email_init' ) ):
function et_core_api_email_init() {
if ( defined( 'ET_CORE_UPDATED' ) ) {
et_core_api_email_fetch_all_lists();
}
}
endif;
if ( ! function_exists( 'et_core_api_email_fetch_all_lists' ) ):
/**
* Fetch the latest email lists for all provider accounts and update the database accordingly.
*
* @since 3.4
*/
function et_core_api_email_fetch_all_lists() {
$providers = ET_Core_API_Email_Providers::instance();
$all_accounts = $providers->accounts();
foreach ( $all_accounts as $provider_slug => $accounts ) {
$provider = $providers->get( $provider_slug, '' );
foreach ( $accounts as $account ) {
$provider->set_account_name( $account );
$provider->fetch_subscriber_lists();
}
}
}
endif;
if ( ! function_exists( 'et_core_api_email_fetch_lists' ) ):
/**
* Fetch the latest email lists for a provider account and update the database accordingly.
*
* @param string $name_or_slug The provider name or slug.
* @param string $account The account name.
* @param string $api_key Optional. The api key (if fetch succeeds, the key will be saved).
*
* @return string 'success' if successful, an error message otherwise.
*/
function et_core_api_email_fetch_lists( $name_or_slug, $account, $api_key = '' ) {
if ( ! empty( $api_key ) ) {
// The account provided either doesn't exist yet or has a new api key.
et_core_security_check( 'manage_options' );
}
if ( empty( $name_or_slug ) || empty( $account ) ) {
return __( 'ERROR: Invalid arguments.', 'et_core' );
}
$providers = ET_Core_API_Email_Providers::instance();
$provider = $providers->get( $name_or_slug, $account, 'builder' );
if ( ! $provider ) {
return '';
}
if ( is_array( $api_key ) ) {
foreach ( $api_key as $field_name => $value ) {
$provider->data[ $field_name ] = sanitize_text_field( $value );
}
} else if ( '' !== $api_key ) {
$provider->data['api_key'] = sanitize_text_field( $api_key );
}
return $provider->fetch_subscriber_lists();
}
endif;
if ( ! function_exists( 'et_core_api_email_providers' ) ):
/**
* @deprecated {@see ET_Core_API_Email_Providers::instance()}
*
* @return ET_Core_API_Email_Providers
*/
function et_core_api_email_providers() {
return ET_Core_API_Email_Providers::instance();
}
endif;
if ( ! function_exists( 'et_core_api_email_remove_account' ) ):
/**
* Delete an existing provider account.
*
* @param string $name_or_slug The provider name or slug.
* @param string $account The account name.
*/
function et_core_api_email_remove_account( $name_or_slug, $account ) {
et_core_security_check( 'manage_options' );
if ( empty( $name_or_slug ) || empty( $account ) ) {
return;
}
// If the account being removed is a legacy account (pre-dates core api), remove the old data.
switch( $account ) {
case 'Divi Builder Aweber':
et_delete_option( 'divi_aweber_consumer_key' );
et_delete_option( 'divi_aweber_consumer_secret' );
et_delete_option( 'divi_aweber_access_key' );
et_delete_option( 'divi_aweber_access_secret' );
break;
case 'Divi Builder Plugin Aweber':
$opts = (array) get_option( 'et_pb_builder_options' );
unset( $opts['aweber_consumer_key'], $opts['aweber_consumer_secret'], $opts['aweber_access_key'], $opts['aweber_access_secret'] );
update_option( 'et_pb_builder_options', $opts );
break;
case 'Divi Builder MailChimp':
et_delete_option( 'divi_mailchimp_api_key' );
break;
case 'Divi Builder Plugin MailChimp':
$options = (array) get_option( 'et_pb_builder_options' );
unset( $options['newsletter_main_mailchimp_key'] );
update_option( 'et_pb_builder_options', $options );
break;
}
$providers = ET_Core_API_Email_Providers::instance();
$provider = $providers->get( $name_or_slug, $account );
if ( $provider ) {
$provider->delete();
}
}
endif;

View File

@ -0,0 +1,7 @@
<?php
if ( ! function_exists( 'et_core_api_init' ) ):
function et_core_api_init() {
add_action( 'admin_init', array( 'ET_Core_API_OAuthHelper', 'finish_oauth2_authorization' ), 20 );
}
endif;

View File

@ -0,0 +1,46 @@
<?php
abstract class ET_Core_API_SocialNetwork extends ET_Core_API_Service {
public function __construct( $owner = '', $account_name = '' ) {
$this->service_type = 'social';
parent::__construct( $owner, $account_name );
}
/**
* @inheritDoc
*/
protected function _get_data_keys() {
// Implement _get_data_keys() method.
}
/**
* @inheritDoc
*/
public function get_account_fields() {
// Implement get_fields() method.
}
/**
* @inheritDoc
*/
protected function _get_accounts_data() {
// Implement _get_accounts_data() method.
}
/**
* @inheritDoc
*/
public function get_data_keymap( $keymap = array(), $custom_fields_key = '' ) {
// Implement get_data_keymap() method.
}
/**
* @inheritDoc
*/
public function save_data() {
// Implement save_data() method.
}
}

View File

@ -0,0 +1,155 @@
<?php
/**
* High-level wrapper for interacting with the external API's offered by 3rd-party anti-spam providers.
*
* @since 4.0.7
*
* @package ET\Core\API\Spam
*/
abstract class ET_Core_API_Spam_Provider extends ET_Core_API_Service {
/**
* @since 4.0.7
*
* @inheritDoc
*/
public $service_type = 'spam';
/**
* @since 4.0.7
*
* @inheritDoc
*/
protected function _get_data() {
$options = parent::_get_data();
// return empty array in case of empty name
if ( '' === $this->account_name || ! is_string( $this->account_name ) ) {
return array();
}
$provider = sanitize_text_field( $this->slug );
$account = sanitize_text_field( $this->account_name );
if ( ! isset( $options['accounts'][ $provider ][ $account ] ) ) {
$options['accounts'][ $provider ][ $account ] = array();
update_option( 'et_core_api_spam_options', $options );
}
return $options['accounts'][ $provider ][ $account ];
}
/**
* Returns whether or not an account exists in the database.
*
* @since 4.0.7
*
* @param string $provider
* @param string $account_name
*
* @return bool
*/
public static function account_exists( $provider, $account_name ) {
$all_accounts = self::get_accounts();
return isset( $all_accounts[ $provider ][ $account_name ] );
}
/**
* @since 4.0.7
*
* @inheritDoc
*/
public function delete() {
self::remove_account( $this->slug, $this->account_name );
$this->account_name = '';
$this->_get_data();
}
/**
* Retrieves the email accounts data from the database.
*
* @since 4.0.7
*
* @return array
*/
public static function get_accounts() {
$options = (array) get_option( 'et_core_api_spam_options' );
return isset( $options['accounts'] ) ? $options['accounts'] : array();
}
/**
* @since 4.0.7
*
* @inheritDoc
*/
public function get_data_keymap( $keymap = array() ) {
return $keymap;
}
abstract public function is_enabled();
/**
* Remove an account
*
* @since 4.0.7
*
* @param string $provider
* @param string $account_name
*/
public static function remove_account( $provider, $account_name ) {
$options = (array) get_option( 'et_core_api_spam_options' );
unset( $options['accounts'][ $provider ][ $account_name ] );
update_option( 'et_core_api_spam_options', $options );
}
/**
* @since 4.0.7
*
* @inheritDoc
*/
public function save_data() {
self::update_account( $this->slug, $this->account_name, $this->data );
}
/**
* @since 4.0.7
*
* @inheritDoc
*/
public function set_account_name( $name ) {
$this->account_name = $name;
$this->data = $this->_get_data();
}
/**
* Updates the data for a provider account.
*
* @since 4.0.7
*
* @param string $provider The provider's slug.
* @param string $account The account name.
* @param array $data The new data for the account.
*/
public static function update_account( $provider, $account, $data ) {
if ( empty( $account ) || empty( $provider ) ) {
return;
}
$options = (array) get_option( 'et_core_api_spam_options' );
$provider = sanitize_text_field( $provider );
$account = sanitize_text_field( $account );
self::$_->array_update( $options, "accounts.${provider}.{$account}", $data );
update_option( 'et_core_api_spam_options', $options );
}
abstract public function verify_form_submission();
}

View File

@ -0,0 +1,258 @@
<?php
/**
* Manages anti-spam provider class instances.
*
* @since 4.0.7
*/
class ET_Core_API_Spam_Providers {
private static $_instance;
/**
* @var ET_Core_Data_Utils
*/
protected static $_;
protected static $_fields;
protected static $_metadata;
protected static $_names;
protected static $_names_by_slug;
protected static $_slugs;
public static $providers = array();
public function __construct() {
if ( null === self::$_metadata ) {
$this->_initialize();
}
}
protected function _initialize() {
self::$_ = ET_Core_Data_Utils::instance();
self::$_metadata = et_core_get_components_metadata();
$third_party_providers = et_core_get_third_party_components( 'api/spam' );
$load_fields = is_admin() || et_core_is_saving_builder_modules_cache() || et_core_is_fb_enabled() || isset( $_GET['et_fb'] ); // phpcs:ignore WordPress.Security.NonceVerification.NoNonceVerification
$all_names = array(
'official' => self::$_metadata['groups']['api/spam']['members'],
'third-party' => array_keys( $third_party_providers ),
);
$_names_by_slug = array();
foreach ( $all_names as $provider_type => $provider_names ) {
$_names_by_slug[ $provider_type ] = array();
foreach ( $provider_names as $provider_name ) {
if ( 'Fields' === $provider_name || self::$_->includes( $provider_name, 'Provider' ) ) {
continue;
}
if ( 'official' === $provider_type ) {
$class_name = self::$_metadata[ $provider_name ];
$provider_slug = self::$_metadata[ $class_name ]['slug'];
$provider = $load_fields ? new $class_name( 'ET_Core', '' ) : null;
} else {
$provider = $third_party_providers[ $provider_name ];
$provider_slug = is_object( $provider ) ? $provider->slug : '';
}
if ( ! $provider_slug ) {
continue;
}
$_names_by_slug[ $provider_type ][ $provider_slug ] = $provider_name;
if ( $load_fields && is_object( $provider ) ) {
self::$_fields[ $provider_slug ] = $provider->get_account_fields();
}
}
}
/**
* Filters the enabled anti-spam providers.
*
* @since 4.0.7
*
* @param array[] $_names_by_slug {
*
* @type string[] $provider_type {
*
* @type string $slug Provider name
* }
* }
*/
self::$_names_by_slug = apply_filters( 'et_core_api_spam_enabled_providers', $_names_by_slug );
foreach ( array_keys( $all_names ) as $provider_type ) {
self::$_names[ $provider_type ] = array_values( self::$_names_by_slug[ $provider_type ] );
self::$_slugs[ $provider_type ] = array_keys( self::$_names_by_slug[ $provider_type ] );
}
}
/**
* Returns the spam provider accounts array from core.
*
* @since 4.0.7
*
* @return array|mixed
*/
public function accounts() {
return ET_Core_API_Spam_Provider::get_accounts();
}
/**
* @see {@link \ET_Core_API_Spam_Provider::account_exists()}
*/
public function account_exists( $provider, $account_name ) {
return ET_Core_API_Spam_Provider::account_exists( $provider, $account_name );
}
public function account_fields( $provider = 'all' ) {
if ( 'all' !== $provider ) {
if ( isset( self::$_fields[ $provider ] ) ) {
return self::$_fields[ $provider ];
}
if ( ! is_admin() && et_core_is_saving_builder_modules_cache() ) {
// Need to initialize again because et_core_is_saving_builder_modules_cache
// can't be called too early.
$this->_initialize();
return et_()->array_get( self::$_fields, $provider, array() );
}
return array();
}
return self::$_fields;
}
/**
* Get class instance for a provider. Instance will be created if necessary.
*
* @param string $name_or_slug The provider's name or slug.
* @param string $account_name The identifier for the desired account with the provider.
* @param string $owner The owner for the instance.
*
* @return ET_Core_API_Spam_Provider|bool The provider instance or `false` if not found.
*/
public function get( $name_or_slug, $account_name, $owner = 'ET_Core' ) {
$name_or_slug = str_replace( ' ', '', $name_or_slug );
$is_official = isset( self::$_metadata[ $name_or_slug ] );
if ( ! $is_official && ! $this->is_third_party( $name_or_slug ) ) {
return false;
}
if ( ! in_array( $name_or_slug, array_merge( self::names(), self::slugs() ) ) ) {
return false;
}
// Make sure we have the component name
if ( $is_official ) {
$class_name = self::$_metadata[ $name_or_slug ];
$name = self::$_metadata[ $class_name ]['name'];
} else {
$components = et_core_get_third_party_components( 'api/spam' );
if ( ! $name = array_search( $name_or_slug, self::$_names_by_slug['third-party'] ) ) {
$name = $name_or_slug;
}
}
if ( ! isset( self::$providers[ $name ][ $owner ] ) ) {
self::$providers[ $name ][ $owner ] = $is_official
? new $class_name( $owner, $account_name )
: $components[ $name ];
}
return self::$providers[ $name ][ $owner ];
}
public static function instance() {
if ( null === self::$_instance ) {
self::$_instance = new self;
}
return self::$_instance;
}
public function is_third_party( $name_or_slug ) {
$is_third_party = in_array( $name_or_slug, self::$_names['third-party'] );
return $is_third_party ? $is_third_party : in_array( $name_or_slug, self::$_slugs['third-party'] );
}
/**
* Returns the names of available providers. List can optionally be filtered.
*
* @since 4.0.7
*
* @param string $type The component type to include ('official'|'third-party'|'all'). Default is 'all'.
*
* @return array
*/
public static function names( $type = 'all' ) {
if ( 'all' === $type ) {
$names = array_merge( self::$_names['third-party'], self::$_names['official'] );
} else {
$names = self::$_names[ $type ];
}
return $names;
}
/**
* Returns an array mapping the slugs of available providers to their names.
*
* @since 4.0.7
*
* @param string $type The component type to include ('official'|'third-party'|'all'). Default is 'all'.
*
* @return array
*/
public function names_by_slug( $type = 'all' ) {
if ( 'all' === $type ) {
$names_by_slug = array_merge( self::$_names_by_slug['third-party'], self::$_names_by_slug['official'] );
} else {
$names_by_slug = self::$_names_by_slug[ $type ];
}
return $names_by_slug;
}
/**
* @see {@link \ET_Core_API_Spam_Provider::remove_account()}
*/
public function remove_account( $provider, $account_name ) {
ET_Core_API_Spam_Provider::remove_account( $provider, $account_name );
}
/**
* Returns the slugs of available providers. List can optionally be filtered.
*
* @since 4.0.7
*
* @param string $type The component type to include ('official'|'third-party'|'all'). Default is 'all'.
*
* @return array
*/
public static function slugs( $type = 'all' ) {
if ( 'all' === $type ) {
$names = array_merge( self::$_slugs['third-party'], self::$_slugs['official'] );
} else {
$names = self::$_slugs[ $type ];
}
return $names;
}
/**
* @since 4.0.7
*
* @see {@link \ET_Core_API_Spam_Provider::update_account()}
*/
public function update_account( $provider, $account, $data ) {
ET_Core_API_Spam_Provider::update_account( $provider, $account, $data );
}
}

View File

@ -0,0 +1,129 @@
<?php
/**
* Wrapper for ProviderName's API.
*
* @since 4.0.7
*
* @package ET\Core\API\Misc\ReCaptcha
*/
class ET_Core_API_Spam_ReCaptcha extends ET_Core_API_Spam_Provider {
/**
* @inheritDoc
*/
public $BASE_URL = 'https://www.google.com/recaptcha/api/siteverify';
/**
* @inheritDoc
*/
public $max_accounts = 1;
/**
* @inheritDoc
*/
public $name = 'ReCaptcha';
/**
* @inheritDoc
*/
public $slug = 'recaptcha';
public function __construct( $owner = 'ET_Core', $account_name = '', $api_key = '' ) {
parent::__construct( $owner, $account_name, $api_key );
$this->_add_actions_and_filters();
}
protected function _add_actions_and_filters() {
if ( ! is_admin() && ! et_core_is_fb_enabled() ) {
add_action( 'wp_enqueue_scripts', array( $this, 'action_wp_enqueue_scripts' ) );
}
}
public function action_wp_enqueue_scripts() {
if ( ! $this->is_enabled() ) {
return;
}
/**
* reCAPTCHA v3 actions may only contain alphanumeric characters and slashes/underscore.
* https://developers.google.com/recaptcha/docs/v3#actions
*
* Replace all non-alphanumeric characters with underscore.
* Ex: '?page_id=254980' => '_page_id_254980'
*/
$action = preg_replace( '/[^A-Za-z0-9]/', '_', basename( get_the_permalink() ) );
$deps = array( 'jquery', 'es6-promise', 'et-recaptcha-v3' );
wp_register_script( 'et-recaptcha-v3', "https://www.google.com/recaptcha/api.js?render={$this->data['site_key']}", array(), ET_CORE_VERSION, true );
wp_register_script( 'es6-promise', ET_CORE_URL . 'admin/js/es6-promise.auto.min.js', array(), ET_CORE_VERSION, true );
wp_enqueue_script( 'et-core-api-spam-recaptcha', ET_CORE_URL . 'admin/js/recaptcha.js', $deps, ET_CORE_VERSION, true );
wp_localize_script( 'et-core-api-spam-recaptcha', 'et_core_api_spam_recaptcha', array(
'site_key' => empty( $this->data['site_key'] ) ? '' : $this->data['site_key'],
'page_action' => array( 'action' => $action ),
) );
}
public function is_enabled() {
return isset( $this->data['site_key'], $this->data['secret_key'] )
&& et_()->all( array( $this->data['site_key'], $this->data['secret_key'] ) );
}
/**
* Verify a form submission.
*
* @since 4.0.7
*
* @global $_POST['token']
*
* @return mixed[]|string $result {
* Interaction Result
*
* @type bool $success Whether or not the request was valid for this site.
* @type int $score Score for the request (0 < score < 1).
* @type string $action Action name for this request (important to verify).
* @type string $challenge_ts Timestamp of the challenge load (ISO format yyyy-MM-ddTHH:mm:ssZZ).
* @type string $hostname Hostname of the site where the challenge was solved.
* @type string[] $error-codes Optional
* }
*/
public function verify_form_submission() {
$args = array(
'secret' => $this->data['secret_key'],
'response' => et_()->array_get_sanitized( $_POST, 'token' ),
'remoteip' => et_core_get_ip_address(),
);
$this->prepare_request( $this->BASE_URL, 'POST', false, $args );
$this->make_remote_request();
return $this->response->ERROR ? $this->response->ERROR_MESSAGE : $this->response->DATA;
}
/**
* @inheritDoc
*/
public function get_account_fields() {
return array(
'site_key' => array(
'label' => esc_html__( 'Site Key (v3)', 'et_core' ),
),
'secret_key' => array(
'label' => esc_html__( 'Secret Key (v3)', 'et_core' ),
),
);
}
/**
* @inheritDoc
*/
public function get_data_keymap( $keymap = array() ) {
return array(
'ip_address' => 'remoteip',
'response' => 'response',
'secret_key' => 'secret',
);
}
}

View File

@ -0,0 +1,101 @@
<?php
if ( ! function_exists( 'et_core_api_spam_init' ) ):
function et_core_api_spam_init() {
if ( is_admin() ) {
return;
}
if ( ! et_core_api_spam_find_provider_account() ) {
// Always instantiate ReCaptcha class
ET_Core_API_Spam_Providers::instance()->get( 'recaptcha', '' );
}
}
endif;
if ( ! function_exists('et_builder_spam_add_account' ) ):
function et_core_api_spam_add_account( $name_or_slug, $account, $api_key ) {
et_core_security_check();
if ( empty( $name_or_slug ) || empty( $account ) ) {
return __( 'ERROR: Invalid arguments.', 'et_core' );
}
$providers = ET_Core_API_Spam_Providers::instance();
$provider = $providers->get( $name_or_slug, $account, 'builder' );
if ( ! $provider ) {
return '';
}
if ( is_array( $api_key ) ) {
foreach ( $api_key as $field_name => $value ) {
$provider->data[ $field_name ] = sanitize_text_field( $value );
}
} else if ( '' !== $api_key ) {
$provider->data['api_key'] = sanitize_text_field( $api_key );
}
$provider->save_data();
return 'success';
}
endif;
if ( ! function_exists( 'et_core_api_spam_find_provider_account' ) ):
function et_core_api_spam_find_provider_account() {
$spam_providers = ET_Core_API_Spam_Providers::instance();
if ( $accounts = $spam_providers->accounts() ) {
$enabled_account = '';
$provider = '';
foreach ( $accounts as $provider_slug => $provider_accounts ) {
if ( empty( $provider_accounts ) ) {
continue;
}
foreach ( $provider_accounts as $account_name => $account ) {
if ( isset( $account['site_key'], $account['secret_key'] ) ) {
$provider = $provider_slug;
$enabled_account = $account_name;
break;
}
}
}
if ( $provider && $enabled_account ) {
return $spam_providers->get( $provider, $enabled_account );
}
}
return false;
}
endif;
if ( ! function_exists( 'et_core_api_spam_remove_account' ) ):
/**
* Delete an existing provider account.
*
* @since 4.0.7
*
* @param string $name_or_slug The provider name or slug.
* @param string $account The account name.
*/
function et_core_api_spam_remove_account( $name_or_slug, $account ) {
et_core_security_check();
if ( empty( $name_or_slug ) || empty( $account ) ) {
return;
}
$providers = ET_Core_API_Spam_Providers::instance();
if ( $provider = $providers->get( $name_or_slug, $account ) ) {
$provider->delete();
}
}
endif;

212
core/components/cache/Directory.php vendored Normal file
View File

@ -0,0 +1,212 @@
<?php
class ET_Core_Cache_Directory {
/**
* @since 4.0.8
* @var self
*/
protected static $_instance;
/**
* Whether or not we can write to the cache directory.
*
* @since 4.0.8
* @var bool
*/
public $can_write;
/**
* Absolute path to cache directory
*
* @since 4.0.8
* @var string
*/
public $path = '';
/**
* URL for {@see self::$path}
*
* @since 4.0.8
* @var string
*/
public $url = '';
/**
* @since 4.0.8
* @var WP_Filesystem_Base
*/
public $wpfs;
/**
* ET_Core_Cache_Directory constructor.
*
* @since 4.0.8
*/
public function __construct() {
if ( self::$_instance ) {
et_wrong( 'Use "ET_Core_Cache_Directory::instance()" instead of "new ET_Core_Cache_Directory".' );
return;
}
self::$_instance = $this;
$this->_initialize();
}
/**
* Determines the cache directory path and url based on where we can write files
* and whether or not the user has defined a custom path and url.
*
* @since 4.0.8
*/
protected function _initialize() {
$this->_initialize_wpfs();
if ( $this->_maybe_use_custom_path() ) {
return;
}
$uploads_dir_info = (object) wp_get_upload_dir();
$path = et_()->path( WP_CONTENT_DIR, 'et-cache' );
$url = content_url( 'et-cache' );
$can_write = $this->wpfs->is_writable( $path ) && ! is_file( $path );
$can_create = ! $can_write && $this->wpfs->is_writable( WP_CONTENT_DIR );
if ( ! $can_write && ! $can_create && $this->wpfs->is_writable( $uploads_dir_info->basedir ) ) {
// We can create our cache directory in the uploads directory
$can_create = true;
$path = et_()->path( $uploads_dir_info->basedir, 'et-cache' );
$url = et_()->path( $uploads_dir_info->baseurl, 'et-cache' );
}
$this->can_write = $can_write || $can_create;
$this->path = $path;
$this->url = $url;
$this->_maybe_adjust_path_for_multisite( $uploads_dir_info );
/**
* Absolute path to directory where we can store cache files.
*
* @since 4.0.8
* @var string
*/
define( 'ET_CORE_CACHE_DIR', $this->path );
/**
* URL to {@see ET_CORE_CACHE_DIR}.
*
* @since 4.0.8
* @var string
*/
define( 'ET_CORE_CACHE_DIR_URL', $this->url );
$this->can_write && et_()->ensure_directory_exists( $this->path );
}
/**
* Ensures that the WP Filesystem API has been initialized.
*
* @since??
*
* @return WP_Filesystem_Base
*/
protected function _initialize_wpfs() {
require_once ABSPATH . 'wp-admin/includes/file.php';
if ( defined( 'ET_CORE_CACHE_DIR' ) && @WP_Filesystem( array(), ET_CORE_CACHE_DIR, true ) ) {
// We can write to a user-specified directory
return $this->wpfs = $GLOBALS['wp_filesystem'];
}
if ( @WP_Filesystem( array(), false, true ) ) {
// We can write to WP_CONTENT_DIR
return $this->wpfs = $GLOBALS['wp_filesystem'];
}
$uploads_dir = (object) wp_get_upload_dir();
if ( @WP_Filesystem( array(), $uploads_dir->basedir, true ) ) {
// We can write to the uploads directory
return $this->wpfs = $GLOBALS['wp_filesystem'];
}
// We aren't able to write to the filesystem so let's just make sure $this->wpfs
// is an instance of the filesystem base class so that calling it won't cause errors.
require_once ABSPATH . 'wp-admin/includes/class-wp-filesystem-base.php';
// Write notice to log when WP_DEBUG is enabled.
$nl = PHP_EOL;
$msg = 'Unable to write to filesystem. Please ensure that PHP has write access to one of ';
$msg .= "the following directories:{$nl}{$nl}\t- WP_CONTENT_DIR{$nl}\t- wp_upload_dir(){$nl}\t- ET_CORE_CACHE_DIR.";
et_debug( $msg );
return $this->wpfs = new WP_Filesystem_Base;
}
/**
* Adjusts the path for multisite if necessary.
*
* @since 4.0.8
*
* @param stdClass $uploads_dir_info (object) wp_get_upload_dir()
*/
protected function _maybe_adjust_path_for_multisite( $uploads_dir_info ) {
if ( et_()->starts_with( $this->path, $uploads_dir_info->basedir ) || ! is_multisite() ) {
return;
}
$site = get_site();
$network_id = $site->site_id;
$site_id = $site->blog_id;
$this->path = et_()->path( $this->path, $network_id, $site_id );
$this->url = et_()->path( $this->url, $network_id, $site_id );
}
/**
* Whether or not the user has defined a custom path for the cache directory.
*
* @since 4.0.8
*
* @return bool
*/
protected function _maybe_use_custom_path() {
if ( ! defined( 'ET_CORE_CACHE_DIR' ) ) {
return false;
}
$this->path = ET_CORE_CACHE_DIR;
if ( ! $this->can_write = $this->wpfs->is_writable( $this->path ) ) {
et_wrong( 'ET_CORE_CACHE_DIR is defined but not writable.', true );
}
if ( defined( 'ET_CORE_CACHE_DIR_URL' ) ) {
$this->url = ET_CORE_CACHE_DIR_URL;
} else {
et_wrong( 'When ET_CORE_CACHE_DIR is defined, ET_CORE_CACHE_DIR_URL must also be defined.', true );
}
return true;
}
/**
* Returns the class instance.
*
* @since 4.0.8
*
* @return ET_Core_Cache_Directory
*/
public static function instance() {
if ( ! self::$_instance ) {
self::$_instance = new self;
}
return self::$_instance;
}
}

148
core/components/cache/File.php vendored Normal file
View File

@ -0,0 +1,148 @@
<?php
/**
* Core class that implements an file cache.
*
* @since 3.27.3
*
* The Object Cache stores all of the cache data to file and makes the cache
* contents available by using a file name as key, which is used to name and later retrieve
* the cache contents.
*
* @package ET\Core\Cache_File
*/
class ET_Core_Cache_File {
/**
* Cached data holder.
*
* @since 3.27.3
*
* @var array
*/
protected static $_cache = array();
/**
* Loaded cache file data.
*
* @since 3.27.3
*
* @var array
*/
protected static $_cache_loaded = array();
/**
* Cached data status.
*
* @since 3.27.3
*
* @var array
*/
protected static $_dirty = array();
/**
* Sets the data contents into the cache.
*
* @since 3.27.3
*
* @param string $cache_name What is the file name that storing the cache data.
* @param mixed $data The cache data to be set.
*
* @return void
*/
public static function set( $cache_name, $data ) {
if ( self::is_disabled() ) {
return;
}
self::$_cache[ $cache_name ] = $data;
self::$_dirty[ $cache_name ] = $cache_name;
}
/**
* Retrieves the cache contents, if it exists.
*
* @since 3.27.3
*
* @param string $cache_name What is the file name that storing the cache data.
*
* @return mixed
*/
public static function get( $cache_name ) {
if ( self::is_disabled() ) {
return array();
}
if ( ! isset( self::$_cache_loaded[ $cache_name ] ) ) {
$file = self::get_cache_file_name( $cache_name );
if ( et_()->WPFS()->is_readable( $file ) ) {
self::$_cache[ $cache_name ] = unserialize( et_()->WPFS()->get_contents( $file ) );
} else {
self::$_cache[ $cache_name ] = array();
}
self::$_cache_loaded[ $cache_name ] = true;
}
return isset( self::$_cache[ $cache_name ] ) ? self::$_cache[ $cache_name ] : array();
}
/**
* Saves Cache.
*
* @since 3.27.3
*
* @return void
*/
public static function save_cache() {
if ( self::is_disabled() || ! self::$_dirty || ! self::$_cache ) {
return;
}
foreach ( self::$_dirty as $cache_name ) {
if ( ! isset( self::$_cache[ $cache_name ] ) ) {
continue;
}
$data = self::$_cache[ $cache_name ];
$file = self::get_cache_file_name( $cache_name );
if ( ! wp_is_writable( dirname( $file ) ) ) {
continue;
}
et_()->WPFS()->put_contents( $file, serialize( $data ) );
}
}
/**
* Get full path of cache file name.
*
* The file name will be suffixed with .data
*
* @since 3.27.3
*
* @param string $cache_name What is the file name that storing the cache data.
*
* @return string
*/
public static function get_cache_file_name( $cache_name ) {
return sprintf( '%1$s/%2$s.data', ET_Core_PageResource::get_cache_directory(), $cache_name );
}
/**
* Check is file based caching is disabled.
*
* @since 4.0.8
*
* @return bool
*/
public static function is_disabled() {
return defined( 'ET_DISABLE_FILE_BASED_CACHE' ) && ET_DISABLE_FILE_BASED_CACHE;
}
}
// Hook the shutdown action once.
if ( ! has_action( 'shutdown', 'ET_Core_Cache_File::save_cache' ) ) {
add_action( 'shutdown', 'ET_Core_Cache_File::save_cache' );
}

12
core/components/cache/init.php vendored Normal file
View File

@ -0,0 +1,12 @@
<?php
if ( ! defined( 'et_core_cache_init' ) ):
function et_core_cache_init() {}
endif;
if ( ! defined( 'et_core_cache_dir' ) ):
function et_core_cache_dir() {
return ET_Core_Cache_Directory::instance();
}
endif;

View File

@ -0,0 +1,24 @@
<?php
/**
* Utility class for replacing scripts in a string.
*
* @since 3.18.5
*
* @package ET\Core\Data
*/
class ET_Core_Data_ScriptReplacer {
private $_map = array();
public function replace( $matches ) {
$script = $matches[0];
$id = md5( $script );
$this->_map[ $id ] = $script;
return $id;
}
public function map() {
return $this->_map;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,439 @@
<?php
if ( ! function_exists( 'et_core_data_init' ) ):
function et_core_data_init() {}
endif;
if ( ! function_exists( 'et_' ) ):
function et_() {
global $et_;
if ( ! $et_ ) {
$et_ = ET_Core_Data_Utils::instance();
}
return $et_;
}
endif;
if ( ! function_exists( 'et_html_attr' ) ):
/**
* Generates a properly escaped attribute string.
*
* @param string $name The attribute name.
* @param string $value The attribute value.
* @param bool $space_before Whether or not the result should start with a space. Default is `true`.
*
* @return string
*/
function et_html_attr( $name, $value, $space_before = true ) {
$result = ' ' . esc_attr( $name ) . '="' . esc_attr( $value ) . '"';
return $space_before ? $result : trim( $result );
}
endif;
if ( ! function_exists( 'et_html_attrs' ) ):
/**
* Generate properly escaped attributes string
*
* @since 3.10
*
* @param array $attributes Array of attributes
*
* @return string
*/
function et_html_attrs( $attributes = array() ) {
$output = '';
foreach ( $attributes as $name => $value ) {
$parsed_value = is_array( $value ) ? implode( ' ', $value ) : $value;
$output .= et_html_attr( $name, $parsed_value );
}
return $output;
}
endif;
if ( ! function_exists( 'et_sanitized_previously' ) ):
/**
* Semantical previously sanitized acknowledgement
*
* @deprecated {@see et_core_sanitized_previously()}
*
* @since 3.17.3 Deprecated
*
* @param mixed $value The value being passed-through
*
* @return mixed
*/
function et_sanitized_previously( $value ) {
et_debug( "You're Doing It Wrong! Attempted to call " . __FUNCTION__ . "(), use et_core_sanitized_previously() instead." );
return $value;
}
endif;
if ( ! function_exists( 'et_core_sanitized_previously' ) ):
/**
* Semantical previously sanitized acknowledgement
*
* @since 3.17.3
*
* @param mixed $value The value being passed-through
*
* @return mixed
*/
function et_core_sanitized_previously( $value ) {
return $value;
}
endif;
if ( ! function_exists( 'et_core_esc_attr' ) ):
/**
* Escape attribute value
*
* @since 3.27.1?
*
* @param string $attr_key Element attribute key.
* @param (string|array) $attr_value Element attribute value.
*
* @return (string|array|WP_Error)
*/
function et_core_esc_attr( $attr_key, $attr_value ) {
// Skip validation for landscape image default value.
$image_landscape = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTA4MCIgaGVpZ2h0PSI1NDAiIHZpZXdCb3g9IjAgMCAxMDgwIDU0MCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KICAgIDxnIGZpbGw9Im5vbmUiIGZpbGwtcnVsZT0iZXZlbm9kZCI+CiAgICAgICAgPHBhdGggZmlsbD0iI0VCRUJFQiIgZD0iTTAgMGgxMDgwdjU0MEgweiIvPgogICAgICAgIDxwYXRoIGQ9Ik00NDUuNjQ5IDU0MGgtOTguOTk1TDE0NC42NDkgMzM3Ljk5NSAwIDQ4Mi42NDR2LTk4Ljk5NWwxMTYuMzY1LTExNi4zNjVjMTUuNjItMTUuNjIgNDAuOTQ3LTE1LjYyIDU2LjU2OCAwTDQ0NS42NSA1NDB6IiBmaWxsLW9wYWNpdHk9Ii4xIiBmaWxsPSIjMDAwIiBmaWxsLXJ1bGU9Im5vbnplcm8iLz4KICAgICAgICA8Y2lyY2xlIGZpbGwtb3BhY2l0eT0iLjA1IiBmaWxsPSIjMDAwIiBjeD0iMzMxIiBjeT0iMTQ4IiByPSI3MCIvPgogICAgICAgIDxwYXRoIGQ9Ik0xMDgwIDM3OXYxMTMuMTM3TDcyOC4xNjIgMTQwLjMgMzI4LjQ2MiA1NDBIMjE1LjMyNEw2OTkuODc4IDU1LjQ0NmMxNS42Mi0xNS42MiA0MC45NDgtMTUuNjIgNTYuNTY4IDBMMTA4MCAzNzl6IiBmaWxsLW9wYWNpdHk9Ii4yIiBmaWxsPSIjMDAwIiBmaWxsLXJ1bGU9Im5vbnplcm8iLz4KICAgIDwvZz4KPC9zdmc+Cg==';
if ( $attr_value === $image_landscape ) {
return $attr_value;
}
// Skip validation for portrait image default value.
$image_portrait = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTAwIiBoZWlnaHQ9IjUwMCIgdmlld0JveD0iMCAwIDUwMCA1MDAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgICA8ZyBmaWxsPSJub25lIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiPgogICAgICAgIDxwYXRoIGZpbGw9IiNFQkVCRUIiIGQ9Ik0wIDBoNTAwdjUwMEgweiIvPgogICAgICAgIDxyZWN0IGZpbGwtb3BhY2l0eT0iLjEiIGZpbGw9IiMwMDAiIHg9IjY4IiB5PSIzMDUiIHdpZHRoPSIzNjQiIGhlaWdodD0iNTY4IiByeD0iMTgyIi8+CiAgICAgICAgPGNpcmNsZSBmaWxsLW9wYWNpdHk9Ii4xIiBmaWxsPSIjMDAwIiBjeD0iMjQ5IiBjeT0iMTcyIiByPSIxMDAiLz4KICAgIDwvZz4KPC9zdmc+Cg==';
if ( $attr_value === $image_portrait ) {
return $attr_value;
}
$attr_key = strtolower( $attr_key );
$allowed_attrs_default = array(
// Filter style.
// @see https://developer.wordpress.org/reference/functions/safecss_filter_attr/
'style' => 'safecss_filter_attr',
// We just pick some of the HTML attributes considered safe to use.
'data-*' => 'esc_attr',
'align' => 'esc_attr',
'alt' => 'esc_attr',
'autofocus' => 'esc_attr',
'autoplay' => 'esc_attr',
'class' => 'esc_attr',
'cols' => 'esc_attr',
'controls' => 'esc_attr',
'disabled' => 'esc_attr',
'height' => 'esc_attr',
'id' => 'esc_attr',
'max' => 'esc_attr',
'min' => 'esc_attr',
'multiple' => 'esc_attr',
'name' => 'esc_attr',
'placeholder' => 'esc_attr',
'required' => 'esc_attr',
'rows' => 'esc_attr',
'size' => 'esc_attr',
'sizes' => 'esc_attr',
'srcset' => 'esc_attr',
'step' => 'esc_attr',
'tabindex' => 'esc_attr',
'target' => 'esc_attr',
'title' => 'esc_attr',
'type' => 'esc_attr',
'value' => 'esc_attr',
'width' => 'esc_attr',
// We just pick some of the HTML attributes containing a URL.
// @see https://developer.wordpress.org/reference/functions/wp_kses_uri_attributes/
'action' => 'esc_url',
'background' => 'esc_url',
'formaction' => 'esc_url',
'href' => 'esc_url',
'icon' => 'esc_url',
'poster' => 'esc_url',
'src' => 'esc_url',
'usemap' => 'esc_url',
);
/**
* Filters allowed attributes
*
* @since 3.27.1?
*
* @param array $allowed_attrs_default Key/value paired array, the key used as the attribute identifier,
* the value used as the callback to escape the value.
* @param string $attr_key Element attribute key.
* @param (string|array) $attr_value Element attribute value.
*
* @return array
*/
$allowed_attrs = apply_filters( 'et_core_esc_attr', $allowed_attrs_default, $attr_key, $attr_value );
// Get attribute key callback.
$callback = isset( $allowed_attrs[ $attr_key ] ) && 'data-*' !== $attr_key ? $allowed_attrs[ $attr_key ] : false;
// Get data attribute key callback.
if ( ! $callback && 'data-*' !== $attr_key && 0 === strpos( $attr_key, 'data-' ) && isset( $allowed_attrs['data-*'] ) ) {
$callback = $allowed_attrs['data-*'];
}
if ( ! $callback || ! is_callable( $callback ) ) {
return new WP_Error( 'invalid_attr_key', __( 'Invalid attribute key', 'et_builder' ) );
}
if ( is_array( $attr_value ) ) {
return array_map( $callback, $attr_value );
}
// @phpcs:ignore Generic.PHP.ForbiddenFunctions.Found
return call_user_func( $callback, $attr_value );
}
endif;
if ( ! function_exists( 'et_core_sanitize_element_tag' ) ):
/**
* Sanitize element tag
*
* @since 3.27.1?
*
* @param string $tag Element tag.
*
* @return (string|WP_Error)
*/
function et_core_sanitize_element_tag( $tag ) {
$tag = strtolower( $tag );
$headings = array(
'h1',
'h2',
'h3',
'h4',
'h5',
'h6',
);
// Bail early for heading tags.
if ( in_array( $tag, $headings, true ) ) {
return $tag;
}
$tag = preg_replace( '/[^a-z]/', '', $tag );
$disallowed_tags = array(
'applet',
'body',
'canvas',
'command',
'content',
'element',
'embed',
'frame',
'frameset',
'head',
'html',
'iframe',
'noembed',
'noframes',
'noscript',
'object',
'param',
'script',
'shadow',
'slot',
'style',
'template',
'title',
'xml',
);
if ( empty( $tag ) || in_array( $tag, $disallowed_tags, true ) ) {
return new WP_Error( 'invalid_element_tag', __( 'Invalid tag element', 'et_builder' ) );
}
return $tag;
}
endif;
/**
* Pass thru semantical previously escaped acknowledgement
*
* @since 3.17.3
*
* @param string value being passed through
* @return string
*/
if ( ! function_exists( 'et_core_esc_previously' ) ) :
function et_core_esc_previously( $passthru ) {
return $passthru;
}
endif;
/**
* Pass thru function used to pacfify phpcs sniff.
* Used when the nonce is checked elsewhere.
*
* @since 3.17.3
*
* @return void
*/
if ( ! function_exists( 'et_core_nonce_verified_previously' ) ) :
function et_core_nonce_verified_previously() {
// :)
}
endif;
/**
* Pass thru semantical escaped by WordPress core acknowledgement
*
* @since 3.17.3
*
* @param string value being passed through
* @return string
*/
if ( ! function_exists( 'et_core_esc_wp' ) ) :
function et_core_esc_wp( $passthru ) {
return $passthru;
}
endif;
/**
* Pass thru semantical intentionally unescaped acknowledgement
*
* @since 3.17.3
*
* @param string value being passed through
* @param string excuse the value is allowed to be unescaped
* @return string
*/
if ( ! function_exists( 'et_core_intentionally_unescaped' ) ) :
function et_core_intentionally_unescaped( $passthru, $excuse ) {
// Add valid excuses as they arise
$valid_excuses = array(
'cap_based_sanitized',
'fixed_string',
'react_jsx',
'html',
'underscore_template',
);
if ( ! in_array( $excuse, $valid_excuses ) ) {
_doing_it_wrong( __FUNCTION__, esc_html__( 'This is not a valid excuse to not escape the passed value.', 'et_core' ), esc_html( et_get_theme_version() ) );
}
return $passthru;
}
endif;
/**
* Sanitize value depending on user capability
*
* @since 3.17.3
*
* @return string value being passed through
*/
if ( ! function_exists( 'et_core_sanitize_value_by_cap' ) ) :
function et_core_sanitize_value_by_cap( $passthru, $sanitize_function = 'et_sanitize_html_input_text', $cap = 'unfiltered_html' ) {
if ( ! current_user_can( $cap ) ) {
$passthru = $sanitize_function( $passthru );
}
return $passthru;
}
endif;
/**
* Pass thru semantical intentionally unsanitized acknowledgement
*
* @since 3.17.3
*
* @param string value being passed through
* @param string excuse the value is allowed to be unsanitized
* @return string
*/
if ( ! function_exists( 'et_core_intentionally_unsanitized' ) ) :
function et_core_intentionally_unsanitized( $passthru, $excuse ) {
// Add valid excuses as they arise
$valid_excuses = array();
if ( ! in_array( $excuse, $valid_excuses ) ) {
_doing_it_wrong( __FUNCTION__, esc_html__( 'This is not a valid excuse to not sanitize the passed value.', 'et_core' ), esc_html( et_get_theme_version() ) );
}
return $passthru;
}
endif;
/**
* Fixes unclosed HTML tags
*
* @since 3.18.4
*
* @param string $content source HTML
*
* @return string
*/
if ( ! function_exists( 'et_core_fix_unclosed_html_tags' ) ):
function et_core_fix_unclosed_html_tags( $content ) {
// Exit if source has no HTML tags or we miss what we need to fix them anyway.
if ( false === strpos( $content, '<' ) || ! class_exists( 'DOMDocument' ) ) {
return $content;
}
$scripts = false;
if ( false !== strpos( $content, '<script' ) ) {
// Replace scripts with placeholders so we don't mess with HTML included in JS strings.
$scripts = new ET_Core_Data_ScriptReplacer();
$content = preg_replace_callback( '|<script.*?>[\s\S]+?</script>|', array( $scripts, 'replace' ), $content );
}
$doc = new DOMDocument();
@$doc->loadHTML( sprintf(
'<html><head>%s</head><body>%s</body></html>',
// Use WP charset
sprintf( '<meta http-equiv="content-type" content="text/html; charset=%s" />', get_bloginfo( 'charset' ) ),
$content
) );
if ( preg_match( '|<body>([\s\S]+)</body>|', $doc->saveHTML(), $matches ) ) {
// Extract the fixed content.
$content = $matches[1];
}
if ( $scripts ) {
// Replace placeholders with scripts.
$content = strtr( $content, $scripts->map() );
}
return $content;
}
endif;
/**
* Converts string to UTF-8 if mb_convert_encoding function exists
*
* @since 3.19.17
*
* @param string $string source string
*
* @return string
*/
if ( ! function_exists( 'et_core_maybe_convert_to_utf_8' ) ):
function et_core_maybe_convert_to_utf_8( $string ) {
if ( function_exists( 'mb_convert_encoding' ) ) {
return mb_convert_encoding( $string, 'UTF-8' );
}
return $string;
}
endif;

361
core/components/init.php Normal file
View File

@ -0,0 +1,361 @@
<?php
// phpcs:disable Generic.WhiteSpace.ScopeIndent -- our preference is to not indent the whole inner function in this scenario.
if ( ! function_exists( 'et_core_init' ) ) :
/**
* {@see 'plugins_loaded' (9999999) Must run after cache plugins have been loaded.}
*/
function et_core_init() {
ET_Core_API_Spam_Providers::instance();
ET_Core_Cache_Directory::instance();
ET_Core_PageResource::startup();
ET_Core_CompatibilityWarning::instance();
if ( defined( 'ET_CORE_UPDATED' ) ) {
global $wp_rewrite;
add_action( 'shutdown', array( $wp_rewrite, 'flush_rules' ) );
update_option( 'et_core_page_resource_remove_all', true );
}
$cache_dir = ET_Core_PageResource::get_cache_directory();
if ( file_exists( $cache_dir . '/DONOTCACHEPAGE' ) ) {
! defined( 'DONOTCACHEPAGE' ) ? define( 'DONOTCACHEPAGE', true ) : '';
@unlink( $cache_dir . '/DONOTCACHEPAGE' );
}
if ( get_option( 'et_core_page_resource_remove_all' ) ) {
ET_Core_PageResource::remove_static_resources( 'all', 'all', true );
}
if ( ! wp_next_scheduled( 'et_core_page_resource_auto_clear' ) ) {
wp_schedule_event( time() + MONTH_IN_SECONDS, 'monthly', 'et_core_page_resource_auto_clear' );
}
}
endif;
if ( ! function_exists( 'et_core_site_has_builder' ) ) :
/**
* Check is `et_core_site_has_builder` allowed.
* We can clear cache managed by 3rd party plugins only
* if Divi, Extra, or the Divi Builder plugin
* is active when the core was called.
*
* @return boolean
*/
function et_core_site_has_builder() {
global $shortname;
$core_path = get_transient( 'et_core_path' );
$is_divi_builder_plugin_active = false;
if ( ! empty( $core_path ) && false !== strpos( $core_path, '/divi-builder/' ) && function_exists('is_plugin_active') ) {
$is_divi_builder_plugin_active = is_plugin_active( 'divi-builder/divi-builder.php' );
}
if( $is_divi_builder_plugin_active || in_array( $shortname, array( 'divi', 'extra' ) ) ) {
return true;
}
return false;
}
endif;
if ( ! function_exists( 'et_core_clear_wp_cache' ) ):
function et_core_clear_wp_cache( $post_id = '' ) {
if ( ( ! wp_doing_cron() && ! et_core_security_check_passed( 'edit_posts' ) ) || ! et_core_site_has_builder() ) {
return;
}
try {
// Cache Plugins
// Comet Cache
if ( is_callable( 'comet_cache::clear' ) ) {
comet_cache::clear();
}
// WP Rocket
if ( function_exists( 'rocket_clean_post' ) ) {
if ( '' !== $post_id ) {
rocket_clean_post( $post_id );
} else if ( function_exists( 'rocket_clean_domain' ) ) {
rocket_clean_domain();
}
}
// W3 Total Cache
if ( has_action( 'w3tc_flush_post' ) ) {
'' !== $post_id ? do_action( 'w3tc_flush_post', $post_id ) : do_action( 'w3tc_flush_posts' );
}
// WP Super Cache
if ( function_exists( 'wp_cache_debug' ) && defined( 'WPCACHEHOME' ) ) {
include_once WPCACHEHOME . 'wp-cache-phase1.php';
include_once WPCACHEHOME . 'wp-cache-phase2.php';
if ( '' !== $post_id && function_exists( 'clear_post_supercache' ) ) {
clear_post_supercache( $post_id );
} else if ( '' === $post_id && function_exists( 'wp_cache_clear_cache_on_menu' ) ) {
wp_cache_clear_cache_on_menu();
}
}
// WP Fastest Cache
if ( isset( $GLOBALS['wp_fastest_cache'] ) ) {
if ( '' !== $post_id && method_exists( $GLOBALS['wp_fastest_cache'], 'singleDeleteCache' ) ) {
$GLOBALS['wp_fastest_cache']->singleDeleteCache( $post_id );
} else if ( '' === $post_id && method_exists( $GLOBALS['wp_fastest_cache'], 'deleteCache' ) ) {
$GLOBALS['wp_fastest_cache']->deleteCache();
}
}
// Hummingbird
if ( has_action( 'wphb_clear_page_cache' ) ) {
'' !== $post_id ? do_action( 'wphb_clear_page_cache', $post_id ) : do_action( 'wphb_clear_page_cache' );
}
// WordPress Cache Enabler
if ( has_action( 'ce_clear_cache' ) ) {
'' !== $post_id ? do_action( 'ce_clear_post_cache', $post_id ) : do_action( 'ce_clear_cache' );
}
// LiteSpeed Cache v3.0+.
if ( '' !== $post_id && has_action( 'litespeed_purge_post' ) ) {
do_action( 'litespeed_purge_post', $post_id );
} elseif ( '' === $post_id && has_action( 'litespeed_purge_all' ) ) {
do_action( 'litespeed_purge_all' );
}
// LiteSpeed Cache v1.1.3 until v3.0.
if ( '' !== $post_id && function_exists( 'litespeed_purge_single_post' ) ) {
litespeed_purge_single_post( $post_id );
} elseif ( '' === $post_id && is_callable( 'LiteSpeed_Cache_API::purge_all' ) ) {
LiteSpeed_Cache_API::purge_all();
} elseif ( is_callable( 'LiteSpeed_Cache::get_instance' ) ) {
// LiteSpeed Cache v1.1.3 below. LiteSpeed_Cache still exist on v2.9.9.2, but no
// longer exist on v3.0. Keep it here as backward compatibility for lower version.
$litespeed = LiteSpeed_Cache::get_instance();
if ( '' !== $post_id && method_exists( $litespeed, 'purge_post' ) ) {
$litespeed->purge_post( $post_id );
} else if ( '' === $post_id && method_exists( $litespeed, 'purge_all' ) ) {
$litespeed->purge_all();
}
}
// Hyper Cache
if ( class_exists( 'HyperCache' ) && isset( HyperCache::$instance ) ) {
if ( '' !== $post_id && method_exists( HyperCache::$instance, 'clean_post' ) ) {
HyperCache::$instance->clean_post( $post_id );
} else if ( '' === $post_id && method_exists( HyperCache::$instance, 'clean' ) ) {
HyperCache::$instance->clean_post( $post_id );
}
}
// Hosting Provider Caching
// Pantheon Advanced Page Cache
$pantheon_clear = 'pantheon_wp_clear_edge_keys';
$pantheon_clear_all = 'pantheon_wp_clear_edge_all';
if ( function_exists( $pantheon_clear ) || function_exists( $pantheon_clear_all ) ) {
if ( '' !== $post_id && function_exists( $pantheon_clear ) ) {
pantheon_wp_clear_edge_keys( array( "post-{$post_id}" ) );
} else if ( '' === $post_id && function_exists( $pantheon_clear_all ) ) {
pantheon_wp_clear_edge_all();
}
}
// Siteground
if ( isset( $GLOBALS['sg_cachepress_supercacher'] ) ) {
global $sg_cachepress_supercacher;
if ( is_object( $sg_cachepress_supercacher ) && method_exists( $sg_cachepress_supercacher, 'purge_cache' ) ) {
$sg_cachepress_supercacher->purge_cache( true );
}
} else if ( function_exists( 'sg_cachepress_purge_cache' ) ) {
sg_cachepress_purge_cache();
}
// WP Engine
if ( class_exists( 'WpeCommon' ) ) {
is_callable( 'WpeCommon::purge_memcached' ) ? WpeCommon::purge_memcached() : '';
is_callable( 'WpeCommon::clear_maxcdn_cache' ) ? WpeCommon::clear_maxcdn_cache() : '';
is_callable( 'WpeCommon::purge_varnish_cache' ) ? WpeCommon::purge_varnish_cache() : '';
if ( is_callable( 'WpeCommon::instance' ) && $instance = WpeCommon::instance() ) {
method_exists( $instance, 'purge_object_cache' ) ? $instance->purge_object_cache() : '';
}
}
// Bluehost
if ( class_exists( 'Endurance_Page_Cache' ) ) {
wp_doing_ajax() ? ET_Core_LIB_BluehostCache::get_instance()->clear( $post_id ) : do_action( 'epc_purge' );
}
// Pressable.
if ( isset( $GLOBALS['batcache'] ) && is_object( $GLOBALS['batcache'] ) ) {
wp_cache_flush();
}
// Cloudways - Breeze.
if ( class_exists( 'Breeze_Admin' ) ) {
$breeze_admin = new Breeze_Admin();
$breeze_admin->breeze_clear_all_cache();
}
// Kinsta.
if ( class_exists( '\Kinsta\Cache' ) && isset( $GLOBALS['kinsta_cache'] ) && is_object( $GLOBALS['kinsta_cache'] ) ) {
global $kinsta_cache;
if ( isset( $kinsta_cache->kinsta_cache_purge ) && method_exists( $kinsta_cache->kinsta_cache_purge, 'purge_complete_caches' ) ) {
$kinsta_cache->kinsta_cache_purge->purge_complete_caches();
}
}
// GoDaddy.
if ( class_exists( '\WPaaS\Cache' ) ) {
if ( ! \WPaaS\Cache::has_ban() ) {
remove_action( 'shutdown', array( '\WPaaS\Cache', 'purge' ), PHP_INT_MAX );
add_action( 'shutdown', array( '\WPaaS\Cache', 'ban' ), PHP_INT_MAX );
}
}
// Complimentary Performance Plugins.
// Autoptimize.
if ( is_callable( 'autoptimizeCache::clearall' ) ) {
autoptimizeCache::clearall();
}
// WP Optimize.
if ( class_exists( 'WP_Optimize' ) && defined( 'WPO_PLUGIN_MAIN_PATH' ) ) {
if ( '' !== $post_id && is_callable( 'WPO_Page_Cache::delete_single_post_cache' ) ) {
WPO_Page_Cache::delete_single_post_cache( $post_id );
} elseif ( is_callable( array( 'WP_Optimize', 'get_page_cache' ) ) && is_callable( array( WP_Optimize()->get_page_cache(), 'purge' ) ) ) {
WP_Optimize()->get_page_cache()->purge();
}
}
} catch( Exception $err ) {
ET_Core_Logger::error( 'An exception occurred while attempting to clear site cache.' );
}
}
endif;
if ( ! function_exists( 'et_core_get_nonces' ) ):
/**
* Returns the nonces for this component group.
*
* @return string[]
*/
function et_core_get_nonces() {
static $nonces = null;
return $nonces ? $nonces : $nonces = array(
'clear_page_resources_nonce' => wp_create_nonce( 'clear_page_resources' ),
);
}
endif;
if ( ! function_exists( 'et_core_page_resource_auto_clear' ) ):
function et_core_page_resource_auto_clear() {
ET_Core_PageResource::remove_static_resources( 'all', 'all' );
}
add_action( 'switch_theme', 'et_core_page_resource_auto_clear' );
add_action( 'activated_plugin', 'et_core_page_resource_auto_clear', 10, 0 );
add_action( 'deactivated_plugin', 'et_core_page_resource_auto_clear', 10, 0 );
add_action( 'et_core_page_resource_auto_clear', 'et_core_page_resource_auto_clear' );
endif;
if ( ! function_exists( 'et_core_page_resource_clear' ) ):
/**
* Ajax handler for clearing cached page resources.
*/
function et_core_page_resource_clear() {
et_core_security_check( 'manage_options', 'clear_page_resources' );
if ( empty( $_POST['et_post_id'] ) ) {
et_core_die();
}
$post_id = sanitize_key( $_POST['et_post_id'] );
$owner = sanitize_key( $_POST['et_owner'] );
ET_Core_PageResource::remove_static_resources( $post_id, $owner );
}
add_action( 'wp_ajax_et_core_page_resource_clear', 'et_core_page_resource_clear' );
endif;
if ( ! function_exists( 'et_core_page_resource_get' ) ):
/**
* Get a page resource instance.
*
* @param string $owner The owner of the instance (core|divi|builder|bloom|monarch|custom).
* @param string $slug A string that uniquely identifies the resource.
* @param string|int $post_id The post id that the resource is associated with or `global`.
* If `null`, the return value of {@link get_the_ID()} will be used.
* @param string $type The resource type (style|script). Default: `style`.
* @param string $location Where the resource should be output (head|footer). Default: `head-late`.
*
* @return ET_Core_PageResource
*/
function et_core_page_resource_get( $owner, $slug, $post_id = null, $priority = 10, $location = 'head-late', $type = 'style' ) {
$post_id = $post_id ? $post_id : et_core_page_resource_get_the_ID();
$_slug = "et-{$owner}-{$slug}-{$post_id}-cached-inline-{$type}s";
$all_resources = ET_Core_PageResource::get_resources();
return isset( $all_resources[ $_slug ] )
? $all_resources[ $_slug ]
: new ET_Core_PageResource( $owner, $slug, $post_id, $priority, $location, $type );
}
endif;
if ( ! function_exists( 'et_core_page_resource_get_the_ID' ) ):
function et_core_page_resource_get_the_ID() {
static $post_id = null;
if ( is_int( $post_id ) ) {
return $post_id;
}
return $post_id = apply_filters( 'et_core_page_resource_current_post_id', get_the_ID() );
}
endif;
if ( ! function_exists( 'et_core_page_resource_is_singular' ) ):
function et_core_page_resource_is_singular() {
return apply_filters( 'et_core_page_resource_is_singular', is_singular() );
}
endif;
if ( ! function_exists( 'et_debug' ) ):
function et_debug( $msg, $bt_index = 4, $log_ajax = true ) {
ET_Core_Logger::debug( $msg, $bt_index, $log_ajax );
}
endif;
if ( ! function_exists( 'et_wrong' ) ):
function et_wrong( $msg, $error = false ) {
$msg = "You're Doing It Wrong! {$msg}";
if ( $error ) {
et_error( $msg );
} else {
et_debug( $msg );
}
}
endif;
if ( ! function_exists( 'et_error' ) ):
function et_error( $msg, $bt_index = 4 ) {
ET_Core_Logger::error( "[ERROR]: {$msg}", $bt_index );
}
endif;

View File

@ -0,0 +1,35 @@
<?php
if ( ! class_exists( 'Endurance_Page_Cache' ) ) {
return;
}
// https://github.com/bluehost/endurance-page-cache
class ET_Core_LIB_BluehostCache extends Endurance_Page_Cache {
private static $_instance;
public function __construct() {
$this->purged = array();
$this->trigger = null;
$this->cache_level = get_option( 'endurance_cache_level', 2 );
$this->cache_dir = WP_CONTENT_DIR . '/endurance-page-cache';
$this->cache_exempt = array( 'wp-admin', '.', 'checkout', 'cart', 'wp-json', '%', '=', '@', '&', ':', ';', );
}
public static function get_instance() {
if ( null === self::$_instance ) {
self::$_instance = new self;
}
return self::$_instance;
}
public function clear( $post_id = '' ) {
if ( '' !== $post_id && method_exists( $this, 'purge_single' ) ) {
$this->purge_single( get_the_permalink( $post_id ) );
} else if ( '' === $post_id && method_exists( $this, 'purge_all' ) ) {
$this->purge_all();
}
}
}

View File

@ -0,0 +1,503 @@
<?php
/**
* ET Core OAuth Library
*
* Copyright © 2016-2017 Elegant Themes, Inc.
*
* Based on code from the TwitterOAuth Library:
* - Copyright © 2009-2016 Abraham Williams
* - Copyright © 2007-2009 Andy Smith
*
* @license
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial
* portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
* OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
class ET_Core_LIB_OAuthBase {
/**
* Writes a message to the PHP error log.
*
* @since 1.1.0
*
* @param mixed $msg
*/
public static function write_log( $msg, $level = 'DEBUG', $_this = null ) {
$name = ( null !== $_this ) ? get_class( $_this ) : 'ET_Core_LIB_OAuthBase';
if ( ! is_string( $msg ) ) {
$msg = print_r( $msg, true );
}
error_log( "{$name} [{$level}]: $msg" );
}
}
class ET_Core_LIB_OAuthUtil {
public static function build_http_query( $params, $return_json=false ) {
if ( ! $params ) {
return '';
}
if ( $return_json ) {
// return json string without further processing if needed
return json_encode( $params );
}
// Url encode both the keys and the values
$keys = ET_Core_LIB_OAuthUtil::urlencode_rfc3986( array_keys( $params ) );
$values = ET_Core_LIB_OAuthUtil::urlencode_rfc3986( array_values( $params ) );
$params = array_combine( $keys, $values );
// Parameters are sorted by name, using lexicographical byte value ordering.
// Ref: Spec: 9.1.1 (1)
uksort( $params, 'strcmp' );
$pairs = array();
foreach ( $params as $parameter => $value ) {
if ( is_array( $value ) ) {
// When two or more parameters share the same name, they are sorted by their value
// Ref: Spec: 9.1.1 (1)
// June 12th, 2010 - changed to sort because of issue 164 by hidetaka
sort( $value, SORT_STRING );
foreach ( $value as $duplicate_value ) {
$pairs[] = "{$parameter}={$duplicate_value}";
}
} else {
$pairs[] = "{$parameter}={$value}";
}
}
// For each parameter, the name is separated from the corresponding value by an '=' character (ASCII code 61)
// Each name-value pair is separated by an '&' character (ASCII code 38)
return implode( '&', $pairs );
}
public static function parse_parameters( $input = '' ) {
if ( '' === $input ) {
return array();
}
$pairs = explode( '&', $input );
$parsed_parameters = array();
foreach ( $pairs as $pair ) {
$split = explode( '=', $pair, 2 );
$parameter = ET_Core_LIB_OAuthUtil::urldecode_rfc3986( $split[0] );
$value = isset( $split[1] ) ? ET_Core_LIB_OAuthUtil::urldecode_rfc3986( $split[1] ) : '';
if ( isset( $parsed_parameters[ $parameter ] ) ) {
// We have already received parameter(s) with this name, so add to the list
// of parameters with this name
if ( is_scalar( $parsed_parameters[ $parameter ] ) ) {
// This is the first duplicate, so transform scalar (string) into an array
// so we can add the duplicates
$parsed_parameters[ $parameter ] = array( $parsed_parameters[ $parameter ] );
}
$parsed_parameters[ $parameter ][] = $value;
} else {
$parsed_parameters[ $parameter ] = $value;
}
}
return $parsed_parameters;
}
public static function urldecode_rfc3986( $string ) {
return rawurldecode( $string );
}
public static function urlencode_rfc3986( $input ) {
$output = '';
if ( is_array( $input ) ) {
$output = array_map( array( 'ET_Core_LIB_OAuthUtil', 'urlencode_rfc3986' ), $input );
} else if ( is_scalar( $input ) ) {
$output = rawurlencode( utf8_encode( $input ) );
}
return $output;
}
}
/**
* A base class for implementing a Signature Method
* See section 9 ("Signing Requests") in the spec
*/
abstract class ET_Core_LIB_OAuthSignatureMethod {
/**
* Build the signature
* NOTE: The output of this function MUST NOT be urlencoded. The encoding is handled in
* {@link ET_Core_OAuth_Request} when the final request is serialized.
*
* @param ET_Core_LIB_OAuthRequest $request
* @param ET_Core_LIB_OAuthConsumer $consumer
* @param ET_Core_LIB_OAuthToken|null $token
*
* @return string
*/
abstract public function build_signature( $request, $consumer, $token = null );
/**
* Verifies that a given signature is correct
*
* @param ET_Core_LIB_OAuthRequest $request
* @param ET_Core_LIB_OAuthConsumer $consumer
* @param ET_Core_LIB_OAuthToken $token
* @param string $signature
*
* @return bool
*/
public function check_signature( $request, $consumer, $token, $signature ) {
$built = $this->build_signature( $request, $consumer, $token );
// Check for zero length, although its unlikely here
if ( empty( $built ) || empty( $signature ) ) {
return false;
}
if ( strlen( $built ) !== strlen( $signature ) ) {
return false;
}
// Avoid a timing leak with a (hopefully) time insensitive compare
$result = 0;
for ( $i = 0; $i < strlen( $signature ); $i ++ ) {
$result |= ord( $built[ $i ] ) ^ ord( $signature[ $i ] );
}
return 0 === $result;
}
/**
* Returns the name of this Signature Method (ie HMAC-SHA1)
*
* @return string
*/
abstract public function get_name();
}
/**
* The HMAC-SHA1 signature method uses the HMAC-SHA1 signature algorithm as defined in [RFC2104] where
* the Signature Base String is the text and the key is the concatenated values of the Consumer Secret and
* Token Secret (each encoded per Parameter Encoding first), separated by an '&' character (ASCII code 38)
* even if empty. As per Chapter 9.2 of the HMAC-SHA1 spec.
*/
class ET_Core_LIB_OAuthHMACSHA1 extends ET_Core_LIB_OAuthSignatureMethod {
/**
* @inheritDoc
*/
public function build_signature( $request, $consumer, $token = null ) {
$base_string = $request->get_signature_base_string();
$token = $token ? $token->secret : '';
$request->base_string = $base_string;
$key_parts = array( $consumer->secret, $token );
$key = implode( '&', $key_parts );
return base64_encode( hash_hmac( 'sha1', $base_string, $key, true ) );
}
/**
* @inheritDoc
*/
public function get_name() {
return 'HMAC-SHA1';
}
}
class ET_Core_LIB_OAuthConsumer {
public $callback_url;
public $id;
public $key;
public $secret;
public function __construct( $id, $secret, $callback_url = '' ) {
$this->id = $this->key = $id;
$this->secret = $secret;
$this->callback_url = $callback_url;
}
function __toString() {
$name = get_class( $this );
$key = 'ET_Core_LIB_OAuthConsumer' === $name ? 'key' : 'id';
return "{$name}[{$key}={$this->key}, secret={$this->secret}]";
}
}
class ET_Core_LIB_OAuthToken {
public $key;
public $secret;
public $refresh_token;
/**
* @param string $key The OAuth Token
* @param string $secret The OAuth Token Secret
*/
public function __construct( $key, $secret ) {
$this->key = $key;
$this->secret = $secret;
}
/**
* Generates the basic string serialization of a token that a server
* would respond to 'request_token' and 'access_token' calls with
*
* @return string
*/
public function __toString() {
return sprintf( "oauth_token=%s&oauth_token_secret=%s",
ET_Core_LIB_OAuthUtil::urlencode_rfc3986( $this->key ),
ET_Core_LIB_OAuthUtil::urlencode_rfc3986( $this->secret )
);
}
}
class ET_Core_LIB_OAuthRequest extends ET_Core_LIB_OAuthBase {
protected $parameters;
protected $http_method;
protected $http_url;
public static $version = '1.0';
public $base_string;
/**
* ET_Core_OAuth_Request Constructor
*
* @param string $http_method
* @param string $http_url
* @param array|null $parameters
*/
public function __construct( $http_method, $http_url, $parameters = array() ) {
$this->parameters = $parameters;
$this->http_method = $http_method;
$this->http_url = $http_url;
}
/**
* pretty much a helper function to set up the request
*
* @param ET_Core_LIB_OAuthConsumer $consumer
* @param ET_Core_LIB_OAuthToken $token
* @param string $http_method
* @param string $http_url
* @param array $parameters
*
* @return ET_Core_LIB_OAuthRequest
*/
public static function from_consumer_and_token( $consumer, $token, $http_method, $http_url, $parameters = array() ) {
$defaults = array(
"oauth_version" => ET_Core_LIB_OAuthRequest::$version,
"oauth_nonce" => ET_Core_LIB_OAuthRequest::generate_nonce(),
"oauth_timestamp" => time(),
"oauth_consumer_key" => $consumer->key
);
if ( $token ) {
$defaults['oauth_token'] = $token->key;
}
$parameters = wp_parse_args( $parameters, $defaults );
return new ET_Core_LIB_OAuthRequest( $http_method, $http_url, $parameters );
}
/**
* Returns the HTTP Method in uppercase
*
* @return string
*/
public function get_normalized_http_method() {
return strtoupper( $this->http_method );
}
/**
* parses the url and rebuilds it to be
* scheme://host/path
*
* @return string
*/
public function get_normalized_http_url() {
$parts = parse_url( $this->http_url );
$scheme = $parts['scheme'];
$host = strtolower( $parts['host'] );
$path = $parts['path'];
return "{$scheme}://{$host}{$path}";
}
/**
* @param $name
*
* @return string|null
*/
public function get_parameter( $name ) {
return isset( $this->parameters[ $name ] ) ? $this->parameters[ $name ] : null;
}
/**
* @return array
*/
public function get_parameters() {
return $this->parameters;
}
/**
* The request parameters, sorted and concatenated into a normalized string.
*
* @return string
*/
public function get_signable_parameters() {
// Grab a copy of all parameters
$params = $this->parameters;
// Remove oauth_signature if present
// Ref: Spec: 9.1.1 ("The oauth_signature parameter MUST be excluded.")
if ( isset( $params['oauth_signature'] ) ) {
unset( $params['oauth_signature'] );
}
return ET_Core_LIB_OAuthUtil::build_http_query( $params );
}
/**
* Returns the base string of this request
*
* The base string defined as the method, the url, and the parameters (normalized),
* each urlencoded and then concatenated with '&'.
*
* @return string
*/
public function get_signature_base_string() {
$parts = array(
$this->get_normalized_http_method(),
$this->get_normalized_http_url(),
$this->get_signable_parameters()
);
$parts = ET_Core_LIB_OAuthUtil::urlencode_rfc3986( $parts );
return implode( '&', $parts );
}
/**
* @param $name
*/
public function remove_parameter( $name ) {
unset( $this->parameters[ $name ] );
}
/**
* @param string $name
* @param string $value
*/
public function set_parameter( $name, $value ) {
$this->parameters[ $name ] = $value;
}
/**
* Builds the data one would send in a POST request
* @param bool $need_json indicates the query data format ( http query string or json string )
*
* @return string
*/
public function to_post_data( $need_json = false ) {
return ET_Core_LIB_OAuthUtil::build_http_query( $this->parameters, $need_json );
}
/**
* Builds a url usable for a GET request
*
* @return string
*/
public function to_url() {
$postData = $this->to_post_data();
$out = $this->get_normalized_http_url();
if ( $postData ) {
$out .= '?' . $postData;
}
return $out;
}
/**
* Builds the HTTP Authorization Header
*
* @return string
*/
public function to_header() {
$out = '';
foreach ( $this->parameters as $parameter => $value ) {
if ( 0 !== strpos( 'oauth', $parameter ) ) {
continue;
}
if ( is_array( $value ) ) {
self::write_log( 'Arrays not supported in headers!', 'ERROR' );
continue;
}
$out .= ( '' === $out ) ? 'OAuth ' : ', ';
$out .= ET_Core_LIB_OAuthUtil::urlencode_rfc3986( $parameter );
$out .= '="' . ET_Core_LIB_OAuthUtil::urlencode_rfc3986( $value ) . '"';
}
return $out;
}
/**
* @return string
*/
public function __toString() {
return $this->to_url();
}
/**
* @param ET_Core_LIB_OAuthSignatureMethod $signature_method
* @param ET_Core_LIB_OAuthConsumer $consumer
* @param ET_Core_LIB_OAuthToken $token
*/
public function sign_request( $signature_method, $consumer, $token = null ) {
$this->set_parameter( 'oauth_signature_method', $signature_method->get_name() );
$signature = $this->build_signature( $signature_method, $consumer, $token );
$this->set_parameter( 'oauth_signature', $signature );
}
/**
* @param ET_Core_LIB_OAuthSignatureMethod $signatureMethod
* @param ET_Core_LIB_OAuthConsumer $consumer
* @param ET_Core_LIB_OAuthToken $token
*
* @return string
*/
public function build_signature( $signatureMethod, $consumer, $token = null ) {
return $signatureMethod->build_signature( $this, $consumer, $token );
}
/**
* @return string
*/
public static function generate_nonce() {
return md5( microtime() . mt_rand() );
}
}

View File

@ -0,0 +1,27 @@
<?php
require_once( ABSPATH . 'wp-admin/includes/class-wp-upgrader.php' );
if ( ! class_exists( 'ET_Core_LIB_SilentThemeUpgraderSkin' ) ):
/**
* Theme Upgrader skin which does not output feedback.
*
* @since 3.10
*
* @private
*/
class ET_Core_LIB_SilentThemeUpgraderSkin extends WP_Upgrader_Skin {
/**
* Override feedback method.
*
* @since 3.10
*
* @private
*
* @param string $string Feedback string.
* @param mixed ...$args Optional text replacements.
*/
public function feedback( $string, ...$args ) {
return; // Suppress all feedback.
}
}
endif;

View File

@ -0,0 +1,354 @@
<?php
require_once ABSPATH . WPINC . '/class-http.php';
/**
* Some 3rd-party APIs require data to be sent in the request body for
* GET requests (eg. SendinBlue). This is not currently possible using the WP
* HTTP API. I've submitted a patch to WP Core for this. Until its merged, we
* have to extend the WP_HTTP class and override the method in question.
*
* @see https://core.trac.wordpress.org/ticket/39043
*
* @private
*/
class ET_Core_LIB_WPHttp extends WP_Http {
/**
* Send an HTTP request to a URI.
*
* Please note: The only URI that are supported in the HTTP Transport implementation
* are the HTTP and HTTPS protocols.
*
* @access public
* @since 2.7.0
*
* @param string $url The request URL.
* @param string|array $args {
* Optional. Array or string of HTTP request arguments.
*
* @type string $method Request method. Accepts 'GET', 'POST', 'HEAD', or 'PUT'.
* Some transports technically allow others, but should not be
* assumed. Default 'GET'.
* @type int $timeout How long the connection should stay open in seconds. Default 5.
* @type int $redirection Number of allowed redirects. Not supported by all transports
* Default 5.
* @type string $httpversion Version of the HTTP protocol to use. Accepts '1.0' and '1.1'.
* Default '1.0'.
* @type string $user-agent User-agent value sent.
* Default WordPress/' . get_bloginfo( 'version' ) . '; ' . get_bloginfo( 'url' ).
* @type bool $reject_unsafe_urls Whether to pass URLs through wp_http_validate_url().
* Default false.
* @type bool $blocking Whether the calling code requires the result of the request.
* If set to false, the request will be sent to the remote server,
* and processing returned to the calling code immediately, the caller
* will know if the request succeeded or failed, but will not receive
* any response from the remote server. Default true.
* @type string|array $headers Array or string of headers to send with the request.
* Default empty array.
* @type array $cookies List of cookies to send with the request. Default empty array.
* @type string|array $body Body to send with the request. Default null.
* @type bool $compress Whether to compress the $body when sending the request.
* Default false.
* @type bool $decompress Whether to decompress a compressed response. If set to false and
* compressed content is returned in the response anyway, it will
* need to be separately decompressed. Default true.
* @type bool $sslverify Whether to verify SSL for the request. Default true.
* @type string sslcertificates Absolute path to an SSL certificate .crt file.
* Default ABSPATH . WPINC . '/certificates/ca-bundle.crt'.
* @type bool $stream Whether to stream to a file. If set to true and no filename was
* given, the stream will be output to a new file in the WP temp dir
* using a name generated from the basename of the URL. Default false.
* @type string $filename Filename of the file to write to when streaming. $stream must be
* set to true. Default null.
* @type int $limit_response_size Size in bytes to limit the response to. Default null.
* @type bool|null $data_format How the `$data` should be sent ('query' or 'body'). Default null.
* If null, data will be sent as 'query' for HEAD/GET and as
* 'body' for POST/PUT/OPTIONS/PATCH/DELETE.
*
* }
* @return array|WP_Error Array containing 'headers', 'body', 'response', 'cookies', 'filename'.
* A WP_Error instance upon error.
*/
public function request( $url, $args = array() ) {
$defaults = array(
'method' => 'GET',
/**
* Filters the timeout value for an HTTP request.
*
* @since 2.7.0
*
* @param int $timeout_value Time in seconds until a request times out.
* Default 5.
*/
'timeout' => apply_filters( 'http_request_timeout', 5 ),
/**
* Filters the number of redirects allowed during an HTTP request.
*
* @since 2.7.0
*
* @param int $redirect_count Number of redirects allowed. Default 5.
*/
'redirection' => apply_filters( 'http_request_redirection_count', 5 ),
/**
* Filters the version of the HTTP protocol used in a request.
*
* @since 2.7.0
*
* @param string $version Version of HTTP used. Accepts '1.0' and '1.1'.
* Default '1.0'.
*/
'httpversion' => apply_filters( 'http_request_version', '1.0' ),
/**
* Filters the user agent value sent with an HTTP request.
*
* @since 2.7.0
*
* @param string $user_agent WordPress user agent string.
*/
'user-agent' => apply_filters( 'http_headers_useragent', 'WordPress/' . get_bloginfo( 'version' ) . '; ' . get_bloginfo( 'url' ) ),
/**
* Filters whether to pass URLs through wp_http_validate_url() in an HTTP request.
*
* @since 3.6.0
*
* @param bool $pass_url Whether to pass URLs through wp_http_validate_url().
* Default false.
*/
'reject_unsafe_urls' => apply_filters( 'http_request_reject_unsafe_urls', false ),
'blocking' => true,
'headers' => array(),
'cookies' => array(),
'body' => null,
'compress' => false,
'decompress' => true,
'sslverify' => true,
'sslcertificates' => ABSPATH . WPINC . '/certificates/ca-bundle.crt',
'stream' => false,
'filename' => null,
'limit_response_size' => null,
'data_format' => null,
);
// Pre-parse for the HEAD checks.
$args = wp_parse_args( $args );
// By default, Head requests do not cause redirections.
if ( isset( $args['method'] ) && 'HEAD' === $args['method'] ) {
$defaults['redirection'] = 0;
}
$request_args = wp_parse_args( $args, $defaults );
/**
* Filters the arguments used in an HTTP request.
*
* @since 2.7.0
*
* @param array $request_args An array of HTTP request arguments.
* @param string $url The request URL.
*/
$request_args = apply_filters( 'http_request_args', $request_args, $url );
// The transports decrement this, store a copy of the original value for loop purposes.
if ( ! isset( $request_args['_redirection'] ) ) {
$request_args['_redirection'] = $request_args['redirection'];
}
/**
* Filters whether to preempt an HTTP request's return value.
*
* Returning a non-false value from the filter will short-circuit the HTTP request and return
* early with that value. A filter should return either:
*
* - An array containing 'headers', 'body', 'response', 'cookies', and 'filename' elements
* - A WP_Error instance
* - boolean false (to avoid short-circuiting the response)
*
* Returning any other value may result in unexpected behaviour.
*
* @since 2.9.0
*
* @param false|array|WP_Error $preempt Whether to preempt an HTTP request's return value. Default false.
* @param array $request_args HTTP request arguments.
* @param string $url The request URL.
*/
$pre = apply_filters( 'pre_http_request', false, $request_args, $url );
if ( false !== $pre ) {
return $pre;
}
if ( function_exists( 'wp_kses_bad_protocol' ) ) {
if ( $request_args['reject_unsafe_urls'] ) {
$url = wp_http_validate_url( $url );
}
if ( $url ) {
$url = wp_kses_bad_protocol( $url, array( 'http', 'https', 'ssl' ) );
}
}
$arrURL = @parse_url( $url );
if ( empty( $url ) || empty( $arrURL['scheme'] ) ) {
return new WP_Error( 'http_request_failed', esc_html__( 'A valid URL was not provided.' ) );
}
if ( $this->block_request( $url ) ) {
return new WP_Error( 'http_request_failed', esc_html__( 'User has blocked requests through HTTP.' ) );
}
// If we are streaming to a file but no filename was given drop it in the WP temp dir
// and pick its name using the basename of the $url
if ( $request_args['stream'] ) {
if ( empty( $request_args['filename'] ) ) {
$request_args['filename'] = get_temp_dir() . basename( $url );
}
// Force some settings if we are streaming to a file and check for existence and perms of destination directory
$request_args['blocking'] = true;
if ( ! wp_is_writable( dirname( $request_args['filename'] ) ) ) {
return new WP_Error( 'http_request_failed', esc_html__( 'Destination directory for file streaming does not exist or is not writable.' ) );
}
}
if ( is_null( $request_args['headers'] ) ) {
$request_args['headers'] = array();
}
// WP allows passing in headers as a string, weirdly.
if ( ! is_array( $request_args['headers'] ) ) {
$processedHeaders = WP_Http::processHeaders( $request_args['headers'] );
$request_args['headers'] = $processedHeaders['headers'];
}
// Setup arguments
$headers = $request_args['headers'];
$data = $request_args['body'];
$type = $request_args['method'];
$options = array(
'timeout' => $request_args['timeout'],
'useragent' => $request_args['user-agent'],
'blocking' => $request_args['blocking'],
'hooks' => new WP_HTTP_Requests_Hooks( $url, $request_args ),
);
// Ensure redirects follow browser behaviour.
$options['hooks']->register( 'requests.before_redirect', array(
get_class(),
'browser_redirect_compatibility'
) );
if ( $request_args['stream'] ) {
$options['filename'] = $request_args['filename'];
}
if ( empty( $request_args['redirection'] ) ) {
$options['follow_redirects'] = false;
} else {
$options['redirects'] = $request_args['redirection'];
}
// Use byte limit, if we can
if ( isset( $request_args['limit_response_size'] ) ) {
$options['max_bytes'] = $request_args['limit_response_size'];
}
// If we've got cookies, use and convert them to Requests_Cookie.
if ( ! empty( $request_args['cookies'] ) ) {
$options['cookies'] = WP_Http::normalize_cookies( $request_args['cookies'] );
}
// SSL certificate handling
if ( ! $request_args['sslverify'] ) {
$options['verify'] = false;
$options['verifyname'] = false;
} else {
$options['verify'] = $request_args['sslcertificates'];
}
if ( null !== $request_args['data_format'] ) {
$options['data_format'] = $request_args['data_format'];
} elseif ( 'HEAD' !== $type && 'GET' !== $type ) {
// All non-GET/HEAD requests should put the arguments in the form body.
$options['data_format'] = 'body';
}
/**
* Filters whether SSL should be verified for non-local requests.
*
* @since 2.8.0
*
* @param bool $ssl_verify Whether to verify the SSL connection. Default true.
*/
$options['verify'] = apply_filters( 'https_ssl_verify', $options['verify'] );
// Check for proxies.
$proxy = new WP_HTTP_Proxy();
if ( $proxy->is_enabled() && $proxy->send_through_proxy( $url ) ) {
$options['proxy'] = new Requests_Proxy_HTTP( $proxy->host() . ':' . $proxy->port() );
if ( $proxy->use_authentication() ) {
$options['proxy']->use_authentication = true;
$options['proxy']->user = $proxy->username();
$options['proxy']->pass = $proxy->password();
}
}
// Avoid issues where mbstring.func_overload is enabled
mbstring_binary_safe_encoding();
try {
$requests_response = Requests::request( $url, $headers, $data, $type, $options );
// Convert the response into an array
$http_response = new WP_HTTP_Requests_Response( $requests_response, $request_args['filename'] );
$response = $http_response->to_array();
// Add the original object to the array.
$response['http_response'] = $http_response;
} catch ( Requests_Exception $e ) {
$response = new WP_Error( 'http_request_failed', $e->getMessage() );
}
reset_mbstring_encoding();
/**
* Fires after an HTTP API response is received and before the response is returned.
*
* @since 2.8.0
*
* @param array|WP_Error $response HTTP response or WP_Error object.
* @param string $context Context under which the hook is fired.
* @param string $class HTTP transport used.
* @param array $args HTTP request arguments.
* @param string $url The request URL.
*/
do_action( 'http_api_debug', $response, 'response', 'Requests', $request_args, $url );
if ( is_wp_error( $response ) ) {
return $response;
}
if ( ! $request_args['blocking'] ) {
return array(
'headers' => array(),
'body' => '',
'response' => array(
'code' => false,
'message' => false,
),
'cookies' => array(),
'http_response' => null,
);
}
/**
* Filters the HTTP API response immediately before the response is returned.
*
* @since 2.9.0
*
* @param array $response HTTP response.
* @param array $request_args HTTP request arguments.
* @param string $url The request URL.
*/
return apply_filters( 'http_response', $response, $request_args, $url );
}
}

View File

@ -0,0 +1,74 @@
<?php
/**
* Plugin Name: Elegant Themes Support Center :: Safe Mode Child Theme Disabler
* Plugin URI: http://www.elegantthemes.com
* Description: When the ET Support Center's Safe Mode is activated, this Must-Use Plugin will temporarily disable any active child themes for that user.
* Author: Elegant Themes
* Author URI: http://www.elegantthemes.com
* License: GPLv2 or later
*
* @package ET\Core\SupportCenter\SafeModeDisableChildThemes
* @author Elegant Themes <http://www.elegantthemes.com>
* @license GNU General Public License v2 <http://www.gnu.org/licenses/gpl-2.0.html>
*/
// Quick exit if accessed directly
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Disable Child Theme (if Safe Mode is active)
*
* The `is_child_theme()` function returns TRUE if a child theme is active. Parent theme info can be gathered from
* the child theme's settings, so in the case of an active child theme we can capture the parent theme's info and
* temporarily push the parent theme as active (similar to how WP lets the user preview a theme before activation).
*
* @since 3.20
* @since 3.23 Moved from `ET_Core_SupportCenter::maybe_disable_child_theme()` for an improved Safe Mode experience.
*
* @param $current_theme
*
* @return false|string
*/
function et_safe_mode_maybe_disable_child_theme( $current_theme ) {
// Verbose logging: only log if `wp-config.php` has defined `ET_DEBUG='support_center'`
$DEBUG_ET_SUPPORT_CENTER = defined( 'ET_DEBUG' ) && 'support_center' === ET_DEBUG;
if ( ! isset( $_COOKIE['et-support-center-safe-mode'] ) ) {
if ( $DEBUG_ET_SUPPORT_CENTER ) {
error_log( 'ET Support Center :: Safe Mode: No cookie found' );
}
return $current_theme;
}
$verify = get_option( 'et-support-center-safe-mode-verify' );
if ( ! $verify ) {
if ( $DEBUG_ET_SUPPORT_CENTER ) {
error_log( 'ET Support Center :: Safe Mode: No option found to verify cookie' );
}
return $current_theme;
}
if ( $_COOKIE['et-support-center-safe-mode'] !== $verify ) {
if ( $DEBUG_ET_SUPPORT_CENTER ) {
error_log( 'ET Support Center :: Safe Mode: Cookie/Option mismatch' );
}
return $current_theme;
}
$template = get_option( 'template' );
if ( $template !== $current_theme ) {
return $template;
}
return $current_theme;
}
add_filter( 'template', 'et_safe_mode_maybe_disable_child_theme' );
add_filter( 'stylesheet', 'et_safe_mode_maybe_disable_child_theme' );

View File

@ -0,0 +1,78 @@
<?php
/**
* Plugin Name: Elegant Themes Support Center :: Safe Mode Plugin Disabler
* Plugin URI: http://www.elegantthemes.com
* Description: When the ET Support Center's Safe Mode is activated, this Must-Use Plugin will temporarily disable plugins for that user.
* Author: Elegant Themes
* Author URI: http://www.elegantthemes.com
* License: GPLv2 or later
*
* @package ET\Core\SupportCenter\SafeModeDisablePlugins
* @author Elegant Themes <http://www.elegantthemes.com>
* @license GNU General Public License v2 <http://www.gnu.org/licenses/gpl-2.0.html>
*/
// Quick exit if accessed directly
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Disable Plugins (if Safe Mode is active)
*
* @since 3.20
*
* @param array $plugins WP's array of active plugins
*
* @return array
*/
function et_safe_mode_maybe_disable_plugins( $plugins = array() ) {
// Verbose logging: only log if `wp-config.php` has defined `ET_DEBUG='support_center'`
$DEBUG_ET_SUPPORT_CENTER = defined( 'ET_DEBUG' ) && 'support_center' === ET_DEBUG;
if ( ! isset( $_COOKIE['et-support-center-safe-mode'] ) ) {
if ( $DEBUG_ET_SUPPORT_CENTER ) {
error_log( 'ET Support Center :: Safe Mode: No cookie found' );
}
return $plugins;
}
$verify = get_option( 'et-support-center-safe-mode-verify' );
if ( ! $verify ) {
if ( $DEBUG_ET_SUPPORT_CENTER ) {
error_log( 'ET Support Center :: Safe Mode: No option found to verify cookie' );
}
return $plugins;
}
if ( $_COOKIE['et-support-center-safe-mode'] !== $verify ) {
if ( $DEBUG_ET_SUPPORT_CENTER ) {
error_log( 'ET Support Center :: Safe Mode: Cookie/Option mismatch' );
}
return $plugins;
}
/** @var array Collection of plugins that we will NOT disable when Safe Mode is activated. */
$plugins_allowlist = get_option( 'et_safe_mode_plugins_allowlist' );
$clean_plugins = $plugins;
foreach ( $clean_plugins as $key => &$plugin ) {
// Check whether this plugin appears in our allowlist
if ( ! in_array( $plugin, $plugins_allowlist ) ) {
if ( $DEBUG_ET_SUPPORT_CENTER ) {
error_log( 'ET Support Center :: Safe Mode: Unsetting plugin: ' . json_encode( $plugin ) );
}
unset( $clean_plugins[ $key ] );
}
}
return $clean_plugins;
}
add_filter( 'option_active_plugins', 'et_safe_mode_maybe_disable_plugins' );
add_filter( 'option_active_sitewide_plugins', 'et_safe_mode_maybe_disable_plugins' );

View File

@ -0,0 +1,221 @@
<?php
abstract class ET_Core_Post_Object {
/**
* @var ET_Core_Data_Utils
*/
protected static $_;
/**
* Current instances of this class organized by type.
*
* @since 3.0.99
* @var array[] {
*
* @type ET_Core_Post_Object[] $type {
*
* @type ET_Core_Post_Object $name Instance.
* ...
* }
* ...
* }
*/
private static $_instances = array();
/**
* The `$args` array used when registering this post object.
*
* @since 3.0.99
* @var array
*/
protected $_args;
/**
* The owner of this instance. Default 'core'. Accepts 'divi', 'builder', 'epanel', 'bloom', 'monarch'.
*
* @since 3.0.99
* @var string
*/
protected $_owner = 'core';
/**
* Whether or not the object has been registered.
*
* @since 3.0.99
* @var bool
*/
protected $_registered = false;
/**
* The WP object for this instance.
*
* @since 3.0.99
* @var WP_Post_Type|WP_Taxonomy
*/
protected $_wp_object;
/**
* Post object key.
*
* @since 3.0.99
* @var string
*/
public $name;
/**
* Post object type. Accepts 'cpt', 'taxonomy'.
*
* @since 3.0.99
* @var string
*/
public $wp_type;
/**
* ET_Core_Post_Base constructor.
*/
public function __construct() {
$this->_args = $this->_get_args();
$this->_args['labels'] = $this->_get_labels();
$this->_apply_filters();
$this->_sanity_check();
if ( empty( self::$_instances ) ) {
self::$_instances['cpt'] = array();
self::$_instances['taxonomy'] = array();
add_action( 'init', 'ET_Core_Post_Object::register_all' );
}
self::$_instances[ $this->wp_type ][ $this->name ] = $this;
}
/**
* Applies filters to the instance's filterable properties.
*/
protected function _apply_filters() {
$name = $this->name;
if ( 'cpt' === $this->wp_type ) {
/**
* Filters the `$args` for a custom post type. The dynamic portion of the
* filter, `$name`, refers to the name/key of the post type being registered.
*
* @since 3.0.99
*
* @param array $args {@see register_post_type()}
*/
$this->_args = apply_filters( "et_core_cpt_{$name}_args", $this->_args );
} else if ( 'taxonomy' === $this->wp_type ) {
/**
* Filters the `$args` for a custom post taxonomy. The dynamic portion of the
* filter, `$name`, refers to the name/key of the taxonomy being registered.
*
* @since 3.0.99
*
* @param array $args {@see register_taxonomy()}
*/
$this->_args = apply_filters( "et_core_taxonomy_{$name}_args", $this->_args );
}
}
/**
* This method is called right before registering the object. It is intended to be
* overridden by child classes as needed.
*/
protected function _before_register() {}
/**
* Returns the args for the instance.
* See {@see register_post_type()} or {@see register_taxonomy()}.
*
* @return array $args
*/
abstract protected function _get_args();
/**
* Returns labels for the instance.
* See {@see register_post_type()} or {@see register_taxonomy()}.
*
* @return array $labels
*/
abstract protected function _get_labels();
/**
* Checks for required properties and existing instances.
*/
protected function _sanity_check() {
if ( ! $this->_args || ! $this->name || ! $this->wp_type ) {
et_error( 'Missing required properties!' );
wp_die();
} else if ( isset( self::$_instances[ $this->wp_type ][ $this->name ] ) ) {
et_error( 'Multiple instances are not allowed!' );
wp_die();
}
}
/**
* Get a derived class instance.
*
* @since 3.0.99
*
* @param string $type See {@see self::$wp_type} for accepted values. Default is 'cpt'.
* @param string $name The name/slug of the derived object. Default is an empty string.
*
* @return self|null
*/
public static function instance( $type = 'cpt', $name = '' ) {
if ( ! self::$_ ) {
self::$_ = ET_Core_Data_Utils::instance();
}
return self::$_->array_get( self::$_instances, "{$type}.{$name}", null );
}
/**
* Calls either {@see register_post_type} or {@see register_taxonomy} for each instance.
*
* @since 3.0.99
*
* @param string $owner Optional. Only register objects owned by a part of the codebase.
* See {@see self::_owner} for accepted values.
*/
public static function register_all( $owner = null ) {
if ( empty( self::$_instances ) ) {
return;
}
global $wp_taxonomies;
foreach ( self::$_instances['taxonomy'] as $name => $instance ) {
$can_register = is_null( $owner ) || $owner === $instance->_owner;
if ( $instance->_registered || ! $can_register ) {
continue;
}
$instance->_before_register();
register_taxonomy( $name, $instance->post_types, $instance->_args );
$instance->_wp_object = $wp_taxonomies[ $name ];
$instance->_registered = true;
}
foreach ( self::$_instances['cpt'] as $name => $instance ) {
$can_register = is_null( $owner ) || $owner === $instance->_owner;
if ( $instance->_registered || ! $can_register ) {
continue;
}
$instance->_before_register();
$instance->_wp_object = register_post_type( $name, $instance->_args );
$instance->_registered = true;
}
}
}

View File

@ -0,0 +1,326 @@
<?php
class ET_Core_Post_Query {
/**
* @var ET_Core_Data_Utils
*/
protected static $_;
/**
* Whether or not to negate the next query arg that is set. Default 'false'.
*
* @since 3.0.99
* @var bool
*/
protected $_negate = false;
/**
* The query result.
*
* @since 3.0.99
* @var WP_Post|WP_Post[]
*/
protected $_query_result;
/**
* The args that will be passed to {@see WP_Query} the next time {@see self::run()} is called.
*
* @since 3.0.99
* @var array
*/
protected $_wp_query_args;
/**
* The name of the primary category-style taxonomy for this post type.
*
* @since 3.0.99
* @var string
*/
public $category_tax;
/**
* The post type (slug) for this instance.
*
* @since 3.0.99
* @var string
*/
public $post_type;
/**
* The name of the primary tag-style taxonomy for this post type.
*
* @since 3.0.99
* @var string
*/
public $tag_tax;
/**
* ET_Core_Post_Query constructor.
*
* @since 3.0.99
*
* @param string $post_type See {@see self::$post_type}
* @param string $category_tax See {@see self::$category_tax}
* @param string $tag_tax See {@see self::$tag_tax}
*/
public function __construct( $post_type = '', $category_tax = '', $tag_tax = '' ) {
$this->post_type = $this->post_type ? $this->post_type : $post_type;
$this->category_tax = $this->category_tax ? $this->category_tax : $category_tax;
$this->tag_tax = $this->tag_tax ? $this->tag_tax : $tag_tax;
$this->_wp_query_args = array(
'post_type' => $this->post_type,
'posts_per_page' => -1,
);
if ( ! self::$_ ) {
self::$_ = ET_Core_Data_Utils::instance();
}
}
/**
* Adds a meta query to the WP Query args for this instance.
*
* @since 3.0.99
*
* @param string $key The meta key.
* @param string $value The meta value.
* @param bool $negate Whether or not to negate this meta query.
*/
protected function _add_meta_query( $key, $value, $negate ) {
if ( ! isset( $this->_wp_query_args['meta_query'] ) ) {
$this->_wp_query_args['meta_query'] = array();
}
if ( is_null( $value ) ) {
$compare = $negate ? 'NOT EXISTS' : 'EXISTS';
} else if ( is_array( $value ) ) {
$compare = $negate ? 'NOT IN' : 'IN';
} else {
$compare = $negate ? '!=' : '=';
}
$query = array(
'key' => $key,
'compare' => $compare,
);
if ( ! is_null( $value ) ) {
$query['value'] = $value;
}
if ( '!=' === $compare ) {
$query = array(
'relation' => 'OR',
array(
'key' => $key,
'compare' => 'NOT EXISTS',
),
$query,
);
}
$this->_wp_query_args['meta_query'][] = $query;
}
/**
* Adds a tax query to the WP Query args for this instance.
*
* @since 3.0.99
*
* @param string $taxonomy The taxonomy name.
* @param array $terms Taxonomy terms.
* @param bool $negate Whether or not to negate this tax query.
*/
protected function _add_tax_query( $taxonomy, $terms, $negate ) {
if ( ! isset( $this->_wp_query_args['tax_query'] ) ) {
$this->_wp_query_args['tax_query'] = array();
}
$operator = $negate ? 'NOT IN' : 'IN';
$field = is_int( $terms[0] ) ? 'term_id' : 'name';
$query = array(
'taxonomy' => $taxonomy,
'field' => $field,
'terms' => $terms,
'operator' => $operator,
);
if ( $negate ) {
$query = array(
'relation' => 'OR',
array(
'taxonomy' => $taxonomy,
'operator' => 'NOT EXISTS',
),
$query,
);
}
$this->_wp_query_args['tax_query'][] = $query;
}
/**
* Resets {@see self::$_negate} to default then returns the previous value.
*
* @return bool
*/
protected function _reset_negate() {
$negate = $this->_negate;
$this->_negate = false;
return $negate;
}
/**
* Adds a tax query to this instance's WP Query args for it's category taxonomy.
*
* @since 3.0.99
*
* @param mixed ...$categories Variable number of category arguments where each arg can be
* a single category name or ID or an array of names or IDs.
*
* @return $this
*/
public function in_category() {
$negate = $this->_reset_negate();
if ( ! $this->category_tax ) {
et_error( 'A category taxonomy has not been set for this query!' );
return $this;
}
$args = func_get_args();
$args = self::$_->array_flatten( $args );
if ( ! $args ) {
return $this;
}
$this->_add_tax_query( $this->category_tax, $args, $negate );
return $this;
}
/**
* Negates the next query arg that is set.
*
* @since 3.0.99
*
* @return $this
*/
public function not() {
$this->_negate = true;
return $this;
}
/**
* Performs a new WP Query using the instance's current query params and then returns the
* results. Typically, this method is the last method call in a set of chained calls to other
* methods on this class during which various query params are set.
*
* Examples:
*
* $cpt_query
* ->in_category( 'some_cat' )
* ->with_tag( 'some_tag' )
* ->run();
*
* $cpt_query
* ->with_tag( 'some_tag' )
* ->not()->in_category( 'some_cat' )
* ->run();
*
* @since 3.0.99
*
* @param array $args Optional. Additional arguments for {@see WP_Query}.
*
* @return WP_Post|WP_Post[] $posts
*/
public function run( $args = array() ) {
if ( ! is_null( $this->_query_result ) ) {
return $this->_query_result;
}
$name = $this->post_type;
if ( $args ) {
$this->_wp_query_args = array_merge_recursive( $this->_wp_query_args, $args );
}
/**
* Filters the WP Query args for a custom post type query. The dynamic portion of
* the filter name, $name, refers to the name of the custom post type.
*
* @since 3.0.99
*
* @param array $args {@see WP_Query::__construct()}
*/
$this->_wp_query_args = apply_filters( "et_core_cpt_{$name}_query_args", $this->_wp_query_args );
$query = new WP_Query( $this->_wp_query_args );
$this->_query_result = $query->posts;
if ( 1 === count( $this->_query_result ) ) {
$this->_query_result = array_pop( $this->_query_result );
}
return $this->_query_result;
}
/**
* Adds a meta query to this instance's WP Query args.
*
* @since 3.0.99
*
* @param string $key The meta key.
* @param mixed $value Optional. The meta value to compare. When `$value` is not provided,
* the comparison will be 'EXISTS' or 'NOT EXISTS' (when negated).
* When `$value` is an array, comparison will be 'IN' or 'NOT IN'.
* When `$value` is not an array, comparison will be '=' or '!='.
*
* @return $this
*/
public function with_meta( $key, $value = null ) {
$this->_add_meta_query( $key, $value, $this->_reset_negate() );
return $this;
}
/**
* Adds a tax query to this instance's WP Query args for it's primary tag-like taxonomy.
*
* @since 3.0.99
*
* @param mixed ...$tags Variable number of tag arguments where each arg can be
* a single tag name or ID, or an array of tag names or IDs.
*
* @return $this
*/
public function with_tag() {
$negate = $this->_reset_negate();
if ( ! $this->tag_tax ) {
et_error( 'A tag taxonomy has not been set for this query!' );
return $this;
}
$args = func_get_args();
$args = self::$_->array_flatten( $args );
if ( ! $args ) {
return $this;
}
$this->_add_tax_query( $this->tag_tax, $args, $negate );
return $this;
}
}

View File

@ -0,0 +1,95 @@
<?php
abstract class ET_Core_Post_Taxonomy extends ET_Core_Post_Object {
/**
* The `$args` array used when registering this taxonomy.
*
* @since 3.0.99
* @var array
*/
protected $_args;
/**
* The WP Taxonomy object for this instance.
*
* @since 3.0.99
* @var WP_Taxonomy
*/
protected $_wp_object;
/**
* Taxonomy key.
*
* @since 3.0.99
* @var string
*/
public $name;
/**
* The post types to which this taxonomy applies.
*
* @since 3.0.99
* @var array
*/
public $post_types;
/**
* This taxonomy's terms.
*
* @var WP_Term[]
*/
public $terms;
/**
* @inheritDoc
*/
public $wp_type = 'taxonomy';
/**
* ET_Core_Post_Taxonomy constructor.
*/
public function __construct() {
parent::__construct();
$name = $this->name;
/**
* Filters the supported post types for a custom taxonomy. The dynamic portion of the
* filter name, $name, refers to the name of the custom taxonomy.
*
* @since 3.0.99
*
* @param array
*/
$this->post_types = apply_filters( "et_core_taxonomy_{$name}_post_types", $this->post_types );
}
/**
* Get the terms for this taxonomy.
*
* @return array|int|WP_Error|WP_Term[]
*/
public function get() {
if ( is_null( $this->terms ) ) {
$this->terms = get_terms( $this->name, array( 'hide_empty' => false ) );
}
return $this->terms;
}
/**
* Get a derived class instance.
*
* @since 3.0.99
*
* @param string $type See {@see self::$wp_type} for accepted values. Default is 'taxonomy'.
* @param string $name The name/slug of the derived object. Default is an empty string.
*
* @return self|null
*/
public static function instance( $type = 'taxonomy', $name = '' ) {
return parent::instance( $type, $name );
}
}

View File

@ -0,0 +1,57 @@
<?php
abstract class ET_Core_Post_Type extends ET_Core_Post_Object {
/**
* The `$args` array used when registering this post type.
*
* @since 3.0.99
* @var array
*/
protected $_args;
/**
* The name of the primary category-style taxonomy for this post type.
*
* @since 3.0.99
* @var string
*/
protected $_category_tax = '';
/**
* The name of the primary tag-style taxonomy for this post type.
*
* @since 3.0.99
* @var string
*/
protected $_tag_tax = '';
/**
* The WP Post Type object for this instance.
*
* @since 3.0.99
* @var WP_Post_Type
*/
protected $_wp_object;
/**
* Post type key.
*
* @since 3.0.99
* @var string
*/
public $name;
/**
* @inheritDoc
*/
public $wp_type = 'cpt';
/**
* @return ET_Core_Post_Query
*/
public function query() {
return new ET_Core_Post_Query( $this->name, $this->_category_tax, $this->_tag_tax );
}
}