This commit is contained in:
2026-04-26 23:47:49 +02:00
parent 1b95f03d1e
commit b073e009d8
5288 changed files with 1112699 additions and 55536 deletions

View File

@@ -0,0 +1,92 @@
<?php
/**
* Abstract Class PPWP Background Task Manager
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
abstract class PPW_Background_Task_Manager extends PPW_Module {
/**
* @var Background_Task
*/
protected $task_runner;
abstract public function get_action();
abstract public function get_plugin_name();
abstract public function get_plugin_label();
abstract public function get_task_runner_class();
abstract public function get_query_limit();
abstract protected function start_run();
public function __construct() {
if ( empty( $_GET[ $this->get_action() ] ) ) {
return;
}
if ( 'run' === $_GET[ $this->get_action() ] && check_admin_referer( $this->get_action() . 'run' ) ) {
$this->start_run();
}
if ( 'continue' === $_GET[ $this->get_action() ] && check_admin_referer( $this->get_action() . 'continue' ) ) {
$this->continue_run();
}
wp_safe_redirect(
remove_query_arg(
array(
$this->get_action(),
'_wpnonce',
)
)
);
die;
}
public function on_runner_start() {
// Implement logger here
}
public function on_runner_complete( $did_tasks = false ) {
// Implement logger here
$this->add_flag( 'completed' );
}
public function get_task_runner() {
if ( empty( $this->task_runner ) ) {
$class_name = $this->get_task_runner_class();
$this->task_runner = new $class_name( $this );
}
return $this->task_runner;
}
protected function add_flag( $flag ) {
update_option( $this->get_plugin_name() . '_' . $this->get_action() . '_' . $flag, 1 );
}
protected function get_flag( $flag ) {
return get_option( $this->get_plugin_name() . '_' . $this->get_action() . '_' . $flag );
}
protected function delete_flag( $flag ) {
delete_option( $this->get_plugin_name() . '_' . $this->get_action() . '_' . $flag );
}
protected function get_start_action_url() {
return wp_nonce_url( add_query_arg( $this->get_action(), 'run' ), $this->get_action() . 'run' );
}
protected function get_continue_action_url() {
return wp_nonce_url( add_query_arg( $this->get_action(), 'continue' ), $this->get_action() . 'continue' );
}
private function continue_run() {
$runner = $this->get_task_runner();
$runner->continue_run();
}
}

View File

@@ -0,0 +1,316 @@
<?php
/**
* Abstract Class PPWP Background Task
*/
defined ( 'ABSPATH' ) || exit;
if ( ! class_exists( 'WP_Async_Request', false ) ) {
include_once PPW_DIR_PATH . '/includes/libs/wp-background-process/wp-async-request.php';
}
if ( ! class_exists( 'WP_Background_Process', false ) ) {
include_once PPW_DIR_PATH . '/includes/libs/wp-background-process/wp-background-process.php';
}
if ( ! class_exists( 'PPW_Pro_Background_Task' ) ) {
/**
* Class PPW_Background_Task
*/
abstract class PPW_Background_Task extends WP_Background_Process {
protected $current_item;
protected $manager;
public function __construct( $manager ) {
$this->manager = $manager;
$this->prefix = 'ppw_' . get_current_blog_id();
$this->action = $this->manager->get_action();
parent::__construct();
}
/**
* Dispatch updater
*
* Updater will still run via cron job if this fails for any reason.
*/
public function dispatch() {
$dispatched = parent::dispatch();
if ( is_wp_error( $dispatched ) ) {
wp_die( ppw_escape_wp_error( $dispatched ) ); // phpcs:ignores -- we already escape the wp_error
}
}
public function query_col( $sql ) {
global $wpdb;
// Add Calc.
$item = $this->get_current_item();
if ( empty( $item['total'] ) ) {
$sql = preg_replace( '/^SELECT/', 'SELECT SQL_CALC_FOUND_ROWS', $sql );
}
// Add offset & limit.
$sql = preg_replace( '/;$/', '', $sql );
$sql .= ' LIMIT %d, %d;';
$results = $wpdb->get_col( $wpdb->prepare( $sql, $this->get_current_offset(), $this->get_limit() ) ); // phpcs:ignore -- WPCS: unprepared SQL OK.
if ( ! empty( $results ) ) {
$this->set_total();
}
return $results;
}
/**
* @return mixed
*/
public function get_current_item() {
error_log( 'Current item: ' . wp_json_encode( $this->current_item ) );
return $this->current_item;
}
/**
* Get batch.
*
* @return \stdClass Return the first batch from the queue.
*/
protected function get_batch() {
$batch = parent::get_batch();
if (is_object($batch) && isset($batch->data)) {
$batch->data = array_filter((array) $batch->data);
} else {
$batch = new stdClass();
}
return $batch;
}
/**
* Get batch.
*
* @return \stdClass Return the first batch from the queue.
*/
// protected function get_batch() {
// $batch = parent::get_batch();
// $batch->data = array_filter((array) $batch->data);
// return $batch;
// }
public function get_current_offset() {
$limit = $this->get_limit();
return ( $this->current_item['iterate_num'] - 1 ) * $limit;
}
public function get_limit() {
return $this->manager->get_query_limit();
}
public function set_total() {
global $wpdb;
if ( empty( $this->current_item['total'] ) ) {
$total_rows = $wpdb->get_var( 'SELECT FOUND_ROWS();' ); // phpcs:ignore -- WPCS: db call ok. cache ok.
$total_iterates = ceil( $total_rows / $this->get_limit() );
$this->current_item['total'] = $total_iterates;
}
}
/**
* Complete
*
* Override if applicable, but ensure that the below actions are
* performed, or, call parent::complete().
*/
protected function complete() {
$this->manager->on_runner_complete( true );
parent::complete();
}
public function continue_run() {
// Used to fire an action added in WP_Background_Process::_construct() that calls WP_Background_Process::handle_cron_healthcheck().
// This method will make sure the database updates are executed even if cron is disabled. Nothing will happen if the updates are already running.
do_action( $this->cron_hook_identifier );
}
protected function task( $item ) {
$result = false;
if ( is_callable( $item['callback'] ) ) {
$result = (bool) call_user_func( $item['callback'], $this );
}
return $result ? $item : false;
}
/**
* See if the batch limit has been exceeded.
*
* @return bool
*/
protected function batch_limit_exceeded() {
return $this->time_exceeded() || $this->memory_exceeded();
}
/**
* Handle.
*
* Pass each queue item to the task handler, while remaining
* within server memory and time limit constraints.
*/
protected function handle() {
$this->manager->on_runner_start();
$this->lock_process();
do {
$batch = $this->get_batch();
foreach ( $batch->data as $key => $value ) {
$task = $this->task( $value );
if ( false !== $task ) {
$batch->data[ $key ] = $task;
} else {
unset( $batch->data[ $key ] );
}
if ( $this->batch_limit_exceeded() ) {
// Batch limits reached.
break;
}
}
// Update or delete current batch.
if ( ! empty( $batch->data ) ) {
$this->update( $batch->key, $batch->data );
} else {
$this->delete( $batch->key );
}
} while ( ! $this->batch_limit_exceeded() && ! $this->is_queue_empty() );
$this->unlock_process();
// Start next batch or complete process.
if ( ! $this->is_queue_empty() ) {
$this->dispatch();
} else {
$this->complete();
}
}
/**
* Use the protected `is_process_running` method as a public method.
* @return bool
*/
public function is_process_locked() {
return $this->is_process_running();
}
/**
* Is running?
*
* @return boolean
*/
public function is_running() {
return false === $this->is_queue_empty();
}
/**
* Schedule cron healthcheck.
*
* @param array $schedules Schedules.
*
* @return array
*/
public function schedule_cron_healthcheck( $schedules ) {
$interval = apply_filters( $this->identifier . '_cron_interval', 5 );
// Adds every 5 minutes to the existing schedules.
$schedules[ $this->identifier . '_cron_interval' ] = array(
'interval' => MINUTE_IN_SECONDS * $interval,
/* translators: %d: interval */
'display' => sprintf( __( 'Every %d minutes', 'password-protect-page' ), $interval ),
);
return $schedules;
}
/**
* Delete all batches.
*
* @return self
*/
public function delete_all_batches() {
global $wpdb;
$table = $wpdb->options;
$column = 'option_name';
if ( is_multisite() ) {
$table = $wpdb->sitemeta;
$column = 'meta_key';
}
$key = $wpdb->esc_like( $this->identifier . '_batch_' ) . '%';
$wpdb->query( $wpdb->prepare( "DELETE FROM {$table} WHERE {$column} LIKE %s", $key ) ); // @codingStandardsIgnoreLine.
return $this;
}
/**
* Kill process.
*
* Stop processing queue items, clear cronjob and delete all batches.
*/
public function kill_process() {
if ( ! $this->is_queue_empty() ) {
$this->delete_all_batches();
wp_clear_scheduled_hook( $this->cron_hook_identifier );
}
}
/**
* Handle cron healthcheck
*
* Restart the background process if not already running
* and data exists in the queue.
*/
public function handle_cron_healthcheck() {
error_log( 'Checking health-check: ' . wp_json_encode( 'Hello World' ) );
if ( $this->is_process_running() ) {
error_log( 'PPWP - Background process is running' );
// Background process already running.
return;
}
if ( $this->is_queue_empty() ) {
// No data to process.
$this->clear_scheduled_event();
return;
}
$this->handle();
}
/**
* Schedele Event
*/
protected function schedule_event() {
if ( ! wp_next_scheduled( $this->cron_hook_identifier ) ) {
wp_schedule_event( time() + 20, $this->cron_interval_identifier, $this->cron_hook_identifier );
}
}
}
}

View File

@@ -0,0 +1,155 @@
<?php
/**
* Abstract Class PPWP Migration Manager
*/
if ( ! class_exists( 'PPW_Migration_Manager' ) ) {
abstract class PPW_Migration_Manager extends PPW_Background_Task_Manager {
abstract public function get_migrations_class();
abstract public function get_migration_label();
abstract public function get_success_message();
public function __construct() {
$this->handle_admin_notices();
parent::__construct();
}
/**
* Admin Notice Handling
*/
protected function handle_admin_notices() {
$action = 'admin_notices';
if ( is_admin() && $this->get_flag( 'completed' ) ) {
add_action( $action, array( $this, 'admin_notice_upgrade_is_completed' ) );
}
$migration = $this->get_task_runner();
if ( $migration->is_running() ) {
add_action( $action, array( $this, 'admin_notice_upgrade_is_running' ) );
}
}
/**
* @return PPW_Migration
*/
public function get_task_runner_class() {
return 'PPW_Migration';
}
/**
* @return Query Limit
*/
public function get_query_limit() {
return 100;
}
/**
* @return Boolian value of Migration event
*/
public function should_migrate() {
return true;
}
/**
* Runner Complete
* @param boolian $did_task Deafult is False
*/
public function on_runner_complete( $did_tasks = false ) {
// Implement log here
if ( $did_tasks ) {
$this->add_flag( 'completed' );
}
}
/**
* Start Run
*/
public function start_run() {
$updater = $this->get_task_runner();
if ( $updater->is_running() ) {
return;
}
$upgrade_callbacks = $this->get_migration_callbacks();
if ( empty( $upgrade_callbacks ) ) {
$this->on_runner_complete();
return;
}
foreach ( $upgrade_callbacks as $callback ) {
$updater->push_to_queue(
array(
'callback' => $callback,
)
);
}
$updater->save()->dispatch();
// Use log here
}
/**
* Migration Callback
* @return array $callbacks Callbacks
*/
public function get_migration_callbacks() {
$prefix = 'migrate_v_';
$migrations_class = $this->get_migrations_class();
$migration_reflection = new ReflectionClass( $migrations_class );
$callbacks = array();
$methods = $migration_reflection->getMethods();
foreach ( $methods as $method ) {
$method_name = $method->getName();
if ( false === strpos( $method_name, $prefix ) ) {
continue;
}
if ( ! preg_match_all( "/$prefix(\d+_\d+_\d+)/", $method_name, $matches ) ) {
continue;
}
$method_version = str_replace( '_', '.', $matches[1][0] );
if ( ! version_compare( $method_version, PPW_VERSION, '>' ) ) {
continue;
}
$callbacks[] = array( $migrations_class, $method_name );
}
return $callbacks;
}
/**
* Admin Notice
*/
public function admin_notice_upgrade_is_running() {
$upgrade_link = $this->get_continue_action_url();
$message = '<p>' . sprintf( __( '%s To keep password protecting your private content, we have to <a href="https://passwordprotectwp.com/password-migration/" target="_blank" rel="noopener noreferrer">migrate your passwords</a> to our plugin. The migration process is running in the background.', 'password-protect-page' ), $this->get_updater_label() ) . '</p>';
$message .= '<p>' . sprintf( 'Taking a while? <a href="%s" class="button-primary">Click here to run it now</a>', $upgrade_link ) . '</p>';
echo '<div class="notice notice-warning">' . $message . '</div>'; // phpcs:ignore -- we dont need to escape for this
}
/**
* Admin Notice success message
*/
public function admin_notice_upgrade_is_completed() {
$this->delete_flag( 'completed' );
$message = $this->get_success_message();
if ( ! empty( $message ) ) {
echo '<div class="notice notice-success">' . $message . '</div>'; // phpcs:ignore -- we dont need to escape for this
}
}
}
}

View File

@@ -0,0 +1,45 @@
<?php
/**
* Abstract Class for PPWP Module
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
if ( ! class_exists( 'PPW_Module' ) ) {
abstract class PPW_Module {
/**
* Get module name.
*
* Retrieve the module name.
*
* @since 1.7.0
* @access public
* @abstract
*
* @return string Module name.
*/
abstract public function get_name();
/**
* Instance.
*
* Ensures only one instance of the module class is loaded or can be loaded.
*
* @since 1.7.0
* @access public
* @static
*
* @return Module An instance of the class.
*/
public static function instance() {
$class_name = static::class_name();
if ( empty( static::$_instances[ $class_name ] ) ) {
static::$_instances[ $class_name ] = new static();
}
return static::$_instances[ $class_name ];
}
}
}