first commit

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

View File

@@ -0,0 +1,199 @@
<?php
/**
* Batch Process Handler.
*
* @package PUM
* @copyright Copyright (c) 2023, Code Atlantic LLC
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Implements a basic batch process.
*
* @since 1.7.0
*/
abstract class PUM_Abstract_Batch_Process implements PUM_Interface_Batch_Process {
/**
* Batch process ID.
*
* @var string
*/
public $batch_id;
/**
* The current step being processed.
*
* @var int|string Step number or 'done'.
*/
public $step;
/**
* Number of items to process per step.
*
* @var int
*/
public $per_step = 100;
/**
* Capability needed to perform the current batch process.
*
* @var string
*/
public $capability = 'manage_options';
/**
* Sets up the batch process.
*
* @param int|string $step Step number or 'done'.
*/
public function __construct( $step = 1 ) {
$this->step = $step;
if ( has_filter( "pum_batch_per_step_{$this->batch_id}" ) ) {
/**
* Filters the number of items to process per step for the given batch process.
*
* The dynamic portion of the hook name, `$this->export_type` refers to the export
* type defined in each sub-class.
*
* @param int $per_step The number of items to process for each step. Default 100.
* @param PUM_Abstract_Batch_Process $this Batch process instance.
*/
$this->per_step = apply_filters( "pum_batch_per_step_{$this->batch_id}", $this->per_step, $this );
}
}
/**
* Determines if the current user can perform the current batch process.
*
* @return bool True if the current user has the needed capability, otherwise false.
*/
public function can_process() {
return current_user_can( $this->capability );
}
/**
* Executes a single step in the batch process.
*
* @return int|string|WP_Error Next step number, 'done', or a WP_Error object.
*/
public function process_step() {
return 'done';
}
/**
* Retrieves the calculated completion percentage.
*
* @return int Percentage completed.
*/
public function get_percentage_complete() {
$percentage = 0;
$current_count = $this->get_current_count();
$total_count = $this->get_total_count();
if ( $total_count > 0 ) {
$percentage = ( $current_count / $total_count ) * 100;
}
if ( $percentage > 100 ) {
$percentage = 100;
}
return $percentage;
}
/**
* Retrieves a message based on the given message code.
*
* @param string $code Message code.
*
* @return string Message.
*/
public function get_message( $code ) {
switch ( $code ) {
case 'done':
$final_count = $this->get_current_count();
$message = sprintf( _n( '%s item was successfully processed.', '%s items were successfully processed.', $final_count, 'popup-maker' ), number_format_i18n( $final_count ) );
break;
default:
$message = '';
break;
}
return $message;
}
/**
* Defines logic to execute once batch processing is complete.
*/
public function finish() {
PUM_DataStorage::delete_by_match( "^{$this->batch_id}[0-9a-z\_]+" );
}
/**
* Calculates and retrieves the offset for the current step.
*
* @return int Number of items to offset.
*/
public function get_offset() {
return ( $this->step - 1 ) * $this->per_step;
}
/**
* Retrieves the current, stored count of processed items.
*
* @see get_percentage_complete()
*
* @return int Current number of processed items. Default 0.
*/
protected function get_current_count() {
return PUM_DataStorage::get( "{$this->batch_id}_current_count", 0 );
}
/**
* Sets the current count of processed items.
*
* @param int $count Number of processed items.
*/
protected function set_current_count( $count ) {
PUM_DataStorage::write( "{$this->batch_id}_current_count", $count );
}
/**
* Retrieves the total, stored count of items to process.
*
* @see get_percentage_complete()
*
* @return int Current number of processed items. Default 0.
*/
protected function get_total_count() {
return PUM_DataStorage::get( "{$this->batch_id}_total_count", 0 );
}
/**
* Sets the total count of items to process.
*
* @param int $count Number of items to process.
*/
protected function set_total_count( $count ) {
PUM_DataStorage::write( "{$this->batch_id}_total_count", $count );
}
/**
* Deletes the stored current and total counts of processed items.
*/
protected function delete_counts() {
PUM_DataStorage::delete( "{$this->batch_id}_current_count" );
PUM_DataStorage::delete( "{$this->batch_id}_total_count" );
}
}

View File

@@ -0,0 +1,536 @@
<?php
/**
* Abstract class for database
*
* @package PUM
* @copyright Copyright (c) 2023, Code Atlantic LLC
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Abstract database class.
*
* @package PopupMaker
*/
abstract class PUM_Abstract_Database {
/**
* Instance of the class.
*
* @var static
*/
public static $instance;
/**
* The name of our database table.
*
* @var string
*/
public $table_name = '';
/**
* The version of our database table.
*
* @var integer
*/
public $version = 1;
/**
* The name of the primary column.
*
* @var string
*/
public $primary_key = 'ID';
/**
* Get things started
*/
public function __construct() {
global $wpdb;
$current_db_version = $this->get_installed_version();
if ( ! $current_db_version || $current_db_version < $this->version ) {
// Install the table.
@$this->create_table();
if ( $wpdb->get_var( "SHOW TABLES LIKE '{$this->table_name()}'" ) === $this->table_name() ) {
$this->update_db_version();
}
}
$wpdb->{$this->table_name} = $this->table_name();
}
/**
* Gets db version from new or old source.
*
* @return float
*/
public function get_installed_version() {
// Get list of all current db table versions.
$db_versions = get_option( 'pum_db_versions', [] );
// #1 If it exists in new pum_db_vers[] option, move on.
if ( isset( $db_versions[ $this->table_name ] ) ) {
return (float) $db_versions[ $this->table_name ];
}
// #2 Else look for old key, if exists, migrate and delete.
$db_version_old_key = get_option( $this->table_name . '_db_version' );
if ( $db_version_old_key ) {
if ( $db_version_old_key > 0 ) {
$db_versions[ $this->table_name ] = (float) $db_version_old_key;
update_option( 'pum_db_versions', $db_versions );
}
delete_option( $this->table_name . '_db_version' );
}
return (float) $db_version_old_key;
}
public function update_db_version() {
// Get list of all current db table versions.
$db_versions = get_option( 'pum_db_versions', [] );
$db_versions[ $this->table_name ] = (float) $this->version;
update_option( 'pum_db_versions', $db_versions );
}
/**
* Create the table
*/
abstract public function create_table();
/**
* @return static
* @throws \Exception
*/
public static function instance() {
$class = get_called_class();
if ( ! isset( self::$instance[ $class ] ) ) {
self::$instance[ $class ] = new $class();
}
return self::$instance[ $class ];
}
/**
* Retrieve a row by the primary key
*
* @param $row_id
*
* @return object
*/
public function get( $row_id ) {
global $wpdb;
return $this->prepare_result( $wpdb->get_row( "SELECT * FROM {$this->table_name()} WHERE $this->primary_key = $row_id LIMIT 1;" ) );
}
/**
* @param object|array $result
*
* @return object|array
*/
public function prepare_result( $result ) {
if ( ! $result || ( ! is_array( $result ) && ! is_object( $result ) ) ) {
return $result;
}
if ( is_object( $result ) ) {
$vars = get_object_vars( $result );
foreach ( $vars as $key => $value ) {
if ( is_string( $value ) ) {
$result->$key = maybe_unserialize( $value );
}
}
} elseif ( is_array( $result ) ) {
foreach ( $result as $key => $value ) {
if ( is_string( $value ) ) {
$result[ $key ] = maybe_unserialize( $value );
}
}
}
return $result;
}
/**
* @param array|object[] $results
*
* @return mixed
*/
public function prepare_results( $results ) {
foreach ( $results as $key => $result ) {
$results[ $key ] = $this->prepare_result( $result );
}
return $results;
}
/**
* @return string
*/
public function table_name() {
global $wpdb;
return $wpdb->prefix . $this->table_name;
}
/**
* Retrieve a row by a specific column / value
*
* @param $column
* @param $row_id
*
* @return object
*/
public function get_by( $column, $row_id ) {
global $wpdb;
return $this->prepare_result( $wpdb->get_row( "SELECT * FROM {$this->table_name()} WHERE $column = '$row_id' LIMIT 1;" ) );
}
/**
* Retrieve a specific column's value by the primary key
*
* @param $column
* @param $row_id
*
* @return string
*/
public function get_column( $column, $row_id ) {
global $wpdb;
return $wpdb->get_var( "SELECT $column FROM {$this->table_name()} WHERE $this->primary_key = $row_id LIMIT 1;" );
}
/**
* Retrieve a specific column's value by the the specified column / value
*
* @param $column
* @param $column_where
* @param $column_value
*
* @return string
*/
public function get_column_by( $column, $column_where, $column_value ) {
global $wpdb;
return $wpdb->get_var( "SELECT $column FROM {$this->table_name()} WHERE $column_where = '$column_value' LIMIT 1;" );
}
/**
* Insert a new row
*
* @param $data
* @param string $type
*
* @return int
*/
public function insert( $data ) {
global $wpdb;
// Set default values
$data = wp_parse_args( $data, $this->get_column_defaults() );
do_action( 'pum_pre_insert_' . $this->table_name, $data );
// Initialise column format array
$column_formats = $this->get_columns();
// Force fields to lower case
$data = array_change_key_case( $data );
// White list columns
$data = array_intersect_key( $data, $column_formats );
// Reorder $column_formats to match the order of columns given in $data
$data_keys = array_keys( $data );
$column_formats = array_merge( array_flip( $data_keys ), $column_formats );
foreach ( $data as $key => $value ) {
if ( is_array( $value ) ) {
$data[ $key ] = maybe_serialize( $value );
}
}
$wpdb->insert( $this->table_name(), $data, $column_formats );
do_action( 'pum_post_insert_' . $this->table_name, $wpdb->insert_id, $data );
return $wpdb->insert_id;
}
/**
* Default column values
*
* @return array
*/
public function get_column_defaults() {
return [];
}
/**
* Whitelist of columns
*
* @return array
*/
public function get_columns() {
return [];
}
/**
* Update a row
*
* @param $row_id
* @param array $data
* @param string $where
*
* @return bool
*/
public function update( $row_id, $data = [], $where = '' ) {
global $wpdb;
// Row ID must be positive integer
$row_id = absint( $row_id );
if ( empty( $row_id ) ) {
return false;
}
if ( empty( $where ) ) {
$where = $this->primary_key;
}
// Initialise column format array
$column_formats = $this->get_columns();
// Force fields to lower case
$data = array_change_key_case( $data );
// White list columns
$data = array_intersect_key( $data, $column_formats );
foreach ( $data as $key => $value ) {
if ( is_array( $value ) ) {
$data[ $key ] = maybe_serialize( $value );
}
}
// Reorder $column_formats to match the order of columns given in $data
$data_keys = array_keys( $data );
$column_formats = array_merge( array_flip( $data_keys ), $column_formats );
if ( false === $wpdb->update( $this->table_name(), $data, [ $where => $row_id ], $column_formats ) ) {
return false;
}
return true;
}
/**
* Delete a row identified by the primary key
*
* @param int $row_id
*
* @return bool
*/
public function delete( $row_id = 0 ) {
global $wpdb;
// Row ID must be positive integer
$row_id = absint( $row_id );
if ( empty( $row_id ) ) {
return false;
}
if ( false === $wpdb->query( $wpdb->prepare( "DELETE FROM {$this->table_name()} WHERE $this->primary_key = %d", $row_id ) ) ) {
return false;
}
return true;
}
/**
* Delete a row identified by the primary key
*
* @param $column
* @param $row_id
*
* @return bool
*/
public function delete_by( $column, $row_id ) {
global $wpdb;
if ( empty( $row_id ) ) {
return false;
}
if ( false === $wpdb->query( $wpdb->prepare( "DELETE FROM {$this->table_name()} WHERE $column = '%s'", $row_id ) ) ) {
return false;
}
return true;
}
/**
* Prepare query.
*
* @param $query
* @param array $args
*
* @return string
*/
public function prepare_query( $query, $args = [] ) {
if ( $args['orderby'] ) {
$query .= " ORDER BY {$args['orderby']} {$args['order']}";
}
$query .= " LIMIT {$args['limit']}";
if ( $args['offset'] ) {
$query .= " OFFSET {$args['offset']}";
}
$query .= ';';
return $query;
}
/**
* @param array $args
* @param string $return_type
*
* @return array|mixed|object[]
*/
public function query( $args = [], $return_type = OBJECT ) {
global $wpdb;
$args = wp_parse_args(
$args,
[
'fields' => '*',
'page' => null,
'limit' => null,
'offset' => null,
's' => null,
'orderby' => null,
'order' => null,
]
);
$columns = $this->get_columns();
$fields = $args['fields'];
if ( '*' === $fields ) {
$fields = array_keys( $columns );
} else {
$fields = explode( ',', $args['fields'] );
$fields = array_map( 'trim', $fields );
$fields = array_map( 'sanitize_text_field', $fields );
}
$select_fields = implode( '`, `', $fields );
// Begin building query.
$query = "SELECT `$select_fields` FROM {$this->table_name()}";
// Set up $values array for wpdb::prepare
$values = [];
// Define an empty WHERE clause to start from.
$where = 'WHERE 1=1';
// Build search query.
if ( $args['s'] && ! empty( $args['s'] ) ) {
$search = wp_unslash( trim( $args['s'] ) );
$search_where = [];
foreach ( $columns as $key => $type ) {
if ( in_array( $key, $fields ) ) {
if ( '%s' === $type || ( '%d' === $type && is_numeric( $search ) ) ) {
$values[] = '%' . $wpdb->esc_like( $search ) . '%';
$search_where[] = "`$key` LIKE '%s'";
}
}
}
if ( ! empty( $search_where ) ) {
$where .= ' AND (' . join( ' OR ', $search_where ) . ')';
}
}
$query .= " $where";
if ( ! empty( $args['orderby'] ) ) {
$query .= ' ORDER BY %s';
$values[] = wp_unslash( trim( $args['orderby'] ) );
switch ( $args['order'] ) {
case 'asc':
case 'ASC':
$query .= ' ASC';
break;
case 'desc':
case 'DESC':
default:
$query .= ' DESC';
break;
}
}
if ( ! empty( $args['limit'] ) ) {
$query .= ' LIMIT %d';
$values[] = absint( $args['limit'] );
}
// Pagination.
if ( $args['page'] >= 1 ) {
$args['offset'] = ( $args['page'] * $args['limit'] ) - $args['limit'];
}
if ( ! empty( $args['offset'] ) ) {
$query .= ' OFFSET %d';
$values[] = absint( $args['offset'] );
}
if ( strpos( $query, '%s' ) || strpos( $query, '%d' ) ) {
$query = $wpdb->prepare( $query, $values );
}
return $this->prepare_results( $wpdb->get_results( $query, $return_type ) );
}
/**
* Queries for total rows
*
* @param $args
*
* @return int
*/
public function total_rows( $args ) {
// TODO REVIEW this can probably be done more efficiently. Look at how we do it for DB models.
$args['limit'] = null;
$args['offset'] = null;
$args['page'] = null;
$results = $this->query( $args );
return $results ? count( $results ) : 0;
}
}

View File

@@ -0,0 +1,31 @@
<?php
/**
* Abstract class for Integrations.
*
* @package PUM
* @copyright Copyright (c) 2023, Code Atlantic LLC
*/
abstract class PUM_Abstract_Integration implements PUM_Interface_Integration {
/**
* @var string
*/
public $key;
/**
* @var string
*/
public $type;
/**
* @return string
*/
abstract public function label();
/**
* @return bool
*/
abstract public function enabled();
}

View File

@@ -0,0 +1,85 @@
<?php
/**
* Abstract for Integration Form
*
* @package PUM
* @copyright Copyright (c) 2023, Code Atlantic LLC
*/
abstract class PUM_Abstract_Integration_Form extends PUM_Abstract_Integration implements PUM_Interface_Integration_Form {
/**
* @var string
*/
public $type = 'form';
/**
* @return array
*/
abstract public function get_forms();
/**
* @param string $id
*
* @return mixed
*/
abstract public function get_form( $id );
/**
* @return array
*/
abstract public function get_form_selectlist();
/**
* @param array $js
*
* @return array
*/
public function custom_scripts( $js = [] ) {
return $js;
}
/**
* @param array $css
*
* @return array
*/
public function custom_styles( $css = [] ) {
return $css;
}
/**
* Retrieves the popup ID associated with the form, if any
*
* @return false|int
* @since 1.13.0
*/
public function get_popup_id() {
return isset( $_REQUEST['pum_form_popup_id'] ) && absint( $_REQUEST['pum_form_popup_id'] ) > 0 ? absint( $_REQUEST['pum_form_popup_id'] ) : false;
}
/**
* Increase the conversion count for popup
*
* @param int $popup_id The ID for the popup.
* @since 1.13.0
*/
public function increase_conversion( $popup_id ) {
$popup_id = intval( $popup_id );
$popup = pum_get_popup( $popup_id );
$popup->increase_event_count( 'conversion' );
}
/**
* Returns whether or now we should process any form submissions
*
* @return bool True if we should process the form submission
* @since 1.13.0
*/
public function should_process_submission() {
if ( wp_doing_ajax() || defined( 'REST_REQUEST' ) ) {
return false;
}
return true;
}
}

View File

@@ -0,0 +1,386 @@
<?php
/**
* Abstract for post models
*
* @package PUM
* @copyright Copyright (c) 2023, Code Atlantic LLC
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Class PUM_Abstract_Model_Post
*/
abstract class PUM_Abstract_Model_Post {
/**
* The current model version.
*
* Used for compatibility testing.
* 1 - v1.0.0
*
* @var int
*/
public $model_version = 1;
/**
* The version of the data currently stored for the current item.
*
* 1 - v1.0.0
*
* @var int
*/
public $data_version;
/**
* The post ID
*/
public $ID = 0;
/**
* Declare the default properties in WP_Post as we can't extend it
*/
public $post_author = 0;
/**
* @var string
*/
public $post_date = '0000-00-00 00:00:00';
/**
* @var string
*/
public $post_date_gmt = '0000-00-00 00:00:00';
/**
* @var string
*/
public $post_content = '';
/**
* @var string
*/
public $post_title = '';
/**
* @var string
*/
public $post_excerpt = '';
/**
* @var string
*/
public $post_status = 'publish';
/**
* @var string
*/
public $comment_status = 'open';
/**
* @var string
*/
public $ping_status = 'open';
/**
* @var string
*/
public $post_password = '';
/**
* @var string
*/
public $post_name = '';
/**
* @var string
*/
public $post_type = '';
/**
* @var string
*/
public $to_ping = '';
/**
* @var string
*/
public $pinged = '';
/**
* @var string
*/
public $post_modified = '0000-00-00 00:00:00';
/**
* @var string
*/
public $post_modified_gmt = '0000-00-00 00:00:00';
/**
* @var string
*/
public $post_content_filtered = '';
/**
* @var int
*/
public $post_parent = 0;
/**
* @var string
*/
public $guid = '';
/**
* @var int
*/
public $menu_order = 0;
/**
* @var string
*/
public $post_mime_type = '';
/**
* @var int
*/
public $comment_count = 0;
/**
* @var
*/
public $filter;
/**
* @var WP_Post
*/
public $post;
/**
* The required post type of the object.
*/
protected $required_post_type = false;
/**
* Whether the object is valid.
*/
protected $valid = true;
/**
* Get things going
*
* @param WP_Post|int $post
*/
public function __construct( $post ) {
if ( ! is_a( $post, 'WP_Post' ) ) {
$post = get_post( $post );
}
$this->setup( $post );
}
/**
* Given the post data, let's set the variables
*
* @param WP_Post $post
*/
protected function setup( $post ) {
if ( ! is_a( $post, 'WP_Post' ) || ! $this->is_required_post_type( $post ) ) {
$this->valid = false;
return;
}
$this->post = $post;
foreach ( get_object_vars( $post ) as $key => $value ) {
$this->$key = $value;
}
}
/**
* @param WP_Post $post
*
* @return bool
*/
protected function is_required_post_type( $post ) {
if ( $this->required_post_type ) {
if ( is_array( $this->required_post_type ) && ! in_array( $post->post_type, $this->required_post_type ) ) {
return false;
} elseif ( is_string( $this->required_post_type ) && $this->required_post_type !== $post->post_type ) {
return false;
}
}
return true;
}
/**
* is triggered when invoking inaccessible methods in an object context.
*
* @param $name string
* @param $arguments array
*
* @return mixed
* @link http://php.net/manual/en/language.oop5.overloading.php#language.oop5.overloading.methods
*/
public function __call( $name, $arguments ) {
if ( method_exists( $this, 'get_' . $name ) ) {
return call_user_func_array( [ $this, 'get_' . $name ], $arguments );
}
}
/**
* Magic __get function to dispatch a call to retrieve a private property
*
* @param $key
*
* @return mixed|WP_Error
*/
public function __get( $key ) {
if ( method_exists( $this, 'get_' . $key ) ) {
return call_user_func( [ $this, 'get_' . $key ] );
} else {
$meta = $this->get_meta( $key );
if ( $meta ) {
return $meta;
}
return new WP_Error( 'post-invalid-property', sprintf( __( 'Can\'t get property %s' ), $key ) );
}
}
/**
* Is object valid.
*
* @return bool.
*/
public function is_valid() {
return $this->valid;
}
/**
* @param $key
* @param bool $single
*
* @return mixed|false
*/
public function get_meta( $key, $single = true ) {
/**
* Checks for remapped meta values. This allows easily adding compatibility layers in the object meta.
*/
if ( false !== $remapped_value = $this->remapped_meta( $key ) ) {
return $remapped_value;
}
return get_post_meta( $this->ID, $key, $single );
}
/**
* @param string $key
* @param mixed $value
* @param bool $unique
*
* @return bool|int
*/
public function add_meta( $key, $value, $unique = false ) {
return add_post_meta( $this->ID, $key, $value, $unique );
}
/**
* @param string $key
* @param mixed $value
*
* @return bool|int
*/
public function update_meta( $key, $value ) {
return update_post_meta( $this->ID, $key, $value );
}
/**
* @param string $key
*
* @return bool
*/
public function delete_meta( $key ) {
return delete_post_meta( $this->ID, $key );
}
/**
* Allows for easy backward compatibility layer management in each child class.
*
* @param string $key
*
* @return bool
*/
public function remapped_meta( $key = '' ) {
return false;
}
/**
* @return int
*/
public function author_id() {
return (int) $this->post_author;
}
/**
* Convert object to array.
*
* @return array Object as array.
*/
public function to_array() {
$post = get_object_vars( $this );
return $post;
}
/**
* @return bool
*/
public function is_trash() {
return get_post_status( $this->ID ) === 'trash';
}
/**
* @return bool
*/
public function is_published() {
return get_post_status( $this->ID ) === 'publish';
}
/**
* @return bool
*/
public function is_draft() {
return get_post_status( $this->ID ) === 'draft';
}
/**
* @return bool
*/
public function is_private() {
return get_post_status( $this->ID ) === 'private';
}
/**
* @return bool
*/
public function is_pending() {
return get_post_status( $this->ID ) === 'pending';
}
}

View File

@@ -0,0 +1,309 @@
<?php
/**
* Abstract for user model
*
* @package PUM
* @copyright Copyright (c) 2023, Code Atlantic LLC
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Core class used to implement the custom WP_User object.
*
* @property string $nickname
* @property string $description
* @property string $user_description
* @property string $first_name
* @property string $user_firstname
* @property string $last_name
* @property string $user_lastname
* @property string $user_login
* @property string $user_pass
* @property string $user_nicename
* @property string $user_email
* @property string $user_url
* @property string $user_registered
* @property string $user_activation_key
* @property string $user_status
* @property int $user_level
* @property string $display_name
* @property string $spam
* @property string $deleted
* @property string $locale
*/
abstract class PUM_Abstract_Model_User {
/**
* The current model version.
*
* Used for compatibility testing.
* 1 - v1.0.0
*
* @var int
*/
public $model_version = 1;
/**
* The version of the data currently stored for the current item.
*
* 1 - v1.0.0
*
* @var int
*/
public $data_version;
/**
* The user's ID.
*
* @var int
*/
public $ID = 0;
/**
* @var \WP_User
*/
public $user;
/**
* @var array An array of keys that can be accessed via the $this->user (WP_User) object.
*/
public $core_data_keys = [
'nickname',
'description',
'user_description',
'first_name',
'user_firstname',
'last_name',
'user_lastname',
'user_login',
'user_pass',
'user_nicename',
'user_email',
'user_url',
'user_registered',
'user_activation_key',
'user_status',
'user_level',
'display_name',
'spam',
'deleted',
'locale',
'data',
'ID',
'caps',
'cap_key',
'roles',
'allcaps',
'filter',
];
/**
* The required permission|user_role|capability|user_level of the user.
*/
protected $required_permission = '';
/**
* Get things going
*
* @param WP_User|int $user
*/
public function __construct( $user ) {
if ( ! is_a( $user, 'WP_User' ) ) {
$user = new WP_User( $user );
}
$this->setup( $user );
}
/**
* Given the user data, let's set the variables
*
* @param WP_User $user The User Object
*/
protected function setup( $user ) {
if ( ! is_a( $user, 'WP_User' ) || ( $this->required_permission && ! $user->has_cap( $this->required_permission ) ) ) {
return;
}
if ( ! isset( $user->data->ID ) ) {
$user->data->ID = 0;
}
$this->user = $user;
// Set $this->ID based on the users ID.
$this->ID = $user->ID;
}
/**
* @param $key
*
* @return bool
*/
public function __isset( $key ) {
if ( in_array( $key, $this->core_data_keys ) ) {
return isset( $this->user->$key );
}
}
/**
* @param $key
*/
public function __unset( $key ) {
if ( in_array( $key, $this->core_data_keys ) ) {
unset( $this->user->$key );
}
}
/**
* Magic __get function to dispatch a call to retrieve a private property
*
* @param $key
*
* @return mixed|WP_Error
*/
public function __get( $key ) {
if ( in_array( $key, $this->core_data_keys ) ) {
return $this->user->$key;
} elseif ( method_exists( $this, 'get_' . $key ) ) {
return call_user_func( [ $this, 'get_' . $key ] );
} else {
$meta = get_user_meta( $this->ID, $key, true );
if ( $meta ) {
return $meta;
}
return new WP_Error( 'user-invalid-property', sprintf( __( 'Can\'t get property %s' ), $key ) );
}
}
/**
* @param $name
* @param $arguments
*
* @return mixed
*/
public function __call( $name, $arguments ) {
if ( method_exists( $this->user, $name ) ) {
return call_user_func_array( [ $this->user, $name ], $arguments );
}
}
/**
* Get per site or global user options.
*
* @param $key
*
* @return mixed
*/
public function get_option( $key ) {
return get_user_option( $key, $this->ID );
}
/**
* Used to set per site or global user options.
*
* @param $key
* @param $value
* @param bool $global
*
* @return bool|int
*/
public function update_option( $key, $value, $global = false ) {
return update_user_option( $this->ID, $key, $value, $global );
}
/**
* Used to delete per site or global user options.
*
* @param $key
* @param bool $global
*
* @return bool
*/
public function delete_option( $key, $global = false ) {
return delete_user_option( $this->ID, $key, $global );
}
/**
* Get user meta.
*
* @param $key
* @param bool $single
*
* @return mixed
*/
public function get_meta( $key, $single = true ) {
return get_user_meta( $this->ID, $key, $single );
}
/**
* Add user meta.
*
* @param $key
* @param $value
*
* @return bool|int
*/
public function add_meta( $key, $value, $unique = false ) {
return add_user_meta( $this->ID, $key, $value, $unique );
}
/**
* Update user meta.
*
* @param $key
* @param $value
*
* @return bool|int
*/
public function update_meta( $key, $value ) {
return update_user_meta( $this->ID, $key, $value );
}
/**
* Delete user meta.
*
* @param $key
* @param $value
*
* @return bool|int
*/
public function delete_meta( $key, $value = '' ) {
return delete_user_meta( $this->ID, $key, $value );
}
/**
* @param int $size
*
* @return false|string
*/
public function get_avatar( $size = 35 ) {
return get_avatar( $this->ID, $size );
}
/**
* Convert object to array.
*
* @return array Object as array.
*/
public function to_array() {
$user = $this->user->to_array();
foreach ( get_object_vars( $this ) as $k => $v ) {
$user[ $k ] = $v;
}
return $user;
}
}

View File

@@ -0,0 +1,440 @@
<?php
/**
* Abstract class for Provider
*
* @package PUM
* @copyright Copyright (c) 2023, Code Atlantic LLC
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Class PUM_Abstract_Provider
*/
abstract class PUM_Abstract_Provider implements PUM_Interface_Provider {
/**
* Option name prefix.
*
* @var string
*/
public $opt_prefix = '';
/**
* Email provider name such as 'mailchimp'
*
* @var string
*/
public $id = '';
/**
* Email provider name for labeling such as 'MailChimp's
*
* @var string
*/
public $name = '';
/**
* Version of the email provider implementation. Used for compatibility.
*
* @var int
*/
public $version = 1;
/**
* Latest current version.
*
* @var int
*/
public $current_version = 2;
/**
* The constructor method which sets up all filters and actions to prepare fields and messages
*/
public function __construct() {
/** Register Provider Globally */
PUM_Newsletter_Providers::instance()->add_provider( $this );
/** Settings */
add_filter( 'pum_settings_fields', [ $this, 'register_settings' ] );
add_filter( 'pum_settings_tab_sections', [ $this, 'register_settings_tab_section' ] );
/**
* Don't add the shortcodes or default options or process anything if the provider is disabled.
*/
if ( ! $this->enabled() ) {
return;
}
/** Shortcodes Fields */
add_filter( 'pum_sub_form_shortcode_tabs', [ $this, 'shortcode_tabs' ] );
add_filter( 'pum_sub_form_shortcode_subtabs', [ $this, 'shortcode_subtabs' ] );
add_filter( 'pum_sub_form_shortcode_fields', [ $this, 'shortcode_fields' ] );
add_filter( 'pum_sub_form_shortcode_defaults', [ $this, 'shortcode_defaults' ] );
/** Forms Processing & AJAX */
add_filter( 'pum_sub_form_sanitization', [ $this, 'process_form_sanitization' ], 10 );
add_filter( 'pum_sub_form_validation', [ $this, 'process_form_validation' ], 10, 2 );
add_action( 'pum_sub_form_submission', [ $this, 'process_form_submission' ], 10, 3 );
/** Form Rendering */
add_action( 'pum_sub_form_fields', [ $this, 'render_fields' ] );
}
/**
* Determines whether to load this providers fields in the shortcode editor among other things.
*
* @return bool
*/
abstract public function enabled();
/**
* Contains each providers unique fields.
*
* @deprecated 1.7.0 Use instead: $this->shortcode_tabs, $this->shortcode_subtabs & $this->shortcode_fields instead.
* @uses self::instance()->shortcode_tabs()
*
* @return array
*/
public function fields() {
return PUM_Admin_Helpers::flatten_fields_array( $this->shortcode_fields() );
}
/**
* Contains each providers unique global settings.
*
* @return array
*/
abstract public function register_settings();
/**
* Contains each providers unique global settings tab sections..
*
* @param array $sections Array of settings page tab sections.
*
* @return array
*/
public function register_settings_tab_section( $sections = [] ) {
$sections['subscriptions'][ $this->id ] = $this->name;
return $sections;
}
/**
* Creates the inputs for each of the needed fields for the email provider
*
* TODO Determine how this should really work for visible custom fields.
*
* @param array $shortcode_atts Array of shortcodee attrs.
*/
public function render_fields( $shortcode_atts ) {
$fields = PUM_Admin_Helpers::flatten_fields_array( $this->shortcode_fields() );
foreach ( $fields as $key => $field ) {
if ( ! $field['private'] && isset( $shortcode_atts[ $key ] ) ) {
echo esc_html( '<input type="hidden" name="' . $key . '" value="' . $shortcode_atts[ $key ] . '" />' );
}
}
}
/**
* Process form value sanitization.
*
* @param array $values Values.
*
* @return array $values
*/
public function form_sanitization( $values = [] ) {
return $values;
}
/**
* Process form values for errors.
*
* @param WP_Error $errors Errors object.
* @param array $values Values.
*
* @return WP_Error
*/
public function form_validation( WP_Error $errors, $values = [] ) {
return $errors;
}
/**
* Subscribes the user to the list
*
* @param array $values Values.
* @param array $json_response JSON Response.
* @param WP_Error $errors Errors object.
*/
public function form_submission( $values, &$json_response, WP_Error &$errors ) {
}
/**
* Internally processes sanitization only for the current provider.
*
* @param array $values Values.
*
* @return array $values
*/
public function process_form_sanitization( $values = [] ) {
if ( $this->id !== $values['provider'] && ( 'none' === $values['provider'] && PUM_Utils_Options::get( 'newsletter_default_provider' ) !== $this->id ) ) {
return $values;
}
return $this->form_sanitization( $values );
}
/**
* Internally processes validation only for the current provider.
*
* @param WP_Error $errors Errors object.
* @param array $values Values.
*
* @return WP_Error
*/
public function process_form_validation( WP_Error $errors, $values = [] ) {
if ( $this->id !== $values['provider'] && ( 'none' === $values['provider'] && PUM_Utils_Options::get( 'newsletter_default_provider' ) !== $this->id ) ) {
return $errors;
}
return $this->form_validation( $errors, $values );
}
/**
* Internally processes submission only for the current provider.
*
* @param array $values Values.
* @param array $json_response AJAX JSON Response array.
* @param WP_Error $errors Errors object.
*/
public function process_form_submission( $values, &$json_response, WP_Error &$errors ) {
if ( $this->id !== $values['provider'] && ( 'none' === $values['provider'] && PUM_Utils_Options::get( 'newsletter_default_provider' ) !== $this->id ) ) {
return;
}
$this->form_submission( $values, $json_response, $errors );
}
/**
*
*
* @return string $tab_id;
*/
public function shortcode_tab_id() {
return 'provider_' . $this->id;
}
/**
* Adds a tab for each provider. These will be hidden except for the chosen provider.
*
* @param array $tabs Array of tab.
*
* @return array
*/
public function shortcode_tabs( $tabs = [] ) {
$resorted_tabs = [];
foreach ( $tabs as $tab_id => $label ) {
$resorted_tabs[ $tab_id ] = $label;
if ( 'general' === $tab_id ) {
$resorted_tabs[ $this->shortcode_tab_id() ] = $this->name;
}
}
return $resorted_tabs;
}
/**
* Adds a subtabs for each provider. These will be hidden except for the chosen provider.
*
* @param array $subtabs Array of tab=>subtabs.
*
* @return array
*/
public function shortcode_subtabs( $subtabs = [] ) {
return array_merge(
$subtabs,
[
$this->shortcode_tab_id() => [
'main' => $this->name,
],
]
);
}
/**
* Registers the fields for this providers shortcode tab.
*
* @param array $fields Array of fields.
*
* @return array
*/
public function shortcode_fields( $fields = [] ) {
$new_fields = $this->version < 2 ? PUM_Admin_Helpers::flatten_fields_array( $this->fields() ) : [];
foreach ( $new_fields as $field_id => $field ) {
if ( isset( $field['options'] ) ) {
$new_fields[ $field_id ]['options'] = array_flip( $field['options'] );
}
}
return array_merge(
$fields,
[
$this->shortcode_tab_id() => [
'main' => $new_fields,
],
]
);
}
/**
* Registers the defaults for this provider.
*
* @param array $defaults Array of default values.
*
* @return array
*/
public function shortcode_defaults( $defaults ) {
// Flatten fields array.
$fields = PUM_Admin_Helpers::flatten_fields_array( $this->shortcode_fields() );
return array_merge( $defaults, PUM_Admin_Helpers::get_field_defaults( $fields ) );
}
/**
* Gets default messages.
*
* @param string|null $context Context of the message to be returned.
*
* @return array|mixed|string
*/
public function default_messages( $context = null ) {
return pum_get_newsletter_default_messages( $context );
}
/**
* Get default or customized messages.
*
* @param string $context Context.
* @param array $values Array of values.
*
* @return string
*/
public function get_message( $context, $values = [] ) {
$message = PUM_Utils_Options::get( "{$this->opt_prefix}{$context}_message", '' );
if ( empty( $message ) ) {
$message = $this->default_messages( $context );
}
if ( strpos( $message, '{' ) ) {
$message = $this->dynamic_message( $message, $values );
}
return apply_filters( "pum_newsletter_{$context}_message", $message, $this );
}
/**
* Process a message with dynamic values.
*
* @param string $message Message.
* @param array $values Array of values.
*
* @return mixed|string
*/
protected function dynamic_message( $message = '', $values = [] ) {
preg_match_all( '/{(.*?)}/', $message, $found );
if ( count( $found[1] ) ) {
foreach ( $found[1] as $key => $match ) {
$message = $this->message_text_replace( $message, $match, $values );
}
}
return $message;
}
/**
* Replaces a single matched message.
*
* @param string $message Message.
* @param string $match Matched phrase.
* @param array $values Values for replacement.
*
* @return mixed|string
*/
protected function message_text_replace( $message = '', $match = '', $values = [] ) {
if ( empty( $match ) ) {
return $message;
}
if ( strpos( $match, '||' ) !== false ) {
$matches = explode( '||', $match );
} else {
$matches = [ $match ];
}
$replace = '';
foreach ( $matches as $string ) {
if ( ! array_key_exists( $string, $values ) ) {
// If its not a valid code it is likely a fallback.
$replace = $string;
} else {
// This is a form field value, replace accordingly.
switch ( $string ) {
default:
$replace = $values[ $string ];
break;
}
}
// If we found a replacement stop the loop.
if ( ! empty( $replace ) ) {
break;
}
}
return str_replace( '{' . $match . '}', $replace, $message );
}
/**
* Magic method replacement.
*
* @param string $name Function or field name.
*
* @return mixed
*/
public function __get( $name ) {
if ( method_exists( $this, 'get_' . $name ) ) {
$method = 'get_' . $name;
return $this->$method();
}
if ( property_exists( $this, $name ) ) {
return $this->$name;
}
return false;
}
}

View File

@@ -0,0 +1,91 @@
<?php
/**
* Abstract class for Registry
*
* @package PUM
* @copyright Copyright (c) 2023, Code Atlantic LLC
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Defines the construct for building an item registry or collection.
*
* @since 1.7.0
*/
abstract class PUM_Abstract_Registry {
/**
* Array of registry items.
*
* @var array
*/
protected $items = [];
/**
* Adds an item to the registry.
*
* @param int $item_id Item ID.
* @param array|object|mixed $attributes {
* Item attributes.
*
* @type string $class Item handler class.
* @type string $file Item handler class file.
* }
*
* @return true Always true.
*/
public function add_item( $item_id, $attributes ) {
foreach ( $attributes as $attribute => $value ) {
$this->items[ $item_id ][ $attribute ] = $value;
}
return true;
}
/**
* Removes an item from the registry by ID.
*
* @param string $item_id Item ID.
*/
public function remove_item( $item_id ) {
unset( $this->items[ $item_id ] );
}
/**
* Retrieves an item and its associated attributes.
*
* @param string $item_id Item ID.
*
* @return array|false Array of attributes for the item if registered, otherwise false.
*/
public function get( $item_id ) {
if ( array_key_exists( $item_id, $this->items ) ) {
return $this->items[ $item_id ];
}
return false;
}
/**
* Retrieves registered items.
*
* @return array The list of registered items.
*/
public function get_items() {
return $this->items;
}
/**
* Only intended for use by tests.
*/
public function _reset_items() {
if ( ! defined( 'WP_TESTS_DOMAIN' ) ) {
_doing_it_wrong( 'PUM_Abstract_Registry::_reset_items', 'This method is only intended for use in phpunit tests', '1.7.0' );
} else {
$this->items = [];
}
}
}

View File

@@ -0,0 +1,433 @@
<?php
/**
* Abstract for posts repository
*
* @package PUM
* @copyright Copyright (c) 2023, Code Atlantic LLC
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Class PUM_Abstract_Repository_Posts
*
* Interface between WP_Query and our data needs. Essentially a query factory.
*/
abstract class PUM_Abstract_Repository_Posts implements PUM_Interface_Repository {
/**
* WordPress query object.
*
* @var WP_Query
*/
protected $query;
/**
* Array of hydrated object models.
*
* @var array
*/
protected $cache = [
'objects' => [],
'queries' => [],
];
/**
* @var string
*/
protected $model;
/**
* Should return a valid post type to test against.
*
* @return string
*/
protected function get_post_type() {
return 'post';
}
/**
* Initialize the repository.
*/
protected function init() {
$this->query = new WP_Query();
$this->reset_strict_query_args();
}
public function __construct() {
$this->init();
}
/**
* @return array
*/
public function default_query_args() {
return [];
}
/**
* @var array
*/
protected $strict_query_args = [];
/**
* Returns an array of default strict query args that can't be over ridden, such as post type.
*
* @return array
*/
protected function default_strict_query_args() {
return [
'post_type' => $this->get_post_type(),
];
}
/**
* Returns an array of enforced query args that can't be over ridden, such as post type.
*
* @return array
*/
protected function get_strict_query_args() {
return $this->strict_query_args;
}
/**
* Sets a specific query arg to a strict value.
*
* @param $key
* @param null $value
*/
protected function set_strict_query_arg( $key, $value = null ) {
$this->strict_query_args[ $key ] = $value;
}
/**
* Returns an array of enforced query args that can't be over ridden, such as post type.
*
* @return array
*/
protected function reset_strict_query_args() {
$this->strict_query_args = $this->default_strict_query_args();
return $this->strict_query_args;
}
/**
* @param array $args
*
* @return array
*/
protected function _build_wp_query_args( $args = [] ) {
$args = wp_parse_args( $args, $this->default_query_args() );
$args = $this->build_wp_query_args( $args );
return array_merge( $args, $this->get_strict_query_args() );
}
/**
* @param array $args
*
* @return array
*/
protected function build_wp_query_args( $args = [] ) {
return $args;
}
/**
* @param int $id
*
* @return WP_Post|PUM_Abstract_Model_Post
* @throws \InvalidArgumentException
*/
public function get_item( $id ) {
if ( ! $this->has_item( $id ) ) {
throw new InvalidArgumentException( sprintf( __( 'No %1$s found with id %2$d.', 'popup-maker' ), $this->get_post_type(), $id ) );
}
return $this->get_model( $id );
}
/**
* @param $field
* @param $value
*
* @return PUM_Abstract_Model_Post|\WP_Post
*/
public function get_item_by( $field, $value ) {
global $wpdb;
$id = $wpdb->get_var( $wpdb->prepare( "SELECT ID FROM $wpdb->posts WHERE %s = %s", $field, $value ) );
if ( ! $id || ! $this->has_item( $id ) ) {
throw new InvalidArgumentException( sprintf( __( 'No user found with %1$s %2$s.', 'popup-maker' ), $field, $value ) );
}
return $this->get_model( $id );
}
/**
* @param int $id
*
* @return bool
*/
public function has_item( $id ) {
return get_post_type( $id ) === $this->get_post_type();
}
/**
* @param $args
*
* @return string
*/
protected function get_args_hash( $args ) {
return md5( serialize( $args ) );
}
/**
* @param array $args
*
* @return WP_Post[]|PUM_Abstract_Model_Post[]
*/
public function get_items( $args = [] ) {
/** Reset default strict query args. */
$this->reset_strict_query_args();
$args = $this->_build_wp_query_args( $args );
$hash = $this->get_args_hash( $args );
if ( ! isset( $this->cache['queries'][ $hash ] ) ) {
/**
* Initialize a new query and return it.
*
* This also keeps the query cached for potential later usage via $this->get_last_query();
*/
$this->query->query( $args );
$this->cache['queries'][ $hash ] = (array) $this->query->posts;
}
/** @var array $posts */
$posts = $this->cache['queries'][ $hash ];
/**
* Only convert to models if the model set is valid and not the WP_Post default.
*/
foreach ( $posts as $key => $post ) {
$posts[ $key ] = $this->get_model( $post );
}
return $posts;
}
/**
* @param array $args
*
* @return int
*/
public function count_items( $args = [] ) {
/** Reset default strict query args. */
$this->reset_strict_query_args();
/** Set several strict query arg overrides, no matter what args were passed. */
$this->set_strict_query_arg( 'fields', 'ids' );
$this->set_strict_query_arg( 'posts_per_page', 1 );
/** We don't use $this->query here to avoid returning count queries via $this->>get_last_query(); */
$query = new WP_Query( $this->_build_wp_query_args( $args ) );
return (int) $query->found_posts;
}
/**
* @return \WP_Query
*/
public function get_last_query() {
return $this->query;
}
/**
* Assert that data is valid.
*
* @param array $data
*
* @throws InvalidArgumentException
*
* TODO Add better Exceptions via these guides:
* - https://www.brandonsavage.net/using-interfaces-for-exceptions/
* - https://www.alainschlesser.com/structuring-php-exceptions/
*
* if ( isset( $data['subject'] ) && ! $data['subject'] ) {
* throw new InvalidArgumentException( 'The subject is required.' );
* }
*/
abstract protected function assert_data( $data );
/**
* @param array $data
*
* @return WP_Post|PUM_Abstract_Model_Post
* @throws InvalidArgumentException
*/
public function create_item( $data ) {
$data = wp_parse_args(
$data,
[
'content' => '',
'title' => '',
'meta_input' => [],
]
);
$this->assert_data( $data );
$post_id = wp_insert_post(
[
'post_type' => $this->get_post_type(),
'post_status' => 'publish',
'post_title' => $data['title'],
'post_content' => $data['content'],
'meta_input' => $data['meta_input'],
],
true
);
if ( is_wp_error( $post_id ) ) {
throw new InvalidArgumentException( $post_id->get_error_message() );
}
return $this->get_item( $post_id );
}
/**
* @param int $id
* @param array $data
*
* @return WP_Post|PUM_Abstract_Model_Post
* @throws Exception
*/
public function update_item( $id, $data ) {
$this->assert_data( $data );
/** @var WP_Post|PUM_Abstract_Model_Post $original */
$original = $this->get_item( $id );
$post_update = [];
foreach ( $data as $key => $value ) {
if ( $original->$key === $value ) {
continue;
}
switch ( $key ) {
default:
$post_update[ $key ] = $value;
break;
case 'title':
$post_update['post_title'] = $value;
break;
case 'content':
$post_update['post_content'] = $value;
break;
case 'custom_meta_key':
update_post_meta( $id, '_custom_meta_key', $value );
}
}
if ( count( $post_update ) ) {
$post_update['ID'] = $id;
wp_update_post( $post_update );
}
return $this->get_item( $id );
}
/**
* @param $post
*
* @return string
*/
protected function get_post_hash( $post ) {
return md5( serialize( $post ) );
}
/**
* @param $post
*
* @return bool
*/
protected function cached_model_exists( $post ) {
return isset( $this->cache['objects'][ $post->ID ] ) && $this->get_post_hash( $post ) === $this->cache['objects'][ $post->ID ]['hash'];
}
/**
* @param int|WP_Post $id
*
* @return WP_Post|PUM_Abstract_Model_Post
*/
protected function get_model( $id ) {
$post = is_a( $id, 'WP_Post' ) ? $id : get_post( $id );
/**
* Only convert to models if the model set is valid and not the WP_Post default.
*/
$model = $this->model;
if ( ! $model || 'WP_Post' === $model || ! class_exists( $model ) || is_a( $post, $model ) ) {
return $post;
}
if ( ! $this->cached_model_exists( $post ) ) {
$object = new $model( $post );
$this->cache['objects'][ $post->ID ] = [
'object' => $object,
'hash' => $this->get_post_hash( $post ),
];
}
return $this->cache['objects'][ $post->ID ]['object'];
}
/**
* @param int $id
*
* @return bool
*/
public function delete_item( $id ) {
return EMPTY_TRASH_DAYS && (bool) wp_trash_post( $id );
}
/**
* @param int $id
*
* @return bool
*/
public function is_item_trashed( $id ) {
return get_post_status( $id ) === 'trash';
}
/**
* @param int $id
*
* @return bool
*/
public function untrash_item( $id ) {
return (bool) wp_untrash_post( $id );
}
/**
* @param int $id
*
* @return bool
*/
public function force_delete_item( $id ) {
return (bool) wp_delete_post( $id, true );
}
}

View File

@@ -0,0 +1,53 @@
<?php
/**
* Abstract class for Upgrade
*
* @package PUM
* @copyright Copyright (c) 2023, Code Atlantic LLC
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Implements a basic upgrade process.
*
* Handles marking complete and resume management.
*
* @since 1.7.0
*/
abstract class PUM_Abstract_Upgrade extends PUM_Abstract_Batch_Process {
/**
* Store the current upgrade args in case we need to redo somehting
*
* @param int $step
*/
public function __construct( $step = 1 ) {
update_option(
'pum_doing_upgrade',
[
'upgrade_id' => $this->batch_id,
'step' => $step,
]
);
parent::__construct( $step );
}
/**
* Defines logic to execute once batch processing is complete.
*/
public function finish() {
/**
* Clear the doing upgrade flag to prevent issues later.
*/
delete_option( 'pum_doing_upgrade' );
parent::finish();
}
}

View File

@@ -0,0 +1,47 @@
<?php
/**
* Abstract for Popup Upgrades
*
* @package PUM
* @copyright Copyright (c) 2023, Code Atlantic LLC
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Implements a batch processor for migrating existing popups to new data structure.
*
* @since 1.7.0
*
* @see PUM_Abstract_Upgrade
* @see PUM_Interface_Batch_PrefetchProcess
* @see PUM_Interface_Upgrade_Posts
*/
abstract class PUM_Abstract_Upgrade_Popups extends PUM_Abstract_Upgrade_Posts implements PUM_Interface_Upgrade_Posts {
/**
* Post type.
*
* @var string
*/
public $post_type = 'popup';
/**
* Process needed upgrades on each post.
*
* @param int $post_id
*/
public function process_post( $post_id = 0 ) {
$this->process_popup( $post_id );
}
/**
* Process needed upgrades on each popup.
*
* @param int $popup_id
*/
abstract public function process_popup( $popup_id = 0 );
}

View File

@@ -0,0 +1,300 @@
<?php
/**
* Abstract for posts upgrade
*
* @package PUM
* @copyright Copyright (c) 2023, Code Atlantic LLC
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Implements a batch processor for migrating existing posts to new data structure.
*
* @since 1.7.0
*
* @see PUM_Abstract_Upgrade
* @see PUM_Interface_Batch_PrefetchProcess
* @see PUM_Interface_Upgrade_Posts
*/
abstract class PUM_Abstract_Upgrade_Posts extends PUM_Abstract_Upgrade implements PUM_Interface_Upgrade_Posts {
/**
* Batch process ID.
*
* @var string
*/
public $batch_id;
/**
* Post type.
*
* @var string
*/
public $post_type = 'post';
/**
* Post status to update.
*
* @var array
*/
public $post_status = [ 'publish', 'pending', 'draft', 'auto-draft', 'future', 'private', 'inherit', 'trash' ];
/**
* Number of posts to migrate per step.
*
* @var int
*/
public $per_step = 1;
/**
* @var array
*/
public $post_ids;
/**
* @var array
*/
public $completed_post_ids;
/**
* Allows disabling of the post_id array query prefetch for stepping.
*
* When true will prefetch all post_ids from the query and cache them, stepping through that array. WP_Query is only called once.
*
* When false the stepping will occur via a new WP_Query with pagination.
*
* True is useful if you are querying on data that will be changed during processing.
*
* False is useful if there may be a massive amount of post data to migrate.
* False is not useful when the query args are targeting data that will be changed.
* Ex: Query all posts with old_meta, then during each step moving old_meta to new_meta.
* In this example, the second query will not include posts updated in the first step, but then also sets an offset skipping posts that need update still.
*
* @var bool
*/
public $prefetch_ids = true;
public function init( $data = null ) {
}
public function pre_fetch() {
$total_to_migrate = $this->get_total_count();
if ( ! $total_to_migrate ) {
$posts = $this->get_posts(
[
'fields' => 'ids',
'posts_per_page' => - 1,
]
);
$posts = wp_parse_id_list( $posts );
$total_to_migrate = count( $posts );
if ( $this->prefetch_ids ) {
$this->set_post_ids( $posts );
}
$this->set_total_count( $total_to_migrate );
}
}
/**
* Gets the results of a custom post query.
*
* @param array $args
*
* @return array
*/
public function get_posts( $args = [] ) {
return get_posts( $this->query_args( $args ) );
}
/**
* Generates an array of query args for this upgrade.
*
* @uses self::custom_query_args();
*
* @param array $args
*
* @return array
*/
public function query_args( $args = [] ) {
$defaults = wp_parse_args(
$this->custom_query_args(),
[
'post_status' => $this->post_status,
'post_type' => $this->post_type,
]
);
return wp_parse_args( $args, $defaults );
}
/**
* @return array
*/
public function custom_query_args() {
return [];
}
/**
* Executes a single step in the batch process.
*
* @return int|string|WP_Error Next step number, 'done', or a WP_Error object.
*/
public function process_step() {
$completed_post_ids = $this->get_completed_post_ids();
if ( $this->prefetch_ids ) {
$all_posts = $this->get_post_ids();
$remaining_post_ids = array_diff( $all_posts, $completed_post_ids );
$posts = array_slice( $remaining_post_ids, 0, $this->per_step );
} else {
$posts = $this->get_posts(
[
'fields' => 'ids',
'posts_per_page' => $this->per_step,
'offset' => $this->get_offset(),
'orderby' => 'ID',
'order' => 'ASC',
]
);
}
if ( empty( $posts ) ) {
return 'done';
}
foreach ( $posts as $post_id ) {
$this->process_post( $post_id );
$completed_post_ids[] = $post_id;
}
// Deduplicate.
$completed_post_ids = wp_parse_id_list( $completed_post_ids );
$this->set_completed_post_ids( $completed_post_ids );
$this->set_current_count( count( $completed_post_ids ) );
return ++ $this->step;
}
/**
* Retrieves a message for the given code.
*
* @param string $code Message code.
*
* @return string Message.
*/
public function get_message( $code ) {
$post_type = get_post_type_object( $this->post_type );
$labels = get_post_type_labels( $post_type );
$singular = strtolower( $labels->singular_name );
$plural = strtolower( $labels->name );
switch ( $code ) {
case 'start':
$total_count = $this->get_total_count();
$message = sprintf( _n( 'Updating %d %2$s.', 'Updating %d %3$s.', $total_count, 'popup-maker' ), number_format_i18n( $total_count ), $singular, $plural );
break;
case 'done':
$final_count = $this->get_current_count();
$message = sprintf( _n( '%s %2$s was updated successfully.', '%s %3$s were updated successfully.', $final_count, 'popup-maker' ), number_format_i18n( $final_count ), $singular, $plural );
break;
default:
$message = '';
break;
}
return $message;
}
/**
* Process needed upgrades on each post.
*
* @param int $post_id
*/
abstract public function process_post( $post_id = 0 );
/**
* Full list of post_ids to be processed.
*
* @return array|bool Default false.
*/
protected function get_post_ids() {
if ( ! isset( $this->post_ids ) || ! $this->post_ids ) {
$this->post_ids = PUM_DataStorage::get( "{$this->batch_id}_post_ids", false );
if ( is_array( $this->post_ids ) ) {
$this->post_ids = wp_parse_id_list( $this->post_ids );
}
}
return $this->post_ids;
}
/**
* Sets list of post_ids to be processed.
*
* @param array $post_ids Full list of post_ids to be processed.
*/
protected function set_post_ids( $post_ids = [] ) {
$this->post_ids = $post_ids;
PUM_DataStorage::write( "{$this->batch_id}_post_ids", $post_ids );
}
/**
* Deletes the stored data for this process.
*/
protected function delete_post_ids() {
$this->post_ids = false;
PUM_DataStorage::delete( "{$this->batch_id}_post_ids" );
}
/**
* Full list of completed_post_ids to be processed.
*
* @return array|bool Default false.
*/
protected function get_completed_post_ids() {
if ( ! isset( $this->completed_post_ids ) || ! $this->completed_post_ids ) {
$completed_post_ids = PUM_DataStorage::get( "{$this->batch_id}_completed_post_ids", [] );
$this->completed_post_ids = wp_parse_id_list( $completed_post_ids );
}
return $this->completed_post_ids;
}
/**
* Sets list of completed_post_ids to be processed.
*
* @param array $completed_post_ids Full list of post_ids to be processed.
*/
protected function set_completed_post_ids( $completed_post_ids = [] ) {
$this->completed_post_ids = wp_parse_id_list( $completed_post_ids );
PUM_DataStorage::write( "{$this->batch_id}_completed_post_ids", $completed_post_ids );
}
/**
* Deletes the stored data for this process.
*/
protected function delete_completed_post_ids() {
$this->completed_post_ids = false;
PUM_DataStorage::delete( "{$this->batch_id}_completed_post_ids" );
}
}

View File

@@ -0,0 +1,83 @@
<?php
/**
* Abstract for settings upgrade
*
* @package PUM
* @copyright Copyright (c) 2023, Code Atlantic LLC
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Implements a batch processor for updating settings after new version.
*
* @since 1.7.0
*
* @see PUM_Abstract_Upgrade
*/
abstract class PUM_Abstract_Upgrade_Settings extends PUM_Abstract_Upgrade {
/**
* Batch process ID.
*
* @var string
*/
public $batch_id = '';
/**
* Executes a single step in the batch process.
*
* @return int|string|WP_Error Next step number, 'done', or a WP_Error object.
*/
public function process_step() {
// Allows sending a start & success message separately.
if ( $this->step > 1 ) {
return 'done';
}
$settings = pum_get_options();
$this->process_settings( $settings );
return ++ $this->step;
}
/**
* Retrieves a message for the given code.
*
* @param string $code Message code.
*
* @return string Message.
*/
public function get_message( $code ) {
switch ( $code ) {
case 'start':
$message = sprintf( __( 'Updating settings for v%s compatibility.', 'popup-maker' ), '1.7' );
break;
case 'done':
$message = __( 'Settings updated successfully.', 'popup-maker' );
break;
default:
$message = '';
break;
}
return $message;
}
/**
* Process needed upgrades on Popup Maker settings
*
* You need to handle saving!!!
*
* @param array $settings
*/
abstract public function process_settings( $settings = [] );
}

View File

@@ -0,0 +1,49 @@
<?php
/**
* Abstract for themes upgrade
*
* @package PUM
* @copyright Copyright (c) 2023, Code Atlantic LLC
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Implements a batch processor for migrating existing popup themes to new data structure.
*
* @since 1.7.0
*
* @see PUM_Abstract_Upgrade
* @see PUM_Interface_Batch_PrefetchProcess
* @see PUM_Interface_Upgrade_Posts
*/
abstract class PUM_Abstract_Upgrade_Themes extends PUM_Abstract_Upgrade_Posts implements PUM_Interface_Upgrade_Posts {
/**
* Post type.
*
* @var string
*/
public $post_type = 'popup_theme';
/**
* Process needed upgrades on each post.
*
* @param int $post_id
*/
public function process_post( $post_id = 0 ) {
$this->process_theme( $post_id );
}
/**
* Process needed upgrades on each popup theme.
*
* @param int $theme_id
*
* @return int $theme_id
*/
abstract public function process_theme( $theme_id = 0 );
}