scan = $scan; } /** * Get the outdated period of the plugin. * * @return string */ public static function get_outdated_period(): string { // Filter to override outdated threshold period. $period = (string) apply_filters( 'wpdef_scan_outdated_period', self::OUTDATED_PERIOD ); // If time period is invalid, use the default one. return strtotime( '- ' . $period ) ? $period : self::OUTDATED_PERIOD; } /** * Check if there are abandoned plugins. * * @return bool */ public function abandoned_plugin_check(): bool { $ignored_issues = $this->get_ignored_issues(); $actioned_plugins = get_site_option( Scan_Component::PLUGINS_ACTIONED ); $plugin_list = Scan_Component::are_actioned_plugins( $actioned_plugins ) ? $actioned_plugins : $this->get_plugin_slugs(); foreach ( $plugin_list as $slug => $plugin_data ) { $this->check_abandoned_plugin_by( $slug ); } $this->scan->calculate_percent( 100, 4 ); $this->add_ignored_issues( $ignored_issues ); return true; } /** * Retrieve the list of ignored issues. * * @return array The list of ignored issues. */ private function get_ignored_issues(): array { $last = Scan::get_last(); return is_object( $last ) ? $last->get_issues( Scan::get_abandoned_types(), Scan_Item::STATUS_IGNORE ) : array(); } /** * Add ignored issues to the scan. * * @param array $ignored_issues The array of ignored issues to add. */ private function add_ignored_issues( array $ignored_issues ) { if ( ! empty( $ignored_issues ) ) { foreach ( $ignored_issues as $issue ) { $this->scan->add_item( $issue->type, $issue->raw_data, Scan_Item::STATUS_IGNORE ); } } } /** * Handle the error by different actions, e.g. logging. * * @param string $error_message The error message. */ private function handle_error( string $error_message ) { $this->log( $error_message, Scan_Controller::SCAN_LOG ); } /** * Prepare data before saving. * * @param array $body_json Data from WP API. * @param array $plugin_details Data from local plugin headers. * * @return array { * Plugin data. Values will be empty if not supplied by the plugin or API sides. * * @type string $name Plugin name. * @type string $slug Plugin slug. * @type string $url Plugin URL. * @type string $version Plugin version. * @type string $tested_wp Latest tested WordPress version. * @type string $last_updated Last updated date. * @type string $reason_text Reason for saving or update. * } */ private function prepare_item_data( array $body_json, array $plugin_details ): array { $data = array(); // Check plugin name. if ( isset( $body_json['name'] ) ) { $data['name'] = $body_json['name']; } elseif ( isset( $plugin_details['Name'] ) ) { $data['name'] = $plugin_details['Name']; } else { $data['name'] = ''; } // Check plugin slug. if ( isset( $body_json['slug'] ) ) { $data['slug'] = $body_json['slug']; } elseif ( isset( $plugin_details['slug'] ) ) { $data['slug'] = $plugin_details['slug']; } else { $data['slug'] = ''; } // Check plugin URL. Sometimes URL values are empty. if ( isset( $body_json['homepage'] ) && '' !== $body_json['homepage'] ) { $data['url'] = $body_json['homepage']; } elseif ( isset( $plugin_details['PluginURI'] ) && '' !== $plugin_details['PluginURI'] ) { $data['url'] = $plugin_details['PluginURI']; } elseif ( isset( $plugin_details['AuthorURI'] ) && '' !== $plugin_details['AuthorURI'] ) { $data['url'] = $plugin_details['AuthorURI']; } else { $data['url'] = ''; } // Check plugin version. if ( isset( $body_json['version'] ) ) { $data['version'] = $body_json['version']; } elseif ( isset( $plugin_details['Version'] ) ) { $data['version'] = $plugin_details['Version']; } else { $data['version'] = ''; } // Check a tested WP version. if ( isset( $body_json['tested'] ) ) { $data['tested_wp'] = $body_json['tested']; } else { $data['tested_wp'] = ''; } // Check plugin updated time. if ( isset( $body_json['closed_date'] ) ) { $data['last_updated'] = $body_json['closed_date']; } elseif ( isset( $body_json['last_updated'] ) ) { $data['last_updated'] = $body_json['last_updated']; } else { $data['last_updated'] = ''; } // Specific key for closed plugin. if ( isset( $body_json['reason_text'] ) ) { $data['reason_text'] = $body_json['reason_text']; } else { $data['reason_text'] = ''; } return $data; } /** * Get list of plugin slugs whose names do not match the specified names on wp.org. */ private function get_plugin_slugs_with_mismatched_names(): array { return array( 'miniorange-malware-protection', 'admin-bar-user-switching', 'custom-post-template', ); } /** * Is abandoned plugin? * * @param string $slug The slug to check. * * @return bool */ public function check_abandoned_plugin_by( string $slug ): bool { // Check if the plugin is not from wp.org. $premium_plugin_slugs = get_site_option( Plugin_Integrity::PLUGIN_PREMIUM_SLUGS, false ); if ( ( is_array( $premium_plugin_slugs ) && in_array( $slug, $premium_plugin_slugs, true ) ) || ! $this->is_likely_wporg_slug( $slug ) ) { return true; } if ( $this->scan->is_issue_ignored( $slug ) ) { $this->log( sprintf( 'skip %s because it is already ignored.', $slug ), Scan_Controller::SCAN_LOG ); return true; } $results = $this->handle_wp_org_response_by( $slug ); if ( ! $results['success'] ) { $this->handle_error( $results['message'] ); return false; } else { $body_json = $results['body']; $plugin_details = $this->get_plugin_details_by( $slug ); if ( isset( $body_json['name'], $plugin_details['Name'] ) && $plugin_details['Name'] !== $body_json['name'] && ! in_array( $slug, $this->get_plugin_slugs_with_mismatched_names(), true ) ) { // This is a premium plugin with the same slug as on wp.org but with another name OR the plugin developer named the plugin differently on wp.org and inside the main file. return false; } if ( isset( $body_json['error'] ) && 'closed' === $body_json['error'] ) { $this->log( sprintf( 'Plugin slug closed: %s', $slug ), Scan_Controller::SCAN_LOG ); $this->scan->add_item( Scan_Item::TYPE_PLUGIN_CLOSED, $this->prepare_item_data( $body_json, $plugin_details ) ); return true; } if ( '' !== $body_json['last_updated'] ) { $last_updated_time = $body_json['last_updated']; $ttl = strtotime( $last_updated_time ); if ( $ttl < strtotime( '- ' . self::get_outdated_period() ) ) { $this->log( sprintf( 'Plugin slug outdated: %s', $slug ), Scan_Controller::SCAN_LOG ); $plugin_details = $this->prepare_item_data( $body_json, $plugin_details ); // Improve the date view. $plugin_details['last_updated'] = gmdate( 'F j, Y', $ttl ); $this->scan->add_item( Scan_Item::TYPE_PLUGIN_OUTDATED, $plugin_details ); return true; } } return false; } } }