dvadf
File manager - Edit - /home/centroca/public_html/Tasks.tar
Back
Meta.php 0000644 00000031344 15252506741 0006157 0 ustar 00 <?php namespace WPMailSMTP\Tasks; /** * Class Meta helps to manage the tasks meta information * between Action Scheduler and WP Mail SMTP hooks arguments. * We can't pass arguments longer than >191 chars in JSON to AS, * so we need to store them somewhere (and clean from time to time). * * @since 2.1.0 */ class Meta { /** * Database table name. * * @since 2.1.0 * * @var string */ public $table_name; /** * Database version. * * @since 2.1.0 * * @var string */ public $version; /** * Primary key (unique field) for the database table. * * @since 2.1.0 * * @var string */ public $primary_key = 'id'; /** * Database type identifier. * * @since 2.1.0 * * @var string */ public $type = 'tasks_meta'; /** * Primary class constructor. * * @since 2.1.0 */ public function __construct() { $this->table_name = self::get_table_name(); } /** * Get the DB table name. * * @since 2.1.0 * * @return string */ public static function get_table_name() { global $wpdb; return $wpdb->prefix . 'wpmailsmtp_tasks_meta'; } /** * Get table columns. * * @since 2.1.0 */ public function get_columns() { return array( 'id' => '%d', 'action' => '%s', 'data' => '%s', 'date' => '%s', ); } /** * Default column values. * * @since 2.1.0 * * @return array */ public function get_column_defaults() { return array( 'action' => '', 'data' => '', 'date' => gmdate( 'Y-m-d H:i:s' ), ); } /** * Retrieve a row from the database based on a given row ID. * * @since 2.1.0 * * @param int $row_id Row ID. * * @return null|object */ private function get_from_db( $row_id ) { global $wpdb; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching return $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$this->table_name} WHERE {$this->primary_key} = %s LIMIT 1;", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared $row_id ) ); } /** * Retrieve a row based on column and row ID. * * @since 2.1.0 * * @param string $column Column name. * @param int|string $row_id Row ID. * * @return object|null|bool Database query result, object or null on failure. */ public function get_by( $column, $row_id ) { global $wpdb; if ( empty( $row_id ) || ! array_key_exists( $column, $this->get_columns() ) ) { return false; } // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching return $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $this->table_name WHERE $column = '%s' LIMIT 1;", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.QuotedSimplePlaceholder $row_id ) ); } /** * Retrieve a value based on column name and row ID. * * @since 2.1.0 * * @param string $column Column name. * @param int|string $row_id Row ID. * * @return string|null Database query result (as string), or null on failure. */ public function get_column( $column, $row_id ) { global $wpdb; if ( empty( $row_id ) || ! array_key_exists( $column, $this->get_columns() ) ) { return false; } // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching return $wpdb->get_var( $wpdb->prepare( "SELECT $column FROM $this->table_name WHERE $this->primary_key = '%s' LIMIT 1;", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.QuotedSimplePlaceholder $row_id ) ); } /** * Retrieve one column value based on another given column and matching value. * * @since 2.1.0 * * @param string $column Column name. * @param string $column_where Column to match against in the WHERE clause. * @param string $column_value Value to match to the column in the WHERE clause. * * @return string|null Database query result (as string), or null on failure. */ public function get_column_by( $column, $column_where, $column_value ) { global $wpdb; if ( empty( $column ) || empty( $column_where ) || empty( $column_value ) || ! array_key_exists( $column, $this->get_columns() ) ) { return false; } // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching return $wpdb->get_var( $wpdb->prepare( "SELECT $column FROM $this->table_name WHERE $column_where = %s LIMIT 1;", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared $column_value ) ); } /** * Insert a new record into the database. * * @since 2.1.0 * * @param array $data Column data. * @param string $type Optional. Data type context. * * @return int ID for the newly inserted record. 0 otherwise. */ private function add_to_db( $data, $type = '' ) { global $wpdb; // Set default values. $data = wp_parse_args( $data, $this->get_column_defaults() ); do_action( 'wp_mail_smtp_pre_insert_' . $type, $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 ); $wpdb->insert( $this->table_name, $data, $column_formats ); do_action( 'wp_mail_smtp_post_insert_' . $type, $wpdb->insert_id, $data ); return $wpdb->insert_id; } /** * Update an existing record in the database. * * @since 2.1.0 * * @param int|string $row_id Row ID for the record being updated. * @param array $data Optional. Array of columns and associated data to update. Default empty array. * @param string $where Optional. Column to match against in the WHERE clause. If empty, $primary_key * will be used. Default empty. * @param string $type Optional. Data type context, e.g. 'affiliate', 'creative', etc. Default empty. * * @return bool False if the record could not be updated, true otherwise. */ public function update( $row_id, $data = array(), $where = '', $type = '' ) { global $wpdb; // Row ID must be a positive integer. $row_id = absint( $row_id ); if ( empty( $row_id ) ) { return false; } if ( empty( $where ) ) { $where = $this->primary_key; } do_action( 'wp_mail_smtp_pre_update_' . $type, $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 ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching if ( false === $wpdb->update( $this->table_name, $data, array( $where => $row_id ), $column_formats ) ) { return false; } do_action( 'wp_mail_smtp_post_update_' . $type, $data ); return true; } /** * Delete a record from the database. * * @since 2.1.0 * * @param int|string $row_id Row ID. * * @return bool False if the record could not be deleted, true otherwise. */ 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; } do_action( 'wp_mail_smtp_pre_delete', $row_id ); do_action( 'wp_mail_smtp_pre_delete_' . $this->type, $row_id ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared if ( false === $wpdb->query( $wpdb->prepare( "DELETE FROM {$this->table_name} WHERE {$this->primary_key} = %d", $row_id ) ) ) { return false; } do_action( 'wp_mail_smtp_post_delete', $row_id ); do_action( 'wp_mail_smtp_post_delete_' . $this->type, $row_id ); return true; } /** * Delete a record from the database by column. * * @since 2.1.0 * * @param string $column Column name. * @param int|string $column_value Column value. * * @return bool False if the record could not be deleted, true otherwise. */ public function delete_by( $column, $column_value ) { global $wpdb; if ( empty( $column ) || empty( $column_value ) || ! array_key_exists( $column, $this->get_columns() ) ) { return false; } do_action( 'wp_mail_smtp_pre_delete', $column_value ); do_action( 'wp_mail_smtp_pre_delete_' . $this->type, $column_value ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared if ( false === $wpdb->query( $wpdb->prepare( "DELETE FROM {$this->table_name} WHERE $column = %s", $column_value ) ) ) { return false; } do_action( 'wp_mail_smtp_post_delete', $column_value ); do_action( 'wp_mail_smtp_post_delete_' . $this->type, $column_value ); return true; } /** * Check if the given table exists. * * @since 2.1.0 * * @param string $table The table name. Defaults to the child class table name. * * @return string|null If the table name exists. */ public function table_exists( $table = '' ) { global $wpdb; if ( ! empty( $table ) ) { $table = sanitize_text_field( $table ); } else { $table = $this->table_name; } // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching $db_result = $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table ) ); if ( is_null( $db_result ) ) { return false; } return strtolower( $db_result ) === strtolower( $table ); } /** * Create custom entry meta database table. * Used in migration. * * @since 2.1.0 */ public function create_table() { global $wpdb; require_once ABSPATH . 'wp-admin/includes/upgrade.php'; $charset_collate = ''; if ( ! empty( $wpdb->charset ) ) { $charset_collate .= "DEFAULT CHARACTER SET {$wpdb->charset}"; } if ( ! empty( $wpdb->collate ) ) { $charset_collate .= " COLLATE {$wpdb->collate}"; } $sql = "CREATE TABLE {$this->table_name} ( id bigint(20) NOT NULL AUTO_INCREMENT, action varchar(255) NOT NULL, data longtext NOT NULL, date datetime NOT NULL, PRIMARY KEY (id) ) {$charset_collate};"; dbDelta( $sql ); } /** * Remove queue records for a defined period of time in the past. * Calling this method will remove queue records that are older than $period seconds. * * @since 2.1.0 * * @param string $action Action that should be cleaned up. * @param int $interval Number of seconds from now. * * @return int Number of removed tasks meta records. */ public function clean_by( $action, $interval ) { global $wpdb; if ( empty( $action ) || empty( $interval ) ) { return 0; } $table = self::get_table_name(); $action = sanitize_key( $action ); $date = gmdate( 'Y-m-d H:i:s', time() - (int) $interval ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching return (int) $wpdb->query( $wpdb->prepare( "DELETE FROM `$table` WHERE action = %s AND date < %s", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared $action, $date ) ); } /** * Inserts a new record into the database. * * @since 2.1.0 * * @param array $data Column data. * @param string $type Optional. Data type context. * * @return int ID for the newly inserted record. 0 otherwise. */ public function add( $data, $type = '' ) { if ( empty( $data['action'] ) || ! is_string( $data['action'] ) ) { return 0; } $data['action'] = sanitize_key( $data['action'] ); if ( isset( $data['data'] ) ) { $string = wp_json_encode( $data['data'] ); if ( $string === false ) { $string = ''; } /* * We are encoding the string representation of all the data * to make sure that nothing can harm the database. * This is not an encryption, and we need this data later as is, * so we are using one of the fastest way to do that. * This data is removed from DB on a daily basis. */ // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode $data['data'] = base64_encode( $string ); } if ( empty( $type ) ) { $type = $this->type; } return $this->add_to_db( $data, $type ); } /** * Retrieve a row from the database based on a given row ID. * * @since 2.1.0} * * @param int $meta_id Meta ID. * * @return null|object */ public function get( $meta_id ) { $meta = $this->get_from_db( $meta_id ); if ( empty( $meta ) || empty( $meta->data ) ) { return $meta; } // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode $decoded = base64_decode( $meta->data ); if ( $decoded === false || ! is_string( $decoded ) ) { $meta->data = ''; } else { $meta->data = json_decode( $decoded, true ); } return $meta; } } Actions/FormsLocatorScanTask.php 0000644 00000031353 15252506741 0012733 0 ustar 00 <?php // phpcs:disable Generic.Commenting.DocComment.MissingShort /** @noinspection PhpUnnecessaryCurlyVarSyntaxInspection */ /** @noinspection SqlResolve */ // phpcs:enable Generic.Commenting.DocComment.MissingShort namespace WPForms\Tasks\Actions; use WP_Post; use WP_Query; use WP_Screen; use WPForms\Forms\Locator; use WPForms\Tasks\Meta; use WPForms\Tasks\Task; use WPForms\Tasks\Tasks; /** * Class FormLocatorScanTask. * * @since 1.7.4 */ class FormsLocatorScanTask extends Task { /** * Scan action name for this task. * * @since 1.7.4 */ const SCAN_ACTION = 'wpforms_process_forms_locator_scan'; /** * Re-scan action name for this task. * * @since 1.7.4 */ const RESCAN_ACTION = 'wpforms_process_forms_locator_rescan'; /** * Save action name for this task. * * @since 1.7.4 */ const SAVE_ACTION = 'wpforms_process_forms_locator_save'; /** * Delete action name for this task. * * @since 1.7.4 */ const DELETE_ACTION = 'wpforms_process_forms_locator_delete'; /** * Scan status option name. * * @since 1.7.4 */ const SCAN_STATUS = 'wpforms_process_forms_locator_status'; /** * Scan status "In Progress". * * @since 1.7.4 */ const SCAN_STATUS_IN_PROGRESS = 'in progress'; /** * Scan status "Completed". * * @since 1.7.4 */ const SCAN_STATUS_COMPLETED = 'completed'; /** * Locations query arg. * * @since 1.7.4 */ const LOCATIONS_QUERY_ARG = 'locations'; /** * Chunk size to use in get_form_locations(). * Specifies how many posts to load for scanning in one db request. * Affects memory usage. * * @since 1.7.4 */ const CHUNK_SIZE = 50; /** * Locator class instance. * * @since 1.7.4 * * @var Locator */ private $locator; /** * Tasks class instance. * * @since 1.7.4 * * @var Tasks */ private $tasks; /** * Task recurring interval in seconds. * * @since 1.7.4 * * @var int */ private $interval; /** * Log title. * * @since 1.9.1 * * @var string */ protected $log_title = 'Forms Locator'; /** * Class constructor. * * @since 1.7.4 */ public function __construct() { parent::__construct( self::SCAN_ACTION ); $this->init(); } /** * Initialize the task with all the proper checks. * * @since 1.7.4 */ public function init() { $this->locator = wpforms()->obj( 'locator' ); /** * Allow developers to modify the task interval. * * @since 1.7.4 * * @param int $interval The task recurring interval in seconds. If <= 0, the task will be cancelled. */ $this->interval = (int) apply_filters( 'wpforms_tasks_actions_forms_locator_scan_task_interval', DAY_IN_SECONDS ); $this->hooks(); $this->tasks = wpforms()->obj( 'tasks' ); // Do not add a new one if scheduled. if ( $this->tasks->is_scheduled( self::SCAN_ACTION ) !== false ) { if ( $this->interval <= 0 ) { $this->cancel(); } return; } $this->add_scan_task(); } /** * Add scan task. * * @since 1.7.4 */ private function add_scan_task() { if ( $this->interval <= 0 ) { return; } // Add a new task if none exists. $this->recurring( time(), $this->interval ) ->params() ->register(); } /** * Add hooks. * * @since 1.7.4 */ private function hooks() { // Register hidden action for testing and support. add_action( 'current_screen', [ $this, 'maybe_run_actions_in_admin' ] ); // Register Action Scheduler actions. add_action( self::SCAN_ACTION, [ $this, 'scan' ] ); add_action( self::RESCAN_ACTION, [ $this, 'rescan' ] ); add_action( self::SAVE_ACTION, [ $this, 'save' ] ); add_action( self::DELETE_ACTION, [ $this, 'delete' ] ); add_action( 'action_scheduler_after_process_queue', [ $this, 'after_process_queue' ] ); } /** * Maybe rescan or delete locations. * Hidden undocumented actions for tests and support. * * @since 1.7.4 * * @param WP_Screen $current_screen Current WP_Screen object. */ public function maybe_run_actions_in_admin( $current_screen ) { // phpcs:disable WordPress.Security.NonceVerification.Recommended if ( ! $current_screen || $current_screen->id !== 'toplevel_page_wpforms-overview' || ! isset( $_GET[ self::LOCATIONS_QUERY_ARG ] ) || ! wpforms_debug() ) { return; } if ( $_GET[ self::LOCATIONS_QUERY_ARG ] === 'delete' ) { $this->delete(); } if ( $_GET[ self::LOCATIONS_QUERY_ARG ] === 'scan' ) { $this->rescan(); } // phpcs:enable WordPress.Security.NonceVerification.Recommended wp_safe_redirect( remove_query_arg( [ self::LOCATIONS_QUERY_ARG ] ) ); exit; } /** * Run scan task. * * @since 1.7.4 */ public function scan() { if ( ! $this->tasks ) { return; } // Bail out if the scan is already in progress. if ( self::SCAN_STATUS_IN_PROGRESS === (string) get_option( self::SCAN_STATUS ) ) { return; } // Mark that scan is in progress. update_option( self::SCAN_STATUS, self::SCAN_STATUS_IN_PROGRESS ); $this->log( 'Forms Locator scan action started.' ); // This part of the scan shouldn't take more than 1 second even on big sites. $post_ids = $this->search_in_posts(); $post_locations = $this->get_form_locations( $post_ids ); $widget_locations = $this->locator->search_in_widgets(); $standalone_locations = $this->search_in_standalone_forms(); $locations = array_merge( $post_locations, $widget_locations, $standalone_locations ); $form_location_metas = $this->get_form_location_metas( $locations ); /** * This part of the scan can take a while. * Saving hundreds of metas with a potentially very high number of locations could be time and memory consuming. * That is why we perform save via Action Scheduler. */ $meta_chunks = array_chunk( $form_location_metas, self::CHUNK_SIZE, true ); $count = count( $meta_chunks ); foreach ( $meta_chunks as $index => $meta_chunk ) { $this->tasks->create( self::SAVE_ACTION )->async()->params( $meta_chunk, $index, $count )->register(); } $this->log( 'Save tasks created.' ); } /** * Run immediate scan. * * @since 1.7.4 */ public function rescan() { $this->cancel(); $this->add_scan_task(); } /** * Save form locations. * * @since 1.7.4 * * @param int $meta_id Action meta id. */ public function save( $meta_id ) { $params = ( new Meta() )->get( $meta_id ); if ( ! $params ) { return; } list( $meta_chunk, $index, $count ) = $params->data; foreach ( $meta_chunk as $form_id => $meta ) { update_post_meta( $form_id, Locator::LOCATIONS_META, $meta ); } $this->log( sprintf( 'Forms Locator save action %1$d/%2$d completed.', $index + 1, $count ) ); } /** * Delete form locations. * * @since 1.7.4 */ public function delete() { global $wpdb; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching $wpdb->query( $wpdb->prepare( "DELETE FROM $wpdb->postmeta WHERE meta_key = %s", Locator::LOCATIONS_META ) ); delete_option( self::SCAN_STATUS ); wp_cache_flush(); } /** * After process queue action. * Delete transient to indicate that scanning is completed. * * @since 1.7.4 */ public function after_process_queue() { if ( $this->tasks->is_scheduled( self::SAVE_ACTION ) ) { return; } // Mark that scan is finished. if ( (string) get_option( self::SCAN_STATUS ) === self::SCAN_STATUS_IN_PROGRESS ) { update_option( self::SCAN_STATUS, self::SCAN_STATUS_COMPLETED ); $this->log( 'Forms Locator scan action completed.' ); } } /** * Search form in posts. * * @since 1.7.4 * * @return int[] */ private function search_in_posts() { global $wpdb; $post_statuses = wpforms_wpdb_prepare_in( $this->locator->get_post_statuses() ); $post_types = wpforms_wpdb_prepare_in( $this->locator->get_post_types() ); // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared $ids = $wpdb->get_col( "SELECT p.ID FROM (SELECT ID FROM $wpdb->posts WHERE post_status IN ( $post_statuses ) AND post_type IN ( $post_types ) ) AS ids INNER JOIN $wpdb->posts as p ON ids.ID = p.ID WHERE p.post_content REGEXP '\\\[wpforms|wpforms/form-selector'" ); // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared return array_map( 'intval', $ids ); } /** * Filters the SELECT clause of the query. * Get a minimal set of fields from the post record. * * @since 1.7.4 * * @param string $fields The SELECT clause of the query. * @param WP_Query $query The WP_Query instance (passed by reference). * * @return string * * @noinspection PhpUnusedParameterInspection */ public function posts_fields_filter( $fields, $query ) { global $wpdb; $fields_arr = [ 'ID', 'post_title', 'post_status', 'post_type', 'post_content', 'post_name' ]; $fields_arr = array_map( static function ( $field ) use ( $wpdb ) { return "$wpdb->posts." . $field; }, $fields_arr ); return implode( ', ', $fields_arr ); } /** * Get form locations. * * @since 1.7.4 * * @param int[] $post_ids Post IDs. * * @return array */ private function get_form_locations( $post_ids ) { // phpcs:ignore WPForms.PHP.HooksMethod.InvalidPlaceForAddingHooks /** * Block caching here, as caching produces unneeded db requests in * update_object_term_cache() and update_postmeta_cache(). */ $query_args = [ 'post_type' => $this->locator->get_post_types(), 'post_status' => $this->locator->get_post_statuses(), 'post__in' => $post_ids, 'no_found_rows' => true, 'posts_per_page' => - 1, 'cache_results' => false, ]; // Get form locations by chunks to prevent out of memory issue. $post_id_chunks = array_chunk( $post_ids, self::CHUNK_SIZE ); $locations = []; add_filter( 'posts_fields', [ $this, 'posts_fields_filter' ], 10, 2 ); foreach ( $post_id_chunks as $post_id_chunk ) { $query_args['post__in'] = $post_id_chunk; $query = new WP_Query( $query_args ); $locations = $this->get_form_locations_from_posts( $query->posts, $locations ); } remove_filter( 'posts_fields', [ $this, 'posts_fields_filter' ] ); return $locations; } /** * Get locations from posts. * * @since 1.7.4 * * @param WP_Post[] $posts Posts. * @param array $locations Locations. * * @return array */ private function get_form_locations_from_posts( $posts, $locations = [] ) { $home_url = home_url(); foreach ( $posts as $post ) { $form_ids = $this->locator->get_form_ids( $post->post_content ); if ( ! $form_ids ) { continue; } $url = get_permalink( $post ); $url = ( $url === false || is_wp_error( $url ) ) ? '' : $url; $url = str_replace( $home_url, '', $url ); foreach ( $form_ids as $form_id ) { $locations[] = [ 'type' => $post->post_type, 'title' => $post->post_title, 'form_id' => $form_id, 'id' => $post->ID, 'status' => $post->post_status, 'url' => $url, ]; } } return $locations; } /** * Search in standalone forms. * * @since 1.8.7 * * @return array */ private function search_in_standalone_forms(): array { global $wpdb; $location_types = []; foreach ( Locator::STANDALONE_LOCATION_TYPES as $location_type ) { $location_types[] = '"' . $location_type . '_enable":"1"'; } $regexp = implode( '|', $location_types ); $post_statuses = wpforms_wpdb_prepare_in( $this->locator->get_post_statuses() ); // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared $standalone_forms = $wpdb->get_results( "SELECT ID, post_content, post_status FROM $wpdb->posts WHERE post_status IN ( $post_statuses ) AND post_type = 'wpforms' AND post_content REGEXP '$regexp';" ); // phpcs:enable WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared $locations = []; foreach ( $standalone_forms as $standalone_form ) { $form_data = json_decode( $standalone_form->post_content, true ); $locations[] = $this->locator->build_standalone_location( (int) $standalone_form->ID, $form_data, $standalone_form->post_status ); } return $locations; } /** * Get form location metas. * * @param array $locations Locations. * * @since 1.7.4 * * @return array */ private function get_form_location_metas( $locations ) { $metas = []; foreach ( $locations as $location ) { if ( empty( $location['form_id'] ) ) { continue; } $metas[ $location['form_id'] ][] = $location; } return $metas; } } Actions/WebhooksAutoConfigurationTask.php 0000644 00000004621 15252506741 0014654 0 ustar 00 <?php namespace WPForms\Tasks\Actions; use WPForms\Tasks\Task; use WPForms\Integrations\Stripe\Api\WebhooksManager; use WPForms\Integrations\Stripe\Helpers; /** * Class WebhooksAutoConfigurationTask. * * @since 1.8.4 */ class WebhooksAutoConfigurationTask extends Task { /** * Action name. * * @since 1.8.4 */ const ACTION = 'wpforms_process_webhooks_auto_configuration'; /** * Status option name. * * @since 1.8.4 */ const STATUS = 'wpforms_process_webhooks_auto_configuration_status'; /** * Start status. * * @since 1.8.4 */ const START = 'start'; /** * In progress status. * * @since 1.8.4 */ const IN_PROGRESS = 'in_progress'; /** * Completed status. * * @since 1.8.4 */ const COMPLETED = 'completed'; /** * Webhooks manager. * * @since 1.8.4 * * @var WebhooksManager */ private $webhooks_manager; /** * Log title. * * @since 1.9.1 * * @var string */ protected $log_title = 'Migration'; /** * Constructor. * * @since 1.8.4 */ public function __construct() { parent::__construct( self::ACTION ); $this->webhooks_manager = new WebhooksManager(); } /** * Process the task. * * @since 1.8.4 */ public function init() { // Get a task status. $status = get_option( self::STATUS ); // This task is run in \WPForms\Migrations\Upgrade184::run(), // and started in \WPForms\Migrations\UpgradeBase::run_async(). // Bail out if a task is not started or completed. if ( ! $status || $status === self::COMPLETED ) { return; } // Mark that the task is in progress. update_option( self::STATUS, self::IN_PROGRESS ); // Register hooks. $this->hooks(); $tasks = wpforms()->obj( 'tasks' ); // Add new if none exists. if ( $tasks->is_scheduled( self::ACTION ) !== false ) { return; } $tasks->create( self::ACTION )->async()->register(); } /** * Register hooks. * * @since 1.8.4 */ private function hooks() { add_action( self::ACTION, [ $this, 'process' ] ); } /** * Process the task. * * @since 1.8.4 */ public function process() { // If the Stripe account is connected, then try to configure webhooks. if ( Helpers::has_stripe_keys() && $this->webhooks_manager->connect() ) { $this->log( 'Stripe Payments: Webhooks configured during migration to WPForms 1.8.4.' ); } // Mark that the task is completed. update_option( self::STATUS, self::COMPLETED ); } } Actions/Migration173Task.php 0000644 00000012460 15252506741 0011676 0 ustar 00 <?php namespace WPForms\Tasks\Actions; use WPForms\Tasks\Meta; use WPForms\Tasks\Task; use WPForms\Tasks\Tasks; use WPForms_Entry_Fields_Handler; use WPForms_Entry_Handler; /** * Class Migration173Task. * * @since 1.7.3 */ class Migration173Task extends Task { /** * Action name for this task. * * @since 1.7.3 */ const ACTION = 'wpforms_process_migration_173'; /** * Status option name. * * @since 1.7.3 */ const STATUS = 'wpforms_process_migration_173_status'; /** * Start status. * * @since 1.7.3 */ const START = 'start'; /** * In progress status. * * @since 1.7.3 */ const IN_PROGRESS = 'in progress'; /** * Completed status. * * @since 1.7.3 */ const COMPLETED = 'completed'; /** * Chunk size to use. * Specifies how many entries to load for scanning in one db request. * Affects memory usage. * * @since 1.7.3 */ const CHUNK_SIZE = 50; /** * Entry handler. * * @since 1.7.3 * * @var WPForms_Entry_Handler */ private $entry_handler; /** * Entry fields handler. * * @since 1.7.3 * * @var WPForms_Entry_Fields_Handler */ private $entry_fields_handler; /** * Class constructor. * * @since 1.7.3 */ public function __construct() { parent::__construct( self::ACTION ); } /** * Initialize the task with all the proper checks. * * @since 1.7.3 */ public function init() { $this->entry_handler = wpforms()->obj( 'entry' ); $this->entry_fields_handler = wpforms()->obj( 'entry_fields' ); if ( ! $this->entry_handler || ! $this->entry_fields_handler ) { return; } // Bail out if migration is not started or completed. $status = get_option( self::STATUS ); if ( ! $status || $status === self::COMPLETED ) { return; } // Mark that migration is in progress. update_option( self::STATUS, self::IN_PROGRESS ); $this->hooks(); $tasks = wpforms()->obj( 'tasks' ); // Add new if none exists. if ( $tasks->is_scheduled( self::ACTION ) !== false ) { return; } // Init migration. $this->init_migration( $tasks ); } /** * Add hooks. * * @since 1.7.3 */ private function hooks() { // Register the migrate action. add_action( self::ACTION, [ $this, 'migrate' ] ); // Register after process queue action. add_action( 'action_scheduler_after_process_queue', [ $this, 'after_process_queue' ] ); } /** * Migrate an entry. * * @since 1.7.3 * * @param int $meta_id Action meta id. */ public function migrate( $meta_id ) { $params = ( new Meta() )->get( $meta_id ); if ( ! $params ) { return; } list( $entry_id_chunk ) = $params->data; foreach ( $entry_id_chunk as $entry_id ) { $this->save_entry( $entry_id ); } } /** * After process queue action. * Set status as completed. * * @since 1.7.3 */ public function after_process_queue() { if ( as_has_scheduled_action( self::ACTION ) ) { return; } // Mark that migration is finished. update_option( self::STATUS, self::COMPLETED ); } /** * Init migration. * * @since 1.7.3 * * @param Tasks $tasks Tasks class instance. */ private function init_migration( $tasks ) { // This part of the migration shouldn't take more than 1 second even on big sites. $entry_ids = $this->get_legacy_entry_ids(); if ( ! $entry_ids ) { // Mark that migration is completed. update_option( self::STATUS, self::COMPLETED ); return; } /** * This part of the migration can take a while. * Saving hundreds of entries with a potentially very high number of entry fields could be time and memory consuming. * That is why we perform save via Action Scheduler. */ $entry_id_chunks = array_chunk( $entry_ids, self::CHUNK_SIZE, true ); foreach ( $entry_id_chunks as $entry_id_chunk ) { $tasks->create( self::ACTION )->async()->params( $entry_id_chunk )->register(); } } /** * Get entry ids which do not have relevant entry field records. * * @since 1.7.3 * * @return int[] */ private function get_legacy_entry_ids() { global $wpdb; // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.NoCaching $entries = $wpdb->get_results( " SELECT e.entry_id FROM {$this->entry_handler->table_name} e LEFT JOIN {$this->entry_fields_handler->table_name} ef ON e.entry_id=ef.entry_id WHERE e.status IN( 'partial', 'abandoned' ) AND ef.entry_id IS NULL" ); // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.NoCaching if ( ! $entries || ! is_array( $entries ) ) { return []; } return array_map( 'intval', wp_list_pluck( $entries, 'entry_id' ) ); } /** * Save entry properly. * * @since 1.7.3 * * @param int $entry_id Entry id. */ private function save_entry( $entry_id ) { $entry = $this->entry_handler->get( $entry_id ); if ( ! $entry || ! isset( $entry->form_id, $entry->fields, $entry->date_modified ) ) { return; } $fields = json_decode( $entry->fields, true ); if ( ! is_array( $fields ) ) { return; } $form_data = [ 'id' => (int) $entry->form_id, 'date' => $entry->date_modified, ]; $this->entry_fields_handler->save( $fields, $form_data, $entry_id, true ); } } Actions/IconChoicesFontAwesomeUpgradeTask.php 0000644 00000006107 15252506741 0015361 0 ustar 00 <?php namespace WPForms\Tasks\Actions; use WPForms\Tasks\Task; /** * Class Font Awesome Upgrade task. * * @since 1.8.3 */ class IconChoicesFontAwesomeUpgradeTask extends Task { /** * Action name for this task. * * @since 1.8.3 */ const ACTION = 'wpforms_process_font_awesome_upgrade'; /** * Status option name. * * @since 1.8.3 */ const STATUS = 'wpforms_process_font_awesome_upgrade_status'; /** * Start status. * * @since 1.8.3 */ const START = 'start'; /** * In progress status. * * @since 1.8.3 */ const IN_PROGRESS = 'in_progress'; /** * Completed status. * * @since 1.8.3 */ const COMPLETED = 'completed'; /** * Log title. * * @since 1.9.1 * * @var string */ protected $log_title = 'Migration'; /** * Constructor. * * @since 1.8.3 */ public function __construct() { parent::__construct( self::ACTION ); } /** * Process the task. * * @since 1.8.3 */ public function init() { // Bail out if migration is not started or completed. $status = get_option( self::STATUS ); // This task is run in \WPForms\Pro\Migrations\Upgrade183::run(), // and started in \WPForms\Migrations\UpgradeBase::run_async(). // Bail out if a task is not started or completed. if ( ! $status || $status === self::COMPLETED ) { return; } // Mark that migration is in progress. update_option( self::STATUS, self::IN_PROGRESS ); $this->hooks(); $tasks = wpforms()->obj( 'tasks' ); // Add new if none exists. if ( $tasks->is_scheduled( self::ACTION ) !== false ) { return; } $tasks->create( self::ACTION )->async()->register(); } /** * Hooks. * * @since 1.8.3 */ private function hooks() { add_action( self::ACTION, [ $this, 'upgrade' ] ); } /** * Upgrade. * * @since 1.8.3 */ public function upgrade() { $upload_dir = wpforms_upload_dir(); $tmp_base_path = $upload_dir['path'] . '/icon-choices-tmp'; $cache_base_path = $upload_dir['path'] . '/icon-choices'; $icons_data_file = $cache_base_path . '/icons.json'; if ( ! file_exists( $icons_data_file ) ) { $this->log( 'Font Awesome Upgrade: Font Awesome Upgrade: Library is not present, nothing to upgrade.' ); update_option( self::STATUS, self::COMPLETED ); return; } require_once ABSPATH . 'wp-admin/includes/file.php'; WP_Filesystem(); global $wp_filesystem; $wp_filesystem->rmdir( $tmp_base_path, true ); wpforms()->obj( 'icon_choices' )->run_install( $tmp_base_path ); if ( is_dir( $tmp_base_path ) ) { // Remove old cache. $this->log( 'Font Awesome Upgrade: Removing existing instance of the library.' ); $wp_filesystem->rmdir( $cache_base_path, true ); // Rename temporary directory. $this->log( 'Font Awesome Upgrade: Renaming temporary directory.' ); $wp_filesystem->move( $tmp_base_path, $cache_base_path ); // Mark that migration is finished. $this->log( 'Font Awesome Upgrade: Finished upgrading.' ); update_option( self::STATUS, self::COMPLETED ); return; } $this->log( 'Font Awesome Upgrade: Something went wrong, library was not upgraded.' ); } } Actions/PurgeSpamTask.php 0000644 00000003636 15252506741 0011422 0 ustar 00 <?php namespace WPForms\Tasks\Actions; use WPForms\Tasks\Task; /** * Class PurgeSpamTask. * * @since 1.9.1 */ class PurgeSpamTask extends Task { /** * Action name for this task. * * @since 1.9.1 */ const ACTION = 'wpforms_process_purge_spam'; /** * Interval in seconds. * * @since 1.9.1 * * @var int */ private $interval; /** * Tasks class instance. * * @since 1.9.1 * * @var Tasks */ private $tasks; /** * Log title. * * @since 1.9.1 * * @var string */ protected $log_title = 'Purge Spam'; /** * Class constructor. * * @since 1.9.1 */ public function __construct() { parent::__construct( self::ACTION ); $this->init(); $this->hooks(); } /** * Init. * * @since 1.9.1 */ public function init() { /** * Filter the interval for the purge spam task, in seconds. * * @since 1.9.1 * * @param int $interval Interval in seconds. * * @return int */ $this->interval = (int) apply_filters( 'wpforms_tasks_actions_purge_spam_task_interval', DAY_IN_SECONDS ); $this->tasks = wpforms()->obj( 'tasks' ); // Do not add a new one if scheduled. if ( $this->tasks->is_scheduled( self::ACTION ) !== false ) { if ( $this->interval <= 0 ) { $this->cancel(); } return; } $this->add_scan_task(); } /** * Add hooks. * * @since 1.9.1 */ public function hooks() { add_action( self::ACTION, [ $this, 'process' ] ); } /** * Add a new task. * * @since 1.9.1 */ private function add_scan_task() { if ( $this->interval <= 0 ) { return; } $this->tasks->create( self::ACTION ) ->recurring( time(), $this->interval ) ->params() ->register(); } /** * Purge spam action. * * @since 1.9.1 */ public function process() { $entry_obj = wpforms()->obj( 'entry' ); if ( ! $entry_obj ) { return; } $entry_obj->purge_spam(); $this->log( 'Purge spam completed.' ); } } Actions/EntryEmailsTask.php 0000644 00000002532 15252506741 0011745 0 ustar 00 <?php namespace WPForms\Tasks\Actions; use WPForms\Tasks\Task; use WPForms\Tasks\Meta; /** * Class EntryEmailsTask is responsible for defining how to send emails, * when the form was submitted. * * @since 1.5.9 */ class EntryEmailsTask extends Task { /** * Action name for this task. * * @since 1.5.9 */ const ACTION = 'wpforms_process_entry_emails'; /** * Class constructor. * * @since 1.5.9 */ public function __construct() { parent::__construct( self::ACTION ); $this->async(); } /** * Get the data from Tasks meta table, check/unpack it and * send the email straight away. * * @since 1.5.9 * @since 1.5.9.3 Send immediately instead of calling \WPForms_Process::entry_email() method. * * @param int $meta_id ID for meta information for a task. */ public static function process( $meta_id ) { $task_meta = new Meta(); $meta = $task_meta->get( (int) $meta_id ); // We should actually receive something. if ( empty( $meta ) || empty( $meta->data ) ) { return; } // We expect a certain number of params. if ( count( $meta->data ) !== 5 ) { return; } // We expect a certain meta data structure for this task. list( $to, $subject, $message, $headers, $attachments ) = $meta->data; // Let's do this NOW, finally. wp_mail( $to, $subject, $message, $headers, $attachments ); } } Actions/EntryEmailsMetaCleanupTask.php 0000644 00000004164 15252506741 0014067 0 ustar 00 <?php namespace WPForms\Tasks\Actions; use WPForms\Tasks\Task; use WPForms\Tasks\Meta; /** * Class EntryEmailsMetaCleanupTask. * * @since 1.5.9 */ class EntryEmailsMetaCleanupTask extends Task { /** * Action name for this task. * * @since 1.5.9 */ const ACTION = 'wpforms_process_entry_emails_meta_cleanup'; /** * Class constructor. * * @since 1.5.9 */ public function __construct() { parent::__construct( self::ACTION ); $this->init(); } /** * Initialize the task with all the proper checks. * * @since 1.5.9 */ public function init() { // Register the action handler. $this->hooks(); $tasks = wpforms()->obj( 'tasks' ); $email_async = wpforms_setting( 'email-async' ); // Add new if none exists. if ( $tasks->is_scheduled( self::ACTION ) !== false ) { // Cancel scheduled action if email async option is not set. if ( ! $email_async ) { $this->cancel(); } return; } // Do not schedule action if email async option is not set. if ( ! $email_async ) { return; } // phpcs:disable WPForms.PHP.ValidateHooks.InvalidHookName /** * Filters the email cleanup task interval. * * @since 1.5.9 * * @param int $interval Interval in seconds. */ $interval = (int) apply_filters( 'wpforms_tasks_entry_emails_meta_cleanup_interval', DAY_IN_SECONDS ); // phpcs:enable WPForms.PHP.ValidateHooks.InvalidHookName $this->recurring( strtotime( 'tomorrow' ), $interval ) ->params( $interval ) ->register(); } /** * Add hooks. * * @since 1.7.3 */ private function hooks() { add_action( self::ACTION, [ $this, 'process' ] ); } /** * Perform the cleanup action: remove outdated meta for entry emails task. * * @since 1.5.9 * * @param int $meta_id ID for meta information for a task. */ public function process( $meta_id ) { $task_meta = new Meta(); $meta = $task_meta->get( (int) $meta_id ); // We should actually receive something. if ( empty( $meta ) || empty( $meta->data ) ) { return; } list( $interval ) = $meta->data; $task_meta->clean_by( EntryEmailsTask::ACTION, (int) $interval ); } } Actions/Migration175Task.php 0000644 00000027505 15252506741 0011706 0 ustar 00 <?php namespace WPForms\Tasks\Actions; use WPForms\Tasks\Task; use WPForms\Tasks\Tasks; use WPForms_Entry_Handler; use WPForms_Entry_Meta_Handler; /** * Class Migration175Task. * * @since 1.7.5 */ class Migration175Task extends Task { /** * Action name for this task. * * @since 1.7.5 */ const ACTION = 'wpforms_process_migration_175'; /** * Status option name. * * @since 1.7.5 */ const STATUS = 'wpforms_process_migration_175_status'; /** * Start status. * * @since 1.7.5 */ const START = 'start'; /** * In progress status. * * @since 1.7.5 */ const IN_PROGRESS = 'in progress'; /** * Completed status. * * @since 1.7.5 */ const COMPLETED = 'completed'; /** * Chunk size to use. * Specifies how many entries to convert in one db request. * * @since 1.7.5 */ const CHUNK_SIZE = 5000; /** * Chunk size of the migration task. * Specifies how many entry ids to load at once for further conversion. * * @since 1.7.5 */ const TASK_CHUNK_SIZE = self::CHUNK_SIZE * 10; /** * Entry handler. * * @since 1.7.5 * * @var WPForms_Entry_Handler */ private $entry_handler; /** * Entry meta handler. * * @since 1.7.5 * * @var WPForms_Entry_Meta_Handler */ private $entry_meta_handler; /** * Temporary table name. * * @since 1.7.5 * * @var string */ private $temp_table_name; /** * Class constructor. * * @since 1.7.5 */ public function __construct() { parent::__construct( self::ACTION ); } /** * Initialize the task with all the proper checks. * * @since 1.7.5 */ public function init() { global $wpdb; $this->entry_handler = wpforms()->obj( 'entry' ); $this->entry_meta_handler = wpforms()->obj( 'entry_meta' ); $this->temp_table_name = "{$wpdb->prefix}wpforms_temp_entry_ids"; if ( ! $this->entry_handler || ! $this->entry_meta_handler ) { return; } // Bail out if migration is not started or completed. $status = get_option( self::STATUS ); if ( ! $status || $status === self::COMPLETED ) { return; } $this->hooks(); if ( $status === self::START ) { // Mark that migration is in progress. update_option( self::STATUS, self::IN_PROGRESS ); // Alter entry meta table. $this->alter_entry_meta_table(); // Init migration. $this->init_migration(); } } /** * Modify field in the entry meta table. * * @since 1.7.5 */ private function alter_entry_meta_table() { global $wpdb; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, WordPress.DB.PreparedSQL.InterpolatedNotPrepared $wpdb->query( "ALTER TABLE {$this->entry_meta_handler->table_name} MODIFY type VARCHAR(255)" ); } /** * Add index to a table. * * @since 1.7.5 * * @param string $table_name Table. * @param string $index_name Index name. * @param string $key_part Key part. * * @return void */ private function add_index( $table_name, $index_name, $key_part ) { global $wpdb; // Check id index already exists. // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.NoCaching $result = $wpdb->get_var( "SELECT COUNT(1) IndexIsThere FROM INFORMATION_SCHEMA.STATISTICS WHERE table_schema = DATABASE() AND table_name = '$table_name' AND index_name = '$index_name'" ); if ( $result === '1' ) { return; } // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.NoCaching // Change the column length for the wp_wpforms_entry_meta.type column to 255 and add an index. // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.SchemaChange, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.NoCaching $wpdb->query( "CREATE INDEX $index_name ON $table_name ( $key_part )" ); // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.SchemaChange, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.NoCaching } /** * Add hooks. * * @since 1.7.5 */ private function hooks() { // Register the migrate action. add_action( self::ACTION, [ $this, 'migrate' ] ); // Register after process queue action. add_action( 'action_scheduler_after_process_queue', [ $this, 'after_process_queue' ] ); } /** * Migrate an entry. * * @param int $action_index Action index. * * @since 1.7.5 */ public function migrate( $action_index ) { global $wpdb; $db_indexes = [ - 3 => [ 'table_name' => $this->entry_meta_handler->table_name, 'index_name' => 'form_id', 'key_part' => 'form_id', ], - 2 => [ 'table_name' => $this->entry_meta_handler->table_name, 'index_name' => 'type', 'key_part' => 'type', ], - 1 => [ 'table_name' => $this->entry_meta_handler->table_name, 'index_name' => 'data', 'key_part' => 'data(32)', ], ]; // We create indexes in the background as it could take significant time on a big database. if ( array_key_exists( $action_index, $db_indexes ) ) { $this->add_index( $db_indexes[ $action_index ]['table_name'], $db_indexes[ $action_index ]['index_name'], $db_indexes[ $action_index ]['key_part'] ); return; } // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.NoCaching // The query length in migrate_payment_data() is about 500 chars for 1 entry (7 metas). // The length of the query is defined by MAX_ALLOWED_PACKET variable, which defaults to 4 MB on MySQL 5.7. // We increase MAX_ALLOWED_PACKET variable to fit the number of entries specified in self::CHUNK_SIZE. $new_max_allowed_packet = 500 * self::CHUNK_SIZE; $max_allowed_packet = (int) $wpdb->get_var( "SHOW VARIABLES LIKE 'MAX_ALLOWED_PACKET'", 1 ); if ( $new_max_allowed_packet > $max_allowed_packet ) { $wpdb->query( "SET MAX_ALLOWED_PACKET = $new_max_allowed_packet" ); } // Using OFFSET makes a way longer request, as MySQL has to access all rows before OFFSET. // We follow very fast way with indexed column (id > $action_index). $entry_ids = $wpdb->get_col( $wpdb->prepare( "SELECT entry_id FROM $this->temp_table_name WHERE id > %d LIMIT %d", $action_index, self::TASK_CHUNK_SIZE ) ); $i = 0; $entry_ids_count = count( $entry_ids ); // This cycle is twice less memory consuming than array_chunk( $entry_ids ). while ( $i < $entry_ids_count ) { $entry_ids_chunk = array_slice( $entry_ids, $i, self::CHUNK_SIZE ); $this->migrate_payment_data( implode( ',', $entry_ids_chunk ) ); $i += self::CHUNK_SIZE; } if ( $new_max_allowed_packet > $max_allowed_packet ) { $wpdb->query( "SET MAX_ALLOWED_PACKET = $max_allowed_packet" ); } // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.NoCaching } /** * After process queue action. * Set status as completed. * * @since 1.7.5 */ public function after_process_queue() { $tasks = wpforms()->obj( 'tasks' ); if ( ! $tasks || $tasks->is_scheduled( self::ACTION ) ) { return; } $this->drop_temp_table(); // Mark that migration is finished. update_option( self::STATUS, self::COMPLETED ); } /** * Init migration. * * @since 1.7.5 * @noinspection PhpUndefinedFunctionInspection */ private function init_migration() { // Get all payment entries. $count = $this->get_unprocessed_payment_entry_ids(); if ( ! $count ) { $this->drop_temp_table(); } // We need 3 preliminary steps to create indexes. $index = - 3; while ( $index < $count ) { // We do not use Task class here as we do not need meta. So, we reduce the number of DB requests. as_enqueue_async_action( self::ACTION, [ $index ], Tasks::GROUP ); $index = $index < 0 ? $index + 1 : $index + self::CHUNK_SIZE; } } /** * Migrate payment data to the correct table. * * @param string $entry_ids_list List of entry ids. * * @since 1.7.5 */ private function migrate_payment_data( $entry_ids_list ) { global $wpdb; // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.NoCaching $wpdb->query( "SELECT entry_id, form_id, user_id, status, meta, date FROM {$this->entry_handler->table_name} WHERE entry_id IN ( $entry_ids_list )" ); // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.NoCaching $values = []; foreach ( $wpdb->last_result as $entry ) { $meta = json_decode( $entry->meta, true ); if ( ! is_array( $meta ) ) { continue; } foreach ( $meta as $meta_key => $meta_value ) { // If meta_key doesn't begin with `payment_`, prefix it. $meta_key = strpos( $meta_key, 'payment_' ) === 0 ? $meta_key : "payment_$meta_key"; // We do not use $wpdb->prepare here, as it is 5 times slower. // Prepare takes 1.3 sec to prepare 1000 entries (6000 meta records). // It is incomparable with the two queries here. // With sprintf, the total processing time of this method is 0.15 sec for 1000 entries. $values[] = sprintf( "( %d, %d, %d, '%s', '%s', '%s', '%s' )", $entry->entry_id, $entry->form_id, $entry->user_id, $entry->status, $meta_key, $meta_value, $entry->date ); } } // Bail out if there is no found payment meta. if ( empty( $values ) ) { return; } $values = implode( ', ', $values ); // The following query length is about 500 chars for 1 entry (7 metas). // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.NoCaching $wpdb->query( "INSERT INTO {$this->entry_meta_handler->table_name} ( entry_id, form_id, user_id, status, type, data, date ) VALUES $values" ); // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.NoCaching } /** * Get entry ids which do not have relevant entry field records. * Store them in a temporary table. * * @since 1.7.5 * * @return int */ private function get_unprocessed_payment_entry_ids() { global $wpdb; $this->drop_temp_table(); // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, WordPress.DB.PreparedSQL.InterpolatedNotPrepared $wpdb->query( "CREATE TABLE $this->temp_table_name ( id BIGINT AUTO_INCREMENT PRIMARY KEY, entry_id BIGINT NOT NULL )" ); $wpdb->query( "INSERT INTO $this->temp_table_name (entry_id) SELECT entry_id FROM {$this->entry_handler->table_name} WHERE type = 'payment' AND entry_id NOT IN (SELECT entry_id FROM {$this->entry_meta_handler->table_name} WHERE type LIKE 'payment_%')" ); // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, WordPress.DB.PreparedSQL.InterpolatedNotPrepared return $wpdb->rows_affected; } /** * Drop a temporary table. * * @since 1.7.5 */ private function drop_temp_table() { global $wpdb; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.SchemaChange $wpdb->query( "DROP TABLE IF EXISTS $this->temp_table_name" ); } } Actions/AnalyticsAggregationTask.php 0000644 00000013325 15252506741 0013612 0 ustar 00 <?php namespace WPForms\Tasks\Actions; use DateTimeImmutable; use WPForms\Analytics\Aggregation; use WPForms\Analytics\Analytics; use WPForms\Db\Analytics\DB as AnalyticsDB; use WPForms\Tasks\Task; use WPForms\Tasks\Tasks; // phpcs:ignore WPForms.PHP.UseStatement.UnusedUseStatement /** * Nightly aggregation task for Form Analytics. * * Schedules itself recurring at site-local midnight + abandonment grace * (default 1h after midnight). Delegates work to the Loader-resolved * Analytics\Aggregation (Lite) or Pro\Analytics\Aggregation (Pro). * * @since 2.0.0 */ class AnalyticsAggregationTask extends Task { /** * Action Scheduler action name. * * @since 2.0.0 */ public const ACTION = 'wpforms_analytics_aggregate'; /** * Option key storing the last-applied interval (seconds). * * Used by reconcile_schedule() to detect cadence drift between the * filter's current return value and the cadence the recurring action * was created with. Autoloaded so init() stays on the cheap path. * * @since 2.0.0 */ public const INTERVAL_OPTION = 'wpforms_analytics_aggregation_interval'; /** * Interval in seconds. 0 cancels schedule; non-zero floored at DAY_IN_SECONDS. * * @since 2.0.0 * * @var int */ private $interval; /** * Tasks instance. * * @since 2.0.0 * * @var Tasks|null */ private $tasks; /** * Log title. * * @since 2.0.0 * * @var string */ protected $log_title = 'Analytics Aggregation'; /** * Class constructor. * * @since 2.0.0 */ public function __construct() { parent::__construct( self::ACTION ); $this->init(); $this->hooks(); } /** * Register the recurring schedule. * * @since 2.0.0 */ private function init(): void { $this->tasks = wpforms()->obj( 'tasks' ); if ( ! $this->tasks ) { return; } // Schedule add/remove is an admin/cron concern. Skip it on anonymous // front-end requests, where the schedule never needs adjusting — this // avoids an option read and the interval filter on every page view. if ( ! is_admin() && ! wp_doing_cron() ) { return; } if ( ! Analytics::is_enabled() ) { $this->remove_task(); return; } // Don't schedule until the migration has created the analytics tables. if ( ! AnalyticsDB::tables_exist() ) { return; } /** * Filter the aggregation task interval (seconds). * * Return 0 to cancel the schedule entirely. Non-zero values below * DAY_IN_SECONDS are floored to DAY_IN_SECONDS to preserve the * abandonment-grace correctness boundary. * * @since 2.0.0 * * @param int $interval Interval in seconds. Default DAY_IN_SECONDS. */ $raw = (int) apply_filters( 'wpforms_tasks_actions_analytics_aggregation_task_init_interval', DAY_IN_SECONDS ); $this->interval = $raw <= 0 ? 0 : max( DAY_IN_SECONDS, $raw ); $this->reconcile_schedule(); } /** * Cancel any scheduled aggregation action and clear the recorded interval. * * Called from init() when the analytics kill switch is on so the documented * "no scheduled task" contract holds even if analytics was disabled after a * recurring action had already been registered. * * @since 2.0.0 */ private function remove_task(): void { if ( $this->tasks->is_scheduled( self::ACTION ) !== false ) { $this->cancel(); } delete_option( self::INTERVAL_OPTION ); } /** * Converge the scheduled action to the filtered desired interval. * * Self-healing reconciliation across plugin boots: * - Not scheduled + interval > 0 → schedule + record interval. * - Scheduled + interval == 0 → cancel + drop recorded interval. * - Scheduled + interval changed → cancel + reschedule + update record. * - Scheduled + interval same → no-op (steady state). * * The recorded interval lives in an autoloaded option, so the * steady-state path is one cached lookup with no DB write. * * @since 2.0.0 */ private function reconcile_schedule(): void { $scheduled = $this->tasks->is_scheduled( self::ACTION ) !== false; // Cancellation requested. if ( $this->interval <= 0 ) { if ( $scheduled ) { $this->cancel(); delete_option( self::INTERVAL_OPTION ); } return; } // First-time scheduling — no existing recurring action. if ( ! $scheduled ) { $this->add_task(); update_option( self::INTERVAL_OPTION, $this->interval ); return; } // Already scheduled — re-arm only if cadence changed. if ( (int) get_option( self::INTERVAL_OPTION, 0 ) === $this->interval ) { return; } $this->cancel(); $this->add_task(); update_option( self::INTERVAL_OPTION, $this->interval ); } /** * Bind the recurring action to process(). * * @since 2.0.0 */ private function hooks(): void { add_action( self::ACTION, [ $this, 'process' ] ); } /** * Schedule the first run and recurring cadence. * * @since 2.0.0 */ private function add_task(): void { if ( $this->interval <= 0 ) { return; } $this->tasks->create( self::ACTION ) ->recurring( $this->next_run_timestamp(), $this->interval ) ->params() ->register(); } /** * Compute the first-run timestamp: next site-local midnight + grace. * * @since 2.0.0 * * @return int Unix timestamp. */ private function next_run_timestamp(): int { $midnight = ( new DateTimeImmutable( 'tomorrow', wp_timezone() ) )->getTimestamp(); return $midnight + Aggregation::ABANDONMENT_GRACE_SECONDS; } /** * Recurring callback. Delegates to the resolved Aggregation instance. * * @since 2.0.0 */ public function process(): void { $aggregator = wpforms()->obj( 'analytics_aggregation' ); if ( ! $aggregator || ! method_exists( $aggregator, 'run' ) ) { return; } $aggregator->run(); $this->log( 'Analytics aggregation completed.' ); } } Actions/StripeLinkSubscriptionsTask.php 0000644 00000015471 15252506741 0014373 0 ustar 00 <?php namespace WPForms\Tasks\Actions; use WPForms\Integrations\Stripe\Api\PaymentIntents; use WPForms\Tasks\Task; use WPForms\Integrations\Stripe\Helpers; /** * Class StripeLinkSubscriptionsTask. * * @since 1.8.7 */ class StripeLinkSubscriptionsTask extends Task { /** * Action name for this task. * * @since 1.8.7 */ const ACTION = 'wpforms_process_stripe_link_subscriptions'; /** * Status option name. * * @since 1.8.7 */ const STATUS = 'wpforms_process_stripe_link_subscriptions_status'; /** * Start status. * * @since 1.8.7 */ const START = 'start'; /** * In progress status. * * @since 1.8.7 */ const IN_PROGRESS = 'in_progress'; /** * Completed status. * * @since 1.8.7 */ const COMPLETED = 'completed'; /** * Latest processed payment id. * * @since 1.8.7 */ const LATEST_PROCESSED_OPTION = 'wpforms_stripe_link_subscriptions_latest_processed'; /** * Stripe PaymentIntents API. * * @since 1.8.7 * * @var PaymentIntents */ private $api; /** * Log title. * * @since 1.9.1 * * @var string */ protected $log_title = 'Migration'; /** * Class constructor. * * @since 1.8.7 */ public function __construct() { parent::__construct( self::ACTION ); } /** * Initialize the task. * * @since 1.8.7 */ public function init() { // Get a task status. $status = get_option( self::STATUS ); // This task is run in \WPForms\Migrations\Upgrade187::run(), // and started in \WPForms\Migrations\UpgradeBase::run_async(). // Bail out if a task is not started or completed. if ( ! $status || $status === self::COMPLETED ) { return; } // Mark that the task is in progress. if ( $status === self::START ) { update_option( self::STATUS, self::IN_PROGRESS ); } // Register hooks. $this->hooks(); $tasks = wpforms()->obj( 'tasks' ); // Add new if none exists. if ( $tasks->is_scheduled( self::ACTION ) !== false ) { return; } // Add a new task if none exists. $tasks->create( self::ACTION ) ->async() ->register(); } /** * Register hooks. * * @since 1.8.7 */ private function hooks() { // Register the migrate action. add_action( self::ACTION, [ $this, 'run' ] ); } /** * Run a process task. * * @since 1.8.7 */ public function run() { // Bail if no Stripe account is connected. if ( ! Helpers::has_stripe_keys() ) { $this->complete(); return; } $link_subscriptions = $this->get_link_subscriptions(); // Bail if all subscription were processed. if ( empty( $link_subscriptions ) ) { $this->complete(); return; } $this->api = new PaymentIntents(); $this->process( $link_subscriptions ); } /** * Process subscriptions. * * @since 1.8.7 * * @param array $subscriptions Array of subscriptions. */ private function process( array $subscriptions ) { foreach ( $subscriptions as $subscription ) { $this->update_latest_processed( $subscription->id ); // Use subscription mode to cover all cases (e.g. mode might be switched to test while upgrading). $payment = $this->api->retrieve_payment_intent( $subscription->transaction_id, [ 'mode' => $subscription->mode ] ); // Bail if original payment was unsuccessful. if ( is_null( $payment ) || empty( $payment->status ) || $payment->status !== 'succeeded' ) { continue; } $setup_intent_data = $this->prepare_setup_intent_data( $payment, $subscription ); // Bail if subscription has already had correct mandate. if ( ! $setup_intent_data ) { continue; } $intent = $this->api->create_setup_intent( $setup_intent_data, [ 'mode' => $subscription->mode ] ); // Log failed subscription payment id. if ( empty( $intent ) ) { $this->log( 'Stripe Link Subscriptions: Failed ' . $subscription->id ); } } } /** * Update latest processed id. * * @since 1.8.7 * * @param int $id Subscription ID. */ private function update_latest_processed( int $id ) { update_option( self::LATEST_PROCESSED_OPTION, $id ); } /** * Get all Stripe subscriptions charged through Link. * * @since 1.8.7 * * @return array */ private function get_link_subscriptions(): array { global $wpdb; $latest_payment = (int) get_option( self::LATEST_PROCESSED_OPTION, 0 ); $payments_table = wpforms()->obj( 'payment' )->table_name; $paymentmeta_table = wpforms()->obj( 'payment_meta' )->table_name; $query[] = "SELECT p.* FROM {$payments_table} as p"; $query[] = "INNER JOIN {$paymentmeta_table} as pm ON p.id = pm.payment_id"; $query[] = "WHERE p.id > %d AND p.gateway = 'stripe' AND p.type = 'subscription' AND pm.meta_key = 'method_type' AND pm.meta_value = 'link'"; // Stripe API allows up to 100 read operations per second and 100 write operations per second in live mode, // and 25 operations per second for each in test mode. $query[] = 'ORDER BY p.id LIMIT 20'; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare return $wpdb->get_results( $wpdb->prepare( implode( ' ', $query ), $latest_payment ), OBJECT_K ); } /** * Prepare Setup Intent data. * * @since 1.8.7 * * @param object $payment Stripe payment object. * @param object $subscription Subscription object. * * @return array */ private function prepare_setup_intent_data( $payment, $subscription ): array { if ( ! empty( $payment->mandate ) ) { $mandate = $this->api->retrieve_mandate( $payment->mandate, [ 'mode' => $subscription->mode ] ); } $data = [ 'payment_method_types' => [ 'link' ], 'customer' => $payment->customer, 'payment_method' => $payment->payment_method, 'usage' => 'off_session', 'confirm' => true, ]; // Prepare default data in case mandate is not available. if ( empty( $mandate ) ) { $subscription_meta = wpforms()->obj( 'payment_meta' )->get_all( $subscription->id ); $data['mandate_data'] = [ 'customer_acceptance' => [ 'type' => 'online', 'online' => [ 'ip_address' => $subscription_meta['ip_address']->value, 'user_agent' => $subscription_meta['user_agent']->value, ], ], ]; return $data; } // Mandate is correct so no actions needed. if ( $mandate->type !== 'single_use' ) { return []; } $data['mandate_data'] = [ 'customer_acceptance' => [ 'type' => 'online', 'online' => [ 'ip_address' => $mandate->customer_acceptance->online->ip_address, 'user_agent' => $mandate->customer_acceptance->online->user_agent, ], ], ]; return $data; } /** * Mark that the task is completed. * * @since 1.8.7 */ public function complete() { $this->log( 'Stripe Link Subscriptions: Completed' ); update_option( self::STATUS, self::COMPLETED ); } } Actions/DomainAutoRegistrationTask.php 0000644 00000004601 15252506741 0014143 0 ustar 00 <?php namespace WPForms\Tasks\Actions; use WPForms\Tasks\Task; use WPForms\Integrations\Stripe\Api\DomainManager; use WPForms\Integrations\Stripe\Helpers; /** * Class DomainAutoRegistrationTask. * * @since 1.8.6 */ class DomainAutoRegistrationTask extends Task { /** * Action name. * * @since 1.8.6 */ const ACTION = 'wpforms_process_domain_auto_registration'; /** * Status option name. * * @since 1.8.6 */ const STATUS = 'wpforms_process_domain_auto_registration_status'; /** * Start status. * * @since 1.8.6 */ const START = 'start'; /** * In progress status. * * @since 1.8.6 */ const IN_PROGRESS = 'in_progress'; /** * Completed status. * * @since 1.8.6 */ const COMPLETED = 'completed'; /** * Domain manager. * * @since 1.8.6 * * @var DomainManager */ private $domain_manager; /** * Log title. * * @since 1.9.1 * * @var string */ protected $log_title = 'Migration'; /** * Constructor. * * @since 1.8.6 */ public function __construct() { parent::__construct( self::ACTION ); $this->domain_manager = new DomainManager(); } /** * Process the task. * * @since 1.8.6 */ public function init() { // Get a task status. $status = get_option( self::STATUS ); // This task is run in \WPForms\Migrations\Upgrade186::run(), // and started in \WPForms\Migrations\UpgradeBase::run_async(). // Bail out if a task is not started or completed. if ( ! $status || $status === self::COMPLETED ) { return; } // Mark that the task is in progress. update_option( self::STATUS, self::IN_PROGRESS ); // Register hooks. $this->hooks(); $tasks = wpforms()->obj( 'tasks' ); // Add new if none exists. if ( $tasks->is_scheduled( self::ACTION ) !== false ) { return; } $tasks->create( self::ACTION )->async()->register(); } /** * Register hooks. * * @since 1.8.6 */ private function hooks() { add_action( self::ACTION, [ $this, 'process' ] ); } /** * Process the task. * * @since 1.8.6 */ public function process() { // If the Stripe account is connected, then try to register domain. if ( Helpers::has_stripe_keys() && $this->domain_manager->validate() ) { $this->log( 'Stripe Payments: Stripe domain auto registration during migration to WPForms 1.8.6.' ); } // Mark that the task is completed. update_option( self::STATUS, self::COMPLETED ); } } Actions/AsyncRequestTask.php 0000644 00000002135 15252506741 0012136 0 ustar 00 <?php namespace WPForms\Tasks\Actions; use WPForms\Tasks\Task; use WPForms\Tasks\Meta; /** * Class AsyncRequestTask is responsible to send information in the background. * * @since 1.7.5 */ class AsyncRequestTask extends Task { /** * Action name for this task. * * @since 1.7.5 */ const ACTION = 'wpforms_process_async_request'; /** * Class constructor. * * @since 1.7.5 */ public function __construct() { // Task functionality is needed on cron request only. if ( ! ( defined( 'DOING_CRON' ) && DOING_CRON ) ) { return; } parent::__construct( self::ACTION ); $this->hooks(); } /** * Add hooks. * * @since 1.7.5 */ private function hooks() { // Register the migrate action. add_action( self::ACTION, [ $this, 'process' ] ); } /** * Send usage tracking to the server. * * @since 1.7.5 * * @param int $meta_id Action meta id. */ public static function process( $meta_id ) { $params = ( new Meta() )->get( $meta_id ); if ( ! $params ) { return; } list( $url, $args ) = $params->data; wp_safe_remote_get( $url, $args ); } } Actions/SquareSubscriptionTransactionIDTask.php 0000644 00000006056 15252506741 0016006 0 ustar 00 <?php namespace WPForms\Tasks\Actions; use WPForms\Integrations\Square\Api\Api; use WPForms\Integrations\Square\Connection; use WPForms\Tasks\Task; use WPForms\Tasks\Meta; /** * Class SquareSubscriptionTransactionIDTask. * * @since 1.9.5 */ class SquareSubscriptionTransactionIDTask extends Task { /** * Action name. * * @since 1.9.5 */ private const ACTION = 'wpforms_process_square_subscription_transaction_id'; /** * Constructor. * * @since 1.9.5 */ public function __construct() { parent::__construct( self::ACTION ); $this->init(); } /** * Initialize. * * @since 1.9.5 */ private function init() { $this->hooks(); } /** * Register hooks. * * @since 1.9.5 */ private function hooks() { add_action( 'wpforms_process_payment_saved', [ $this, 'add_task' ], 999, 3 ); add_action( self::ACTION, [ $this, 'process' ] ); } /** * Add task to the queue. * * @since 1.9.5 * * @param string $payment_id Payment ID. * @param array $fields Final/sanitized submitted field data. * @param array $form_data Form data and settings. */ public function add_task( $payment_id, array $fields, array $form_data ) { $payment_obj = wpforms()->obj( 'payment' ); if ( ! $payment_obj ) { return; } $payment = $payment_obj->get( (int) $payment_id ); if ( ! $payment ) { return; } // Bail early if not Square subscription. if ( $payment->gateway !== 'square' || $payment->type !== 'subscription' ) { return; } // Bail early if transaction_id is already set via webhooks. if ( ! empty( $payment->transaction_id ) ) { return; } // Add task to the queue. wpforms()->obj( 'tasks' ) ->create( self::ACTION ) ->once( time() + MINUTE_IN_SECONDS ) ->params( (int) $payment_id ) ->register(); } /** * Process the task. * * @since 1.9.5 * * @param int $meta_id Meta ID. */ public function process( $meta_id ) { $task_meta = new Meta(); $meta = $task_meta->get( (int) $meta_id ); if ( empty( $meta ) || empty( $meta->data ) ) { return; } [ $payment_id ] = $meta->data; $payment = wpforms()->obj( 'payment' )->get( (int) $payment_id ); // Bail early if transaction_id is already set via webhooks. if ( ! empty( $payment->transaction_id ) ) { return; } if ( ! Connection::get() ) { return; } $api = new Api( Connection::get() ); $subscription = $api->retrieve_subscription( $payment->subscription_id ); if ( $subscription === null ) { return; } $invoice = $api->get_latest_subscription_invoice( $subscription ); if ( $invoice === null ) { return; } $transaction_id = $api->get_latest_invoice_transaction_id( $invoice ); // Set transaction_id for the subscription in case it not received earlier. wpforms()->obj( 'payment' )->update( $payment_id, [ 'transaction_id' => $transaction_id ], '', '', [ 'cap' => false ] ); // Log. wpforms()->obj( 'payment_meta' )->add_log( $payment_id, sprintf( 'Square subscription was created. (Invoice ID: %s)', $invoice->getId() ) ); } } Tasks.php 0000644 00000017641 15252506741 0006362 0 ustar 00 <?php namespace WPMailSMTP\Tasks; use ActionScheduler_Action; use ActionScheduler_DataController; use ActionScheduler_DBStore; use WPMailSMTP\Tasks\Queue\CleanupQueueTask; use WPMailSMTP\Tasks\Queue\ProcessQueueTask; use WPMailSMTP\Tasks\Queue\SendEnqueuedEmailTask; use WPMailSMTP\Tasks\Reports\SummaryEmailTask; /** * Class Tasks manages the tasks queue and provides API to work with it. * * @since 2.1.0 */ class Tasks { /** * Group that will be assigned to all actions. * * @since 2.1.0 */ const GROUP = 'wp_mail_smtp'; /** * WP Mail SMTP pending or in-progress actions. * * @since 3.3.0 * * @var array */ private static $active_actions = null; /** * Perform certain things on class init. * * @since 2.1.0 */ public function init() { // phpcs:ignore WPForms.PHP.HooksMethod.InvalidPlaceForAddingHooks // Hide the Action Scheduler admin menu item. add_action( 'admin_menu', [ $this, 'admin_hide_as_menu' ], PHP_INT_MAX ); // Skip tasks registration if Action Scheduler is not usable yet. if ( ! self::is_usable() ) { return; } // Register tasks. foreach ( $this->get_tasks() as $task ) { if ( ! is_subclass_of( $task, '\WPMailSMTP\Tasks\Task' ) ) { continue; } $new_task = new $task(); // Run the init method, if a task has one defined. if ( method_exists( $new_task, 'init' ) ) { $new_task->init(); } } // Remove scheduled action meta after action execution. add_action( 'action_scheduler_after_execute', [ $this, 'clear_action_meta' ], PHP_INT_MAX, 2 ); // Cancel tasks on plugin deactivation. register_deactivation_hook( WPMS_PLUGIN_FILE, [ $this, 'cancel_all' ] ); } /** * Get the list of default scheduled tasks. * Tasks, that are fired under certain specific circumstances * (like sending emails) are not listed here. * * @since 2.1.0 * * @return Task[] List of tasks classes. */ public function get_tasks() { $tasks = [ SummaryEmailTask::class, DebugEventsCleanupTask::class, ProcessQueueTask::class, CleanupQueueTask::class, SendEnqueuedEmailTask::class, NotificationsUpdateTask::class, ]; /** * Filters list of tasks classes. * * @since 2.1.2 * * @param Task[] $tasks List of tasks classes. */ return apply_filters( 'wp_mail_smtp_tasks_get_tasks', $tasks ); } /** * Hide Action Scheduler admin area when not in debug mode. * * @since 2.1.0 */ public function admin_hide_as_menu() { $plugin_exceptions = [ 'woocommerce/woocommerce.php', 'action-scheduler/action-scheduler.php', ]; /** * Filters the list of plugins for which * the Action Scheduler Tools ->Scheduled Actions menu item * should remain visible. * * @since 4.3.0 * * @param array $plugin_exceptions List of plugins exceptions. */ $plugin_exceptions = apply_filters( 'wp_mail_smtp_tasks_tasks_action_scheduler_tools_plugin_exceptions', $plugin_exceptions ); $hide_as_menu = empty( array_filter( $plugin_exceptions, 'is_plugin_active' ) ); // Filter to redefine that WP Mail SMTP hides Tools > Action Scheduler menu item. if ( apply_filters( 'wp_mail_smtp_tasks_admin_hide_as_menu', $hide_as_menu ) ) { remove_submenu_page( 'tools.php', 'action-scheduler' ); } } /** * Create a new task. * Used for "inline" tasks, that require additional information * from the plugin runtime before they can be scheduled. * * Example: * wp_mail_smtp()->get( 'tasks' ) * ->create( 'i_am_the_dude' ) * ->async() * ->params( 'The Big Lebowski', 1998 ) * ->register(); * * This `i_am_the_dude` action will be later processed as: * add_action( 'i_am_the_dude', 'thats_what_you_call_me' ); * * @since 2.1.0 * * @param string $action Action that will be used as a hook. * * @return Task */ public function create( $action ) { return new Task( $action ); } /** * Cancel all the AS actions for a group. * * @since 2.1.0 * * @param string $group Group to cancel all actions for. */ public function cancel_all( $group = '' ) { if ( empty( $group ) ) { $group = self::GROUP; } else { $group = sanitize_key( $group ); } if ( class_exists( 'ActionScheduler_DBStore' ) ) { ActionScheduler_DBStore::instance()->cancel_actions_by_group( $group ); } } /** * Remove all the AS actions for a group and remove group. * * @since 3.7.0 * * @param string $group Group to remove all actions for. */ public function remove_all( $group = '' ) { global $wpdb; if ( empty( $group ) ) { $group = self::GROUP; } else { $group = sanitize_key( $group ); } if ( class_exists( 'ActionScheduler_DBStore' ) && isset( $wpdb->actionscheduler_actions ) && isset( $wpdb->actionscheduler_groups ) ) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching $group_id = $wpdb->get_var( $wpdb->prepare( "SELECT group_id FROM {$wpdb->actionscheduler_groups} WHERE slug=%s", $group ) ); if ( ! empty( $group_id ) ) { // Delete actions. // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching $wpdb->delete( $wpdb->actionscheduler_actions, [ 'group_id' => (int) $group_id ], [ '%d' ] ); // Delete group. // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching $wpdb->delete( $wpdb->actionscheduler_groups, [ 'slug' => $group ], [ '%s' ] ); } } } /** * Clear the meta after action complete. * Fired before an action is marked as completed. * * @since 3.5.0 * * @param integer $action_id Action ID. * @param ActionScheduler_Action $action Action name. */ public function clear_action_meta( $action_id, $action ) { $action_schedule = $action->get_schedule(); if ( $action_schedule === null || $action_schedule->is_recurring() || $action->get_group() !== self::GROUP ) { return; } $hook_args = $action->get_args(); if ( ! is_numeric( $hook_args[0] ) ) { return; } $meta = new Meta(); $meta->delete( $hook_args[0] ); } /** * Whether ActionScheduler thinks that it has migrated or not. * * @since 2.1.0 * * @return bool */ public static function is_usable() { // No tasks if ActionScheduler wasn't loaded. if ( ! class_exists( 'ActionScheduler_DataController' ) ) { return false; } return ActionScheduler_DataController::is_migration_complete(); } /** * Whether task has been scheduled and is pending. * * @since 2.1.0 * * @param string $hook Hook to check for. * * @return bool|null */ public static function is_scheduled( $hook ) { // If ActionScheduler wasn't loaded, then no tasks are scheduled. if ( ! function_exists( 'as_next_scheduled_action' ) ) { return null; } if ( is_null( self::$active_actions ) ) { self::$active_actions = self::get_active_actions(); } if ( in_array( $hook, self::$active_actions, true ) ) { return true; } // Action is not in the array, so it is not scheduled or belongs to another group. if ( function_exists( 'as_has_scheduled_action' ) ) { // This function more performant than `as_next_scheduled_action`, but it is available only since AS 3.3.0. return as_has_scheduled_action( $hook ); } else { return as_next_scheduled_action( $hook ) !== false; } } /** * Get all WP Mail SMTP pending or in-progress actions. * * @since 3.3.0 */ private static function get_active_actions() { global $wpdb; $group = self::GROUP; $sql = "SELECT a.hook FROM {$wpdb->prefix}actionscheduler_actions a JOIN {$wpdb->prefix}actionscheduler_groups g ON g.group_id = a.group_id WHERE g.slug = '$group' AND a.status IN ('in-progress', 'pending')"; // phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared $results = $wpdb->get_results( $sql, 'ARRAY_N' ); // phpcs:enable WordPress.DB.DirectDatabaseQuery.NoCaching // phpcs:enable WordPress.DB.PreparedSQL.NotPrepared return $results ? array_merge( ...$results ) : []; } } Task.php 0000644 00000020464 15252506741 0006174 0 ustar 00 <?php namespace WPMailSMTP\Tasks; use ActionScheduler; /** * Class Task. * * @since 2.1.0 */ class Task { /** * This task is async (runs asap). * * @since 2.1.0 */ const TYPE_ASYNC = 'async'; /** * This task is a recurring. * * @since 2.1.0 */ const TYPE_RECURRING = 'scheduled'; /** * This task is run once. * * @since 2.1.0 */ const TYPE_ONCE = 'once'; /** * Type of the task. * * @since 2.1.0 * * @var string */ private $type; /** * Action that will be used as a hook. * * @since 2.1.0 * * @var string */ private $action; /** * Task meta ID. * * @since 2.1.0 * * @var int */ private $meta_id; /** * All the params that should be passed to the hook. * * @since 2.1.0 * * @var array */ private $params; /** * When the first instance of the job will run. * Used for ONCE ane RECURRING tasks. * * @since 2.1.0 * * @var int */ private $timestamp; /** * How long to wait between runs. * Used for RECURRING tasks. * * @since 2.1.0 * * @var int */ private $interval; /** * Whether this task is unique. * * @since 4.0.0 * * @var bool */ private $unique = false; /** * Task constructor. * * @since 2.1.0 * * @param string $action Action of the task. * * @throws \InvalidArgumentException When action is not a string. * @throws \UnexpectedValueException When action is empty. */ public function __construct( $action ) { if ( ! is_string( $action ) ) { throw new \InvalidArgumentException( 'Task action should be a string.' ); } $this->action = sanitize_key( $action ); if ( empty( $this->action ) ) { throw new \UnexpectedValueException( 'Task action cannot be empty.' ); } } /** * Define the type of the task as async. * * @since 2.1.0 * * @return Task */ public function async() { $this->type = self::TYPE_ASYNC; return $this; } /** * Define the type of the task as recurring. * * @since 2.1.0 * * @param int $timestamp When the first instance of the job will run. * @param int $interval How long to wait between runs. * * @return Task */ public function recurring( $timestamp, $interval ) { $this->type = self::TYPE_RECURRING; $this->timestamp = (int) $timestamp; $this->interval = (int) $interval; return $this; } /** * Define the type of the task as one-time. * * @since 2.1.0 * * @param int $timestamp When the first instance of the job will run. * * @return Task */ public function once( $timestamp ) { $this->type = self::TYPE_ONCE; $this->timestamp = (int) $timestamp; return $this; } /** * Set this task as unique. * * @since 4.0.0 * * @return Task */ public function unique() { $this->unique = true; return $this; } /** * Pass any number of params that should be saved to Meta table. * * @since 2.1.0 * * @return Task */ public function params() { $args = func_get_args(); if ( ! empty( $args ) ) { $this->params = $args; } return $this; } /** * Register the action. * Should be the final call in a chain. * * @since 2.1.0 * * @return null|string Action ID. */ public function register() { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh $action_id = null; // No processing if ActionScheduler is not usable. if ( ! Tasks::is_usable() ) { return $action_id; } // Save data to tasks meta table. if ( ! is_null( $this->params ) ) { $task_meta = new Meta(); // No processing if meta table was not created. if ( ! $task_meta->table_exists() ) { return $action_id; } $this->meta_id = $task_meta->add( [ 'action' => $this->action, 'data' => isset( $this->params ) ? $this->params : [], ] ); if ( empty( $this->meta_id ) ) { return $action_id; } } // Prevent 500 errors when Action Scheduler tables don't exist. try { switch ( $this->type ) { case self::TYPE_ASYNC: $action_id = $this->register_async(); break; case self::TYPE_RECURRING: $action_id = $this->register_recurring(); break; case self::TYPE_ONCE: $action_id = $this->register_once(); break; } } catch ( \RuntimeException $exception ) { $action_id = null; } return $action_id; } /** * Register the async task. * * @since 2.1.0 * * @return null|string Action ID. */ protected function register_async() { if ( ! function_exists( 'as_enqueue_async_action' ) ) { return null; } return as_enqueue_async_action( $this->action, [ $this->meta_id ], Tasks::GROUP, $this->unique ); } /** * Register the recurring task. * * @since 2.1.0 * * @return null|string Action ID. */ protected function register_recurring() { if ( ! function_exists( 'as_schedule_recurring_action' ) ) { return null; } return as_schedule_recurring_action( $this->timestamp, $this->interval, $this->action, [ $this->meta_id ], Tasks::GROUP, $this->unique ); } /** * Register the one-time task. * * @since 2.1.0 * * @return null|string Action ID. */ protected function register_once() { if ( ! function_exists( 'as_schedule_single_action' ) ) { return null; } return as_schedule_single_action( $this->timestamp, $this->action, [ $this->meta_id ], Tasks::GROUP, $this->unique ); } /** * Cancel all occurrences of this task. * * @since 2.1.0 * * @return null|bool|string Null if no matching action found, * false if AS library is missing, * string of the scheduled action ID if a scheduled action was found and unscheduled. */ public function cancel() { // Exit if AS function does not exist. if ( ! function_exists( 'as_unschedule_all_actions' ) || ! Tasks::is_usable() ) { return false; } as_unschedule_all_actions( $this->action ); return true; } /** * Cancel all occurrences of this task, * preventing it from re-registering itself. * * @since 4.0.0 */ public function cancel_force() { // phpcs:ignore WPForms.PHP.HooksMethod.InvalidPlaceForAddingHooks add_action( 'shutdown', [ $this, 'cancel' ], PHP_INT_MAX ); } /** * Remove completed occurrences of this task. * * @since 4.1.0 * * @param int $limit The amount of rows to remove. */ protected function remove_completed( $limit = 0 ) { // Make sure that all used functions, classes, and methods exist. if ( ! function_exists( 'as_get_scheduled_actions' ) || ! class_exists( 'ActionScheduler' ) || ! method_exists( 'ActionScheduler', 'store' ) || ! class_exists( 'ActionScheduler_Store' ) || ! method_exists( 'ActionScheduler_Store', 'delete_action' ) ) { return; } // Cap the query result to prevent performing a large number of individual delete actions at once. $per_page = min( 10, max( 0, intval( $limit ) ) ); // Get completed occurrences of this task. $action_ids = as_get_scheduled_actions( [ 'hook' => $this->action, 'status' => 'complete', 'per_page' => $per_page, ], 'ids' ); if ( empty( $action_ids ) ) { return; } // Delete actions through the Action Scheduler API so that associated // `actionscheduler_logs` rows are cleaned up via the // `action_scheduler_deleted_action` hook. foreach ( $action_ids as $action_id ) { ActionScheduler::store()->delete_action( $action_id ); } } /** * Remove pending occurrences of this task. * * @since 4.3.0 * * @param int $limit The amount of rows to remove. */ protected function remove_pending( $limit = 0 ) { // Make sure that all used functions, classes, and methods exist. if ( ! function_exists( 'as_get_scheduled_actions' ) || ! class_exists( 'ActionScheduler' ) || ! method_exists( 'ActionScheduler', 'store' ) || ! class_exists( 'ActionScheduler_Store' ) || ! method_exists( 'ActionScheduler_Store', 'delete_action' ) ) { return; } $per_page = max( 0, intval( $limit ) ); // Get all pending license check actions. $action_ids = as_get_scheduled_actions( [ 'hook' => $this->action, 'status' => 'pending', 'per_page' => $per_page, ], 'ids' ); if ( empty( $action_ids ) ) { return; } // Delete all pending license check actions. foreach ( $action_ids as $action_id ) { ActionScheduler::store()->delete_action( $action_id ); } } } DebugEventsCleanupTask.php 0000644 00000005733 15252526345 0011644 0 ustar 00 <?php namespace WPMailSMTP\Tasks; use DateTime; use Exception; use WPMailSMTP\Admin\DebugEvents\DebugEvents; use WPMailSMTP\Options; use WPMailSMTP\WP; /** * Class DebugEventsCleanupTask. * * @since 3.6.0 */ class DebugEventsCleanupTask extends Task { /** * Action name for this task. * * @since 3.6.0 */ const ACTION = 'wp_mail_smtp_process_debug_events_cleanup'; /** * Class constructor. * * @since 3.6.0 */ public function __construct() { parent::__construct( self::ACTION ); } /** * Initialize the task with all the proper checks. * * @since 3.6.0 */ public function init() { // phpcs:ignore WPForms.PHP.HooksMethod.InvalidPlaceForAddingHooks // Register the action handler. add_action( self::ACTION, [ $this, 'process' ] ); // Get the retention period value from the Debug Events settings. $retention_period = Options::init()->get( 'debug_events', 'retention_period' ); // Exit if the retention period is not defined (set to "forever") or this task is already scheduled. if ( empty( $retention_period ) || Tasks::is_scheduled( self::ACTION ) !== false ) { return; } // Schedule the task. $this->recurring( strtotime( 'tomorrow' ), $this->get_debug_events_cleanup_interval() ) ->params( $retention_period ) ->register(); } /** * Get the cleanup interval for the debug events. * * @since 3.6.0 * * @return int */ private function get_debug_events_cleanup_interval() { $day_in_seconds = DAY_IN_SECONDS; /** * Filter for the debug events cleanup interval. * * @since 3.6.0 * * @param int $day_in_seconds Debug events cleanup interval. */ return (int) apply_filters( 'wpmailsmtp_tasks_get_debug_events_cleanup_interval', $day_in_seconds ); } /** * Perform the cleanup action: remove outdated debug events. * * @since 3.6.0 * * @param int $meta_id The Meta ID with the stored task parameters. * * @throws Exception Exception will be logged in the Action Scheduler logs table. */ public function process( $meta_id ) { $task_meta = new Meta(); $meta = $task_meta->get( (int) $meta_id ); // We should actually receive the passed parameter. if ( empty( $meta ) || empty( $meta->data ) || count( $meta->data ) !== 1 ) { return; } /** * Date in seconds (examples: 86400, 100500). * Debug Events older than this period will be deleted. * * @var int $retention_period */ $retention_period = (int) $meta->data[0]; if ( empty( $retention_period ) ) { return; } // Bail if DB tables was not created. if ( ! DebugEvents::is_valid_db() ) { return; } $wpdb = WP::wpdb(); $table = DebugEvents::get_table_name(); $date = ( new DateTime( "- $retention_period seconds" ) )->format( WP::datetime_mysql_format() ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching $wpdb->query( // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared $wpdb->prepare( "DELETE FROM `$table` WHERE created_at < %s", $date ) ); } } Reports/SummaryEmailTask.php 0000644 00000004350 15252526345 0012156 0 ustar 00 <?php namespace WPMailSMTP\Tasks\Reports; use WPMailSMTP\Tasks\Tasks; use WPMailSMTP\WP; use WPMailSMTP\Tasks\Task; use WPMailSMTP\Reports\Emails\Summary as SummaryReportEmail; /** * Class SummaryEmailTask. * * @since 3.0.0 */ class SummaryEmailTask extends Task { /** * Action name for this task. * * @since 3.0.0 */ const ACTION = 'wp_mail_smtp_summary_report_email'; /** * Class constructor. * * @since 3.0.0 */ public function __construct() { parent::__construct( self::ACTION ); } /** * Initialize the task with all the proper checks. * * @since 3.0.0 */ public function init() { // Register the action handler. add_action( self::ACTION, array( $this, 'process' ) ); $is_disabled = SummaryReportEmail::is_disabled(); // Exit if summary report email is disabled or this task is already scheduled. if ( ! empty( $is_disabled ) || Tasks::is_scheduled( self::ACTION ) !== false ) { return; } $date = new \DateTime( 'next monday 2pm', WP::wp_timezone() ); // Schedule the task. $this ->recurring( $date->getTimestamp(), WEEK_IN_SECONDS ) ->unique() ->register(); } /** * Process summary report email send. * * @since 3.0.0 * * @param int $meta_id The Meta ID with the stored task parameters. */ public function process( $meta_id ) { // Prevent email sending if summary report email is disabled. if ( SummaryReportEmail::is_disabled() || ! $this->is_allowed() ) { return; } // Update the last sent week at the top to prevent multiple emails in case of task failure and retry. update_option( 'wp_mail_smtp_summary_report_email_last_sent_week', current_time( 'W' ) ); $reports = wp_mail_smtp()->get_reports(); $email = $reports->get_summary_report_email(); $email->send(); } /** * Check if the summary report email is allowed to be sent. * * The email is allowed to be sent if it was not sent in the current week. * * @since 4.1.1 * * @return bool */ private function is_allowed() { $last_sent_week = get_option( 'wp_mail_smtp_summary_report_email_last_sent_week' ); $current_week = current_time( 'W' ); if ( $last_sent_week === false || ( (int) $current_week !== (int) $last_sent_week ) ) { return true; } return false; } } Queue/SendEnqueuedEmailTask.php 0000644 00000003344 15252526345 0012536 0 ustar 00 <?php namespace WPMailSMTP\Tasks\Queue; use WPMailSMTP\Tasks\Meta; use WPMailSMTP\Tasks\Task; /** * Class SendEnqueuedEmailTask. * * @since 4.0.0 */ class SendEnqueuedEmailTask extends Task { /** * Action name for this task. * * @since 4.0.0 */ const ACTION = 'wp_mail_smtp_send_enqueued_email'; /** * Class constructor. * * @since 4.0.0 */ public function __construct() { parent::__construct( self::ACTION ); } /** * Initialize the task. * * @since 4.0.0 */ public function init() { // phpcs:ignore WPForms.PHP.HooksMethod.InvalidPlaceForAddingHooks // Register the action handler. add_action( self::ACTION, [ $this, 'process' ] ); // Cleanup completed task occurrences. add_action( 'action_scheduler_after_process_queue', [ $this, 'cleanup' ] ); } /** * Schedule email sending. * * @since 4.0.0 * * @param int $email_id Email id. */ public function schedule( $email_id ) { // Exit if AS function does not exist. if ( ! function_exists( 'as_has_scheduled_action' ) ) { return; } // Schedule the task. $this->async() ->params( $email_id ) ->register(); } /** * Perform email sending. * * @since 4.0.0 * * @param int $meta_id The Meta ID with the stored task parameters. */ public function process( $meta_id ) { $task_meta = new Meta(); $meta = $task_meta->get( (int) $meta_id ); // We should actually receive the passed parameter. if ( empty( $meta ) || empty( $meta->data ) || count( $meta->data ) < 1 ) { return; } $email_id = $meta->data[0]; wp_mail_smtp()->get_queue()->send_email( $email_id ); } /** * Cleanup completed tasks. * * @since 4.1.0 */ public function cleanup() { $this->remove_completed( 10 ); } } Queue/CleanupQueueTask.php 0000644 00000003704 15252526345 0011575 0 ustar 00 <?php namespace WPMailSMTP\Tasks\Queue; use DateTime; use DateTimeZone; use WPMailSMTP\Queue\Attachments; use WPMailSMTP\Tasks\Task; use WPMailSMTP\Tasks\Tasks; /** * Class CleanupQueueTask. * * @since 4.0.0 */ class CleanupQueueTask extends Task { /** * Action name for this task. * * @since 4.0.0 */ const ACTION = 'wp_mail_smtp_queue_cleanup'; /** * Class constructor. * * @since 4.0.0 */ public function __construct() { parent::__construct( self::ACTION ); } /** * Initialize the task. * * @since 4.0.0 */ public function init() { // phpcs:ignore WPForms.PHP.HooksMethod.InvalidPlaceForAddingHooks // Register the action handler. add_action( self::ACTION, [ $this, 'process' ] ); // Exit if this task the queue is disabled, or it's already scheduled. if ( ! wp_mail_smtp()->get_queue()->is_enabled() || Tasks::is_scheduled( self::ACTION ) !== false ) { return; } // Schedule the task. $this->recurring( strtotime( 'now' ), DAY_IN_SECONDS ) ->unique() ->register(); } /** * Perform email sending. * * @since 4.0.0 */ public function process() { $queue = wp_mail_smtp()->get_queue(); $attachments = new Attachments(); // Cleanup processed emails. $queue->cleanup(); // Cleanup older-than-a-month attachments. $attachments->delete_attachments( null, new DateTime( '1 month ago', new DateTimeZone( 'UTC' ) ) ); if ( ! $queue->is_enabled() ) { // If the query has been disabled in the meanwhile, // and there aren't any emails left, // cancel the cleanup task. $queued_emails_count = $queue->count_queued_emails(); $processed_emails_count = $queue->count_processed_emails(); if ( $queued_emails_count === 0 && $processed_emails_count === 0 ) { // Cleanup any remaining, older-than-an-hour attachments. $attachments->delete_attachments( null, new DateTime( '1 hour ago', new DateTimeZone( 'UTC' ) ) ); $this->cancel_force(); } } } } Queue/ProcessQueueTask.php 0000644 00000002661 15252526345 0011625 0 ustar 00 <?php namespace WPMailSMTP\Tasks\Queue; use WPMailSMTP\Tasks\Task; use WPMailSMTP\Tasks\Tasks; /** * Class ProcessQueueTask. * * @since 4.0.0 */ class ProcessQueueTask extends Task { /** * Action name for this task. * * @since 4.0.0 */ const ACTION = 'wp_mail_smtp_queue_process'; /** * Class constructor. * * @since 4.0.0 */ public function __construct() { parent::__construct( self::ACTION ); } /** * Initialize the task. * * @since 4.0.0 */ public function init() { // phpcs:ignore WPForms.PHP.HooksMethod.InvalidPlaceForAddingHooks // Register the action handler. add_action( self::ACTION, [ $this, 'process' ] ); // Cleanup completed task occurrences. add_action( 'action_scheduler_after_process_queue', [ $this, 'cleanup' ] ); // Exit if this task the queue is disabled, or it's already scheduled. if ( ! wp_mail_smtp()->get_queue()->is_enabled() || Tasks::is_scheduled( self::ACTION ) !== false ) { return; } // Schedule the task. $this->recurring( strtotime( 'now' ), MINUTE_IN_SECONDS ) ->unique() ->register(); } /** * Perform email sending. * * @since 4.0.0 */ public function process() { $queue = wp_mail_smtp()->get_queue(); $queue->process(); if ( ! $queue->is_enabled() ) { $this->cancel_force(); } } /** * Cleanup completed tasks. * * @since 4.1.0 */ public function cleanup() { $this->remove_completed( 10 ); } } NotificationsUpdateTask.php 0000644 00000002626 15252526345 0012073 0 ustar 00 <?php namespace WPMailSMTP\Tasks; use Exception; /** * Class NotificationsUpdateTask. * * @since 4.3.0 */ class NotificationsUpdateTask extends Task { /** * Action name for this task. * * @since 4.3.0 */ const ACTION = 'wp_mail_smtp_admin_notifications_update'; /** * Class constructor. * * @since 4.3.0 */ public function __construct() { parent::__construct( self::ACTION ); } /** * Initialize the task with all the proper checks. * * @since 4.3.0 */ public function init() { // phpcs:ignore WPForms.PHP.HooksMethod.InvalidPlaceForAddingHooks // Register the action handler. add_action( self::ACTION, [ $this, 'process' ] ); // Exit if notifications are disabled // or this task is already scheduled. if ( ! wp_mail_smtp()->get_notifications()->is_enabled() || Tasks::is_scheduled( self::ACTION ) !== false ) { return; } // Schedule the task. $this->recurring( strtotime( '+1 minute' ), wp_mail_smtp()->get_notifications()->get_notification_update_task_interval() ) ->unique() ->register(); } /** * Update the notification feed. * * @since 4.3.0 */ public function process() { // Delete task duplicates. try { $this->remove_pending( 1000 ); } catch ( Exception $e ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch // Do nothing. } wp_mail_smtp()->get_notifications()->update(); } }
dvadf
dvadf
| ver. 1.4 |
Github
|
.
| PHP 7.3.33 | Generation time: 0 |
proxy
|
phpinfo
|
Settings