Your IP : 216.73.216.215


Current Path : /home/flapst5/ekmtoronto.com/wp-content/plugins/code-snippets/php/Admin/Menus/
Upload File :
Current File : /home/flapst5/ekmtoronto.com/wp-content/plugins/code-snippets/php/Admin/Menus/Manage_Menu.php

<?php

namespace Code_Snippets\Admin\Menus;

use Code_Snippets\Admin\Contextual_Help;
use Code_Snippets\Migration\Export\Download_Code;
use Code_Snippets\Utils\Code_Highlighter;
use WP_Error;
use function Code_Snippets\code_snippets;
use function Code_Snippets\get_snippet;
use function Code_Snippets\get_snippets;
use function Code_Snippets\Settings\get_setting;
use const Code_Snippets\PLUGIN_FILE;
use const Code_Snippets\PLUGIN_VERSION;

/**
 * This class handles the manage snippets menu
 *
 * @since   2.4.0
 * @package Code_Snippets
 */
class Manage_Menu extends Admin_Menu {

	/**
	 * Handle for JavaScript asset file.
	 */
	public const JS_HANDLE = 'code-snippets-manage-menu';

	/**
	 * Handle for CSS asset file.
	 */
	public const CSS_HANDLE = 'code-snippets-manage';

	/**
	 * Default number of snippets shown per page in the manage table.
	 */
	public const DEFAULT_SNIPPETS_PER_PAGE = 100;

	/**
	 * Class constructor
	 */
	public function __construct() {
		parent::__construct(
			'manage',
			_x( 'All Snippets', 'menu label', 'code-snippets' ),
			__( 'Snippets', 'code-snippets' )
		);

		if ( code_snippets()->is_compact_menu() ) {
			add_action( 'admin_menu', array( $this, 'register_compact_menu' ), 2 );
			add_action( 'network_admin_menu', array( $this, 'register_compact_menu' ), 2 );
		}

		add_action( 'admin_menu', array( $this, 'register_upgrade_menu' ), 500 );
		add_action( 'admin_init', array( $this, 'handle_bulk_download_request' ) );
		add_action( 'admin_init', array( $this, 'save_truncation_preference' ) );
		add_filter( 'set-screen-option', array( $this, 'save_screen_option' ), 10, 3 );
		add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_menu_css' ) );
		add_action( 'wp_ajax_update_code_snippet', array( $this, 'ajax_callback' ) );
	}

	/**
	 * Register the top-level 'Snippets' menu and associated 'Manage' subpage
	 */
	public function register() {
		add_menu_page(
			__( 'Snippets', 'code-snippets' ),
			_x( 'Snippets', 'top-level menu label', 'code-snippets' ),
			code_snippets()->get_cap(),
			code_snippets()->get_menu_slug(),
			array( $this, 'render' ),
			'none', // Added through CSS as a mask to prevent loading 'blinking'.
			apply_filters( 'code_snippets/admin/menu_position', is_network_admin() ? 21 : 67 )
		);

		// Register the sub-menu.
		parent::register();
	}

	/**
	 * Register the 'upgrade' menu item.
	 *
	 * @return void
	 */
	public function register_upgrade_menu() {
		if ( code_snippets()->licensing->is_licensed() || get_setting( 'general', 'hide_upgrade_menu' ) ) {
			return;
		}

		$menu_title = sprintf(
			'<span class="button button-primary code-snippets-upgrade-button">%s %s</span>',
			_x( 'Go Pro', 'top-level menu label', 'code-snippets' ),
			'<span class="dashicons dashicons-external" aria-hidden="true"></span>'
		);

		$hook = add_submenu_page(
			code_snippets()->get_menu_slug(),
			__( 'Upgrade to Pro', 'code-snippets' ),
			$menu_title,
			code_snippets()->get_cap(),
			'code_snippets_upgrade',
			'__return_empty_string',
			100
		);

		add_action( "load-$hook", [ $this, 'load_upgrade_menu' ] );
	}

	/**
	 * Print CSS required for the admin menu icon.
	 *
	 * @return void
	 */
	public function enqueue_menu_css() {
		wp_enqueue_style(
			'code-snippets-menu',
			plugins_url( 'dist/menu.css', PLUGIN_FILE ),
			[],
			PLUGIN_VERSION
		);
	}

	/**
	 * Redirect the user upon opening the upgrade menu.
	 *
	 * @return void
	 */
	public function load_upgrade_menu() {
		wp_safe_redirect( 'https://snipco.de/JE2f' );
		exit;
	}

	/**
	 * Add menu pages for the compact menu
	 */
	public function register_compact_menu() {

		if ( ! code_snippets()->is_compact_menu() ) {
			return;
		}

		// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Value is matched to known classes.
		$sub = code_snippets()->get_menu_slug( isset( $_GET['sub'] ) ? sanitize_key( $_GET['sub'] ) : 'snippets' );

		$classmap = array(
			'snippets'             => 'manage',
			'add-snippet'          => 'edit',
			'edit-snippet'         => 'edit',
			'import-code-snippets' => 'import',
			'snippets-settings'    => 'settings',
		);

		$menus = code_snippets()->admin->menus;
		$class = isset( $classmap[ $sub ], $menus[ $classmap[ $sub ] ] ) ? $menus[ $classmap[ $sub ] ] : $this;

		/* Add a submenu to the Tools menu */
		$hook = add_submenu_page(
			'tools.php',
			__( 'Snippets', 'code-snippets' ),
			_x( 'Snippets', 'tools submenu label', 'code-snippets' ),
			code_snippets()->get_cap(),
			code_snippets()->get_menu_slug(),
			array( $class, 'render' )
		);

		add_action( 'load-' . $hook, array( $class, 'load' ) );
	}

	/**
	 * Executed when the admin page is loaded.
	 */
	public function load() {
		parent::load();

		$screen = get_current_screen();

		if ( $screen && ! $this->is_cloud_community_view() ) {
			add_filter( "manage_{$screen->id}_columns", array( $this, 'get_screen_columns' ) );
			add_filter( 'screen_settings', array( $this, 'render_screen_settings' ), 10, 2 );
		}

		add_screen_option(
			'per_page',
			array(
				'label'   => __( 'Snippets per page', 'code-snippets' ),
				'default' => $this->get_default_snippets_per_page(),
				'option'  => 'snippets_per_page',
			)
		);

		$contextual_help = new Contextual_Help( 'edit' );
		$contextual_help->load();
	}

	/**
	 * Enqueue scripts and stylesheets for the admin page.
	 */
	public function enqueue_assets() {
		$plugin = code_snippets();

		wp_enqueue_style(
			self::CSS_HANDLE,
			plugins_url( 'dist/manage.css', PLUGIN_FILE ),
			self::$style_deps,
			PLUGIN_VERSION
		);

		wp_enqueue_script(
			self::JS_HANDLE,
			plugins_url( 'dist/manage.js', PLUGIN_FILE ),
			self::$script_deps,
			PLUGIN_VERSION,
			[ 'in_footer' => true ]
		);

		Code_Highlighter::enqueue_all_prism_themes();

		wp_set_script_translations( self::JS_HANDLE, 'code-snippets' );
		$plugin->localize_script( self::JS_HANDLE );

		$localized = [
			'hasNetworkCap'        => current_user_can( code_snippets()->get_network_cap_name() ),
			'hiddenColumns'        => $this->get_hidden_manage_columns(),
			'truncateRowValues'    => (int) $this->truncate_row_values(),
			'snippetsPerPage'      => $this->get_snippets_per_page(),
			'cloudSearchPerPage'   => $this->get_cloud_search_per_page(),
			'isSafeModeActive'     => code_snippets()->evaluate_functions->is_safe_mode_active(),
			'bulkDownloadNonce'    => wp_create_nonce( 'code_snippets_bulk_download' ),
			'supportsZipDownloads' => class_exists( 'ZipArchive' ),
		];

		// Only the manage-table view consumes the full snippets list; skip the
		// table scan and inline payload on subpages that don't render the table.
		if ( $this->is_manage_table_view() ) {
			$localized['snippetsList'] = array_map(
				function ( $snippet ) {
					return $snippet->get_fields();
				},
				get_snippets()
			);
		}

		wp_localize_script( self::JS_HANDLE, 'CODE_SNIPPETS_MANAGE', $localized );
	}

	/**
	 * Get the number of snippets to show per page.
	 *
	 * @return int
	 */
	protected function get_snippets_per_page(): int {
		$per_page = (int) get_user_option( 'snippets_per_page' );

		if ( empty( $per_page ) || $per_page < 1 ) {
			$per_page = $this->get_default_snippets_per_page();
		}

		return (int) apply_filters( 'snippets_per_page', $per_page );
	}

	/**
	 * Get the default number of snippets to show per page.
	 *
	 * @return int
	 */
	protected function get_default_snippets_per_page(): int {
		$default = (int) apply_filters( 'code_snippets/snippets_per_page_default', self::DEFAULT_SNIPPETS_PER_PAGE );

		return max( 1, $default );
	}

	/**
	 * Get the number of snippets to show per page in the cloud search.
	 *
	 * The value defaults to the user's local snippets-per-page preference but can
	 * be overridden independently via the `code_snippets/cloud_search/per_page` filter.
	 *
	 * @return int
	 */
	protected function get_cloud_search_per_page(): int {
		return (int) apply_filters( 'code_snippets/cloud_search/per_page', $this->get_snippets_per_page() );
	}

	/**
	 * Render the snippets table interface.
	 *
	 * @return void
	 */
	public function render() {
		echo '<div id="manage-snippets-container" class="wrap"></div>';
	}

	/**
	 * Return the columns available in Screen Options for the snippets table.
	 *
	 * @param string[] $columns Existing columns.
	 *
	 * @return string[]
	 */
	public function get_screen_columns( array $columns = array() ): array {
		return array_merge(
			$columns,
			array(
				'_title'   => __( 'Columns', 'code-snippets' ),
				'activate' => __( 'Active', 'code-snippets' ),
				'name'     => __( 'Name', 'code-snippets' ),
				'type'     => __( 'Type', 'code-snippets' ),
				'desc'     => __( 'Description', 'code-snippets' ),
				'tags'     => __( 'Tags', 'code-snippets' ),
				'date'     => __( 'Modified', 'code-snippets' ),
				'priority' => __( 'Priority', 'code-snippets' ),
			)
		);
	}

	/**
	 * Get the list of columns hidden for the current user on the snippets screen.
	 *
	 * @return string[]
	 */
	protected function get_hidden_manage_columns(): array {
		$screen = get_current_screen();

		return $screen ? get_hidden_columns( $screen ) : array();
	}

	/**
	 * Whether to truncate long row values in the snippets table.
	 *
	 * @return bool
	 */
	protected function truncate_row_values(): bool {
		$setting = get_user_option( 'snippets_table_truncate_row_values' );

		return false === $setting ? true : (bool) $setting;
	}

	/**
	 * Read the current `subpage` routing parameter.
	 *
	 * @return string
	 */
	protected function get_current_subpage(): string {
		// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only routing parameter.
		return isset( $_REQUEST['subpage'] ) ? sanitize_key( wp_unslash( $_REQUEST['subpage'] ) ) : '';
	}

	/**
	 * Whether the current request renders the snippets-table view (the default subpage).
	 *
	 * @return bool
	 */
	protected function is_manage_table_view(): bool {
		return ! $this->get_current_subpage();
	}

	/**
	 * Whether the current manage subpage is the Community Cloud view.
	 *
	 * @return bool
	 */
	protected function is_cloud_community_view(): bool {
		return 'cloud-community' === $this->get_current_subpage();
	}

	/**
	 * Render extra Screen Options controls for the snippets table.
	 *
	 * @param string     $screen_settings Existing screen settings HTML.
	 * @param \WP_Screen $screen          Current screen object.
	 *
	 * @return string
	 */
	public function render_screen_settings( string $screen_settings, \WP_Screen $screen ): string {
		if ( $this->is_cloud_community_view() ) {
			return $screen_settings;
		}

		ob_start();
		?>
		<fieldset class="metabox-prefs table-options-prefs">
			<legend><?php esc_html_e( 'Table Options', 'code-snippets' ); ?></legend>
			<div class="metabox-prefs-container">
				<label for="snippets-table-truncate-row-values">
					<input
						id="snippets-table-truncate-row-values"
						name="snippets_table_truncate_row_values"
						type="checkbox"
						value="1"
						<?php checked( $this->truncate_row_values() ); ?>
					/>
					<?php esc_html_e( 'Truncate long snippet names and descriptions', 'code-snippets' ); ?>
				</label>
			</div>
		</fieldset>
		<?php

		return $screen_settings . ob_get_clean();
	}

	/**
	 * Persist the snippets table truncation preference from Screen Options.
	 *
	 * @return void
	 */
	public function save_truncation_preference(): void {
		// phpcs:ignore WordPress.Security.NonceVerification.Missing -- Verified below before persisting the option.
		if ( empty( $_POST['wp_screen_options'] ) || ! is_array( $_POST['wp_screen_options'] ) ) {
			return;
		}

		// phpcs:ignore WordPress.Security.NonceVerification.Missing -- Sanitized and matched to a known admin page.
		$page = isset( $_REQUEST['page'] ) ? sanitize_key( wp_unslash( $_REQUEST['page'] ) ) : '';

		if ( code_snippets()->get_menu_slug() !== $page || ! current_user_can( code_snippets()->get_cap() ) ) {
			return;
		}

		if ( $this->is_cloud_community_view() ) {
			return;
		}

		// phpcs:ignore WordPress.Security.NonceVerification.Missing -- Verified here before persisting the option.
		$nonce = isset( $_POST['screenoptionnonce'] ) ? sanitize_text_field( wp_unslash( $_POST['screenoptionnonce'] ) ) : '';

		if ( ! wp_verify_nonce( $nonce, 'screen-options-nonce' ) ) {
			return;
		}

		update_user_option(
			get_current_user_id(),
			'snippets_table_truncate_row_values',
			// phpcs:ignore WordPress.Security.NonceVerification.Missing -- Verified above before reading the checkbox state.
			isset( $_POST['snippets_table_truncate_row_values'] ) ? 1 : 0
		);
	}

	/**
	 * Handles saving the user's snippets per page preference
	 *
	 * @param mixed  $status Current screen option status.
	 * @param string $option The screen option name.
	 * @param mixed  $value  Screen option value.
	 *
	 * @return mixed
	 */
	public function save_screen_option( $status, string $option, $value ) {
		return 'snippets_per_page' === $option ? $value : $status;
	}

	/**
	 * Handle bulk snippet code downloads from the manage screen.
	 *
	 * @return void
	 */
	public function handle_bulk_download_request(): void {
		if ( ! $this->is_bulk_download_request() ) {
			return;
		}

		if ( ! current_user_can( code_snippets()->get_cap() ) ) {
			$this->send_download_error( __( 'You are not allowed to download these snippets.', 'code-snippets' ), 403 );
			return;
		}

		$nonce = filter_input( INPUT_POST, 'code_snippets_bulk_download_nonce', FILTER_SANITIZE_FULL_SPECIAL_CHARS ) ?? '';

		if ( ! wp_verify_nonce( $nonce, 'code_snippets_bulk_download' ) ) {
			$this->send_download_error( __( 'The download request is no longer valid. Please refresh and try again.', 'code-snippets' ), 403 );
			return;
		}

		$snippets = $this->get_requested_download_snippets();

		if ( $snippets instanceof WP_Error ) {
			$status = $snippets->get_error_data( 'status' );
			$this->send_download_error( $snippets->get_error_message(), is_numeric( $status ) ? (int) $status : 403 );
			return;
		}

		if ( empty( $snippets ) ) {
			$this->send_download_error( __( 'No snippets were selected for download.', 'code-snippets' ) );
			return;
		}

		$download = 1 === count( $snippets )
			? Download_Code::build_snippet_download( $snippets[0] )
			: Download_Code::build_archive_download( $snippets );

		if ( $download instanceof WP_Error ) {
			$status = $download->get_error_data( 'status' );
			$this->send_download_error( $download->get_error_message(), is_numeric( $status ) ? (int) $status : 500 );
			return;
		}

		$this->send_download_response( $download );
	}

	/**
	 * Determine whether the current request is a bulk download request.
	 *
	 * @return bool
	 */
	private function is_bulk_download_request(): bool {
		$page = sanitize_key( filter_input( INPUT_GET, 'page' ) ?? '' );
		$action = sanitize_key( filter_input( INPUT_POST, 'code_snippets_action' ) ?? '' );

		return code_snippets()->get_menu_slug() === $page && 'bulk-download' === $action;
	}

	/**
	 * Resolve the snippets requested for download.
	 *
	 * @return array<\Code_Snippets\Model\Snippet>|WP_Error
	 */
	private function get_requested_download_snippets() {
		$snippets_json = wp_unslash( filter_input( INPUT_POST, 'snippets', FILTER_DEFAULT ) ?? '' );
		$payload = '' === $snippets_json ? [] : json_decode( $snippets_json, true );

		if ( ! is_array( $payload ) ) {
			return [];
		}

		$snippets = [];

		foreach ( $payload as $snippet_data ) {
			if ( ! is_array( $snippet_data ) || empty( $snippet_data['id'] ) ) {
				continue;
			}

			if ( ! empty( $snippet_data['network'] ) && ! current_user_can( code_snippets()->get_network_cap_name() ) ) {
				return new WP_Error(
					'code_snippets_forbidden_network_download',
					__( 'You are not allowed to download network snippets.', 'code-snippets' ),
					[ 'status' => 403 ]
				);
			}

			$snippet = get_snippet(
				absint( $snippet_data['id'] ),
				! empty( $snippet_data['network'] )
			);

			if ( $snippet->id ) {
				$snippets[] = $snippet;
			}
		}

		return $snippets;
	}

	/**
	 * Send a download file response and end execution.
	 *
	 * @param array{filename:string, content_type:string, content:string} $download Download data.
	 *
	 * @return void
	 */
	private function send_download_response( array $download ): void {
		while ( ob_get_level() ) {
			ob_end_clean();
		}

		nocache_headers();
		send_nosniff_header();

		header( 'Content-Description: File Transfer' );
		header( 'Content-Type: ' . $download['content_type'] );
		header( 'Content-Disposition: attachment; filename="' . $download['filename'] . '"' );
		header( 'Content-Length: ' . strlen( $download['content'] ) );
		header( 'X-Suggested-Filename: ' . $download['filename'] );

		// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Binary download payload.
		echo $download['content'];
		exit;
	}

	/**
	 * Send a plain text download error response and end execution.
	 *
	 * @param string $message Error message.
	 * @param int    $status  HTTP status code.
	 *
	 * @return void
	 */
	private function send_download_error( string $message, int $status = 400 ): void {
		while ( ob_get_level() ) {
			ob_end_clean();
		}

		status_header( $status );
		nocache_headers();
		send_nosniff_header();
		header( 'Content-Type: text/plain; charset=' . get_option( 'blog_charset' ) );

		echo esc_html( $message );
		exit;
	}
}