Your IP : 216.73.216.215


Current Path : /home/flapst5/ekmtoronto.com/wp-content/plugins/wp-defender/src/traits/
Upload File :
Current File : /home/flapst5/ekmtoronto.com/wp-content/plugins/wp-defender/src/traits/plugin.php

<?php
/**
 * Helper functions for plugin related tasks.
 *
 * @package WP_Defender\Traits
 */

namespace WP_Defender\Traits;

use WP_Error;
use WP_Defender\Model\Scan_Item;
use WP_Filesystem_Base;

trait Plugin {

	/**
	 * Version URL.
	 *
	 * @var string Versioned path of the plugin file.
	 */
	private $url_plugin_vcs = 'https://plugins.svn.wordpress.org/%s/tags/%s/%s';

	/**
	 * Trunk URL.
	 *
	 * @var string Trunk path of the plugin file.
	 */
	private $url_plugin_vcs_trunk = 'https://plugins.svn.wordpress.org/%s/trunk/%s';

	/**
	 * Key for org slugs transient.
	 *
	 * @var string
	 */
	public static string $org_slugs = 'wp-defender-org-slugs';

	/**
	 * Key for org responses transient.
	 *
	 * @var string
	 */
	public static string $org_responses = 'wp-defender-org-responses';

	/**
	 * Get all installed plugins.
	 *
	 * @return array
	 */
	public function get_plugins(): array {
		if ( ! function_exists( 'get_plugins' ) ) {
			require_once ABSPATH . 'wp-admin/includes/plugin.php';
		}

		// WordPress caches this internally.
		return get_plugins();
	}

	/**
	 * Get plugin slug.
	 *
	 * @param string $main_plugin_file The main plugin file name.
	 *
	 * @return string Plugin dir name, e.g. 'plugin-test', or a file name with the extension if this is a single plugin file, e.g. 'plugin-test.php'.
	 */
	public function get_plugin_slug_by( string $main_plugin_file ): string {
		$dir = dirname( $main_plugin_file );
		if ( '.' === $dir ) {
			// If this is a single plugin file in the plugin root, take the file name.
			$slug = explode( '/', $main_plugin_file );
			$slug = array_shift( $slug );
		} else {
			$slug = $dir;
		}

		return $slug;
	}

	/**
	 * Get all slugs.
	 *
	 * @return array
	 */
	public function get_plugin_slugs(): array {
		$slugs = array();
		foreach ( $this->get_plugins() as $main_plugin_file => $plugin ) {
			$slugs[] = $this->get_plugin_slug_by( $main_plugin_file );
		}

		return $slugs;
	}

	/**
	 * Get plugin details by given slug.
	 *
	 * @param string $plugin_slug The plugin slug.
	 *
	 * @return array
	 */
	public function get_plugin_details_by( string $plugin_slug ): array {
		foreach ( $this->get_plugins() as $main_plugin_file => $plugin_data ) {
			if ( $this->get_plugin_slug_by( $main_plugin_file ) === $plugin_slug ) {
				$plugin_data['slug'] = $plugin_slug;

				return $plugin_data;
			}
		}

		return array();
	}

	/**
	 * Check if a plugin exists on WordPress.org repository by slug.
	 *
	 * @param string $slug The plugin slug.
	 *
	 * @return array|WP_Error
	 */
	public function ping_wp_org_by_plugin_slug( string $slug ) {
		$url       = 'https://api.wordpress.org/plugins/info/1.0/' . $slug . '.json';
		$http_args = array(
			'timeout'    => 15,
			'sslverify'  => true,
			'user-agent' => defender_get_own_user_agent(),
		);

		return wp_remote_get( $url, $http_args );
	}

	/**
	 * Handle a response from wp.org by the plugin slug.
	 *
	 * @param string $slug The plugin slug.
	 *
	 * @return array
	 */
	public function handle_wp_org_response_by( string $slug ): array {
		$default_error = esc_html__( 'Plugin unknown error.', 'wpdef' );
		$response      = $this->ping_wp_org_by_plugin_slug( $slug );
		if ( is_wp_error( $response ) ) {
			return array(
				'message' => $default_error,
				'success' => false,
			);
		}

		$results = json_decode( wp_remote_retrieve_body( $response ), true );
		if ( ! is_array( $results ) ) {
			return array(
				'message' => esc_html__( 'Plugin response is not in expected format.', 'wpdef' ),
				'success' => false,
			);
		}
		// Sometimes wp.org can return 404th response code for an existed plugin but with 'closed' status.
		if ( 200 !== wp_remote_retrieve_response_code( $response )
			&& isset( $results['error'] ) && 'closed' !== $results['error']
		) {
			return array(
				'message' => $results['description'] ?? $results['error'],
				'success' => false,
			);
		}

		return array(
			'success' => true,
			'message' => '',
			'body'    => $results,
		);
	}

	/**
	 * Check if a plugin exists on WordPress.org repository.
	 *
	 * @param string $slug The slug of the plugin.
	 *
	 * @return array Index message: describes what happened.
	 *               Index data: Plugin data from wp.org.
	 *               Index success: true if plugin in WordPress plugin repository, else false.
	 */
	public function check_plugin_on_wp_org( string $slug ): array {
		// Check if data exists in transient.
		$transient = get_site_transient( self::$org_responses ) ?? array();
		if ( isset( $transient[ $slug ] ) ) {
			return array(
				'message' => __( 'Plugin exists in WordPress repository.', 'wpdef' ),
				'data'    => $transient[ $slug ],
				'success' => true,
			);
		}

		$results = $this->handle_wp_org_response_by( $slug );
		if ( ! $results['success'] ) {
			return $results;
		}
		// Cache the relevant data in the transient.
		$filtered_results   = array_intersect_key( $results, array_flip( array( 'homepage', 'version', 'author' ) ) );
		$transient[ $slug ] = $filtered_results;
		set_site_transient( self::$org_responses, $transient, WEEK_IN_SECONDS );

		return array(
			'message' => esc_html__( 'Plugin exists in WordPress repository.', 'wpdef' ),
			'data'    => $results,
			'success' => true,
		);
	}

	/**
	 * Check for readme.txt or readme.md files.
	 * Sometimes plugins from wp.org don't have readme.txt file.
	 *
	 * @param string $readme_file_path Path to readme.* file.
	 *
	 * @return bool
	 */
	public function check_by_readme_file( string $readme_file_path ): bool {
		if ( file_exists( $readme_file_path ) && is_readable( $readme_file_path ) ) {
			global $wp_filesystem;
			// Initialize the WP filesystem, no more using 'file-put-contents' function.
			if ( ! $wp_filesystem instanceof WP_Filesystem_Base ) {
				require_once ABSPATH . '/wp-admin/includes/file.php';
				WP_Filesystem();
			}
			$contents = trim( (string) $wp_filesystem->get_contents( $readme_file_path ) );

			if ( false !== strpos( $contents, '===' ) ) {
				return true;
			}

			if ( false !== strpos( $contents, '#' ) ) {
				return true;
			}
		}

		return false;
	}

	/**
	 * Is this plugin likely to be a WordPress.org plugin?
	 *
	 * @param string|null $slug Plugin directory name.
	 *
	 * @return bool Return true if likely, else false.
	 */
	public function is_likely_wporg_slug( ?string $slug ): bool {
		// Ensure slug is valid.
		if ( ! is_string( $slug ) || '' === $slug ) {
			return false;
		}
		// Check if data exists in transient.
		$transient = get_site_transient( self::$org_slugs );
		if ( ! is_array( $transient ) ) {
			$transient = array();
		}
		if ( isset( $transient[ $slug ] ) ) {
			return $transient[ $slug ];
		}
		// Criterion #1: Check if plugin name contains 'Pro' mention.
		$actioned_plugins = get_site_option( \WP_Defender\Component\Scan::PLUGINS_ACTIONED );
		if ( is_array( $actioned_plugins ) && isset( $actioned_plugins[ $slug ] )
			&& preg_match( '/ Pro$/i', $actioned_plugins[ $slug ]['Name'] )
		) {
			return false;
		}

		$plugin_path = $this->get_abs_plugin_path_by_slug( $slug ) . DIRECTORY_SEPARATOR;
		// Some plugins do not follow the readme file naming rule. Let's list the possible names.
		$readme_files = array(
			'readme.txt',
			'README.txt',
			'readme.md',
			'README.md',
		);
		// Criterion #2: Check if there is Readme file.
		foreach ( $readme_files as $readme_file ) {
			if ( $this->check_by_readme_file( $plugin_path . $readme_file ) ) {
				$transient[ $slug ] = true;
				// Collect plugin slugs that are on wp.org.
				set_site_transient( self::$org_slugs, $transient, DAY_IN_SECONDS );

				return true;
			}
		}

		return false;
	}

	/**
	 * Check if the plugin is active.
	 *
	 * @param string $file_path  Absolute file path to the plugin file.
	 *
	 * @return bool
	 */
	public function is_active_plugin( $file_path ): bool {
		$path_data = explode( DIRECTORY_SEPARATOR, plugin_basename( $file_path ) );
		if ( '' !== $path_data[0] ) {
			$plugin_slug = $path_data[0];
		} else {
			return false;
		}

		$active = false;
		foreach ( $this->get_plugins() as $slug => $data ) {
			if ( $plugin_slug === $slug || 0 === strpos( $slug, $plugin_slug ) ) {
				$active = is_multisite() ? is_plugin_active_for_network( $slug ) : is_plugin_active( $slug );
				break;
			}
		}

		return $active;
	}

	/**
	 * Get plugin header from any file of the plugin.
	 *
	 * @param string $file_path Absolute file path to the plugin file.
	 *
	 * @return array Return plugin details as array.
	 */
	public function get_plugin_headers( $file_path ): array {
		$plugin_directory = $this->get_plugin_directory_name( $file_path );

		return get_plugins( DIRECTORY_SEPARATOR . $plugin_directory );
	}

	/**
	 * Get plugin directory name.
	 *
	 * @param string $file_path Absolute file path to the plugin file.
	 *
	 * @return string Return plugin directory name.
	 */
	public function get_plugin_directory_name( $file_path ): string {
		return (string) strtok( plugin_basename( $file_path ), '/' );
	}

	/**
	 * Get plugin relative path.
	 *
	 * @param string $file_path Absolute file path to the plugin file.
	 *
	 * @return string Return plugin relative path.
	 */
	public function get_plugin_relative_path( $file_path ): string {
		// Normalize the directory separators for cross-platform compatibility.
		$normalized_path = str_replace( '\\', '/', plugin_basename( $file_path ) );
		// Initialize strtok.
		strtok( $normalized_path, '/' );
		// Now fetch the next token, if available, otherwise return an empty string.
		return (string) strtok( '' );
	}

	/**
	 * Get plugin absolute path.
	 *
	 * @param string $slug Plugin slug.
	 *
	 * @return string
	 */
	public function get_abs_plugin_path_by_slug( string $slug = '' ): string {
		if ( defined( 'WP_PLUGIN_DIR' ) ) {
			return wp_normalize_path( WP_PLUGIN_DIR ) . DIRECTORY_SEPARATOR . $slug;
		}

		return wp_normalize_path( WP_CONTENT_DIR ) . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR . $slug;
	}

	/**
	 * Check file exists at wp.org svn.
	 *
	 * @param string $url URL of the file.
	 *
	 * @return boolean Return true for file exists else false.
	 */
	private function is_origin_file_exists( $url ): bool {
		$result = wp_remote_head( $url );

		if ( ! is_wp_error( $result ) ) {
			$http_status_code = wp_remote_retrieve_response_code( $result );

			return 200 === $http_status_code;
		}

		return false;
	}

	/**
	 * Generates the URL for a specific version of a plugin file.
	 *
	 * @param  string $directory_name  The name of the directory.
	 * @param  string $version  The version of the file.
	 * @param  string $file_path  The path of the file.
	 *
	 * @return string The URL of the versioned file.
	 */
	private function get_version_url( string $directory_name, string $version, string $file_path ): string {
		return sprintf(
			$this->url_plugin_vcs,
			$directory_name,
			$version,
			$file_path
		);
	}

	/**
	 * Generate the URL for the trunk of a plugin based on the directory name and file path.
	 *
	 * @param  string $directory_name  The name of the directory.
	 * @param  string $file_path  The path of the file.
	 *
	 * @return string The URL of the trunk of the plugin.
	 */
	private function get_trunk_url( string $directory_name, string $file_path ): string {
		return sprintf(
			$this->url_plugin_vcs_trunk,
			$directory_name,
			$file_path
		);
	}

	/**
	 * Generate the URL for a file based on the directory name, version, and file path.
	 *
	 * @param  string $directory_name  The name of the directory.
	 * @param  string $version  The version of the file.
	 * @param  string $file_path  The path of the file.
	 *
	 * @return string The URL of the file.
	 */
	private function get_file_url( string $directory_name, string $version, string $file_path ): string {
		// First try to use the version number provided.
		$file_url = $this->get_version_url( $directory_name, $version, $file_path );
		if ( $this->is_origin_file_exists( $file_url ) ) {
			return $file_url;
		}
		// If that does not exist, try to use the version number with a `.0` suffix.
		$file_url = $this->get_version_url( $directory_name, $version . '.0', $file_path );
		if ( $this->is_origin_file_exists( $file_url ) ) {
			return $file_url;
		}
		// If all else fails, use the trunk of the plugin.
		return $this->get_trunk_url( $directory_name, $file_path );
	}

	/**
	 * Retrieves the content of a URL by downloading it and reading the contents of the downloaded file.
	 *
	 * @param string $url The URL to download the content from.
	 *
	 * @return string|WP_Error The content of the URL if successful, otherwise a WP_Error object.
	 */
	private function get_url_content( $url ) {
		global $wp_filesystem;
		// Initialize the WP filesystem, no more using 'file-put-contents' function.
		if ( ! $wp_filesystem instanceof WP_Filesystem_Base ) {
			require_once ABSPATH . '/wp-admin/includes/file.php';
			WP_Filesystem();
		}
		if ( ! function_exists( 'download_url' ) ) {
			$ds = DIRECTORY_SEPARATOR;
			require_once ABSPATH . 'wp-admin' . $ds . 'includes' . $ds . 'file.php';
		}

		$tmp = download_url( $url );
		if ( is_wp_error( $tmp ) ) {
			return $tmp;
		}

		$content = $wp_filesystem->get_contents( $tmp );
		wp_delete_file( $tmp );

		return $content;
	}

	/**
	 * Quarantine a plugin.
	 *
	 * @param string    $parent_action  Parent action.
	 * @param Scan_Item $scan_item      Scan item model object.
	 *
	 * @return array|WP_Error
	 */
	public function quarantine( string $parent_action, Scan_Item $scan_item ) {
		$quarantine_component = wd_di()->get( \WP_Defender\Component\Quarantine::class );

		return $quarantine_component->quarantine_file( $scan_item, $parent_action );
	}

	/**
	 * Detect if the plugin file is quarantinable.
	 *
	 * @param string $file_path File path.
	 *
	 * @return bool Return true if file is in wp.org plugin else false.
	 */
	private function is_quarantinable( $file_path ): bool {
		return $this->is_likely_wporg_slug(
			$this->get_plugin_directory_name(
				$file_path
			)
		);
	}

	/**
	 * Get a list of the active plugins.
	 *
	 * @return array
	 */
	private function get_active_and_valid_plugin_files(): array {
		$active_plugins = is_multisite() ? wp_get_active_network_plugins() : array();
		$active_plugins = array_merge( $active_plugins, wp_get_active_and_valid_plugins() );

		return array_unique( $active_plugins );
	}

	/**
	 * Get a plugin name.
	 *
	 * @param string $plugin_file Absolute path to the main plugin file.
	 *
	 * @return string
	 */
	private function get_plugin_name( $plugin_file ): string {
		$plugin_data = get_plugin_data( $plugin_file );

		return isset( $plugin_data['Name'] ) ? $plugin_data['Name'] : '';
	}

	/**
	 * Get active plugin names.
	 *
	 * @return array
	 */
	public function get_active_plugin_names(): array {
		$active_plugins = $this->get_active_and_valid_plugin_files();
		$plugin_names   = array();

		// Convert paths to names.
		foreach ( $active_plugins as $plugin_file ) {
			$plugin_name = $this->get_plugin_name( $plugin_file );
			if ( '' !== $plugin_name ) {
				$plugin_names[] = $plugin_name;
			}
		}

		return $plugin_names;
	}

	/**
	 * Data for the “update Defender” notification bubble (React).
	 *
	 * Uses the update_plugins site transient (WordPress.org for Free; WPMU DEV Dashboard merges
	 * Pro updates into the same transient when that plugin is active).
	 *
	 * @return array{
	 *     available:bool,
	 *     newVersion:string,
	 *     learnMoreUrl:string,
	 *     updateUrl:string,
	 *     canUpdate:bool,
	 *     currentVersion:string,
	 *     releaseDate:string
	 * }
	 */
	public function get_plugin_update_notice_data(): array {
		$basename = DEFENDER_PLUGIN_BASENAME;

		$default_learn_more = defender_is_wp_org_version()
			? 'https://wordpress.org/plugins/defender-security/'
			: 'https://wpmudev.com/project/wp-defender/#changelog_all';

		$learn_more_url = $default_learn_more;
		$new_version    = '';
		$has_update     = false;

		$updates = get_site_transient( 'update_plugins' );
		if ( is_object( $updates ) && isset( $updates->response[ $basename ] ) ) {
			$pkg = $updates->response[ $basename ];
			if ( is_object( $pkg ) ) {
				$has_update  = true;
				$new_version = isset( $pkg->new_version ) ? (string) $pkg->new_version : '';
				if ( isset( $pkg->url ) && is_string( $pkg->url ) && '' !== $pkg->url ) {
					$learn_more_url = $pkg->url;
				}
			} elseif ( is_array( $pkg ) ) {
				$has_update  = true;
				$new_version = isset( $pkg['new_version'] ) ? (string) $pkg['new_version'] : '';
				if ( isset( $pkg['url'] ) && is_string( $pkg['url'] ) && '' !== $pkg['url'] ) {
					$learn_more_url = $pkg['url'];
				}
			}
		}

		$can_update = $has_update && current_user_can( 'update_plugins' );
		$update_url = '';
		if ( $can_update ) {
			// Build a plain URL for wp_localize_script (wp_nonce_url() HTML-escapes and breaks JSON).
			$update_url = add_query_arg(
				array(
					'_wpnonce' => wp_create_nonce( 'upgrade-plugin_' . $basename ),
					'action'   => 'upgrade-plugin',
					'plugin'   => $basename,
				),
				self_admin_url( 'update.php' )
			);
		}

		$release_date_raw = defined( 'DEFENDER_RELEASE_DATE' ) ? DEFENDER_RELEASE_DATE : '';
		$timestamp        = strtotime( $release_date_raw );
		$release_date     = false !== $timestamp
			? date_i18n( get_option( 'date_format' ), $timestamp )
			: '';

		return array(
			'available'      => $has_update,
			'newVersion'     => $new_version,
			'learnMoreUrl'   => esc_url_raw( $learn_more_url ),
			'updateUrl'      => esc_url_raw( $update_url ),
			'canUpdate'      => $can_update && '' !== $update_url,
			'currentVersion' => DEFENDER_VERSION,
			'releaseDate'    => $release_date,
		);
	}
}