PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / 5.5.0
Defender Security – Malware Scanner, Login Security & Firewall v5.5.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
defender-security / src / behavior / scan / class-abandoned-plugin.php

class-abandoned-plugin.php in Defender Security – Malware Scanner, Login Security & Firewall 5.5.0, at src/behavior/scan/class-abandoned-plugin.php

269 lines 7.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Handle abandoned plugin scan.
4 *
5 * @package WP_Defender\Behavior\Scan
6 */
7
8 namespace WP_Defender\Behavior\Scan;
9
10 use Calotes\Component\Behavior;
11 use WP_Defender\Controller\Scan as Scan_Controller;
12 use WP_Defender\Model\Scan;
13 use WP_Defender\Model\Scan_Item;
14 use WP_Defender\Traits\IO;
15 use WP_Defender\Traits\Plugin;
16
17 /**
18 * Handle abandoned plugin scan.
19 */
20 class Abandoned_Plugin extends Behavior {
21
22 use IO;
23 use Plugin;
24
25 /**
26 * Hold the Scan model or null if not set.
27 *
28 * @var Scan|null
29 */
30 private ?Scan $scan;
31
32 /**
33 * Outdated plugin period.
34 *
35 * @var string
36 */
37 private const OUTDATED_PERIOD = '6 months';
38
39 /**
40 * Initialize Scan instance.
41 *
42 * @param Scan $scan The Scan model.
43 */
44 public function __construct( Scan $scan ) {
45 $this->scan = $scan;
46 }
47
48 /**
49 * Get the outdated period of the plugin.
50 *
51 * @return string
52 */
53 public static function get_outdated_period() {
54 return self::OUTDATED_PERIOD;
55 }
56
57 /**
58 * Check if there are abandoned plugins.
59 *
60 * @return bool
61 */
62 public function abandoned_plugin_check(): bool {
63 $ignored_issues = $this->get_ignored_issues();
64 $model = $this->scan;
65
66 foreach ( $this->get_plugin_slugs() as $slug ) {
67 if ( is_object( $model ) && $model->is_issue_ignored( $slug ) ) {
68 continue;
69 }
70 $this->check_abandoned_plugin_by( $slug );
71 }
72
73 $this->scan->calculate_percent( 100, 4 );
74 $this->add_ignored_issues( $ignored_issues );
75
76 return true;
77 }
78
79 /**
80 * Retrieve the list of ignored issues.
81 *
82 * @return array The list of ignored issues.
83 */
84 private function get_ignored_issues(): array {
85 $last = Scan::get_last();
86
87 return is_object( $last )
88 ? $last->get_issues(
89 Scan::get_abandoned_types(),
90 Scan_Item::STATUS_IGNORE
91 )
92 : array();
93 }
94
95 /**
96 * Add ignored issues to the scan.
97 *
98 * @param array $ignored_issues The array of ignored issues to add.
99 */
100 private function add_ignored_issues( array $ignored_issues ) {
101 if ( ! empty( $ignored_issues ) ) {
102 foreach ( $ignored_issues as $issue ) {
103 $this->scan->add_item( $issue->type, $issue->raw_data, Scan_Item::STATUS_IGNORE );
104 }
105 }
106 }
107
108 /**
109 * Handle the error by different actions, e.g. logging.
110 *
111 * @param string $error_message The error message.
112 */
113 private function handle_error( string $error_message ) {
114 $this->log( $error_message, Scan_Controller::SCAN_LOG );
115 }
116
117 /**
118 * Prepare data before saving.
119 *
120 * @param array $body_json Data from WP API.
121 * @param array $plugin_details Data from local plugin headers.
122 *
123 * @return array {
124 * Plugin data. Values will be empty if not supplied by the plugin or API sides.
125 *
126 * @type string $name Plugin name.
127 * @type string $slug Plugin slug.
128 * @type string $url Plugin URL.
129 * @type string $version Plugin version.
130 * @type string $tested_wp Latest tested WordPress version.
131 * @type string $last_updated Last updated date.
132 * @type string $reason_text Reason for saving or update.
133 * }
134 */
135 private function prepare_item_data( array $body_json, array $plugin_details ): array {
136 $data = array();
137 // Check plugin name.
138 if ( isset( $body_json['name'] ) ) {
139 $data['name'] = $body_json['name'];
140 } elseif ( isset( $plugin_details['Name'] ) ) {
141 $data['name'] = $plugin_details['Name'];
142 } else {
143 $data['name'] = '';
144 }
145 // Check plugin slug.
146 if ( isset( $body_json['slug'] ) ) {
147 $data['slug'] = $body_json['slug'];
148 } elseif ( isset( $plugin_details['slug'] ) ) {
149 $data['slug'] = $plugin_details['slug'];
150 } else {
151 $data['slug'] = '';
152 }
153 // Check plugin URL. Sometimes URL values are empty.
154 if ( isset( $body_json['homepage'] ) && '' !== $body_json['homepage'] ) {
155 $data['url'] = $body_json['homepage'];
156 } elseif ( isset( $plugin_details['PluginURI'] ) && '' !== $plugin_details['PluginURI'] ) {
157 $data['url'] = $plugin_details['PluginURI'];
158 } elseif ( isset( $plugin_details['AuthorURI'] ) && '' !== $plugin_details['AuthorURI'] ) {
159 $data['url'] = $plugin_details['AuthorURI'];
160 } else {
161 $data['url'] = '';
162 }
163 // Check plugin version.
164 if ( isset( $body_json['version'] ) ) {
165 $data['version'] = $body_json['version'];
166 } elseif ( isset( $plugin_details['Version'] ) ) {
167 $data['version'] = $plugin_details['Version'];
168 } else {
169 $data['version'] = '';
170 }
171 // Check a tested WP version.
172 if ( isset( $body_json['tested'] ) ) {
173 $data['tested_wp'] = $body_json['tested'];
174 } else {
175 $data['tested_wp'] = '';
176 }
177 // Check plugin updated time.
178 if ( isset( $body_json['closed_date'] ) ) {
179 $data['last_updated'] = $body_json['closed_date'];
180 } elseif ( isset( $body_json['last_updated'] ) ) {
181 $data['last_updated'] = $body_json['last_updated'];
182 } else {
183 $data['last_updated'] = '';
184 }
185 // Specific key for closed plugin.
186 if ( isset( $body_json['reason_text'] ) ) {
187 $data['reason_text'] = $body_json['reason_text'];
188 } else {
189 $data['reason_text'] = '';
190 }
191
192 return $data;
193 }
194
195 /**
196 * Get list of plugin slugs whose names do not match the specified names.
197 */
198 private function get_plugin_slugs_with_mismatched_names(): array {
199 return array(
200 'miniorange-malware-protection',
201 );
202 }
203
204 /**
205 * Is abandoned plugin?
206 *
207 * @param string $slug The slug to check.
208 *
209 * @return bool
210 */
211 public function check_abandoned_plugin_by( string $slug ): bool {
212 $premium_plugin_slugs = get_site_option( Plugin_Integrity::PLUGIN_PREMIUM_SLUGS, false );
213 if ( is_array( $premium_plugin_slugs ) && in_array( $slug, $premium_plugin_slugs, true ) ) {
214 return true;
215 } elseif ( ! $this->is_likely_wporg_slug( $slug ) ) {
216 return true;
217 }
218
219 $results = $this->handle_wp_org_response_by( $slug );
220 if ( ! $results['success'] ) {
221 $this->handle_error( $results['message'] );
222
223 return false;
224 } else {
225 $body_json = $results['body'];
226 $plugin_details = $this->get_plugin_details_by( $slug );
227 if (
228 isset( $body_json['name'], $plugin_details['Name'] )
229 && $plugin_details['Name'] !== $body_json['name']
230 && ! in_array( $slug, $this->get_plugin_slugs_with_mismatched_names(), true )
231 ) {
232 // This is premium plugin with the same slug as on wp.org but with another name value.
233 return false;
234 }
235
236 if ( isset( $body_json['error'] ) && 'closed' === $body_json['error'] ) {
237 $this->log( sprintf( 'Plugin slug closed: %s', $slug ), Scan_Controller::SCAN_LOG );
238
239 $this->scan->add_item(
240 Scan_Item::TYPE_PLUGIN_CLOSED,
241 $this->prepare_item_data( $body_json, $plugin_details )
242 );
243
244 return true;
245 }
246
247 if ( '' !== $body_json['last_updated'] ) {
248 $last_updated_time = $body_json['last_updated'];
249 $ttl = strtotime( $last_updated_time );
250 if ( $ttl < strtotime( '- ' . self::get_outdated_period() ) ) {
251 $this->log( sprintf( 'Plugin slug outdated: %s', $slug ), Scan_Controller::SCAN_LOG );
252 $plugin_details = $this->prepare_item_data( $body_json, $plugin_details );
253 // Improve the date view.
254 $plugin_details['last_updated'] = gmdate( 'F j, Y', $ttl );
255
256 $this->scan->add_item(
257 Scan_Item::TYPE_PLUGIN_OUTDATED,
258 $plugin_details
259 );
260
261 return true;
262 }
263 }
264
265 return false;
266 }
267 }
268 }
269