Automattic\WooCommerce\Internal\ProductDownloads\ApprovedDirectories\Admin

UI{}WC 1.0

Manages user interactions for product download URL safety.

No Hooks.

Usage

$UI = new UI();
// use class methods

Methods

  1. public add_section( array $sections )
  2. private admin_notices()
  3. private display_title()
  4. private edit_screen( int $url_id )
  5. private handle_search()
  6. public init( Register $register )
  7. public init_hooks()
  8. private is_download_urls_screen()
  9. private process_actions()
  10. private process_all_actions( string $action )
  11. private process_bulk_actions( array $ids, string $action )
  12. private process_edits( int $url_id )
  13. private process_on_off( string $action )
  14. public render()
  15. private security_check()
  16. public setup()

Code of UI{} WC 7.1.0

<?php
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' ) );
		}
	}
}