File: /var/www/vhosts/uyarreklam.com.tr/httpdocs/ApprovedDirectories.tar
Admin/SyncUI.php 0000644 00000010246 15155010064 0007457 0 ustar 00 <?php
namespace Automattic\WooCommerce\Internal\ProductDownloads\ApprovedDirectories\Admin;
use Automattic\WooCommerce\Internal\ProductDownloads\ApprovedDirectories\Register;
use Automattic\WooCommerce\Internal\ProductDownloads\ApprovedDirectories\Synchronize;
use Automattic\WooCommerce\Internal\Utilities\Users;
/**
* Adds tools to the Status > Tools page that can be used to (re-)initiate or stop a synchronization process
* for Approved Download Directories.
*/
class SyncUI {
/**
* The active register of approved directories.
*
* @var Register
*/
private $register;
/**
* Sets up UI controls for product download URLs.
*
* @internal
*
* @param Register $register Register of approved directories.
*/
final public function init( Register $register ) {
$this->register = $register;
}
/**
* Performs any work needed to add hooks and otherwise integrate with the wider system,
* except in the case where the current user is not a site administrator, no hooks will
* be initialized.
*/
final public function init_hooks() {
if ( ! Users::is_site_administrator() ) {
return;
}
add_filter( 'woocommerce_debug_tools', array( $this, 'add_tools' ) );
}
/**
* Adds Approved Directory list-related entries to the tools page.
*
* @param array $tools Admin tool definitions.
*
* @return array
*/
public function add_tools( array $tools ): array {
$sync = wc_get_container()->get( Synchronize::class );
if ( ! $sync->in_progress() ) {
// Provide tools to trigger a fresh scan (migration) and to clear the Approved Directories list.
$tools['approved_directories_sync'] = array(
'name' => __( 'Synchronize approved download directories', 'woocommerce' ),
'desc' => __( 'Updates the list of Approved Product Download Directories. Note that triggering this tool does not impact whether the Approved Download Directories list is enabled or not.', 'woocommerce' ),
'button' => __( 'Update', 'woocommerce' ),
'callback' => array( $this, 'trigger_sync' ),
'requires_refresh' => true,
);
$tools['approved_directories_clear'] = array(
'name' => __( 'Empty the approved download directories list', 'woocommerce' ),
'desc' => __( 'Removes all existing entries from the Approved Product Download Directories list.', 'woocommerce' ),
'button' => __( 'Clear', 'woocommerce' ),
'callback' => array( $this, 'clear_existing_entries' ),
'requires_refresh' => true,
);
} else {
// Or if a scan (migration) is already in progress, offer a means of cancelling it.
$tools['cancel_directories_scan'] = array(
'name' => __( 'Cancel synchronization of approved directories', 'woocommerce' ),
'desc' => sprintf(
/* translators: %d is an integer between 0-100 representing the percentage complete of the current scan. */
__( 'The Approved Product Download Directories list is currently being synchronized with the product catalog (%d%% complete). If you need to, you can cancel it.', 'woocommerce' ),
$sync->get_progress()
),
'button' => __( 'Cancel', 'woocommerce' ),
'callback' => array( $this, 'cancel_sync' ),
);
}
return $tools;
}
/**
* Triggers a new migration.
*/
public function trigger_sync() {
$this->security_check();
wc_get_container()->get( Synchronize::class )->start();
}
/**
* Clears all existing rules from the Approved Directories list.
*/
public function clear_existing_entries() {
$this->security_check();
$this->register->delete_all();
}
/**
* If a migration is in progress, this will attempt to cancel it.
*/
public function cancel_sync() {
$this->security_check();
wc_get_logger()->log( 'info', __( 'Approved Download Directories sync: scan has been cancelled.', 'woocommerce' ) );
wc_get_container()->get( Synchronize::class )->stop();
}
/**
* Makes sure the user has appropriate permissions and that we have a valid nonce.
*/
private function security_check() {
if ( ! Users::is_site_administrator() ) {
wp_die( esc_html__( 'You do not have permission to modify the list of approved directories for product downloads.', 'woocommerce' ) );
}
}
}
Admin/Table.php 0000644 00000023766 15155010064 0007347 0 ustar 00 <?php
namespace Automattic\WooCommerce\Internal\ProductDownloads\ApprovedDirectories\Admin;
use Automattic\WooCommerce\Internal\ProductDownloads\ApprovedDirectories\Register;
use Automattic\WooCommerce\Internal\ProductDownloads\ApprovedDirectories\StoredUrl;
use WP_List_Table;
use WP_Screen;
/**
* Admin list table used to render our current list of approved directories.
*/
class Table extends WP_List_Table {
/**
* Initialize the webhook table list.
*/
public function __construct() {
parent::__construct(
array(
'singular' => 'url',
'plural' => 'urls',
'ajax' => false,
)
);
add_filter( 'manage_woocommerce_page_wc-settings_columns', array( $this, 'get_columns' ) );
$this->items_per_page();
set_screen_options();
}
/**
* Sets up an items-per-page control.
*/
private function items_per_page() {
add_screen_option(
'per_page',
array(
'default' => 20,
'option' => 'edit_approved_directories_per_page',
)
);
add_filter( 'set_screen_option_edit_approved_directories_per_page', array( $this, 'set_items_per_page' ), 10, 3 );
}
/**
* Saves the items-per-page setting.
*
* @param mixed $default The default value.
* @param string $option The option being configured.
* @param int $value The submitted option value.
*
* @return mixed
*/
public function set_items_per_page( $default, string $option, int $value ) {
return 'edit_approved_directories_per_page' === $option ? absint( $value ) : $default;
}
/**
* No items found text.
*/
public function no_items() {
esc_html_e( 'No approved directory URLs found.', 'woocommerce' );
}
/**
* Displays the list of views available on this table.
*/
public function render_views() {
$register = wc_get_container()->get( Register::class );
$enabled_count = $register->count( true );
$disabled_count = $register->count( false );
$all_count = $enabled_count + $disabled_count;
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
$selected_view = isset( $_REQUEST['view'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['view'] ) ) : 'all';
$all_url = esc_url( add_query_arg( 'view', 'all', $this->get_base_url() ) );
$all_class = 'all' === $selected_view ? 'class="current"' : '';
$all_text = sprintf(
/* translators: %s is the count of approved directory list entries. */
_nx(
'All <span class="count">(%s)</span>',
'All <span class="count">(%s)</span>',
$all_count,
'Approved product download directory views',
'woocommerce'
),
$all_count
);
$enabled_url = esc_url( add_query_arg( 'view', 'enabled', $this->get_base_url() ) );
$enabled_class = 'enabled' === $selected_view ? 'class="current"' : '';
$enabled_text = sprintf(
/* translators: %s is the count of enabled approved directory list entries. */
_nx(
'Enabled <span class="count">(%s)</span>',
'Enabled <span class="count">(%s)</span>',
$enabled_count,
'Approved product download directory views',
'woocommerce'
),
$enabled_count
);
$disabled_url = esc_url( add_query_arg( 'view', 'disabled', $this->get_base_url() ) );
$disabled_class = 'disabled' === $selected_view ? 'class="current"' : '';
$disabled_text = sprintf(
/* translators: %s is the count of disabled directory list entries. */
_nx(
'Disabled <span class="count">(%s)</span>',
'Disabled <span class="count">(%s)</span>',
$disabled_count,
'Approved product download directory views',
'woocommerce'
),
$disabled_count
);
$views = array(
'all' => "<a href='{$all_url}' {$all_class}>{$all_text}</a>",
'enabled' => "<a href='{$enabled_url}' {$enabled_class}>{$enabled_text}</a>",
'disabled' => "<a href='{$disabled_url}' {$disabled_class}>{$disabled_text}</a>",
);
$this->screen->render_screen_reader_content( 'heading_views' );
echo '<ul class="subsubsub list-table-filters">';
foreach ( $views as $slug => $view ) {
$views[ $slug ] = "<li class='{$slug}'>{$view}";
}
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo implode( ' | </li>', $views ) . "</li>\n";
echo '</ul>';
}
/**
* Get list columns.
*
* @return array
*/
public function get_columns() {
return array(
'cb' => '<input type="checkbox" />',
'title' => _x( 'URL', 'Approved product download directories', 'woocommerce' ),
'enabled' => _x( 'Enabled', 'Approved product download directories', 'woocommerce' ),
);
}
/**
* Checklist column, used for selecting items for processing by a bulk action.
*
* @param StoredUrl $item The approved directory information for the current row.
*
* @return string
*/
public function column_cb( $item ) {
return sprintf( '<input type="checkbox" name="%1$s[]" value="%2$s" />', esc_attr( $this->_args['singular'] ), esc_attr( $item->get_id() ) );
}
/**
* URL column.
*
* @param StoredUrl $item The approved directory information for the current row.
*
* @return string
*/
public function column_title( $item ) {
$id = (int) $item->get_id();
$url = esc_html( $item->get_url() );
$enabled = $item->is_enabled();
$edit_url = esc_url( $this->get_action_url( 'edit', $id ) );
$enable_disable_url = esc_url( $enabled ? $this->get_action_url( 'disable', $id ) : $this->get_action_url( 'enable', $id ) );
$enable_disable_text = esc_html( $enabled ? __( 'Disable', 'woocommerce' ) : __( 'Enable', 'woocommerce' ) );
$delete_url = esc_url( $this->get_action_url( 'delete', $id ) );
$edit_link = "<a href='{$edit_url}'>" . esc_html_x( 'Edit', 'Product downloads list', 'woocommerce' ) . '</a>';
$enable_disable_link = "<a href='{$enable_disable_url}'>{$enable_disable_text}</a>";
$delete_link = "<a href='{$delete_url}' class='submitdelete wc-confirm-delete'>" . esc_html_x( 'Delete permanently', 'Product downloads list', 'woocommerce' ) . '</a>';
$url_link = "<a href='{$edit_url}'>{$url}</a>";
return "
<strong>{$url_link}</strong>
<div class='row-actions'>
<span class='id'>ID: {$id}</span> |
<span class='edit'>{$edit_link}</span> |
<span class='enable-disable'>{$enable_disable_link}</span> |
<span class='delete'><a class='submitdelete'>{$delete_link}</a></span>
</div>
";
}
/**
* Rule-is-enabled column.
*
* @param StoredUrl $item The approved directory information for the current row.
*
* @return string
*/
public function column_enabled( StoredUrl $item ): string {
return $item->is_enabled()
? '<mark class="yes" title="' . esc_html__( 'Enabled', 'woocommerce' ) . '"><span class="dashicons dashicons-yes"></span></mark>'
: '<mark class="no" title="' . esc_html__( 'Disabled', 'woocommerce' ) . '">–</mark>';
}
/**
* Get bulk actions.
*
* @return array
*/
protected function get_bulk_actions() {
return array(
'enable' => __( 'Enable rule', 'woocommerce' ),
'disable' => __( 'Disable rule', 'woocommerce' ),
'delete' => __( 'Delete permanently', 'woocommerce' ),
);
}
/**
* Builds an action URL (ie, to edit or delete a row).
*
* @param string $action The action to be created.
* @param int $id The ID that is the subject of the action.
* @param string $nonce_action Action used to add a nonce to the URL.
*
* @return string
*/
public function get_action_url( string $action, int $id, string $nonce_action = 'modify_approved_directories' ): string {
return add_query_arg(
array(
'check' => wp_create_nonce( $nonce_action ),
'action' => $action,
'url' => $id,
),
$this->get_base_url()
);
}
/**
* Supplies the 'base' admin URL for this admin table.
*
* @return string
*/
public function get_base_url(): string {
return add_query_arg(
array(
'page' => 'wc-settings',
'tab' => 'products',
'section' => 'download_urls',
),
admin_url( 'admin.php' )
);
}
/**
* Generate the table navigation above or below the table.
* Included to remove extra nonce input.
*
* @param string $which The location of the extra table nav markup: 'top' or 'bottom'.
*/
protected function display_tablenav( $which ) {
$directories = wc_get_container()->get( Register::class );
echo '<div class="tablenav ' . esc_attr( $which ) . '">';
if ( $this->has_items() ) {
echo '<div class="alignleft actions bulkactions">';
$this->bulk_actions( $which );
if ( $directories->count( false ) > 0 ) {
echo '<a href="' . esc_url( $this->get_action_url( 'enable-all', 0 ) ) . '" class="wp-core-ui button">' . esc_html_x( 'Enable All', 'Approved product download directories', 'woocommerce' ) . '</a> ';
}
if ( $directories->count( true ) > 0 ) {
echo '<a href="' . esc_url( $this->get_action_url( 'disable-all', 0 ) ) . '" class="wp-core-ui button">' . esc_html_x( 'Disable All', 'Approved product download directories', 'woocommerce' ) . '</a>';
}
echo '</div>';
}
$this->pagination( $which );
echo '<br class="clear" />';
echo '</div>';
}
/**
* Prepare table list items.
*/
public function prepare_items() {
// phpcs:disable WordPress.Security.NonceVerification.Recommended
// phpcs:disable WordPress.Security.NonceVerification.Missing
$current_page = $this->get_pagenum();
$per_page = $this->get_items_per_page( 'edit_approved_directories_per_page' );
$search = sanitize_text_field( wp_unslash( $_REQUEST['s'] ?? '' ) );
switch ( $_REQUEST['view'] ?? '' ) {
case 'enabled':
$enabled = true;
break;
case 'disabled':
$enabled = false;
break;
default:
$enabled = null;
break;
}
// phpcs:enable
$approved_directories = wc_get_container()->get( Register::class )->list(
array(
'page' => $current_page,
'per_page' => $per_page,
'search' => $search,
'enabled' => $enabled,
)
);
$this->items = $approved_directories['approved_directories'];
// Set the pagination.
$this->set_pagination_args(
array(
'total_items' => $approved_directories['total_urls'],
'total_pages' => $approved_directories['total_pages'],
'per_page' => $per_page,
)
);
}
}
Admin/UI.php 0000644 00000035040 15155010064 0006621 0 ustar 00 <?php
namespace Automattic\WooCommerce\Internal\ProductDownloads\ApprovedDirectories\Admin;
use Automattic\WooCommerce\Internal\ProductDownloads\ApprovedDirectories\Register;
use Automattic\WooCommerce\Internal\Utilities\Users;
use Exception;
use WC_Admin_Settings;
/**
* Manages user interactions for product download URL safety.
*/
class UI {
/**
* The active register of approved directories.
*
* @var Register
*/
private $register;
/**
* The WP_List_Table instance used to display approved directories.
*
* @var Table
*/
private $table;
/**
* Sets up UI controls for product download URLs.
*
* @internal
*
* @param Register $register Register of approved directories.
*/
final public function init( Register $register ) {
$this->register = $register;
}
/**
* Performs any work needed to add hooks and otherwise integrate with the wider system,
* except in the case where the current user is not a site administrator, no hooks will
* be initialized.
*/
final public function init_hooks() {
if ( ! Users::is_site_administrator() ) {
return;
}
add_filter( 'woocommerce_get_sections_products', array( $this, 'add_section' ) );
add_action( 'load-woocommerce_page_wc-settings', array( $this, 'setup' ) );
add_action( 'woocommerce_settings_products', array( $this, 'render' ) );
}
/**
* Injects our new settings section (when approved directory rules are disabled, it will not show).
*
* @param array $sections Other admin settings sections.
*
* @return array
*/
public function add_section( array $sections ): array {
$sections['download_urls'] = __( 'Approved download directories', 'woocommerce' );
return $sections;
}
/**
* Sets up the table, renders any notices and processes actions as needed.
*/
public function setup() {
if ( ! $this->is_download_urls_screen() ) {
return;
}
$this->table = new Table();
$this->admin_notices();
$this->handle_search();
$this->process_actions();
}
/**
* Renders the UI.
*/
public function render() {
if ( null === $this->table || ! $this->is_download_urls_screen() ) {
return;
}
// phpcs:disable WordPress.Security.NonceVerification.Recommended
if ( isset( $_REQUEST['action'] ) && 'edit' === $_REQUEST['action'] && isset( $_REQUEST['url'] ) ) {
$this->edit_screen( (int) $_REQUEST['url'] );
return;
}
// phpcs:enable
// Show list table.
$this->table->prepare_items();
wp_nonce_field( 'modify_approved_directories', 'check' );
$this->display_title();
$this->table->render_views();
$this->table->search_box( _x( 'Search', 'Approved Directory URLs', 'woocommerce' ), 'download_url_search' );
$this->table->display();
}
/**
* Indicates if we are currently on the download URLs admin screen.
*
* @return bool
*/
private function is_download_urls_screen(): bool {
// phpcs:disable WordPress.Security.NonceVerification.Recommended
return isset( $_GET['tab'] )
&& 'products' === $_GET['tab']
&& isset( $_GET['section'] )
&& 'download_urls' === $_GET['section'];
// phpcs:enable
}
/**
* Process bulk and single-row actions.
*/
private function process_actions() {
// phpcs:disable WordPress.Security.NonceVerification.Recommended
$ids = isset( $_REQUEST['url'] ) ? array_map( 'absint', (array) $_REQUEST['url'] ) : array();
if ( empty( $ids ) || empty( $_REQUEST['action'] ) ) {
return;
}
$this->security_check();
$action = sanitize_text_field( wp_unslash( $_REQUEST['action'] ) );
switch ( $action ) {
case 'edit':
$this->process_edits( current( $ids ) );
break;
case 'delete':
case 'enable':
case 'disable':
$this->process_bulk_actions( $ids, $action );
break;
case 'enable-all':
case 'disable-all':
$this->process_all_actions( $action );
break;
case 'turn-on':
case 'turn-off':
$this->process_on_off( $action );
break;
}
// phpcs:enable
}
/**
* Support pagination across search results.
*
* In the context of the WC settings screen, form data is submitted by the post method: that poses
* a problem for the default WP_List_Table pagination logic which expects the search value to live
* as part of the URL query. This method is a simple shim to bridge the resulting gap.
*/
private function handle_search() {
// phpcs:disable WordPress.Security.NonceVerification.Missing
// phpcs:disable WordPress.Security.NonceVerification.Recommended
// If a search value has not been POSTed, or if it was POSTed but is already equal to the
// same value in the URL query, we need take no further action.
if ( empty( $_POST['s'] ) || sanitize_text_field( wp_unslash( $_GET['s'] ?? '' ) ) === $_POST['s'] ) {
return;
}
wp_safe_redirect(
add_query_arg(
array(
'paged' => absint( $_GET['paged'] ?? 1 ),
's' => sanitize_text_field( wp_unslash( $_POST['s'] ) ),
),
$this->table->get_base_url()
)
);
// phpcs:enable
exit;
}
/**
* Handles updating or adding a new URL to the list of approved directories.
*
* @param int $url_id The ID of the rule to be edited/created. Zero if we are creating a new entry.
*/
private function process_edits( int $url_id ) {
// phpcs:disable WordPress.Security.NonceVerification.Missing
$url = esc_url_raw( wp_unslash( $_POST['approved_directory_url'] ?? '' ) );
$enabled = (bool) sanitize_text_field( wp_unslash( $_POST['approved_directory_enabled'] ?? '' ) );
if ( empty( $url ) ) {
return;
}
$redirect_url = add_query_arg( 'id', $url_id, $this->table->get_action_url( 'edit', $url_id ) );
try {
$upserted = 0 === $url_id
? $this->register->add_approved_directory( $url, $enabled )
: $this->register->update_approved_directory( $url_id, $url, $enabled );
if ( is_integer( $upserted ) ) {
$redirect_url = add_query_arg( 'url', $upserted, $redirect_url );
}
$redirect_url = add_query_arg( 'edit-status', 0 === $url_id ? 'added' : 'updated', $redirect_url );
} catch ( Exception $e ) {
$redirect_url = add_query_arg(
array(
'edit-status' => 'failure',
'submitted-url' => $url,
),
$redirect_url
);
}
wp_safe_redirect( $redirect_url );
exit;
// phpcs:enable WordPress.Security.NonceVerification.Missing
}
/**
* Processes actions that can be applied in bulk (requests to delete, enable
* or disable).
*
* @param int[] $ids The ID(s) to be updates.
* @param string $action The action to be applied.
*/
private function process_bulk_actions( array $ids, string $action ) {
$deletes = 0;
$enabled = 0;
$disabled = 0;
$register = wc_get_container()->get( Register::class );
foreach ( $ids as $id ) {
if ( 'delete' === $action && $register->delete_by_id( $id ) ) {
$deletes++;
} elseif ( 'enable' === $action && $register->enable_by_id( $id ) ) {
$enabled++;
} elseif ( 'disable' === $action && $register->disable_by_id( $id ) ) {
$disabled ++;
}
}
$fails = count( $ids ) - $deletes - $enabled - $disabled;
$redirect = $this->table->get_base_url();
if ( $deletes ) {
$redirect = add_query_arg( 'deleted-ids', $deletes, $redirect );
} elseif ( $enabled ) {
$redirect = add_query_arg( 'enabled-ids', $enabled, $redirect );
} elseif ( $disabled ) {
$redirect = add_query_arg( 'disabled-ids', $disabled, $redirect );
}
if ( $fails ) {
$redirect = add_query_arg( 'bulk-fails', $fails, $redirect );
}
wp_safe_redirect( $redirect );
exit;
}
/**
* Handles the enable/disable-all actions.
*
* @param string $action The action to be applied.
*/
private function process_all_actions( string $action ) {
$register = wc_get_container()->get( Register::class );
$redirect = $this->table->get_base_url();
switch ( $action ) {
case 'enable-all':
$redirect = add_query_arg( 'enabled-all', (int) $register->enable_all(), $redirect );
break;
case 'disable-all':
$redirect = add_query_arg( 'disabled-all', (int) $register->disable_all(), $redirect );
break;
}
wp_safe_redirect( $redirect );
exit;
}
/**
* Handles turning on/off the entire approved download directory system (vs enabling
* and disabling of individual rules).
*
* @param string $action Whether the feature should be turned on or off.
*/
private function process_on_off( string $action ) {
switch ( $action ) {
case 'turn-on':
$this->register->set_mode( Register::MODE_ENABLED );
break;
case 'turn-off':
$this->register->set_mode( Register::MODE_DISABLED );
break;
}
}
/**
* Displays the screen title, etc.
*/
private function display_title() {
$turn_on_off = $this->register->get_mode() === Register::MODE_ENABLED
? '<a href="' . esc_url( $this->table->get_action_url( 'turn-off', 0 ) ) . '" class="page-title-action">' . esc_html_x( 'Stop Enforcing Rules', 'Approved product download directories', 'woocommerce' ) . '</a>'
: '<a href="' . esc_url( $this->table->get_action_url( 'turn-on', 0 ) ) . '" class="page-title-action">' . esc_html_x( 'Start Enforcing Rules', 'Approved product download directories', 'woocommerce' ) . '</a>';
?>
<h2 class='wc-table-list-header'>
<?php esc_html_e( 'Approved Download Directories', 'woocommerce' ); ?>
<a href='<?php echo esc_url( $this->table->get_action_url( 'edit', 0 ) ); ?>' class='page-title-action'><?php esc_html_e( 'Add New', 'woocommerce' ); ?></a>
<?php echo $turn_on_off; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
</h2>
<?php
}
/**
* Renders the editor screen for approved directory URLs.
*
* @param int $url_id The ID of the rule to be edited (may be zero for new rules).
*/
private function edit_screen( int $url_id ) {
$this->security_check();
$existing = $this->register->get_by_id( $url_id );
if ( 0 !== $url_id && ! $existing ) {
WC_Admin_Settings::add_error( _x( 'The provided ID was invalid.', 'Approved product download directories', 'woocommerce' ) );
WC_Admin_Settings::show_messages();
return;
}
$title = $existing
? __( 'Edit Approved Directory', 'woocommerce' )
: __( 'Add New Approved Directory', 'woocommerce' );
// phpcs:disable WordPress.Security.NonceVerification.Recommended
$submitted = sanitize_text_field( wp_unslash( $_GET['submitted-url'] ?? '' ) );
$existing_url = $existing ? $existing->get_url() : '';
$enabled = $existing ? $existing->is_enabled() : true;
// phpcs:enable
?>
<h2 class='wc-table-list-header'>
<?php echo esc_html( $title ); ?>
<?php if ( $existing ) : ?>
<a href="<?php echo esc_url( $this->table->get_action_url( 'edit', 0 ) ); ?>" class="page-title-action"><?php esc_html_e( 'Add New', 'woocommerce' ); ?></a>
<?php endif; ?>
<a href="<?php echo esc_url( $this->table->get_base_url() ); ?> " class="page-title-action"><?php esc_html_e( 'Cancel', 'woocommerce' ); ?></a>
</h2>
<table class='form-table'>
<tbody>
<tr valign='top'>
<th scope='row' class='titledesc'>
<label for='approved_directory_url'> <?php echo esc_html_x( 'Directory URL', 'Approved product download directories', 'woocommerce' ); ?> </label>
</th>
<td class='forminp'>
<input name='approved_directory_url' id='approved_directory_url' type='text' class='input-text regular-input' value='<?php echo esc_attr( empty( $submitted ) ? $existing_url : $submitted ); ?>'>
</td>
</tr>
<tr valign='top'>
<th scope='row' class='titledesc'>
<label for='approved_directory_enabled'> <?php echo esc_html_x( 'Enabled', 'Approved product download directories', 'woocommerce' ); ?> </label>
</th>
<td class='forminp'>
<input name='approved_directory_enabled' id='approved_directory_enabled' type='checkbox' value='1' <?php checked( true, $enabled ); ?>'>
</td>
</tr>
</tbody>
</table>
<input name='id' id='approved_directory_id' type='hidden' value='{$url_id}'>
<?php
}
/**
* Displays any admin notices that might be needed.
*/
private function admin_notices() {
// phpcs:disable WordPress.Security.NonceVerification.Recommended
$successfully_deleted = isset( $_GET['deleted-ids'] ) ? (int) $_GET['deleted-ids'] : 0;
$successfully_enabled = isset( $_GET['enabled-ids'] ) ? (int) $_GET['enabled-ids'] : 0;
$successfully_disabled = isset( $_GET['disabled-ids'] ) ? (int) $_GET['disabled-ids'] : 0;
$failed_updates = isset( $_GET['bulk-fails'] ) ? (int) $_GET['bulk-fails'] : 0;
$edit_status = sanitize_text_field( wp_unslash( $_GET['edit-status'] ?? '' ) );
$edit_url = esc_attr( sanitize_text_field( wp_unslash( $_GET['submitted-url'] ?? '' ) ) );
// phpcs:enable
if ( $successfully_deleted ) {
WC_Admin_Settings::add_message(
sprintf(
/* translators: %d: count */
_n( '%d approved directory URL deleted.', '%d approved directory URLs deleted.', $successfully_deleted, 'woocommerce' ),
$successfully_deleted
)
);
} elseif ( $successfully_enabled ) {
WC_Admin_Settings::add_message(
sprintf(
/* translators: %d: count */
_n( '%d approved directory URL enabled.', '%d approved directory URLs enabled.', $successfully_enabled, 'woocommerce' ),
$successfully_enabled
)
);
} elseif ( $successfully_disabled ) {
WC_Admin_Settings::add_message(
sprintf(
/* translators: %d: count */
_n( '%d approved directory URL disabled.', '%d approved directory URLs disabled.', $successfully_disabled, 'woocommerce' ),
$successfully_disabled
)
);
}
if ( $failed_updates ) {
WC_Admin_Settings::add_error(
sprintf(
/* translators: %d: count */
_n( '%d URL could not be updated.', '%d URLs could not be updated.', $failed_updates, 'woocommerce' ),
$failed_updates
)
);
}
if ( 'added' === $edit_status ) {
WC_Admin_Settings::add_message( __( 'URL was successfully added.', 'woocommerce' ) );
}
if ( 'updated' === $edit_status ) {
WC_Admin_Settings::add_message( __( 'URL was successfully updated.', 'woocommerce' ) );
}
if ( 'failure' === $edit_status && ! empty( $edit_url ) ) {
WC_Admin_Settings::add_error(
sprintf(
/* translators: %s is the submitted URL. */
__( '"%s" could not be saved. Please review, ensure it is a valid URL and try again.', 'woocommerce' ),
$edit_url
)
);
}
}
/**
* Makes sure the user has appropriate permissions and that we have a valid nonce.
*/
private function security_check() {
if ( ! Users::is_site_administrator() || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_REQUEST['check'] ?? '' ) ), 'modify_approved_directories' ) ) {
wp_die( esc_html__( 'You do not have permission to modify the list of approved directories for product downloads.', 'woocommerce' ) );
}
}
}
ApprovedDirectoriesException.php 0000644 00000000523 15155010064 0013106 0 ustar 00 <?php
namespace Automattic\WooCommerce\Internal\ProductDownloads\ApprovedDirectories;
use Exception;
/**
* Encapsulates a problem encountered while an operation relating to approved directories
* was performed.
*/
class ApprovedDirectoriesException extends Exception {
public const INVALID_URL = 1;
public const DB_ERROR = 2;
}
Register.php 0000644 00000033654 15155010064 0007051 0 ustar 00 <?php
namespace Automattic\WooCommerce\Internal\ProductDownloads\ApprovedDirectories;
use Automattic\WooCommerce\Internal\ProductDownloads\ApprovedDirectories\Admin\SyncUI;
use Automattic\WooCommerce\Internal\ProductDownloads\ApprovedDirectories\Admin\UI;
use Automattic\WooCommerce\Internal\Utilities\URL;
use Automattic\WooCommerce\Internal\Utilities\URLException;
/**
* Maintains and manages the list of approved directories, within which product downloads can
* be stored.
*/
class Register {
/**
* Used to indicate the current mode.
*/
private const MODES = array(
self::MODE_DISABLED,
self::MODE_ENABLED,
);
public const MODE_DISABLED = 'disabled';
public const MODE_ENABLED = 'enabled';
/**
* Name of the option used to store the current mode. See self::MODES for a
* list of acceptable values for the actual option.
*
* @var string
*/
private $mode_option = 'wc_downloads_approved_directories_mode';
/**
* Sets up the approved directories sub-system.
*
* @internal
*/
final public function init() {
add_action(
'admin_init',
function () {
wc_get_container()->get( SyncUI::class )->init_hooks();
wc_get_container()->get( UI::class )->init_hooks();
}
);
add_action(
'before_woocommerce_init',
function() {
if ( get_option( Synchronize::SYNC_TASK_PAGE ) > 0 ) {
wc_get_container()->get( Synchronize::class )->init_hooks();
}
}
);
}
/**
* Supplies the name of the database table used to store approved directories.
*
* @return string
*/
public function get_table(): string {
global $wpdb;
return $wpdb->prefix . 'wc_product_download_directories';
}
/**
* Returns a string indicating the current mode.
*
* May be one of: 'disabled', 'enabled', 'migrating'.
*
* @return string
*/
public function get_mode(): string {
$current_mode = get_option( $this->mode_option, self::MODE_DISABLED );
return in_array( $current_mode, self::MODES, true ) ? $current_mode : self::MODE_DISABLED;
}
/**
* Sets the mode. This effectively controls if approved directories are enforced or not.
*
* May be one of: 'disabled', 'enabled', 'migrating'.
*
* @param string $mode One of the values contained within self::MODES.
*
* @return bool
*/
public function set_mode( string $mode ): bool {
if ( ! in_array( $mode, self::MODES, true ) ) {
return false;
}
update_option( $this->mode_option, $mode );
return get_option( $this->mode_option ) === $mode;
}
/**
* Adds a new URL path.
*
* On success (or if the URL was already added) returns the URL ID, or else
* returns boolean false.
*
* @throws URLException If the URL was invalid.
* @throws ApprovedDirectoriesException If the operation could not be performed.
*
* @param string $url The URL of the approved directory.
* @param bool $enabled If the rule is enabled.
*
* @return int
*/
public function add_approved_directory( string $url, bool $enabled = true ): int {
$url = $this->prepare_url_for_upsert( $url );
$existing = $this->get_by_url( $url );
if ( $existing ) {
return $existing->get_id();
}
global $wpdb;
$insert_fields = array(
'url' => $url,
'enabled' => (int) $enabled,
);
if ( false !== $wpdb->insert( $this->get_table(), $insert_fields ) ) {
return $wpdb->insert_id;
}
throw new ApprovedDirectoriesException( __( 'URL could not be added (probable database error).', 'woocommerce' ), ApprovedDirectoriesException::DB_ERROR );
}
/**
* Updates an existing approved directory.
*
* On success or if there is an existing entry for the same URL, returns true.
*
* @throws ApprovedDirectoriesException If the operation could not be performed.
* @throws URLException If the URL was invalid.
*
* @param int $id The ID of the approved directory to be updated.
* @param string $url The new URL for the specified option.
* @param bool $enabled If the rule is enabled.
*
* @return bool
*/
public function update_approved_directory( int $id, string $url, bool $enabled = true ): bool {
$url = $this->prepare_url_for_upsert( $url );
$existing_path = $this->get_by_url( $url );
// No need to go any further if the URL is already listed and nothing has changed.
if ( $existing_path && $existing_path->get_url() === $url && $enabled === $existing_path->is_enabled() ) {
return true;
}
global $wpdb;
$fields = array(
'url' => $url,
'enabled' => (int) $enabled,
);
if ( false === $wpdb->update( $this->get_table(), $fields, array( 'url_id' => $id ) ) ) {
throw new ApprovedDirectoriesException( __( 'URL could not be updated (probable database error).', 'woocommerce' ), ApprovedDirectoriesException::DB_ERROR );
}
return true;
}
/**
* Indicates if the specified URL is already an approved directory.
*
* @param string $url The URL to check.
*
* @return bool
*/
public function approved_directory_exists( string $url ): bool {
return (bool) $this->get_by_url( $url );
}
/**
* Returns the path identified by $id, or false if it does not exist.
*
* @param int $id The ID of the rule we are looking for.
*
* @return StoredUrl|false
*/
public function get_by_id( int $id ) {
global $wpdb;
$table = $this->get_table();
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$result = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$table} WHERE url_id = %d", array( $id ) ) );
if ( ! $result ) {
return false;
}
return new StoredUrl( $result->url_id, $result->url, $result->enabled );
}
/**
* Returns the path identified by $url, or false if it does not exist.
*
* @param string $url The URL of the rule we are looking for.
*
* @return StoredUrl|false
*/
public function get_by_url( string $url ) {
global $wpdb;
$table = $this->get_table();
$url = trailingslashit( $url );
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$result = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$table} WHERE url = %s", array( $url ) ) );
if ( ! $result ) {
return false;
}
return new StoredUrl( $result->url_id, $result->url, $result->enabled );
}
/**
* Indicates if the URL is within an approved directory. The approved directory must be enabled
* (it is possible for individual approved directories to be disabled).
*
* For instance, for 'https://storage.king/12345/ebook.pdf' to be valid then 'https://storage.king/12345'
* would need to be within our register.
*
* If the provided URL is a filepath it can be passed in without the 'file://' scheme.
*
* @throws URLException If the provided URL is badly formed.
*
* @param string $download_url The URL to check.
*
* @return bool
*/
public function is_valid_path( string $download_url ): bool {
global $wpdb;
$parent_directories = array();
foreach ( ( new URL( $this->normalize_url( $download_url ) ) )->get_all_parent_urls() as $parent ) {
$parent_directories[] = "'" . esc_sql( $parent ) . "'";
}
if ( empty( $parent_directories ) ) {
return false;
}
$parent_directories = join( ',', $parent_directories );
$table = $this->get_table();
// Look for a rule that matches the start of the download URL being tested. Since rules describe parent
// directories, we also ensure it ends with a trailing slash.
//
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$matches = (int) $wpdb->get_var(
"
SELECT COUNT(*)
FROM {$table}
WHERE enabled = 1
AND url IN ( {$parent_directories} )
"
);
// phpcs:enable
return $matches > 0;
}
/**
* Used when a URL string is prepared before potentially adding it to the database.
*
* It will be normalized and trailing-slashed; a length check will also be performed.
*
* @throws ApprovedDirectoriesException If the operation could not be performed.
* @throws URLException If the URL was invalid.
*
* @param string $url The string URL to be normalized and trailing-slashed.
*
* @return string
*/
private function prepare_url_for_upsert( string $url ): string {
$url = trailingslashit( $this->normalize_url( $url ) );
if ( mb_strlen( $url ) > 256 ) {
throw new ApprovedDirectoriesException( __( 'Approved directory URLs cannot be longer than 256 characters.', 'woocommerce' ), ApprovedDirectoriesException::INVALID_URL );
}
return $url;
}
/**
* Normalizes the provided URL, by trimming whitespace per normal PHP conventions
* and removing any trailing slashes. If it lacks a scheme, the file scheme is
* assumed and prepended.
*
* @throws URLException If the URL is badly formed.
*
* @param string $url The URL to be normalized.
*
* @return string
*/
private function normalize_url( string $url ): string {
$url = untrailingslashit( trim( $url ) );
return ( new URL( $url ) )->get_url();
}
/**
* Lists currently approved directories.
*
* Returned array will have the following structure:
*
* [
* 'total_urls' => 12345,
* 'total_pages' => 123,
* 'urls' => [], # StoredUrl[]
* ]
*
* @param array $args {
* Controls pagination and ordering.
*
* @type null|bool $enabled Controls if only enabled (true), disabled (false) or all rules (null) should be listed.
* @type string $order Ordering ('ASC' for ascending, 'DESC' for descending).
* @type string $order_by Field to order by (one of 'url_id' or 'url').
* @type int $page The page of results to retrieve.
* @type int $per_page The number of results to retrieve per page.
* @type string $search Term to search for.
* }
*
* @return array
*/
public function list( array $args ): array {
global $wpdb;
$args = array_merge(
array(
'enabled' => null,
'order' => 'ASC',
'order_by' => 'url',
'page' => 1,
'per_page' => 20,
'search' => '',
),
$args
);
$table = $this->get_table();
$paths = array();
$order = in_array( $args['order'], array( 'ASC', 'DESC' ), true ) ? $args['order'] : 'ASC';
$order_by = in_array( $args['order_by'], array( 'url_id', 'url' ), true ) ? $args['order_by'] : 'url';
$page = absint( $args['page'] );
$per_page = absint( $args['per_page'] );
$enabled = is_bool( $args['enabled'] ) ? $args['enabled'] : null;
$search = '%' . $wpdb->esc_like( sanitize_text_field( $args['search'] ) ) . '%';
if ( $page < 1 ) {
$page = 1;
}
if ( $per_page < 1 ) {
$per_page = 1;
}
$where = array();
$where_sql = '';
if ( ! empty( $search ) ) {
$where[] = $wpdb->prepare( 'url LIKE %s', $search );
}
if ( is_bool( $enabled ) ) {
$where[] = 'enabled = ' . (int) $enabled;
}
if ( ! empty( $where ) ) {
$where_sql = 'WHERE ' . join( ' AND ', $where );
}
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$results = $wpdb->get_results(
$wpdb->prepare(
"
SELECT url_id, url, enabled
FROM {$table}
{$where_sql}
ORDER BY {$order_by} {$order}
LIMIT %d, %d
",
( $page - 1 ) * $per_page,
$per_page
)
);
$total_rows = (int) $wpdb->get_var( "SELECT COUNT( * ) FROM {$table} {$where_sql}" );
// phpcs:enable
foreach ( $results as $single_result ) {
$paths[] = new StoredUrl( $single_result->url_id, $single_result->url, $single_result->enabled );
}
return array(
'total_urls' => $total_rows,
'total_pages' => (int) ceil( $total_rows / $per_page ),
'approved_directories' => $paths,
);
}
/**
* Delete the approved directory identitied by the supplied ID.
*
* @param int $id The ID of the rule to be deleted.
*
* @return bool
*/
public function delete_by_id( int $id ): bool {
global $wpdb;
$table = $this->get_table();
return (bool) $wpdb->delete( $table, array( 'url_id' => $id ) );
}
/**
* Delete the entirev approved directory list.
*
* @return bool
*/
public function delete_all(): bool {
global $wpdb;
$table = $this->get_table();
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
return (bool) $wpdb->query( "DELETE FROM $table" );
}
/**
* Enable the approved directory identitied by the supplied ID.
*
* @param int $id The ID of the rule to be deleted.
*
* @return bool
*/
public function enable_by_id( int $id ): bool {
global $wpdb;
$table = $this->get_table();
return (bool) $wpdb->update( $table, array( 'enabled' => 1 ), array( 'url_id' => $id ) );
}
/**
* Disable the approved directory identitied by the supplied ID.
*
* @param int $id The ID of the rule to be deleted.
*
* @return bool
*/
public function disable_by_id( int $id ): bool {
global $wpdb;
$table = $this->get_table();
return (bool) $wpdb->update( $table, array( 'enabled' => 0 ), array( 'url_id' => $id ) );
}
/**
* Enables all Approved Download Directory rules in a single operation.
*
* @return bool
*/
public function enable_all(): bool {
global $wpdb;
$table = $this->get_table();
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
return (bool) $wpdb->query( "UPDATE {$table} SET enabled = 1" );
}
/**
* Disables all Approved Download Directory rules in a single operation.
*
* @return bool
*/
public function disable_all(): bool {
global $wpdb;
$table = $this->get_table();
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
return (bool) $wpdb->query( "UPDATE {$table} SET enabled = 0" );
}
/**
* Indicates the number of approved directories that are enabled (or disabled, if optional
* param $enabled is set to false).
*
* @param bool $enabled Controls whether enabled or disabled directory rules are counted.
*
* @return int
*/
public function count( bool $enabled = true ): int {
global $wpdb;
$table = $this->get_table();
return (int) $wpdb->get_var(
$wpdb->prepare(
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
"SELECT COUNT(*) FROM {$table} WHERE enabled = %d",
$enabled ? 1 : 0
)
);
}
}
StoredUrl.php 0000644 00000002427 15155010064 0007202 0 ustar 00 <?php
namespace Automattic\WooCommerce\Internal\ProductDownloads\ApprovedDirectories;
/**
* Representation of an approved directory URL, bundling the ID and URL in a single entity.
*/
class StoredUrl {
/**
* The approved directory ID.
*
* @var int
*/
private $id;
/**
* The approved directory URL.
*
* @var string
*/
private $url;
/**
* If the individual rule is enabled or disabled.
*
* @var bool
*/
private $enabled;
/**
* Sets up the approved directory rule.
*
* @param int $id The approved directory ID.
* @param string $url The approved directory URL.
* @param bool $enabled Indicates if the approved directory rule is enabled.
*/
public function __construct( int $id, string $url, bool $enabled ) {
$this->id = $id;
$this->url = $url;
$this->enabled = $enabled;
}
/**
* Supplies the ID of the approved directory.
*
* @return int
*/
public function get_id(): int {
return $this->id;
}
/**
* Supplies the approved directory URL.
*
* @return string
*/
public function get_url(): string {
return $this->url;
}
/**
* Indicates if this rule is enabled or not (rules can be temporarily disabled).
*
* @return bool
*/
public function is_enabled(): bool {
return $this->enabled;
}
}
Synchronize.php 0000644 00000020374 15155010064 0007573 0 ustar 00 <?php
namespace Automattic\WooCommerce\Internal\ProductDownloads\ApprovedDirectories;
use Exception;
use Automattic\WooCommerce\Internal\Utilities\URL;
use WC_Admin_Notices;
use WC_Product;
use WC_Queue_Interface;
/**
* Ensures that any downloadable files have a corresponding entry in the Approved Product
* Download Directories list.
*/
class Synchronize {
/**
* Scheduled action hook used to facilitate scanning the product catalog for downloadable products.
*/
public const SYNC_TASK = 'woocommerce_download_dir_sync';
/**
* The group under which synchronization tasks run (our standard 'woocommerce-db-updates' group).
*/
public const SYNC_TASK_GROUP = 'woocommerce-db-updates';
/**
* Used to track progress throughout the sync process.
*/
public const SYNC_TASK_PAGE = 'wc_product_download_dir_sync_page';
/**
* Used to record an estimation of progress on the current synchronization process. 0 means 0%,
* 100 means 100%.
*
* @param int
*/
public const SYNC_TASK_PROGRESS = 'wc_product_download_dir_sync_progress';
/**
* Number of downloadable products to be processed in each atomic sync task.
*/
public const SYNC_TASK_BATCH_SIZE = 20;
/**
* WC Queue.
*
* @var WC_Queue_Interface
*/
private $queue;
/**
* Register of approved directories.
*
* @var Register
*/
private $register;
/**
* Sets up our checks and controls for downloadable asset URLs, as appropriate for
* the current approved download directory mode.
*
* @internal
* @throws Exception If the WC_Queue instance cannot be obtained.
*
* @param Register $register The active approved download directories instance in use.
*/
final public function init( Register $register ) {
$this->queue = WC()->get_instance_of( WC_Queue_Interface::class );
$this->register = $register;
}
/**
* Performs any work needed to add hooks and otherwise integrate with the wider system.
*/
final public function init_hooks() {
add_action( self::SYNC_TASK, array( $this, 'run' ) );
}
/**
* Initializes the Approved Download Directories feature, typically following an update or
* during initial installation.
*
* @param bool $synchronize Synchronize with existing product downloads. Not needed in a fresh installation.
* @param bool $enable_feature Enable (default) or disable the feature.
*/
public function init_feature( bool $synchronize = true, bool $enable_feature = true ) {
try {
$this->add_default_directories();
if ( $synchronize ) {
$this->start();
}
} catch ( Exception $e ) {
wc_get_logger()->log( 'warning', __( 'It was not possible to synchronize download directories following the most recent update.', 'woocommerce' ) );
}
$this->register->set_mode(
$enable_feature ? Register::MODE_ENABLED : Register::MODE_DISABLED
);
}
/**
* By default we add the woocommerce_uploads directory (file path plus web URL) to the list
* of approved download directories.
*
* @throws Exception If the default directories cannot be added to the Approved List.
*/
public function add_default_directories() {
$upload_dir = wp_get_upload_dir();
$this->register->add_approved_directory( $upload_dir['basedir'] . '/woocommerce_uploads' );
$this->register->add_approved_directory( $upload_dir['baseurl'] . '/woocommerce_uploads' );
}
/**
* Starts the synchronization process.
*
* @return bool
*/
public function start(): bool {
if ( null !== $this->queue->get_next( self::SYNC_TASK ) ) {
wc_get_logger()->log( 'warning', __( 'Synchronization of approved product download directories is already in progress.', 'woocommerce' ) );
return false;
}
update_option( self::SYNC_TASK_PAGE, 1 );
$this->queue->schedule_single( time(), self::SYNC_TASK, array(), self::SYNC_TASK_GROUP );
wc_get_logger()->log( 'info', __( 'Approved Download Directories sync: new scan scheduled.', 'woocommerce' ) );
return true;
}
/**
* Runs the syncronization task.
*/
public function run() {
$products = $this->get_next_set_of_downloadable_products();
foreach ( $products as $product ) {
$this->process_product( $product );
}
// Detect if we have reached the end of the task.
if ( count( $products ) < self::SYNC_TASK_BATCH_SIZE ) {
wc_get_logger()->log( 'info', __( 'Approved Download Directories sync: scan is complete!', 'woocommerce' ) );
$this->stop();
} else {
wc_get_logger()->log(
'info',
sprintf(
/* translators: %1$d is the current batch in the synchronization task, %2$d is the percent complete. */
__( 'Approved Download Directories sync: completed batch %1$d (%2$d%% complete).', 'woocommerce' ),
(int) get_option( self::SYNC_TASK_PAGE, 2 ) - 1,
$this->get_progress()
)
);
$this->queue->schedule_single( time() + 1, self::SYNC_TASK, array(), self::SYNC_TASK_GROUP );
}
}
/**
* Stops/cancels the current synchronization task.
*/
public function stop() {
WC_Admin_Notices::add_notice( 'download_directories_sync_complete', true );
delete_option( self::SYNC_TASK_PAGE );
delete_option( self::SYNC_TASK_PROGRESS );
$this->queue->cancel( self::SYNC_TASK );
}
/**
* Queries for the next batch of downloadable products, applying logic to ensure we only fetch those that actually
* have downloadable files (a downloadable product can be created that does not have downloadable files and/or
* downloadable files can be removed from existing downloadable products).
*
* @return array
*/
private function get_next_set_of_downloadable_products(): array {
$query_filter = function ( array $query ): array {
$query['meta_query'][] = array(
'key' => '_downloadable_files',
'compare' => 'EXISTS',
);
return $query;
};
$page = (int) get_option( self::SYNC_TASK_PAGE, 1 );
add_filter( 'woocommerce_product_data_store_cpt_get_products_query', $query_filter );
$products = wc_get_products(
array(
'limit' => self::SYNC_TASK_BATCH_SIZE,
'page' => $page,
'paginate' => true,
)
);
remove_filter( 'woocommerce_product_data_store_cpt_get_products_query', $query_filter );
$progress = $products->max_num_pages > 0 ? (int) ( ( $page / $products->max_num_pages ) * 100 ) : 1;
update_option( self::SYNC_TASK_PAGE, $page + 1 );
update_option( self::SYNC_TASK_PROGRESS, $progress );
return $products->products;
}
/**
* Processes an individual downloadable product, adding the parent paths for any downloadable files to the
* Approved Download Directories list.
*
* Any such paths will be added with the disabled flag set, because we want a site administrator to review
* and approve first.
*
* @param WC_Product $product The product we wish to examine for downloadable file paths.
*/
private function process_product( WC_Product $product ) {
$downloads = $product->get_downloads();
foreach ( $downloads as $downloadable ) {
$parent_url = _x( 'invalid URL', 'Approved product download URLs migration', 'woocommerce' );
try {
$download_file = $downloadable->get_file();
/**
* Controls whether shortcodes should be resolved and validated using the Approved Download Directory feature.
*
* @param bool $should_validate
*/
if ( apply_filters( 'woocommerce_product_downloads_approved_directory_validation_for_shortcodes', true ) && 'shortcode' === $downloadable->get_type_of_file_path() ) {
$download_file = do_shortcode( $download_file );
}
$parent_url = ( new URL( $download_file ) )->get_parent_url();
$this->register->add_approved_directory( $parent_url, false );
} catch ( Exception $e ) {
wc_get_logger()->log(
'error',
sprintf(
/* translators: %s is a URL, %d is a product ID. */
__( 'Product download migration: %1$s (for product %1$d) could not be added to the list of approved download directories.', 'woocommerce' ),
$parent_url,
$product->get_id()
)
);
}
}
}
/**
* Indicates if a synchronization of product download directories is in progress.
*
* @return bool
*/
public function in_progress(): bool {
return (bool) get_option( self::SYNC_TASK_PAGE, false );
}
/**
* Returns a value between 0 and 100 representing the percentage complete of the current sync.
*
* @return int
*/
public function get_progress(): int {
return min( 100, max( 0, (int) get_option( self::SYNC_TASK_PROGRESS, 0 ) ) );
}
}