Initial commit: Atomaste website
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
<?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_remote_get( $url, $args );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
<?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 );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
<?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 );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
<?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 );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,575 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
<?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.' );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,268 @@
|
||||
<?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 );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,454 @@
|
||||
<?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" );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
<?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.' );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,310 @@
|
||||
<?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 );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
<?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 );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,267 @@
|
||||
<?php
|
||||
|
||||
namespace WPForms\Tasks;
|
||||
|
||||
use WPForms_DB;
|
||||
|
||||
/**
|
||||
* Class Meta helps to manage the tasks meta information
|
||||
* between Action Scheduler and WPForms 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 1.5.9
|
||||
*/
|
||||
class Meta extends WPForms_DB {
|
||||
|
||||
/**
|
||||
* Primary key (unique field) for the database table.
|
||||
*
|
||||
* @since 1.5.9
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $primary_key = 'id';
|
||||
|
||||
/**
|
||||
* Database type identifier.
|
||||
*
|
||||
* @since 1.5.9
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $type = 'tasks_meta';
|
||||
|
||||
/**
|
||||
* Primary class constructor.
|
||||
*
|
||||
* @since 1.5.9
|
||||
*/
|
||||
public function __construct() {
|
||||
|
||||
parent::__construct();
|
||||
|
||||
$this->table_name = self::get_table_name();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the DB table name.
|
||||
*
|
||||
* @since 1.5.9
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_table_name() {
|
||||
|
||||
global $wpdb;
|
||||
|
||||
return $wpdb->prefix . 'wpforms_tasks_meta';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get table columns.
|
||||
*
|
||||
* @since 1.5.9
|
||||
*/
|
||||
public function get_columns() {
|
||||
|
||||
return [
|
||||
'id' => '%d',
|
||||
'action' => '%s',
|
||||
'data' => '%s',
|
||||
'date' => '%s',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Default column values.
|
||||
*
|
||||
* @since 1.5.9
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_column_defaults() {
|
||||
|
||||
return [
|
||||
'action' => '',
|
||||
'data' => '',
|
||||
'date' => gmdate( 'Y-m-d H:i:s' ),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create custom entry meta database table.
|
||||
* Used in migration and on plugin activation.
|
||||
*
|
||||
* @since 1.5.9
|
||||
*
|
||||
* @noinspection UnusedFunctionResultInspection
|
||||
*/
|
||||
public function create_table() {
|
||||
|
||||
global $wpdb;
|
||||
|
||||
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
|
||||
|
||||
$charset_collate = $wpdb->get_charset_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 1.5.9
|
||||
*
|
||||
* @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.DirectQuery, 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 1.5.9
|
||||
*
|
||||
* @param array $data Column data.
|
||||
* @param string $type Optional. Data type context.
|
||||
*
|
||||
* @return int ID for the newly inserted record. Zero 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'] ) ) {
|
||||
$data['data'] = $this->prepare_data( $data['data'] );
|
||||
}
|
||||
|
||||
if ( empty( $type ) ) {
|
||||
$type = $this->type;
|
||||
}
|
||||
|
||||
return parent::add( $data, $type );
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare data.
|
||||
*
|
||||
* @since 1.7.0
|
||||
*
|
||||
* @param array $data Meta data.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function prepare_data( $data ) {
|
||||
|
||||
$string = wp_json_encode( $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 ways to do that.
|
||||
* This data is removed from DB daily.
|
||||
*/
|
||||
// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
|
||||
return base64_encode( $string );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a row from the database based on a given row ID.
|
||||
*
|
||||
* @since 1.5.9
|
||||
*
|
||||
* @param int $meta_id Meta ID.
|
||||
*
|
||||
* @return null|object
|
||||
* @noinspection PhpParameterNameChangedDuringInheritanceInspection
|
||||
*/
|
||||
public function get( $meta_id ) {
|
||||
|
||||
$meta = parent::get( $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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get meta ID by action name and params.
|
||||
*
|
||||
* @since 1.7.0
|
||||
*
|
||||
* @param string $action Action name.
|
||||
* @param array $params Action params.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_meta_id( $action, $params ) {
|
||||
|
||||
global $wpdb;
|
||||
|
||||
$table = self::get_table_name();
|
||||
$action = sanitize_key( $action );
|
||||
$data = $this->prepare_data( array_values( $params ) );
|
||||
|
||||
return absint(
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
|
||||
$wpdb->get_var(
|
||||
$wpdb->prepare(
|
||||
"SELECT id FROM $table WHERE action = %s AND data = %s LIMIT 1", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
$action,
|
||||
$data
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,377 @@
|
||||
<?php
|
||||
|
||||
namespace WPForms\Tasks;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use UnexpectedValueException;
|
||||
|
||||
/**
|
||||
* Class Task.
|
||||
*
|
||||
* @since 1.5.9
|
||||
*/
|
||||
class Task {
|
||||
|
||||
/**
|
||||
* This task is async (runs asap).
|
||||
*
|
||||
* @since 1.5.9
|
||||
*/
|
||||
const TYPE_ASYNC = 'async';
|
||||
|
||||
/**
|
||||
* This task is a recurring.
|
||||
*
|
||||
* @since 1.5.9
|
||||
*/
|
||||
const TYPE_RECURRING = 'scheduled';
|
||||
|
||||
/**
|
||||
* This task is run once.
|
||||
*
|
||||
* @since 1.5.9
|
||||
*/
|
||||
const TYPE_ONCE = 'once';
|
||||
|
||||
/**
|
||||
* Type of the task.
|
||||
*
|
||||
* @since 1.5.9
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $type;
|
||||
|
||||
/**
|
||||
* Action that will be used as a hook.
|
||||
*
|
||||
* @since 1.5.9
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $action;
|
||||
|
||||
/**
|
||||
* Task meta ID.
|
||||
*
|
||||
* @since 1.5.9
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $meta_id;
|
||||
|
||||
/**
|
||||
* All the params that should be passed to the hook.
|
||||
*
|
||||
* @since 1.5.9
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $params;
|
||||
|
||||
/**
|
||||
* When the first instance of the job will run.
|
||||
* Used for ONCE ane RECURRING tasks.
|
||||
*
|
||||
* @since 1.5.9
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $timestamp;
|
||||
|
||||
/**
|
||||
* How long to wait between runs.
|
||||
* Used for RECURRING tasks.
|
||||
*
|
||||
* @since 1.5.9
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $interval;
|
||||
|
||||
/**
|
||||
* Task meta.
|
||||
*
|
||||
* @since 1.7.0
|
||||
*
|
||||
* @var Meta
|
||||
*/
|
||||
private $meta;
|
||||
|
||||
/**
|
||||
* Log title.
|
||||
*
|
||||
* @since 1.9.1
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $log_title = 'Task';
|
||||
|
||||
/**
|
||||
* Task constructor.
|
||||
*
|
||||
* @since 1.5.9
|
||||
*
|
||||
* @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 );
|
||||
$this->meta = new Meta();
|
||||
|
||||
if ( empty( $this->action ) ) {
|
||||
throw new UnexpectedValueException( 'Task action cannot be empty.' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the type of the task as async.
|
||||
*
|
||||
* @since 1.5.9
|
||||
*
|
||||
* @return Task
|
||||
*/
|
||||
public function async() {
|
||||
|
||||
$this->type = self::TYPE_ASYNC;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the type of the task as recurring.
|
||||
*
|
||||
* @since 1.5.9
|
||||
*
|
||||
* @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 1.5.9
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pass any number of params that should be saved to Meta table.
|
||||
*
|
||||
* @since 1.5.9
|
||||
*
|
||||
* @return Task
|
||||
*/
|
||||
public function params() {
|
||||
|
||||
$this->params = func_get_args();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the action.
|
||||
* Should be the final call in a chain.
|
||||
*
|
||||
* @since 1.5.9
|
||||
*
|
||||
* @return null|string Action ID.
|
||||
*/
|
||||
public function register() {
|
||||
|
||||
$action_id = null;
|
||||
|
||||
// No processing if ActionScheduler is not usable.
|
||||
if ( ! wpforms()->obj( 'tasks' )->is_usable() ) {
|
||||
return $action_id;
|
||||
}
|
||||
|
||||
// Save data to tasks meta table.
|
||||
if ( $this->params !== null ) {
|
||||
$this->meta_id = $this->meta->add(
|
||||
[
|
||||
'action' => $this->action,
|
||||
'data' => $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 1.5.9
|
||||
*
|
||||
* @return null|string Action ID.
|
||||
* @noinspection PhpUndefinedFunctionInspection
|
||||
*/
|
||||
protected function register_async() {
|
||||
|
||||
if ( ! function_exists( 'as_enqueue_async_action' ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return as_enqueue_async_action(
|
||||
$this->action,
|
||||
/**
|
||||
* Filter arguments passed to the async task.
|
||||
*
|
||||
* @since 1.9.4
|
||||
*
|
||||
* @param array $args Arguments passed to the async task.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
(array) apply_filters(
|
||||
'wpforms_tasks_task_register_async_args',
|
||||
[
|
||||
'tasks_meta_id' => $this->meta_id,
|
||||
]
|
||||
),
|
||||
Tasks::GROUP
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the recurring task.
|
||||
*
|
||||
* @since 1.5.9
|
||||
*
|
||||
* @return null|string Action ID.
|
||||
* @noinspection PhpUndefinedFunctionInspection
|
||||
*/
|
||||
protected function register_recurring() {
|
||||
|
||||
if ( ! function_exists( 'as_schedule_recurring_action' ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return as_schedule_recurring_action(
|
||||
$this->timestamp,
|
||||
$this->interval,
|
||||
$this->action,
|
||||
[ 'tasks_meta_id' => $this->meta_id ],
|
||||
Tasks::GROUP
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the one-time task.
|
||||
*
|
||||
* @since 1.5.9
|
||||
*
|
||||
* @return null|string Action ID.
|
||||
* @noinspection PhpUndefinedFunctionInspection
|
||||
*/
|
||||
protected function register_once() {
|
||||
|
||||
if ( ! function_exists( 'as_schedule_single_action' ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return as_schedule_single_action(
|
||||
$this->timestamp,
|
||||
$this->action,
|
||||
[ 'tasks_meta_id' => $this->meta_id ],
|
||||
Tasks::GROUP
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel all occurrences of this task.
|
||||
*
|
||||
* @since 1.6.1
|
||||
*
|
||||
* @return null|bool|string Null if no matching action found,
|
||||
* false if AS library is missing,
|
||||
* true if scheduled task has no params,
|
||||
* string of the scheduled action ID if a scheduled action was found and unscheduled.
|
||||
* @noinspection PhpUndefinedFunctionInspection
|
||||
*/
|
||||
public function cancel() {
|
||||
|
||||
if ( ! function_exists( 'as_unschedule_all_actions' ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( $this->params === null ) {
|
||||
as_unschedule_all_actions( $this->action );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->meta_id = $this->meta->get_meta_id( $this->action, $this->params );
|
||||
|
||||
if ( $this->meta_id === null ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return as_unschedule_action( $this->action, [ 'tasks_meta_id' => $this->meta_id ], Tasks::GROUP );
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Log message to WPForms logger and standard debug.log file.
|
||||
*
|
||||
* @since 1.9.1
|
||||
*
|
||||
* @param string $message The error message that should be logged.
|
||||
*/
|
||||
protected function log( $message ) {
|
||||
|
||||
wpforms_log( $this->log_title, $message, [ 'type' => 'log' ] );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,411 @@
|
||||
<?php
|
||||
|
||||
// phpcs:ignore Generic.Commenting.DocComment.MissingShort
|
||||
/** @noinspection PhpUndefinedClassInspection */
|
||||
|
||||
namespace WPForms\Tasks;
|
||||
|
||||
use ActionScheduler;
|
||||
use ActionScheduler_Action;
|
||||
use ActionScheduler_DataController;
|
||||
use ActionScheduler_DBStore;
|
||||
use WPForms\Helpers\Transient;
|
||||
use WPForms\Tasks\Actions\EntryEmailsMetaCleanupTask;
|
||||
use WPForms\Tasks\Actions\EntryEmailsTask;
|
||||
use WPForms\Tasks\Actions\FormsLocatorScanTask;
|
||||
use WPForms\Tasks\Actions\AsyncRequestTask;
|
||||
use WPForms\Tasks\Actions\PurgeSpamTask;
|
||||
|
||||
/**
|
||||
* Class Tasks manages the tasks queue and provides API to work with it.
|
||||
*
|
||||
* @since 1.5.9
|
||||
*/
|
||||
class Tasks {
|
||||
|
||||
/**
|
||||
* Group that will be assigned to all actions.
|
||||
*
|
||||
* @since 1.5.9
|
||||
*/
|
||||
const GROUP = 'wpforms';
|
||||
|
||||
/**
|
||||
* Actions setting name.
|
||||
*
|
||||
* @since 1.7.3
|
||||
*/
|
||||
const ACTIONS = 'actions';
|
||||
|
||||
/**
|
||||
* WPForms pending or in-progress actions.
|
||||
*
|
||||
* @since 1.7.3
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $active_actions;
|
||||
|
||||
/**
|
||||
* Determine if WPForms task is executing.
|
||||
*
|
||||
* @since 1.9.4
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private static $task_executing = false;
|
||||
|
||||
/**
|
||||
* Perform certain things on class init.
|
||||
*
|
||||
* @since 1.5.9
|
||||
*/
|
||||
public function init() {
|
||||
|
||||
// Get WPForms pending or in-progress actions.
|
||||
$this->active_actions = $this->get_active_actions();
|
||||
|
||||
// Register WPForms tasks.
|
||||
foreach ( $this->get_tasks() as $task ) {
|
||||
|
||||
if ( ! is_subclass_of( $task, Task::class ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
new $task();
|
||||
}
|
||||
|
||||
$this->hooks();
|
||||
}
|
||||
|
||||
/**
|
||||
* Hooks.
|
||||
*
|
||||
* @since 1.7.5
|
||||
*/
|
||||
public function hooks() {
|
||||
|
||||
add_action( 'delete_expired_transients', [ Transient::class, 'delete_all_expired' ], 11 );
|
||||
add_action( 'admin_menu', [ $this, 'admin_hide_as_menu' ], PHP_INT_MAX );
|
||||
|
||||
/*
|
||||
* By default we send emails in the same process as the form submission is done.
|
||||
* That means that when many emails are set in form Notifications -
|
||||
* the form submission can take a while because of all those emails that are sending in the background.
|
||||
* Since WPForms 1.6.0 users can enable a new option in Settings > Emails,
|
||||
* called "Optimize Email Sending", to send email in async way.
|
||||
* This feature was enabled for WPForms 1.5.9, but some users were not happy.
|
||||
*/
|
||||
if ( ! (bool) wpforms_setting( 'email-async', false ) ) {
|
||||
add_filter( 'wpforms_tasks_entry_emails_trigger_send_same_process', '__return_true' );
|
||||
}
|
||||
|
||||
add_action( EntryEmailsTask::ACTION, [ EntryEmailsTask::class, 'process' ] );
|
||||
add_action( 'action_scheduler_after_execute', [ $this, 'clear_action_meta' ], PHP_INT_MAX, 2 );
|
||||
|
||||
add_action( 'action_scheduler_begin_execute', [ $this, 'start_executing' ], 1 );
|
||||
add_action( 'action_scheduler_after_execute', [ $this, 'stop_executing' ], 1, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Public interface to check if WPForms task is executing.
|
||||
*
|
||||
* @since 1.9.4
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_executing(): bool {
|
||||
|
||||
return self::$task_executing;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a flag to indicate that WPForms task is executing.
|
||||
*
|
||||
* @since 1.9.4
|
||||
*
|
||||
* @param int $action_id The action ID to process.
|
||||
*/
|
||||
public function start_executing( $action_id ) {
|
||||
|
||||
$action_id = (int) $action_id;
|
||||
|
||||
if ( ! class_exists( 'ActionScheduler' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$store = ActionScheduler::store();
|
||||
|
||||
if ( ! $store ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$action = $store->fetch_action( $action_id );
|
||||
|
||||
if ( ! $action || $action->get_group() !== self::GROUP ) {
|
||||
return;
|
||||
}
|
||||
|
||||
self::$task_executing = true;
|
||||
|
||||
/**
|
||||
* Fires before WPForms task is executing.
|
||||
*
|
||||
* @since 1.9.4
|
||||
*
|
||||
* @param int $action_id The action ID to process.
|
||||
* @param ActionScheduler_Action $action Action Scheduler action object.
|
||||
*/
|
||||
do_action( 'wpforms_tasks_start_executing', $action_id, $action );
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a flag to indicate that WPForms task is executing.
|
||||
*
|
||||
* @since 1.9.4
|
||||
*
|
||||
* @param int $action_id The action ID to process.
|
||||
* @param ActionScheduler_Action $action Action Scheduler action object.
|
||||
*/
|
||||
public function stop_executing( $action_id, $action ) {
|
||||
|
||||
if ( ! $action || ! method_exists( $action, 'get_group' ) || $action->get_group() !== self::GROUP ) {
|
||||
return;
|
||||
}
|
||||
|
||||
self::$task_executing = false;
|
||||
|
||||
/**
|
||||
* Fires after WPForms task is executed.
|
||||
*
|
||||
* @since 1.9.4
|
||||
*
|
||||
* @param int $action_id The action ID to process.
|
||||
* @param ActionScheduler_Action $action Action Scheduler action object.
|
||||
*/
|
||||
do_action( 'wpforms_tasks_stop_executing', $action_id, $action );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of WPForms default scheduled tasks.
|
||||
* Tasks, that are fired under certain specific circumstances
|
||||
* (like sending form submission email notifications)
|
||||
* are not listed here.
|
||||
*
|
||||
* @since 1.5.9
|
||||
*
|
||||
* @return Task[] List of tasks classes.
|
||||
*/
|
||||
public function get_tasks() {
|
||||
|
||||
if ( ! $this->is_usable() ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$tasks = [
|
||||
EntryEmailsMetaCleanupTask::class,
|
||||
FormsLocatorScanTask::class,
|
||||
AsyncRequestTask::class,
|
||||
PurgeSpamTask::class,
|
||||
];
|
||||
|
||||
/**
|
||||
* Filters the task class list to initialize.
|
||||
*
|
||||
* @since 1.5.9
|
||||
*
|
||||
* @param array $tasks Task class list.
|
||||
*/
|
||||
return apply_filters( 'wpforms_tasks_get_tasks', $tasks );
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide Action Scheduler admin area when not in debug mode.
|
||||
*
|
||||
* @since 1.5.9
|
||||
* @since 1.9.4 Does not hide the menu when some popular plugins are active.
|
||||
*/
|
||||
public function admin_hide_as_menu(): void {
|
||||
|
||||
$plugin_exceptions = [
|
||||
'action-scheduler/action-scheduler.php',
|
||||
'woocommerce/woocommerce.php',
|
||||
'wp-rocket/wp-rocket.php',
|
||||
];
|
||||
|
||||
/**
|
||||
* Filters the list of plugins for which
|
||||
* the Action Scheduler Tools -> Scheduled Actions menu item
|
||||
* should remain visible.
|
||||
*
|
||||
* @since 1.9.4
|
||||
*
|
||||
* @param array $plugin_exceptions List of plugin exceptions.
|
||||
*/
|
||||
$plugin_exceptions = apply_filters( 'wpforms_tasks_action_scheduler_tools_plugin_exceptions', $plugin_exceptions );
|
||||
$show_as_menu =
|
||||
( defined( 'WPFORMS_SHOW_ACTION_SCHEDULER_MENU' ) && constant( 'WPFORMS_SHOW_ACTION_SCHEDULER_MENU' ) ) ||
|
||||
wpforms_debug() ||
|
||||
! empty( array_filter( $plugin_exceptions, 'is_plugin_active' ) );
|
||||
$hide_as_menu = ! $show_as_menu;
|
||||
|
||||
/**
|
||||
* Filter to redefine that WPForms hides Tools > Action Scheduler menu item.
|
||||
*
|
||||
* @since 1.5.9
|
||||
*
|
||||
* @param bool $hide_as_menu Hide Tools > Action Scheduler menu item.
|
||||
*/
|
||||
if ( apply_filters( 'wpforms_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:
|
||||
* wpforms()->obj( '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' );
|
||||
*
|
||||
* Function `thats_what_you_call_me()` will receive `$meta_id` param,
|
||||
* and you will be able to receive all params from the action like this:
|
||||
* $params = ( new Meta() )->get( (int) $meta_id );
|
||||
* list( $name, $year ) = $params->data;
|
||||
*
|
||||
* @since 1.5.9
|
||||
*
|
||||
* @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 1.5.9
|
||||
*
|
||||
* @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 );
|
||||
$this->active_actions = $this->get_active_actions();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether ActionScheduler thinks that it has migrated or not.
|
||||
*
|
||||
* @since 1.5.9.3
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public 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 or in-progress.
|
||||
*
|
||||
* @since 1.6.0
|
||||
*
|
||||
* @param string $hook Hook to check for.
|
||||
*
|
||||
* @return bool|null
|
||||
* @noinspection PhpUndefinedFunctionInspection
|
||||
*/
|
||||
public function is_scheduled( $hook ) {
|
||||
|
||||
if ( ! function_exists( 'as_has_scheduled_action' ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ( in_array( $hook, $this->active_actions, true ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Action is not in the array, so it is not scheduled or belongs to another group.
|
||||
return as_has_scheduled_action( $hook );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all WPForms pending or in-progress actions.
|
||||
*
|
||||
* @since 1.7.3
|
||||
*/
|
||||
private 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.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared
|
||||
$results = $wpdb->get_results( $sql, 'ARRAY_N' );
|
||||
// phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared
|
||||
|
||||
return $results ? array_merge( ...$results ) : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the meta after action complete.
|
||||
* Fired before an action is marked as completed.
|
||||
*
|
||||
* @since 1.7.5
|
||||
*
|
||||
* @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() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$hook_name = $action->get_hook();
|
||||
|
||||
if ( ! $this->is_scheduled( $hook_name ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$hook_args = $action->get_args();
|
||||
|
||||
if ( ! isset( $hook_args['tasks_meta_id'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$meta = new Meta();
|
||||
|
||||
$meta->delete( $hook_args['tasks_meta_id'] );
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user