PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / 6.3.0
Defender Security – Malware Scanner, Login Security & Firewall v6.3.0
6.3.0 6.2.3 6.2.4 6.2.0 6.2.1 6.2.2 6.1.0 5.3.1 5.4.0 5.4.1 5.5.0 5.5.1 5.6.0 5.6.1 5.6.2 5.7.0 5.7.1 5.7.2 5.8.0 5.8.1 5.9.0 6.0.0 6.0.1 3.0.1 3.1.0 All 141 releases
← All changes | src/traits/plugin.php +172 -49 5.5.1 → 6.3.0 View file →
@@ -7,9 +7,9 @@
7 7
8 8 namespace WP_Defender\Traits;
9 9
10 10 use WP_Error;
11 -use WP_Defender\Component\Quarantine as Quarantine_Component;
11 +use WP_Defender\Model\Scan_Item;
12 12 use WP_Filesystem_Base;
13 13
14 14 trait Plugin {
15 15
@@ -57,17 +57,18 @@
57 57
58 58 /**
59 59 * Get plugin slug.
60 60 *
61 - * @param string $plugin_file The main plugin file name.
61 + * @param string $main_plugin_file The main plugin file name.
62 62 *
63 - * @return string
63 + * @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'.
64 64 */
65 - public function get_plugin_slug_by( string $plugin_file ): string {
66 - $dir = dirname( $plugin_file );
65 + public function get_plugin_slug_by( string $main_plugin_file ): string {
66 + $dir = dirname( $main_plugin_file );
67 67 if ( '.' === $dir ) {
68 - // If the file is in the root /plugins/, take the file name without the extension.
69 - $slug = basename( $plugin_file, '.php' );
68 + // If this is a single plugin file in the plugin root, take the file name.
69 + $slug = explode( '/', $main_plugin_file );
70 + $slug = array_shift( $slug );
70 71 } else {
71 72 $slug = $dir;
72 73 }
73 74
@@ -80,10 +81,10 @@
80 81 * @return array
81 82 */
82 83 public function get_plugin_slugs(): array {
83 84 $slugs = array();
84 - foreach ( $this->get_plugins() as $plugin_file => $plugin ) {
85 - $slugs[] = $this->get_plugin_slug_by( $plugin_file );
85 + foreach ( $this->get_plugins() as $main_plugin_file => $plugin ) {
86 + $slugs[] = $this->get_plugin_slug_by( $main_plugin_file );
86 87 }
87 88
88 89 return $slugs;
89 90 }
@@ -95,13 +96,13 @@
95 96 *
96 97 * @return array
97 98 */
98 99 public function get_plugin_details_by( string $plugin_slug ): array {
99 - foreach ( $this->get_plugins() as $plugin_file => $plugin ) {
100 - if ( $this->get_plugin_slug_by( $plugin_file ) === $plugin_slug ) {
101 - $plugin['slug'] = $plugin_slug;
100 + foreach ( $this->get_plugins() as $main_plugin_file => $plugin_data ) {
101 + if ( $this->get_plugin_slug_by( $main_plugin_file ) === $plugin_slug ) {
102 + $plugin_data['slug'] = $plugin_slug;
102 103
103 - return $plugin;
104 + return $plugin_data;
104 105 }
105 106 }
106 107
107 108 return array();
@@ -107,21 +108,8 @@
107 108 return array();
108 109 }
109 110
110 111 /**
111 - * Retrieve plugin base directory.
112 - *
113 - * @return string
114 - */
115 - public function get_plugin_base_dir(): string {
116 - if ( defined( 'WP_PLUGIN_DIR' ) ) {
117 - return wp_normalize_path( WP_PLUGIN_DIR . '/' );
118 - }
119 -
120 - return wp_normalize_path( WP_CONTENT_DIR . '/plugins/' );
121 - }
122 -
123 - /**
124 112 * Check if a plugin exists on WordPress.org repository by slug.
125 113 *
126 114 * @param string $slug The plugin slug.
127 115 *
@@ -130,9 +118,9 @@
130 118 public function ping_wp_org_by_plugin_slug( string $slug ) {
131 119 $url = 'https://api.wordpress.org/plugins/info/1.0/' . $slug . '.json';
132 120 $http_args = array(
133 121 'timeout' => 15,
134 - 'sslverify' => false, // Many hosts have no updated CA bundle.
122 + 'sslverify' => true,
135 123 'user-agent' => defender_get_own_user_agent(),
136 124 );
137 125
138 126 return wp_remote_get( $url, $http_args );
@@ -216,16 +204,16 @@
216 204 }
217 205
218 206 /**
219 207 * Check for readme.txt or readme.md files.
220 - * Sometimes plugins from wp.org don't have readme.txt file, e.g. 'wp-crontrol'.
208 + * Sometimes plugins from wp.org don't have readme.txt file.
221 209 *
222 - * @param string $readme_file Path to readme.* file.
210 + * @param string $readme_file_path Path to readme.* file.
223 211 *
224 212 * @return bool
225 213 */
226 - public function check_by_readme_file( $readme_file ): bool {
227 - if ( file_exists( $readme_file ) && is_readable( $readme_file ) ) {
214 + public function check_by_readme_file( string $readme_file_path ): bool {
215 + if ( file_exists( $readme_file_path ) && is_readable( $readme_file_path ) ) {
228 216 global $wp_filesystem;
229 217 // Initialize the WP filesystem, no more using 'file-put-contents' function.
230 218 if ( ! $wp_filesystem instanceof WP_Filesystem_Base ) {
231 219 require_once ABSPATH . '/wp-admin/includes/file.php';
@@ -230,9 +218,9 @@
230 218 if ( ! $wp_filesystem instanceof WP_Filesystem_Base ) {
231 219 require_once ABSPATH . '/wp-admin/includes/file.php';
232 220 WP_Filesystem();
233 221 }
234 - $contents = trim( (string) $wp_filesystem->get_contents( $readme_file ) );
222 + $contents = trim( (string) $wp_filesystem->get_contents( $readme_file_path ) );
235 223
236 224 if ( false !== strpos( $contents, '===' ) ) {
237 225 return true;
238 226 }
@@ -247,9 +235,9 @@
247 235
248 236 /**
249 237 * Is this plugin likely to be a WordPress.org plugin?
250 238 *
251 - * @param string|null $slug of the plugin.
239 + * @param string|null $slug Plugin directory name.
252 240 *
253 241 * @return bool Return true if likely, else false.
254 242 */
255 243 public function is_likely_wporg_slug( ?string $slug ): bool {
@@ -264,10 +252,17 @@
264 252 }
265 253 if ( isset( $transient[ $slug ] ) ) {
266 254 return $transient[ $slug ];
267 255 }
256 + // Criterion #1: Check if plugin name contains 'Pro' mention.
257 + $actioned_plugins = get_site_option( \WP_Defender\Component\Scan::PLUGINS_ACTIONED );
258 + if ( is_array( $actioned_plugins ) && isset( $actioned_plugins[ $slug ] )
259 + && preg_match( '/ Pro$/i', $actioned_plugins[ $slug ]['Name'] )
260 + ) {
261 + return false;
262 + }
268 263
269 - $plugin_path = $this->get_plugin_base_dir() . $slug . '/';
264 + $plugin_path = $this->get_abs_plugin_path_by_slug( $slug ) . DIRECTORY_SEPARATOR;
270 265 // Some plugins do not follow the readme file naming rule. Let's list the possible names.
271 266 $readme_files = array(
272 267 'readme.txt',
273 268 'README.txt',
@@ -273,12 +268,13 @@
273 268 'README.txt',
274 269 'readme.md',
275 270 'README.md',
276 271 );
277 - // Check each Readme-case.
272 + // Criterion #2: Check if there is Readme file.
278 273 foreach ( $readme_files as $readme_file ) {
279 274 if ( $this->check_by_readme_file( $plugin_path . $readme_file ) ) {
280 275 $transient[ $slug ] = true;
276 + // Collect plugin slugs that are on wp.org.
281 277 set_site_transient( self::$org_slugs, $transient, DAY_IN_SECONDS );
282 278
283 279 return true;
284 280 }
@@ -283,11 +279,8 @@
283 279 return true;
284 280 }
285 281 }
286 282
287 - $transient[ $slug ] = false;
288 - set_site_transient( self::$org_slugs, $transient, DAY_IN_SECONDS );
289 -
290 283 return false;
291 284 }
292 285
293 286 /**
@@ -356,8 +349,23 @@
356 349 return (string) strtok( '' );
357 350 }
358 351
359 352 /**
353 + * Get plugin absolute path.
354 + *
355 + * @param string $slug Plugin slug.
356 + *
357 + * @return string
358 + */
359 + public function get_abs_plugin_path_by_slug( string $slug = '' ): string {
360 + if ( defined( 'WP_PLUGIN_DIR' ) ) {
361 + return wp_normalize_path( WP_PLUGIN_DIR ) . DIRECTORY_SEPARATOR . $slug;
362 + }
363 +
364 + return wp_normalize_path( WP_CONTENT_DIR ) . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR . $slug;
365 + }
366 +
367 + /**
360 368 * Check file exists at wp.org svn.
361 369 *
362 370 * @param string $url URL of the file.
363 371 *
@@ -465,23 +473,17 @@
465 473
466 474 /**
467 475 * Quarantine a plugin.
468 476 *
469 - * @param string $parent_action Parent action.
477 + * @param string $parent_action Parent action.
478 + * @param Scan_Item $scan_item Scan item model object.
470 479 *
471 480 * @return array|WP_Error
472 481 */
473 - public function quarantine( string $parent_action ) {
474 - if ( ! class_exists( 'WP_Defender\Controller\Quarantine' ) ) {
475 - return new WP_Error(
476 - 'DEFENDER_PRO_ONLY_FEATURE',
477 - defender_quarantine_pro_only()
478 - );
479 - }
482 + public function quarantine( string $parent_action, Scan_Item $scan_item ) {
483 + $quarantine_component = wd_di()->get( \WP_Defender\Component\Quarantine::class );
480 484
481 - $quarantine_component = wd_di()->get( Quarantine_Component::class );
482 -
483 - return $quarantine_component->quarantine_file( $this->owner, $parent_action );
485 + return $quarantine_component->quarantine_file( $scan_item, $parent_action );
484 486 }
485 487
486 488 /**
487 489 * Detect if the plugin file is quarantinable.
@@ -494,7 +496,128 @@
494 496 return $this->is_likely_wporg_slug(
495 497 $this->get_plugin_directory_name(
496 498 $file_path
497 499 )
500 + );
501 + }
502 +
503 + /**
504 + * Get a list of the active plugins.
505 + *
506 + * @return array
507 + */
508 + private function get_active_and_valid_plugin_files(): array {
509 + $active_plugins = is_multisite() ? wp_get_active_network_plugins() : array();
510 + $active_plugins = array_merge( $active_plugins, wp_get_active_and_valid_plugins() );
511 +
512 + return array_unique( $active_plugins );
513 + }
514 +
515 + /**
516 + * Get a plugin name.
517 + *
518 + * @param string $plugin_file Absolute path to the main plugin file.
519 + *
520 + * @return string
521 + */
522 + private function get_plugin_name( $plugin_file ): string {
523 + $plugin_data = get_plugin_data( $plugin_file );
524 +
525 + return isset( $plugin_data['Name'] ) ? $plugin_data['Name'] : '';
526 + }
527 +
528 + /**
529 + * Get active plugin names.
530 + *
531 + * @return array
532 + */
533 + public function get_active_plugin_names(): array {
534 + $active_plugins = $this->get_active_and_valid_plugin_files();
535 + $plugin_names = array();
536 +
537 + // Convert paths to names.
538 + foreach ( $active_plugins as $plugin_file ) {
539 + $plugin_name = $this->get_plugin_name( $plugin_file );
540 + if ( '' !== $plugin_name ) {
541 + $plugin_names[] = $plugin_name;
542 + }
543 + }
544 +
545 + return $plugin_names;
546 + }
547 +
548 + /**
549 + * Data for the “update Defender” notification bubble (React).
550 + *
551 + * Uses the update_plugins site transient (WordPress.org for Free; WPMU DEV Dashboard merges
552 + * Pro updates into the same transient when that plugin is active).
553 + *
554 + * @return array{
555 + * available:bool,
556 + * newVersion:string,
557 + * learnMoreUrl:string,
558 + * updateUrl:string,
559 + * canUpdate:bool,
560 + * currentVersion:string,
561 + * releaseDate:string
562 + * }
563 + */
564 + public function get_plugin_update_notice_data(): array {
565 + $basename = DEFENDER_PLUGIN_BASENAME;
566 +
567 + $default_learn_more = defender_is_wp_org_version()
568 + ? 'https://wordpress.org/plugins/defender-security/'
569 + : 'https://wpmudev.com/project/wp-defender/#changelog_all';
570 +
571 + $learn_more_url = $default_learn_more;
572 + $new_version = '';
573 + $has_update = false;
574 +
575 + $updates = get_site_transient( 'update_plugins' );
576 + if ( is_object( $updates ) && isset( $updates->response[ $basename ] ) ) {
577 + $pkg = $updates->response[ $basename ];
578 + if ( is_object( $pkg ) ) {
579 + $has_update = true;
580 + $new_version = isset( $pkg->new_version ) ? (string) $pkg->new_version : '';
581 + if ( isset( $pkg->url ) && is_string( $pkg->url ) && '' !== $pkg->url ) {
582 + $learn_more_url = $pkg->url;
583 + }
584 + } elseif ( is_array( $pkg ) ) {
585 + $has_update = true;
586 + $new_version = isset( $pkg['new_version'] ) ? (string) $pkg['new_version'] : '';
587 + if ( isset( $pkg['url'] ) && is_string( $pkg['url'] ) && '' !== $pkg['url'] ) {
588 + $learn_more_url = $pkg['url'];
589 + }
590 + }
591 + }
592 +
593 + $can_update = $has_update && current_user_can( 'update_plugins' );
594 + $update_url = '';
595 + if ( $can_update ) {
596 + // Build a plain URL for wp_localize_script (wp_nonce_url() HTML-escapes and breaks JSON).
597 + $update_url = add_query_arg(
598 + array(
599 + '_wpnonce' => wp_create_nonce( 'upgrade-plugin_' . $basename ),
600 + 'action' => 'upgrade-plugin',
601 + 'plugin' => $basename,
602 + ),
603 + self_admin_url( 'update.php' )
604 + );
605 + }
606 +
607 + $release_date_raw = defined( 'DEFENDER_RELEASE_DATE' ) ? DEFENDER_RELEASE_DATE : '';
608 + $timestamp = strtotime( $release_date_raw );
609 + $release_date = false !== $timestamp
610 + ? date_i18n( get_option( 'date_format' ), $timestamp )
611 + : '';
612 +
613 + return array(
614 + 'available' => $has_update,
615 + 'newVersion' => $new_version,
616 + 'learnMoreUrl' => esc_url_raw( $learn_more_url ),
617 + 'updateUrl' => esc_url_raw( $update_url ),
618 + 'canUpdate' => $can_update && '' !== $update_url,
619 + 'currentVersion' => DEFENDER_VERSION,
620 + 'releaseDate' => $release_date,
498 621 );
499 622 }
500 623 }