PluginProbe ʕ •ᴥ•ʔ
Media Cleaner: Clean your WordPress! / 6.1.5
Media Cleaner: Clean your WordPress! v6.1.5
7.1.1 7.1.0 7.0.9 7.0.8 trunk 3.6.8 3.6.9 3.7.0 3.8.0 3.9.0 4.0.0 4.0.2 4.0.4 4.0.6 4.0.7 4.1.0 4.2.0 4.2.2 4.2.3 4.2.4 4.2.5 4.4.0 4.4.2 4.4.4 4.4.6 4.4.7 4.4.8 4.5.0 4.5.4 4.5.6 4.5.7 4.5.8 4.6.2 4.6.3 4.8.0 4.8.4 5.0.0 5.0.1 5.1.0 5.1.1 5.1.3 5.2.0 5.2.1 5.2.4 5.4.0 5.4.1 5.4.2 5.4.3 5.4.4 5.4.5 5.4.6 5.4.9 5.5.0 5.5.1 5.5.2 5.5.3 5.5.4 5.5.7 5.5.8 5.6.1 5.6.2 5.6.3 5.6.4 6.0.1 6.0.2 6.0.3 6.0.4 6.0.5 6.0.6 6.0.7 6.0.8 6.0.9 6.1.2 6.1.3 6.1.4 6.1.5 6.1.6 6.1.7 6.1.8 6.1.9 6.2.0 6.2.1 6.2.3 6.2.4 6.2.5 6.2.6 6.2.7 6.2.8 6.3.0 6.3.1 6.3.2 6.3.4 6.3.5 6.3.7 6.3.8 6.3.9 6.4.0 6.4.1 6.4.2 6.4.3 6.4.4 6.4.5 6.4.6 6.4.7 6.4.8 6.4.9 6.5.0 6.5.1 6.5.2 6.5.3 6.5.4 6.5.5 6.5.6 6.5.7 6.5.8 6.5.9 6.6.1 6.6.2 6.6.3 6.6.4 6.6.5 6.6.6 6.6.7 6.6.8 6.6.9 6.7.0 6.7.1 6.7.2 6.7.3 6.7.4 6.7.5 6.7.6 6.7.7 6.7.8 6.7.9 6.8.0 6.8.1 6.8.2 6.8.3 6.8.4 6.8.5 6.8.6 6.8.7 6.8.8 6.8.9 6.9.0 6.9.1 6.9.2 6.9.3 6.9.4 6.9.5 6.9.6 6.9.7 6.9.8 6.9.9 7.0.0 7.0.1 7.0.2 7.0.3 7.0.4 7.0.5 7.0.6 7.0.7
media-cleaner / classes / core.php
media-cleaner / classes Last commit date
parsers 5 years ago admin.php 5 years ago core.php 5 years ago engine.php 5 years ago init.php 5 years ago parsers.php 5 years ago rest.php 5 years ago support.php 5 years ago ui.php 5 years ago
core.php
1186 lines
1 <?php
2
3 class Meow_WPMC_Core {
4
5 public $admin = null;
6 public $is_rest = false;
7 public $is_cli = false;
8 public $is_pro = false;
9 public $engine = null;
10 public $catch_timeout = true; // This will halt the plugin before reaching the PHP timeout.
11 private $regex_file = '/[A-Za-z0-9-_,.\(\)\s]+[.]{1}(MIMETYPES)/';
12 public $types = "jpg|jpeg|jpe|gif|png|tiff|bmp|csv|pdf|xls|xlsx|doc|docx|odt|wpd|rtf|tiff|mp3|mp4|mov|wav|lua";
13 public $current_method = 'media';
14 private $refcache = array();
15 public $servername = null; // meowapps.com (site URL without http/https)
16 public $site_url = null; // https://meowapps.com
17 public $upload_path = null; // /www/wp-content/uploads (path to uploads)
18 public $upload_url = null; // wp-content/uploads (uploads without domain)
19 private $check_content = null;
20 private $debug_logs = null;
21 private $multilingual = false;
22 private $languages = array();
23 private $allow_usage = null;
24 private $allow_setup = null;
25
26 public function __construct() {
27 add_action( 'plugins_loaded', array( $this, 'init' ) );
28 }
29
30 function init() {
31
32 // Variables
33 $this->site_url = get_site_url();
34 $this->multilingual = $this->is_multilingual();
35 $this->languages = $this->get_languages();
36 $this->current_method = get_option( 'wpmc_method', 'media' );
37 $this->regex_file = str_replace( "MIMETYPES", $this->types, $this->regex_file );
38 $this->servername = str_replace( 'http://', '', str_replace( 'https://', '', $this->site_url ) );
39 $uploaddir = wp_upload_dir();
40 $this->upload_path = $uploaddir['basedir'];
41 $this->upload_url = substr( $uploaddir['baseurl'], 1 + strlen( $this->site_url ) );
42 $this->check_content = get_option( 'wpmc_content', true );
43 $this->debug_logs = get_option( 'wpmc_debuglogs', false );
44 $this->is_rest = MeowCommon_Helpers::is_rest();
45 $this->is_cli = defined( 'WP_CLI' ) && WP_CLI;
46 global $wpmc;
47 $wpmc = $this;
48
49 // Check the roles
50 $this->allow_usage = apply_filters( 'wpmc_allow_usage', current_user_can( 'administrator' ) );
51 $this->allow_setup = apply_filters( 'wpmc_allow_setup', current_user_can( 'manage_options' ) );
52 if ( !$this->is_cli && !$this->allow_usage ) {
53 return;
54 }
55
56 // Language
57 load_plugin_textdomain( WPMC_DOMAIN, false, basename( WPMC_PATH ) . '/languages' );
58
59 // Admin
60 $this->admin = new Meow_WPMC_Admin( $this->allow_setup );
61
62 // Advanced core
63 if ( class_exists( 'MeowPro_WPMC_Core' ) ) {
64 new MeowPro_WPMC_Core( $this );
65 }
66
67 // Install hooks and engine on;y if they might be used
68 if ( is_admin() || $this->is_rest || $this->is_cli ) {
69 add_action( 'wpmc_initialize_parsers', array( $this, 'initialize_parsers' ), 10, 0 );
70 add_filter( 'wp_unique_filename', array( $this, 'wp_unique_filename' ), 10, 3 );
71 $this->engine = new Meow_WPMC_Engine( $this, $this->admin );
72 }
73
74 // Only for REST
75 if ( $this->is_rest ) {
76 new Meow_WPMC_Rest( $this, $this->admin );
77 }
78
79 if ( is_admin() ) {
80 new Meow_WPMC_UI( $this, $this->admin );
81 }
82 }
83
84 function initialize_parsers() {
85 include_once( 'parsers.php' );
86 new Meow_WPMC_Parsers();
87 }
88
89 function deepsleep( $seconds ) {
90 $start_time = time();
91 while( true ) {
92 if ( ( time() - $start_time ) > $seconds ) {
93 return false;
94 }
95 get_post( array( 'posts_per_page' => 50 ) );
96 }
97 }
98
99 private $start_time;
100 private $time_elapsed = 0;
101 private $item_scan_avg_time = 0;
102 private $wordpress_init_time = 0.5;
103 private $max_execution_time;
104 private $items_checked = 0;
105 private $items_count = 0;
106
107 function get_max_execution_time() {
108 if ( isset( $this->max_execution_time ) )
109 return $this->max_execution_time;
110
111 $this->max_execution_time = ini_get( "max_execution_time" );
112 if ( empty( $this->max_execution_time ) || $this->max_execution_time < 5 )
113 $this->max_execution_time = 30;
114
115 return $this->max_execution_time;
116 }
117
118 function timeout_check_start( $count ) {
119 $this->start_time = time();
120 $this->items_count = $count;
121 $this->get_max_execution_time();
122 }
123
124 function timeout_get_elapsed() {
125 return $this->time_elapsed . 'ms';
126 }
127
128 function timeout_check() {
129 $this->time_elapsed = time() - $this->start_time;
130 $this->time_remaining = $this->max_execution_time - $this->wordpress_init_time - $this->time_elapsed;
131 if ( $this->catch_timeout ) {
132 if ( $this->time_remaining - $this->item_scan_avg_time < 0 ) {
133 error_log("Media Cleaner Timeout! Check the Media Cleaner logs for more info.");
134 $this->log( "😵 Timeout! Some info for debug:" );
135 $this->log( "🍀 Elapsed time: $this->time_elapsed" );
136 $this->log( "🍀 WP init time: $this->wordpress_init_time" );
137 $this->log( "🍀 Remaining time: $this->time_remaining" );
138 $this->log( "🍀 Scan time per item: $this->item_scan_avg_time" );
139 $this->log( "🍀 PHP max_execution_time: $this->max_execution_time" );
140 header("HTTP/1.0 408 Request Timeout");
141 exit;
142 }
143 }
144 }
145
146 function timeout_check_additem() {
147 $this->items_checked++;
148 $this->time_elapsed = time() - $this->start_time;
149 $this->item_scan_avg_time = ceil( ( $this->time_elapsed / $this->items_checked ) * 10 ) / 10;
150 }
151
152 // This checks if a new uploaded filename isn't the same one as a currently
153 // filename in the trash (that would cause issues)
154 function wp_unique_filename( $filename, $ext, $dir ) {
155 $fullpath = trailingslashit( $dir ) . $filename;
156 $relativepath = $this->clean_uploaded_filename( $fullpath );
157 $trashfilepath = trailingslashit( $this->get_trashdir() ) . $relativepath;
158 if ( file_exists( $trashfilepath ) ) {
159 $path_parts = pathinfo( $fullpath );
160 $filename_noext = $path_parts['filename'];
161 $new_filename = $filename_noext . '-' . date('Ymd-His', time()) . '.' . $path_parts['extension'];
162 //error_log( 'POTENTIALLY TRASH PATH: ' . $trashfilepath );
163 //error_log( 'POTENTIALLY NEW FILE: ' . $new_filename );
164 return $new_filename;
165 }
166 return $filename;
167 }
168
169 function array_to_ids_or_urls( &$meta, &$ids, &$urls ) {
170 foreach ( $meta as $k => $m ) {
171 if ( is_numeric( $m ) ) {
172 // Probably a Media ID
173 if ( $m > 0 )
174 array_push( $ids, $m );
175 }
176 else if ( is_array( $m ) ) {
177 // If it's an array with a width, probably that the index is the Media ID
178 if ( isset( $m['width'] ) && is_numeric( $k ) ) {
179 if ( $k > 0 )
180 array_push( $ids, $k );
181 }
182 }
183 else if ( !empty( $m ) ) {
184 // If it's a string, maybe it's a file (with an extension)
185 if ( preg_match( $this->regex_file, $m ) )
186 array_push( $urls, $m );
187 }
188 }
189 }
190
191 function get_favicon() {
192 // Yoast SEO plugin
193 $vals = get_option( 'wpseo_titles' );
194 if ( !empty( $vals ) ) {
195 $url = $vals['company_logo'];
196 if ( $this->is_url( $url ) )
197 return $this->clean_url( $url );
198 }
199 }
200
201 function get_urls_from_html( $html ) {
202 if ( empty( $html ) )
203 return array();
204
205 // Proposal/fix by @copytrans
206 // Discussion: https://wordpress.org/support/topic/bug-in-core-php/#post-11647775
207 $html = mb_convert_encoding( $html, 'HTML-ENTITIES', 'UTF-8' );
208
209 // Resolve src-set and shortcodes
210 if ( !get_option( 'wpmc_shortcodes_disabled', false ) )
211 $html = do_shortcode( $html );
212
213 // TODO: Since WP 5.5, wp_filter_content_tags should be used instead of wp_make_content_images_responsive.
214 $html = function_exists( 'wp_filter_content_tags' ) ? wp_filter_content_tags( $html ) : wp_make_content_images_responsive( $html );
215
216 // Create the DOM Document
217 if ( !class_exists("DOMDocument") ) {
218 error_log( 'Media Cleaner: The DOM extension for PHP is not installed.' );
219 throw new Error( 'The DOM extension for PHP is not installed.' );
220 }
221
222 libxml_use_internal_errors(true);
223 $dom = new DOMDocument();
224 @$dom->loadHTML( $html );
225 libxml_clear_errors();
226 $results = array();
227
228 // <meta> tags in <head> area
229 $metas = $dom->getElementsByTagName( 'meta' );
230 foreach ( $metas as $meta ) {
231 $property = $meta->getAttribute( 'property' );
232 if ( $property == 'og:image' || $property == 'og:image:secure_url' || $property == 'twitter:image' ) {
233 $url = $meta->getAttribute( 'content' );
234 if ( $this->is_url( $url ) ) {
235 $src = $this->clean_url( $url );
236 if ( !empty( $src ) ) {
237 array_push( $results, $src );
238 }
239 }
240 }
241 }
242
243 // IFrames (by Mike Meinz)
244 $iframes = $dom->getElementsByTagName( 'iframe' );
245 foreach( $iframes as $iframe ) {
246 $iframe_src = $iframe->getAttribute( 'src' );
247 // Ignore if the iframe src is not on this server
248 if ( ( strpos( $iframe_src, $this->servername ) !== false) || ( substr( $iframe_src, 0, 1 ) == "/" ) ) {
249 // Create a new DOM Document to hold iframe
250 $iframe_doc = new DOMDocument();
251 // Load the url's contents into the DOM
252 libxml_use_internal_errors( true ); // ignore html formatting problems
253 $rslt = $iframe_doc->loadHTMLFile( $iframe_src );
254 libxml_clear_errors();
255 libxml_use_internal_errors( false );
256 if ( $rslt ) {
257 // Get the resulting html
258 $iframe_html = $iframe_doc->saveHTML();
259 if ( $iframe_html !== false ) {
260 // Scan for links in the iframe
261 $iframe_urls = $this->get_urls_from_html( $iframe_html ); // Recursion
262 if ( !empty( $iframe_urls ) ) {
263 $results = array_merge( $results, $iframe_urls );
264 }
265 }
266 }
267 else {
268 $this->log( '🚫 Failed to load iframe: ' . $iframe_src );
269 }
270 }
271 }
272
273
274 // Images: src, srcset
275 $imgs = $dom->getElementsByTagName( 'img' );
276 foreach ( $imgs as $img ) {
277 //error_log($img->getAttribute('src'));
278 $src = $this->clean_url( $img->getAttribute('src') );
279 array_push( $results, $src );
280 $srcset = $img->getAttribute('srcset');
281 if ( !empty( $srcset ) ) {
282 $setImgs = explode( ',', trim( $srcset ) );
283 foreach ( $setImgs as $setImg ) {
284 $finalSetImg = explode( ' ', trim( $setImg ) );
285 if ( is_array( $finalSetImg ) ) {
286 array_push( $results, $this->clean_url( $finalSetImg[0] ) );
287 }
288 }
289 }
290 }
291
292 // Videos: src
293 $videos = $dom->getElementsByTagName( 'video' );
294 foreach ( $videos as $video ) {
295 //error_log($video->getAttribute('src'));
296 $src = $this->clean_url( $video->getAttribute('src') );
297 array_push( $results, $src );
298 }
299
300 // Links, href
301 $urls = $dom->getElementsByTagName( 'a' );
302 foreach ( $urls as $url ) {
303 $url_href = $url->getAttribute('href'); // mm change
304 if ( $this->is_url( $url_href ) ) { // mm change
305 $src = $this->clean_url( $url_href ); // mm change
306 if ( !empty( $src ) )
307 array_push( $results, $src );
308 }
309 }
310
311 // <link> tags in <head> area
312 $urls = $dom->getElementsByTagName( 'link' );
313 foreach ( $urls as $url ) {
314 $url_href = $url->getAttribute( 'href' );
315 if ( $this->is_url( $url_href ) ) {
316 $src = $this->clean_url( $url_href );
317 if ( !empty( $src ) ) {
318 array_push( $results, $src );
319 }
320 }
321 }
322
323 // PDF
324 preg_match_all( "/((https?:\/\/)?[^\\&\#\[\] \"\?]+\.pdf)/", $html, $res );
325 if ( !empty( $res ) && isset( $res[1] ) && count( $res[1] ) > 0 ) {
326 foreach ( $res[1] as $url ) {
327 if ( $this->is_url($url) )
328 array_push( $results, $this->clean_url( $url ) );
329 }
330 }
331
332 // Background images
333 preg_match_all( "/url\(\'?\"?((https?:\/\/)?[^\\&\#\[\] \"\?]+\.(jpe?g|gif|png))\'?\"?/", $html, $res );
334 if ( !empty( $res ) && isset( $res[1] ) && count( $res[1] ) > 0 ) {
335 foreach ( $res[1] as $url ) {
336 if ( $this->is_url($url) )
337 array_push( $results, $this->clean_url( $url ) );
338 }
339 }
340
341 return $results;
342 }
343
344 // Parse a meta, visit all the arrays, look for the attributes, fill $ids and $urls arrays
345 function get_from_meta( $meta, $lookFor, &$ids, &$urls ) {
346 foreach ( $meta as $key => $value ) {
347 if ( is_object( $value ) || is_array( $value ) )
348 $this->get_from_meta( $value, $lookFor, $ids, $urls );
349 else if ( in_array( $key, $lookFor ) ) {
350 if ( empty( $value ) )
351 continue;
352 else if ( is_numeric( $value ) ) {
353 // It this an ID?
354 array_push( $ids, $value );
355 }
356 else {
357 if ( $this->is_url( $value ) ) {
358 // Is this an URL?
359 array_push( $urls, $this->clean_url( $value ) );
360 }
361 else {
362 // Is this an array of IDs, encoded as a string? (like "20,13")
363 $pieces = explode( ',', $value );
364 foreach ( $pieces as $pval ) {
365 if ( is_numeric( $pval ) ) {
366 array_push( $ids, $pval );
367 }
368 }
369 }
370 }
371 }
372 }
373 }
374
375 function get_images_from_themes( &$ids, &$urls ) {
376 // USE CURRENT THEME AND WP API
377 $ch = get_custom_header();
378 if ( !empty( $ch ) && !empty( $ch->url ) ) {
379 array_push( $urls, $this->clean_url( $ch->url ) );
380 }
381 if ( $this->is_url( $ch->thumbnail_url ) ) {
382 array_push( $urls, $this->clean_url( $ch->thumbnail_url ) );
383 }
384 if ( !empty( $ch ) && !empty( $ch->attachment_id ) ) {
385 array_push( $ids, $ch->attachment_id );
386 }
387 $cl = get_custom_logo();
388 if ( $this->is_url( $cl ) ) {
389 $urls = array_merge( $this->get_urls_from_html( $cl ), $urls );
390 }
391 $custom_logo = get_theme_mod( 'custom_logo' );
392 if ( !empty( $custom_logo ) && is_numeric( $custom_logo ) ) {
393 array_push( $ids, (int)$custom_logo );
394 }
395 $si = get_site_icon_url();
396 if ( $this->is_url( $si ) ) {
397 array_push( $urls, $this->clean_url( $si ) );
398 }
399 $si_id = get_option( 'site_icon' );
400 if ( !empty( $si_id ) && is_numeric( $si_id ) ) {
401 array_push( $ids, (int)$si_id );
402 }
403 $cd = get_background_image();
404 if ( $this->is_url( $cd ) ) {
405 array_push( $urls, $this->clean_url( $cd ) );
406 }
407 $photography_hero_image = get_theme_mod( 'photography_hero_image' );
408 if ( !empty( $photography_hero_image ) ) {
409 array_push( $ids, $photography_hero_image );
410 }
411 $author_profile_picture = get_theme_mod( 'author_profile_picture' );
412 if ( !empty( $author_profile_picture ) ) {
413 array_push( $ids, $author_profile_picture );
414 }
415 if ( function_exists ( 'get_uploaded_header_images' ) ) {
416 $header_images = get_uploaded_header_images();
417 if ( !empty( $header_images ) ) {
418 foreach ( $header_images as $hi ) {
419 if ( !empty ( $hi['attachment_id'] ) ) {
420 array_push( $ids, $hi['attachment_id'] );
421 }
422 }
423 }
424 }
425 }
426
427 function logs_directory_check() {
428 if ( !file_exists( WPMC_PATH . '/logs/' ) ) {
429 mkdir( WPMC_PATH . '/logs/', 0777 );
430 }
431 }
432
433 function log( $data = null, $force = false ) {
434 if ( !$this->debug_logs && !$force )
435 return;
436 $this->logs_directory_check();
437 $fh = @fopen( WPMC_PATH . '/logs/media-cleaner.log', 'a' );
438 if ( !$fh )
439 return false;
440 $date = date( "Y-m-d H:i:s" );
441 if ( is_null( $data ) )
442 fwrite( $fh, "\n" );
443 else
444 fwrite( $fh, "$date: {$data}\n" );
445 fclose( $fh );
446 return true;
447 }
448
449 /**
450 *
451 * HELPERS
452 *
453 */
454
455 function get_trashdir() {
456 return trailingslashit( $this->upload_path ) . 'wpmc-trash';
457 }
458
459 function get_trashurl() {
460 return trailingslashit( $this->upload_url ) . 'wpmc-trash';
461 }
462
463 /**
464 *
465 * I18N RELATED HELPERS
466 *
467 */
468
469 function is_multilingual() {
470 return function_exists( 'icl_object_id' );
471 }
472
473 function get_languages() {
474 $results = array();
475 if ( $this->is_multilingual() ) {
476 $languages = icl_get_languages();
477 foreach ( $languages as $language ) {
478 if ( isset( $language['code'] ) ) {
479 array_push( $results, $language['code'] );
480 }
481 else if ( isset( $language['language_code'] ) ) {
482 array_push( $results, $language['language_code'] );
483 }
484 }
485 }
486 return $results;
487 }
488
489 function get_translated_media_ids( $mediaId ) {
490 $translated_ids = array();
491 foreach ( $this->languages as $language ) {
492 $id = apply_filters( 'wpml_object_id', $mediaId, 'attachment', false, $language );
493 if ( !empty( $id ) ) {
494 array_push( $translated_ids, $id );
495 }
496 }
497 return $translated_ids;
498 }
499
500 /**
501 *
502 * DELETE / SCANNING / RESET
503 *
504 */
505
506 function recover_file( $path ) {
507 $originalPath = trailingslashit( $this->upload_path ) . $path;
508 $trashPath = trailingslashit( $this->get_trashdir() ) . $path;
509 if ( !file_exists( $trashPath ) ) {
510 $this->log( "🚫 The file $originalPath actually does not exist in the trash." );
511 return true;
512 }
513 $path_parts = pathinfo( $originalPath );
514 if ( !file_exists( $path_parts['dirname'] ) && !wp_mkdir_p( $path_parts['dirname'] ) ) {
515 die( 'Failed to create folder.' );
516 }
517 if ( !rename( $trashPath, $originalPath ) ) {
518 die( 'Failed to move the file.' );
519 }
520 return true;
521 }
522
523 function recover( $id ) {
524 global $wpdb;
525 $table_name = $wpdb->prefix . "mclean_scan";
526 $issue = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $table_name WHERE id = %d", $id ), OBJECT );
527 $issue->path = stripslashes( $issue->path );
528
529 // Files
530 if ( $issue->type == 0 ) {
531 $this->recover_file( $issue->path );
532 $wpdb->query( $wpdb->prepare( "UPDATE $table_name SET deleted = 0 WHERE id = %d", $id ) );
533 $this->log( "�
534 Recovered {$issue->path}." );
535 return true;
536 }
537 // Media
538 else if ( $issue->type == 1 ) {
539
540 // If there is no file attached, doesn't handle the files
541 $fullpath = get_attached_file( $issue->postId );
542 if ( empty( $fullpath ) ) {
543 $this->log( "🚫 Media #{$issue->postId} does not have attached file anymore." );
544 error_log( "Media #{$issue->postId} does not have attached file anymore." );
545 return false;
546 }
547
548 $paths = $this->get_paths_from_attachment( $issue->postId );
549 foreach ( $paths as $path ) {
550 if ( !$this->recover_file( $path ) ) {
551 $this->log( "🚫 Could not recover $path." );
552 error_log( "Media Cleaner: Could not recover $path." );
553 }
554 }
555 if ( !wp_untrash_post( $issue->postId ) ) {
556 $this->log( "🚫 Failed to Untrash Post {$issue->postId} (but deleted it from Cleaner DB)." );
557 error_log( "Media Cleaner: Failed to Untrash Post {$issue->postId} (but deleted it from Cleaner DB)." );
558 return false;
559 }
560 $wpdb->query( $wpdb->prepare( "UPDATE $table_name SET deleted = 0 WHERE id = %d", $id ) );
561 $this->log( "�
562 Recovered Media #{$issue->postId}." );
563 return true;
564 }
565 }
566
567 function trash_file( $fileIssuePath ) {
568 $originalPath = trailingslashit( $this->upload_path ) . $fileIssuePath;
569 $trashPath = trailingslashit( $this->get_trashdir() ) . $fileIssuePath;
570 $path_parts = pathinfo( $trashPath );
571
572 try {
573 if ( !file_exists( $path_parts['dirname'] ) && !wp_mkdir_p( $path_parts['dirname'] ) ) {
574 $this->log( "🚫 Could not create the trash directory for Media Cleaner." );
575 error_log( "Media Cleaner: Could not create the trash directory." );
576 return false;
577 }
578 // Rename the file (move). 'is_dir' is just there for security (no way we should move a whole directory)
579 if ( is_dir( $originalPath ) ) {
580 $this->log( "🚫 Attempted to delete a directory instead of a file ($originalPath). Can't do that." );
581 error_log( "Media Cleaner: Attempted to delete a directory instead of a file ($originalPath). Can't do that." );
582 return false;
583 }
584 if ( !file_exists( $originalPath ) ) {
585 $this->log( "🚫 The file $originalPath actually does not exist." );
586 error_log( "Media Cleaner: The file $originalPath actually does not exist." );
587 return true;
588 }
589 if ( !@rename( $originalPath, $trashPath ) ) {
590 error_log( "Media Cleaner: Unknown error occured while trying to delete a file ($originalPath)." );
591 return false;
592 }
593 }
594 catch ( Exception $e ) {
595 return false;
596 }
597 $this->clean_dir( dirname( $originalPath ) );
598 return true;
599 }
600
601 function ignore( $id, $ignore ) {
602 global $wpdb;
603 $table_name = $wpdb->prefix . "mclean_scan";
604 $issue = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $table_name WHERE id = %d", $id ), OBJECT );
605 if ( !$ignore )
606 $wpdb->query( $wpdb->prepare( "UPDATE $table_name SET ignored = 0 WHERE id = %d", $id ) );
607 else {
608 if ( (int) $issue->deleted ) // If it is in trash, recover it
609 $this->recover( $id );
610 $wpdb->query( $wpdb->prepare( "UPDATE $table_name SET ignored = 1 WHERE id = %d", $id ) );
611 }
612 return true;
613 }
614
615 function endsWith( $haystack, $needle )
616 {
617 $length = strlen( $needle );
618 if ( $length == 0 )
619 return true;
620 return ( substr( $haystack, -$length ) === $needle );
621 }
622
623 function clean_dir( $dir ) {
624 if ( !file_exists( $dir ) )
625 return;
626 else if ( $this->endsWith( $dir, 'uploads' ) )
627 return;
628 $found = array_diff( scandir( $dir ), array( '.', '..' ) );
629 if ( count( $found ) < 1 ) {
630 if ( rmdir( $dir ) ) {
631 $this->clean_dir( dirname( $dir ) );
632 }
633 }
634 }
635
636 function delete( $id ) {
637 global $wpdb;
638 $table_name = $wpdb->prefix . "mclean_scan";
639 $issue = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $table_name WHERE id = %d", $id ), OBJECT );
640 $regex = "^(.*)(\\s\\(\\+.*)$";
641 $issue->path = preg_replace( '/' . $regex . '/i', '$1', $issue->path ); // remove " (+ 6 files)" from path
642
643 // Make sure there isn't a media DB entry
644 if ( $issue->type == 0 ) {
645 $attachmentid = $this->find_media_id_from_file( $issue->path, true );
646 if ( $attachmentid ) {
647 $this->log( "🚫 Issue listed as filesystem but Media {$attachmentid} exists." );
648 }
649 }
650
651 if ( $issue->type == 0 ) {
652
653 if ( $issue->deleted == 0 ) {
654 // Move file to the trash
655 if ( $this->trash_file( $issue->path ) )
656 $wpdb->query( $wpdb->prepare( "UPDATE $table_name SET deleted = 1, ignored = 0 WHERE id = %d", $id ) );
657 return true;
658 }
659 else {
660 // Delete file from the trash
661 $trashPath = trailingslashit( $this->get_trashdir() ) . $issue->path;
662 if ( !unlink( $trashPath ) ) {
663 $this->log( "🚫 Failed to delete the file." );
664 error_log( "Media Cleaner: Failed to delete the file." );
665 }
666 $wpdb->query( $wpdb->prepare( "DELETE FROM $table_name WHERE id = %d", $id ) );
667 $this->clean_dir( dirname( $trashPath ) );
668 return true;
669 }
670 }
671
672 if ( $issue->type == 1 ) {
673 if ( $issue->deleted == 0 && MEDIA_TRASH ) {
674 // Move Media to trash
675 // Let's copy the images to the trash so that it can be recovered.
676 $paths = $this->get_paths_from_attachment( $issue->postId );
677 foreach ( $paths as $path ) {
678 if ( !$this->trash_file( $path ) ) {
679 $this->log( "🚫 Could not trash $path." );
680 error_log( "Media Cleaner: Could not trash $path." );
681 }
682 }
683 wp_delete_attachment( $issue->postId, false );
684 $wpdb->query( $wpdb->prepare( "UPDATE $table_name SET deleted = 1, ignored = 0 WHERE id = %d", $id ) );
685 return true;
686 }
687 else {
688 // Trash Media definitely by recovering it (to be like a normal Media) and remove it through the
689 // standard WordPress workflow
690 if ( MEDIA_TRASH )
691 $this->recover( $id );
692 wp_delete_attachment( $issue->postId, true );
693 $wpdb->query( $wpdb->prepare( "DELETE FROM $table_name WHERE id = %d", $id ) );
694 return true;
695 }
696 }
697 return false;
698 }
699
700 /**
701 *
702 * SCANNING / RESET
703 *
704 */
705
706 function add_reference_url( $urlOrUrls, $type, $origin = null, $extra = null ) {
707 $urlOrUrls = !is_array( $urlOrUrls ) ? array( $urlOrUrls ) : $urlOrUrls;
708 foreach ( $urlOrUrls as $url ) {
709 // With files, we need both filename without resolution and filename with resolution, it's important
710 // to make sure the original file is not deleted if a size exists for it.
711 // With media, all URLs should be without resolution to make sure it matches Media.
712 if ( $this->current_method == 'files' )
713 $this->add_reference( null, $url, $type, $origin );
714 $this->add_reference( 0, $this->clean_url_from_resolution( $url ), $type, $origin );
715 }
716 }
717
718 function add_reference_id( $idOrIds, $type, $origin = null, $extra = null ) {
719 $idOrIds = !is_array( $idOrIds ) ? array( $idOrIds ) : $idOrIds;
720 foreach ( $idOrIds as $id ) {
721 $this->add_reference( $id, "", $type, $origin );
722 if ( $this->multilingual ) {
723 $translatedIds = $this->get_translated_media_ids( (int)$id );
724
725 // Test for WPML
726 // if ( $id === '350') {
727 // $translatedIds = $this->get_translated_media_ids( (int)$id );
728 // $count = count($translatedIds);
729 // error_log( "${id} => ${count}" );
730 // }
731
732 if ( !empty( $translatedIds ) ) {
733 foreach ( $translatedIds as $translatedId ) {
734 $this->add_reference( $translatedId, "", $type, $origin );
735 }
736 }
737 }
738 }
739 }
740
741 private $cached_ids = array();
742 private $cached_urls = array();
743
744 // The references are actually not being added directly in the DB, they are being pushed
745 // into a cache ($this->refcache).
746 private function add_reference( $id, $url, $type, $origin = null, $extra = null ) {
747 if ( !empty( $id ) ) {
748 if ( !in_array( $id, $this->cached_ids ) ) {
749 array_push( $this->cached_ids, $id );
750 array_push( $this->refcache, array( 'id' => $id, 'url' => null, 'type' => $type, 'origin' => $origin ) );
751 }
752 }
753 if ( !empty( $url ) ) {
754 // The URL shouldn't contain http, https, javascript at the beginning (and there are probably many more cases)
755 // The URL must be cleaned before being passed as a reference.
756 if ( substr( $url, 0, 5 ) === "http:" || substr( $url, 0, 6 ) === "https:" || substr( $url, 0, 11 ) === "javascript:" ) {
757 return;
758 }
759 if ( !in_array( $url, $this->cached_urls ) ) {
760 array_push( $this->cached_urls, $url );
761 array_push( $this->refcache, array( 'id' => null, 'url' => $url, 'type' => $type, 'origin' => $origin ) );
762 }
763 }
764 }
765
766 // The cache containing the references is wrote to the DB.
767 function write_references() {
768 global $wpdb;
769 $table = $wpdb->prefix . "mclean_refs";
770 $values = array();
771 $place_holders = array();
772 $query = "INSERT INTO $table (mediaId, mediaUrl, originType) VALUES ";
773 foreach ( $this->refcache as $value ) {
774 if ( !is_null( $value['id'] ) ) {
775 array_push( $values, $value['id'], $value['type'] );
776 $place_holders[] = "('%d',NULL,'%s')";
777 if ( $this->debug_logs ) {
778 $this->log( "+ Media #{$value['id']} (as ID)" );
779 }
780 }
781 else if ( !is_null( $value['url'] ) ) {
782 array_push( $values, $value['url'], $value['type'] );
783 $place_holders[] = "(NULL,'%s','%s')";
784 if ( $this->debug_logs ) {
785 $this->log( "{$value['url']}" );
786 }
787 }
788 }
789 if ( !empty( $values ) ) {
790 $query .= implode( ', ', $place_holders );
791 $prepared = $wpdb->prepare( "$query ", $values );
792 $wpdb->query( $prepared );
793 }
794 $this->refcache = array();
795 }
796
797 function check_is_ignore( $file ) {
798 global $wpdb;
799 $table_name = $wpdb->prefix . "mclean_scan";
800 $count = $wpdb->get_var( "SELECT COUNT(*)
801 FROM $table_name
802 WHERE ignored = 1
803 AND path LIKE '%". esc_sql( $wpdb->esc_like( $file ) ) . "%'" );
804 if ( $count > 0 ) {
805 $this->log( "🚫 Could not trash $file." );
806 }
807 return ($count > 0);
808 }
809
810 function find_media_id_from_file( $file, $doLog ) {
811 global $wpdb;
812 $postmeta_table_name = $wpdb->prefix . 'postmeta';
813 $file = $this->clean_uploaded_filename( $file );
814 $sql = $wpdb->prepare( "SELECT post_id
815 FROM {$postmeta_table_name}
816 WHERE meta_key = '_wp_attached_file'
817 AND meta_value = %s", $file
818 );
819 $ret = $wpdb->get_var( $sql );
820 if ( $doLog ) {
821 if ( empty( $ret ) )
822 $this->log( "🚫 File $file not found as _wp_attached_file (Library)." );
823 else {
824 $this->log( "�
825 File $file found as Media $ret." );
826 }
827 }
828
829 return $ret;
830 }
831
832 function get_image_sizes() {
833 $sizes = array();
834 global $_wp_additional_image_sizes;
835 foreach ( get_intermediate_image_sizes() as $s ) {
836 $crop = false;
837 if ( isset( $_wp_additional_image_sizes[$s] ) ) {
838 $width = intval( $_wp_additional_image_sizes[$s]['width'] );
839 $height = intval( $_wp_additional_image_sizes[$s]['height'] );
840 $crop = $_wp_additional_image_sizes[$s]['crop'];
841 } else {
842 $width = get_option( $s.'_size_w' );
843 $height = get_option( $s.'_size_h' );
844 $crop = get_option( $s.'_crop' );
845 }
846 $sizes[$s] = array( 'width' => $width, 'height' => $height, 'crop' => $crop );
847 }
848 return $sizes;
849 }
850
851 function clean_url_from_resolution( $url ) {
852 $pattern = '/[_-]\d+x\d+(?=\.[a-z]{3,4}$)/';
853 $url = preg_replace( $pattern, '', $url );
854 return $url;
855 }
856
857 function is_url( $url ) {
858 return ( (
859 !empty( $url ) ) &&
860 is_string( $url ) &&
861 strlen( $url ) > 4 && (
862 strtolower( substr( $url, 0, 4) ) == 'http' || $url[0] == '/'
863 )
864 );
865 }
866
867 function clean_url_from_resolution_ref( &$url ) {
868 $url = $this->clean_url_from_resolution( $url );
869 }
870
871 // From a url to the shortened and cleaned url (for example '2013/02/file.png')
872 function clean_url( $url ) {
873 // if ( is_array( $url ) ) {
874 // error_log( print_r( $url, 1 ) );
875 // }
876 $dirIndex = strpos( $url, $this->upload_url );
877 if ( empty( $url ) || $dirIndex === false ) {
878 $finalUrl = null;
879 }
880 else {
881 $finalUrl = urldecode( substr( $url, 1 + strlen( $this->upload_url ) + $dirIndex ) );
882 }
883 return $finalUrl;
884 }
885
886 // From a fullpath to the shortened and cleaned path (for example '2013/02/file.png')
887 // Original version by Jordy
888 // function clean_uploaded_filename( $fullpath ) {
889 // $basedir = $this->upload_path;
890 // $file = str_replace( $basedir, '', $fullpath );
891 // $file = str_replace( "./", "", $file );
892 // $file = trim( $file, "/" );
893 // return $file;
894 // }
895
896 // From a fullpath to the shortened and cleaned path (for example '2013/02/file.png')
897 // Faster version, more difficult to read, by Mike Meinz
898 function clean_uploaded_filename( $fullpath ) {
899 $dirIndex = strpos( $fullpath, $this->upload_url );
900 if ( $dirIndex == false ) {
901 $file = $fullpath;
902 }
903 else {
904 // Remove first part of the path leaving yyyy/mm/filename.ext
905 $file = substr( $fullpath, 1 + strlen( $this->upload_url ) + $dirIndex );
906 }
907 if ( substr( $file, 0, 2 ) == './' ) {
908 $file = substr( $file, 2 );
909 }
910 if ( substr( $file, 0, 1 ) == '/' ) {
911 $file = substr( $file, 1 );
912 }
913 return $file;
914 }
915
916 /*
917 Check if the file or the Media ID is used in the install.
918 That file or ID will be checked against the database of references created by the plugin
919 by the parsers.
920 */
921 public function reference_exists( $file, $mediaId ) {
922 global $wpdb;
923 $table = $wpdb->prefix . "mclean_refs";
924 $row = null;
925 if ( !empty( $mediaId ) ) {
926 $row = $wpdb->get_row( $wpdb->prepare( "SELECT originType FROM $table WHERE mediaId = %d", $mediaId ) );
927 if ( !empty( $row ) ) {
928 $origin = $row->originType === 'MEDIA LIBRARY' ? 'Media Library' : 'content';
929 $this->log( "�
930 Media #{$mediaId} used by {$origin}" );
931 return $row->originType;
932 }
933 }
934 if ( !empty( $file ) ) {
935 $row = $wpdb->get_row( $wpdb->prepare( "SELECT originType FROM $table WHERE mediaUrl = %s", $file ) );
936 if ( !empty( $row ) ) {
937 $origin = $row->originType === 'MEDIA LIBRARY' ? 'Media Library' : 'content';
938 $this->log( "�
939 File {$file} used by {$origin}" );
940 return $row->originType;
941 }
942 }
943 return false;
944 }
945
946 function get_paths_from_attachment( $attachmentId ) {
947 $paths = array();
948 $fullpath = get_attached_file( $attachmentId );
949 if ( empty( $fullpath ) ) {
950 error_log( 'Media Cleaner: Could not find attached file for Media ID ' . $attachmentId );
951 return array();
952 }
953 $mainfile = $this->clean_uploaded_filename( $fullpath );
954 array_push( $paths, $mainfile );
955 $baseUp = pathinfo( $mainfile );
956 $filespath = trailingslashit( $this->upload_path ) . trailingslashit( $baseUp['dirname'] );
957 $meta = wp_get_attachment_metadata( $attachmentId );
958 if ( isset( $meta['original_image'] ) ) {
959 $original_image = $this->clean_uploaded_filename( $filespath . $meta['original_image'] );
960 array_push( $paths, $original_image );
961 }
962 $isImage = isset( $meta, $meta['width'], $meta['height'] );
963 $sizes = $this->get_image_sizes();
964 if ( $isImage && isset( $meta['sizes'] ) ) {
965 foreach ( $meta['sizes'] as $name => $attr ) {
966 if ( isset( $attr['file'] ) ) {
967 $file = $this->clean_uploaded_filename( $filespath . $attr['file'] );
968 array_push( $paths, $file );
969 }
970 }
971 }
972 return $paths;
973 }
974
975 function is_media_ignored( $attachmentId ) {
976 global $wpdb;
977 $table_name = $wpdb->prefix . "mclean_scan";
978 $issue = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $table_name WHERE postId = %d", $attachmentId ), OBJECT );
979 //error_log( $attachmentId );
980 //error_log( print_r( $issue, 1 ) );
981 if ( $issue && $issue->ignored )
982 return true;
983 return false;
984 }
985
986 function check_media( $attachmentId, $checkOnly = false ) {
987
988 // Is Media ID ignored, consider as used.
989 if ( $this->is_media_ignored( $attachmentId ) ) {
990 return true;
991 }
992
993 $size = 0;
994 $countfiles = 0;
995 $check_broken_media = !$this->check_content;
996 $fullpath = get_attached_file( $attachmentId );
997 $is_broken = !file_exists( $fullpath );
998
999 // It's a broken-only scan
1000 if ( $check_broken_media && !$is_broken ) {
1001 $is_considered_used = apply_filters( 'wpmc_check_media', true, $attachmentId, false );
1002 return $is_considered_used;
1003 }
1004
1005 // Let's analyze the usage of each path (thumbnails included) for this Media ID.
1006 $issue = 'NO_CONTENT';
1007 $paths = $this->get_paths_from_attachment( $attachmentId );
1008 foreach ( $paths as $path ) {
1009
1010 // If it's found in the content, we stop the scan right away
1011 if ( $this->check_content && $this->reference_exists( $path, $attachmentId ) ) {
1012 $is_considered_used = apply_filters( 'wpmc_check_media', true, $attachmentId, false );
1013 if ( $is_considered_used ) {
1014 return true;
1015 }
1016 }
1017
1018 // Let's count the size of the files for later, in case it's unused
1019 $filepath = trailingslashit( $this->upload_path ) . $path;
1020 if ( file_exists( $filepath ) )
1021 $size += filesize( $filepath );
1022 $countfiles++;
1023 }
1024
1025 // This Media ID seems not in used (or broken)
1026 // Let's double-check through the filter (overridable by users)
1027 $is_considered_used = apply_filters( 'wpmc_check_media', false, $attachmentId, $is_broken );
1028 if ( !$is_considered_used ) {
1029 if ( $is_broken ) {
1030 $this->log( "🚫 File {$fullpath} does not exist." );
1031 $issue = 'ORPHAN_MEDIA';
1032 }
1033 if ( !$checkOnly ) {
1034 global $wpdb;
1035 $table_name = $wpdb->prefix . "mclean_scan";
1036 $mainfile = $this->clean_uploaded_filename( $fullpath );
1037 $wpdb->insert( $table_name,
1038 array(
1039 'time' => current_time('mysql'),
1040 'type' => 1,
1041 'size' => $size,
1042 'path' => $mainfile . ( $countfiles > 0 ? ( " (+ " . $countfiles . " files)" ) : "" ),
1043 'postId' => $attachmentId,
1044 'issue' => $issue
1045 )
1046 );
1047 }
1048 }
1049 return $is_considered_used;
1050 }
1051
1052 // Delete all issues
1053 function reset_issues( $includingIgnored = false ) {
1054 global $wpdb;
1055 $table_name = $wpdb->prefix . "mclean_scan";
1056 if ( $includingIgnored ) {
1057 $wpdb->query( "DELETE FROM $table_name WHERE deleted = 0" );
1058 }
1059 else {
1060 $wpdb->query( "DELETE FROM $table_name WHERE ignored = 0 AND deleted = 0" );
1061 }
1062 if ( file_exists( WPMC_PATH . '/logs/media-cleaner.log' ) ) {
1063 file_put_contents( WPMC_PATH . '/logs/media-cleaner.log', '' );
1064 }
1065 $table_name = $wpdb->prefix . "mclean_refs";
1066 $wpdb->query("TRUNCATE $table_name");
1067 }
1068
1069 function echo_issue( $issue ) {
1070 if ( $issue == 'NO_CONTENT' ) {
1071 _e( "Seems not use", 'media-cleaner' );
1072 }
1073 else if ( $issue == 'ORPHAN_FILE' ) {
1074 _e( "Not in Library", 'media-cleaner' );
1075 }
1076 else if ( $issue == 'ORPHAN_RETINA' ) {
1077 _e( "Orphan Retina", 'media-cleaner' );
1078 }
1079 else if ( $issue == 'ORPHAN_WEBP' ) {
1080 _e( "Orphan WebP", 'media-cleaner' );
1081 }
1082 else if ( $issue == 'ORPHAN_MEDIA' ) {
1083 _e( "No attached file", 'media-cleaner' );
1084 }
1085 else {
1086 echo $issue;
1087 }
1088 }
1089 }
1090
1091 /*
1092 INSTALL / UNINSTALL
1093 */
1094
1095 function wpmc_init( $mainfile ) {
1096 //register_activation_hook( $mainfile, 'wpmc_install' );
1097 //register_deactivation_hook( $mainfile, 'wpmc_uninstall' );
1098 register_uninstall_hook( $mainfile, 'wpmc_uninstall' );
1099 }
1100
1101 function wpmc_install() {
1102 wpmc_create_database();
1103 }
1104
1105 function wpmc_reset () {
1106 wpmc_remove_database();
1107 wpmc_create_database();
1108 }
1109
1110 function wpmc_uninstall () {
1111 //wpmc_remove_options();
1112 //wpmc_remove_database();
1113 }
1114
1115 // Check the DB. If does not exist, let's create it.
1116 // TODO: When PHP 7 only, let's clean this and use anonymous functions.
1117 function wpmc_check_database() {
1118 global $wpdb;
1119 static $wpmc_check_database_done = false;
1120 if ( $wpmc_check_database_done ) {
1121 return true;
1122 }
1123 $table_scan = $wpdb->prefix . "mclean_refs";
1124 $table_refs = $wpdb->prefix . "mclean_scan";
1125 $db_init = !( strtolower( $wpdb->get_var( "SHOW TABLES LIKE '$table_scan'" ) ) != strtolower( $table_scan )
1126 || strtolower( $wpdb->get_var( "SHOW TABLES LIKE '$table_refs'" ) ) != strtolower( $table_refs ) );
1127 if ( !$db_init ) {
1128 wpmc_create_database();
1129 $db_init = !( strtolower( $wpdb->get_var( "SHOW TABLES LIKE '$table_scan'" ) ) != strtolower( $table_scan )
1130 || strtolower( $wpdb->get_var( "SHOW TABLES LIKE '$table_refs'" ) ) != strtolower( $table_refs ) );
1131 }
1132 $wpmc_check_database_done = true;
1133 }
1134
1135 function wpmc_create_database() {
1136 global $wpdb;
1137 $table_name = $wpdb->prefix . "mclean_scan";
1138 $charset_collate = $wpdb->get_charset_collate();
1139 $sql = "CREATE TABLE $table_name (
1140 id BIGINT(20) NOT NULL AUTO_INCREMENT,
1141 time DATETIME DEFAULT '0000-00-00 00:00:00' NOT NULL,
1142 type TINYINT(1) NOT NULL,
1143 postId BIGINT(20) NULL,
1144 path TINYTEXT NULL,
1145 size INT(9) NULL,
1146 ignored TINYINT(1) NOT NULL DEFAULT 0,
1147 deleted TINYINT(1) NOT NULL DEFAULT 0,
1148 issue TINYTEXT NOT NULL,
1149 PRIMARY KEY (id)
1150 ) " . $charset_collate . ";" ;
1151 require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );
1152 dbDelta( $sql );
1153 $sql="ALTER TABLE $table_name ADD INDEX IgnoredIndex (ignored) USING BTREE;";
1154 $wpdb->query($sql);
1155 $table_name = $wpdb->prefix . "mclean_refs";
1156 $charset_collate = $wpdb->get_charset_collate();
1157 // This key doesn't work on too many installs because of the 'Specified key was too long' issue
1158 // KEY mediaLookUp (mediaId, mediaUrl)
1159 $sql = "CREATE TABLE $table_name (
1160 id BIGINT(20) NOT NULL AUTO_INCREMENT,
1161 mediaId BIGINT(20) NULL,
1162 mediaUrl TINYTEXT NULL,
1163 originType TINYTEXT NOT NULL,
1164 PRIMARY KEY (id)
1165 ) " . $charset_collate . ";";
1166 require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );
1167 dbDelta( $sql );
1168 }
1169
1170 function wpmc_remove_options() {
1171 global $wpdb;
1172 $options = $wpdb->get_results( "SELECT option_name FROM $wpdb->options WHERE option_name LIKE 'wpmc_%'" );
1173 foreach( $options as $option ) {
1174 delete_option( $option->option_name );
1175 }
1176 }
1177
1178 function wpmc_remove_database() {
1179 global $wpdb;
1180 $table_name1 = $wpdb->prefix . "mclean_scan";
1181 $table_name2 = $wpdb->prefix . "mclean_refs";
1182 $table_name3 = $wpdb->prefix . "wpmcleaner";
1183 $sql = "DROP TABLE IF EXISTS $table_name1, $table_name2, $table_name3;";
1184 $wpdb->query( $sql );
1185 }
1186