PluginProbe ʕ •ᴥ•ʔ
Media Cleaner: Clean your WordPress! / 5.0.0
Media Cleaner: Clean your WordPress! v5.0.0
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 / core.php
media-cleaner Last commit date
common 7 years ago views 7 years ago admin.php 7 years ago checkers.php 8 years ago core.php 7 years ago custom_checkers.php 8 years ago media-cleaner.css 7 years ago media-cleaner.js 7 years ago media-cleaner.php 7 years ago readme.txt 7 years ago scan.php 7 years ago ui.php 7 years ago
core.php
867 lines
1 <?php
2
3 class Meow_WPMC_Core {
4
5 public $checkers = null;
6 public $admin = null;
7 public $last_analysis = null;
8 public $last_analysis_ids = null;
9 private $regex_file = '/[A-Za-z0-9-_,\s]+[.]{1}(MIMETYPES)/';
10 public $current_method = 'media';
11 private $refcache = array();
12
13 public function __construct( $admin ) {
14 $this->admin = $admin;
15 if ( is_admin() )
16 $this->admin_init();
17 }
18
19 function admin_init() {
20 $this->current_method = get_option( 'wpmc_method', 'media' );
21 $types = "jpg|jpeg|jpe|gif|png|tiff|bmp|csv|pdf|xls|xlsx|doc|docx|tiff|mp3|mp4|wav|lua";
22 $this->regex_file = str_replace( "MIMETYPES", $types, $this->regex_file );
23 require( 'scan.php' );
24 require( 'checkers.php' );
25 new MeowApps_WPMC_Scan( $this );
26 $this->checkers = new Meow_WPMC_Checkers( $this );
27 }
28
29 function deepsleep( $seconds ) {
30 $start_time = time();
31 while( true ) {
32 if ( ( time() - $start_time ) > $seconds ) {
33 return false;
34 }
35 get_post( array( 'posts_per_page' => 50 ) );
36 }
37 }
38
39 private $start_time;
40 private $time_elapsed = 0;
41 private $item_scan_avg_time = 0;
42 private $wordpress_init_time = 0.5;
43 private $max_execution_time;
44 private $items_checked = 0;
45 private $items_count = 0;
46
47 function get_max_execution_time() {
48 if ( isset( $this->max_execution_time ) )
49 return $this->max_execution_time;
50
51 $this->max_execution_time = ini_get( "max_execution_time" );
52 if ( empty( $this->max_execution_time ) || $this->max_execution_time < 5 )
53 $this->max_execution_time = 30;
54
55 return $this->max_execution_time;
56 }
57
58 function timeout_check_start( $count ) {
59 $this->start_time = time();
60 $this->items_count = $count;
61 $this->get_max_execution_time();
62 }
63
64 function timeout_check() {
65 $this->time_elapsed = time() - $this->start_time;
66 $this->time_remaining = $this->max_execution_time - $this->wordpress_init_time - $this->time_elapsed;
67 if ( $this->time_remaining - $this->item_scan_avg_time < 0 ) {
68 error_log("Media Cleaner Timeout! Check the Media Cleaner logs for more info.");
69 $this->log( "Timeout! Some info for debug:" );
70 $this->log( "Elapsed time: $this->time_elapsed" );
71 $this->log( "WP init time: $this->wordpress_init_time" );
72 $this->log( "Remaining time: $this->time_remaining" );
73 $this->log( "Scan time per item: $this->item_scan_avg_time" );
74 $this->log( "PHP max_execution_time: $this->max_execution_time" );
75 header("HTTP/1.0 408 Request Timeout");
76 exit;
77 }
78 }
79
80 function timeout_check_additem() {
81 $this->items_checked++;
82 $this->time_elapsed = time() - $this->start_time;
83 $this->item_scan_avg_time = ceil( ( $this->time_elapsed / $this->items_checked ) * 10 ) / 10;
84 }
85
86 function array_to_ids_or_urls( &$meta, &$ids, &$urls ) {
87 foreach ( $meta as $k => $m ) {
88 if ( is_numeric( $m ) ) {
89 // Probably a Media ID
90 if ( $m > 0 )
91 array_push( $ids, $m );
92 }
93 else if ( is_array( $m ) ) {
94 // If it's an array with a width, probably that the index is the Media ID
95 if ( isset( $m['width'] ) && is_numeric( $k ) ) {
96 if ( $k > 0 )
97 array_push( $ids, $k );
98 }
99 }
100 else if ( !empty( $m ) ) {
101 // If it's a string, maybe it's a file (with an extension)
102 if ( preg_match( $this->regex_file, $m ) )
103 array_push( $urls, $m );
104 }
105 }
106 }
107
108 function get_images_from_widgets( &$ids, &$urls ) {
109 global $wp_registered_widgets;
110 $syswidgets = $wp_registered_widgets;
111 $active_widgets = get_option( 'sidebars_widgets' );
112 foreach ( $active_widgets as $sidebar_name => $widgets ) {
113 if ( $sidebar_name != 'wp_inactive_widgets' && !empty( $widgets ) && is_array( $widgets ) ) {
114 $i = 0;
115 foreach ( $widgets as $key => $widget ) {
116 $widget_class = $syswidgets[$widget]['callback'][0]->option_name;
117 $instance_id = $syswidgets[$widget]['params'][0]['number'];
118 $widget_data = get_option( $widget_class );
119 // error_log( "INSTANCE $key ($instance_id)" );
120 // error_log( print_r( $widget_data, 1 ) );
121 if ( !empty( $widget_data[$instance_id]['text'] ) ) {
122 $html = do_shortcode( $widget_data[$instance_id]['text'] );
123 $urls = array_merge( $urls, $this->get_urls_from_html( $html ) );
124 }
125 if ( !empty( $widget_data[$instance_id]['attachment_id'] ) ) {
126 $id = $widget_data[$instance_id]['attachment_id'];
127 array_push( $ids, $id );
128 }
129 if ( !empty( $widget_data[$instance_id]['url'] ) ) {
130 $url = $this->wpmc_clean_url( $widget_data[$instance_id]['url'] );
131 array_push( $urls, $url );
132 }
133 if ( !empty( $widget_data[$instance_id]['ids'] ) ) {
134 $newIds = $widget_data[$instance_id]['ids'];
135 $ids = array_merge( $ids, $newIds );
136 }
137 $i++;
138 }
139 }
140 }
141 }
142
143 function get_urls_from_html( $html ) {
144 if ( empty( $html ) )
145 return array();
146 libxml_use_internal_errors( false );
147
148 $dom = new DOMDocument();
149 @$dom->loadHTML( $html );
150
151 // Images, src, srcset
152 $imgs = $dom->getElementsByTagName( 'img' );
153 $results = array();
154 foreach ( $imgs as $img ) {
155 $src = $this->wpmc_clean_url( $img->getAttribute('src') );
156 array_push( $results, $src );
157 $srcset = $img->getAttribute('srcset');
158 if ( !empty( $srcset ) ) {
159 $setImgs = explode( ',', trim( $srcset ) );
160 foreach ( $setImgs as $setImg ) {
161 $finalSetImg = explode( ' ', trim( $setImg ) );
162 if ( is_array( $finalSetImg ) ) {
163 array_push( $results, $this->wpmc_clean_url( $finalSetImg[0] ) );
164 }
165 }
166 }
167 }
168
169 // Links, href
170 $urls = $dom->getElementsByTagName( 'a' );
171 foreach ( $urls as $url ) {
172 $src = $this->wpmc_clean_url( $url->getAttribute('href') );
173 array_push( $results, $src );
174 }
175
176 // PDF
177 preg_match_all( "/((https?:\/\/)?[^\\&\#\[\] \"\?]+\.pdf)/", $html, $res );
178 if ( !empty( $res ) && isset( $res[1] ) && count( $res[1] ) > 0 ) {
179 foreach ( $res[1] as $url ) {
180 //error_log(print_r($url, 1));
181 array_push( $results, $this->wpmc_clean_url( $url ) );
182 }
183 }
184
185 // Background images
186 preg_match_all( "/url\(\'?\"?((https?:\/\/)?[^\\&\#\[\] \"\?]+\.(jpe?g|gif|png))\'?\"?/", $html, $res );
187 if ( !empty( $res ) && isset( $res[1] ) && count( $res[1] ) > 0 ) {
188 foreach ( $res[1] as $url ) {
189 //error_log(print_r($res, 1));
190 array_push( $results, $this->wpmc_clean_url( $url ) );
191 }
192 }
193
194 return $results;
195 }
196
197 // Parse a meta, visit all the arrays, look for the attributes, fill $ids and $urls arrays
198 function get_from_meta( $meta, $lookFor, &$ids, &$urls ) {
199 foreach ( $meta as $key => $value ) {
200 if ( is_object( $value ) || is_array( $value ) )
201 $this->get_from_meta( $value, $lookFor, $ids, $urls );
202 else if ( in_array( $key, $lookFor ) ) {
203 if ( empty( $value ) )
204 continue;
205 else if ( is_numeric( $value ) )
206 array_push( $ids, $value );
207 else
208 array_push( $urls, $this->wpmc_clean_url( $value ) );
209 }
210 }
211 }
212
213 function get_images_from_themes( &$ids, &$urls ) {
214 global $wpdb;
215
216 // USE CURRENT THEME AND WP API
217 $ch = get_custom_header();
218 if ( !empty( $ch ) && !empty( $ch->url ) ) {
219 array_push( $urls, $this->wpmc_clean_url( $ch->url ) );
220 }
221 if ( !empty( $ch ) && !empty( $ch->attachment_id ) ) {
222 array_push( $ids, $ch->attachment_id );
223 }
224 $cl = get_custom_logo();
225 if ( !empty( $cl ) ) {
226 $urls = array_merge( $this->get_urls_from_html( $cl ), $urls );
227 }
228 $cd = get_background_image();
229 if ( !empty( $cd ) ) {
230 array_push( $urls, $this->wpmc_clean_url( $cd ) );
231 }
232 $photography_hero_image = get_theme_mod( 'photography_hero_image' );
233 if ( !empty( $photography_hero_image ) ) {
234 array_push( $ids, $photography_hero_image );
235 }
236 $author_profile_picture = get_theme_mod( 'author_profile_picture' );
237 if ( !empty( $author_profile_picture ) ) {
238 array_push( $ids, $author_profile_picture );
239 }
240 }
241
242 function log( $data, $force = false ) {
243 if ( !get_option( 'wpmc_debuglogs', false ) && !$force )
244 return;
245 $fh = @fopen( trailingslashit( dirname(__FILE__) ) . '/media-cleaner.log', 'a' );
246 if ( !$fh )
247 return false;
248 $date = date( "Y-m-d H:i:s" );
249 fwrite( $fh, "$date: {$data}\n" );
250 fclose( $fh );
251 return true;
252 }
253
254 /**
255 *
256 * HELPERS
257 *
258 */
259
260 function wpmc_trashdir() {
261 $upload_folder = wp_upload_dir();
262 return trailingslashit( $upload_folder['basedir'] ) . 'wpmc-trash';
263 }
264
265 /**
266 *
267 * DELETE / SCANNING / RESET
268 *
269 */
270
271 function wpmc_recover_file( $path ) {
272 $basedir = wp_upload_dir();
273 $originalPath = trailingslashit( $basedir['basedir'] ) . $path;
274 $trashPath = trailingslashit( $this->wpmc_trashdir() ) . $path;
275 $path_parts = pathinfo( $originalPath );
276 if ( !file_exists( $path_parts['dirname'] ) && !wp_mkdir_p( $path_parts['dirname'] ) ) {
277 die( 'Failed to create folder.' );
278 }
279 if ( !file_exists( $trashPath ) ) {
280 $this->log( "The file $originalPath actually does not exist in the trash." );
281 return true;
282 }
283 if ( !rename( $trashPath, $originalPath ) ) {
284 die( 'Failed to move the file.' );
285 }
286 return true;
287 }
288
289 function wpmc_recover( $id ) {
290 global $wpdb;
291 $table_name = $wpdb->prefix . "mclean_scan";
292 $issue = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $table_name WHERE id = %d", $id ), OBJECT );
293 $issue->path = stripslashes( $issue->path );
294
295 // Files
296 if ( $issue->type == 0 ) {
297 $this->wpmc_recover_file( $issue->path );
298 $wpdb->query( $wpdb->prepare( "UPDATE $table_name SET deleted = 0 WHERE id = %d", $id ) );
299 return true;
300 }
301 // Media
302 else if ( $issue->type == 1 ) {
303
304 // Copy the main file back
305 $fullpath = get_attached_file( $issue->postId );
306 if ( empty( $fullpath ) ) {
307 error_log( "Media {$issue->postId} does not have attached file anymore." );
308 return false;
309 }
310 $mainfile = $this->wpmc_clean_uploaded_filename( $fullpath );
311 $baseUp = pathinfo( $mainfile );
312 $baseUp = $baseUp['dirname'];
313 $file = $this->wpmc_clean_uploaded_filename( $fullpath );
314 if ( !$this->wpmc_recover_file( $file ) ) {
315 $this->log( "Could not recover $file." );
316 error_log( "Media Cleaner: Could not recover $file." );
317 }
318
319 // If images, copy the other files as well
320 $meta = wp_get_attachment_metadata( $issue->postId );
321 $isImage = isset( $meta, $meta['width'], $meta['height'] );
322 $sizes = $this->wpmc_get_image_sizes();
323 if ( $isImage && isset( $meta['sizes'] ) ) {
324 foreach ( $meta['sizes'] as $name => $attr ) {
325 if ( isset( $attr['file'] ) ) {
326 $filepath = wp_upload_dir();
327 $filepath = $filepath['basedir'];
328 $filepath = trailingslashit( $filepath ) . trailingslashit( $baseUp ) . $attr['file'];
329 $file = $this->wpmc_clean_uploaded_filename( $filepath );
330 if ( !$this->wpmc_recover_file( $file ) ) {
331 $this->log( "Could not recover $file." );
332 error_log( "Media Cleaner: Could not recover $file." );
333 }
334 }
335 }
336 }
337 if ( !wp_untrash_post( $issue->postId ) ) {
338 error_log( "Cleaner: Failed to Untrash Post {$issue->postId} (but deleted it from Cleaner DB)." );
339 }
340 $wpdb->query( $wpdb->prepare( "UPDATE $table_name SET deleted = 0 WHERE id = %d", $id ) );
341 return true;
342 }
343 }
344
345 function wpmc_trash_file( $fileIssuePath ) {
346 global $wpdb;
347 $basedir = wp_upload_dir();
348 $originalPath = trailingslashit( $basedir['basedir'] ) . $fileIssuePath;
349 $trashPath = trailingslashit( $this->wpmc_trashdir() ) . $fileIssuePath;
350 $path_parts = pathinfo( $trashPath );
351
352 try {
353 if ( !file_exists( $path_parts['dirname'] ) && !wp_mkdir_p( $path_parts['dirname'] ) ) {
354 $this->log( "Could not create the trash directory for Media Cleaner." );
355 error_log( "Media Cleaner: Could not create the trash directory." );
356 return false;
357 }
358 // Rename the file (move). 'is_dir' is just there for security (no way we should move a whole directory)
359 if ( is_dir( $originalPath ) ) {
360 $this->log( "Attempted to delete a directory instead of a file ($originalPath). Can't do that." );
361 error_log( "Media Cleaner: Attempted to delete a directory instead of a file ($originalPath). Can't do that." );
362 return false;
363 }
364 if ( !file_exists( $originalPath ) ) {
365 $this->log( "The file $originalPath actually does not exist." );
366 return true;
367 }
368 if ( !@rename( $originalPath, $trashPath ) ) {
369 return false;
370 }
371 }
372 catch ( Exception $e ) {
373 return false;
374 }
375 $this->wpmc_clean_dir( dirname( $originalPath ) );
376 return true;
377 }
378
379 function wpmc_ignore( $id ) {
380 global $wpdb;
381 $table_name = $wpdb->prefix . "mclean_scan";
382 $issue = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $table_name WHERE id = %d", $id ), OBJECT );
383 if ( (int) $issue->ignored )
384 $wpdb->query( $wpdb->prepare( "UPDATE $table_name SET ignored = 0 WHERE id = %d", $id ) );
385 else {
386 if ( (int) $issue->deleted ) // If it is in trash, recover it
387 $this->wpmc_recover( $id );
388 $wpdb->query( $wpdb->prepare( "UPDATE $table_name SET ignored = 1 WHERE id = %d", $id ) );
389 }
390 return true;
391 }
392
393 function wpmc_endsWith( $haystack, $needle )
394 {
395 $length = strlen( $needle );
396 if ( $length == 0 )
397 return true;
398 return ( substr( $haystack, -$length ) === $needle );
399 }
400
401 function wpmc_clean_dir( $dir ) {
402 if ( !file_exists( $dir ) )
403 return;
404 else if ( $this->wpmc_endsWith( $dir, 'uploads' ) )
405 return;
406 $found = array_diff( scandir( $dir ), array( '.', '..' ) );
407 if ( count( $found ) < 1 ) {
408 if ( rmdir( $dir ) ) {
409 $this->wpmc_clean_dir( dirname( $dir ) );
410 }
411 }
412 }
413
414 function wpmc_delete( $id ) {
415 global $wpdb;
416 $table_name = $wpdb->prefix . "mclean_scan";
417 $issue = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $table_name WHERE id = %d", $id ), OBJECT );
418 $regex = "^(.*)(\\s\\(\\+.*)$";
419 $issue->path = preg_replace( '/' . $regex . '/i', '$1', $issue->path ); // remove " (+ 6 files)" from path
420
421 // Make sure there isn't a media DB entry
422 if ( $issue->type == 0 ) {
423 $attachmentid = $this->wpmc_find_attachment_id_by_file( $issue->path );
424 if ( $attachmentid ) {
425 $this->log( "Issue listed as filesystem but Media {$attachmentid} exists." );
426 }
427 }
428
429 if ( $issue->type == 0 ) {
430
431 if ( $issue->deleted == 0 ) {
432 // Move file to the trash
433 if ( $this->wpmc_trash_file( $issue->path ) )
434 $wpdb->query( $wpdb->prepare( "UPDATE $table_name SET deleted = 1, ignored = 0 WHERE id = %d", $id ) );
435 return true;
436 }
437 else {
438 // Delete file from the trash
439 $trashPath = trailingslashit( $this->wpmc_trashdir() ) . $issue->path;
440 if ( !unlink( $trashPath ) ) {
441 $this->log( "Failed to delete the file." );
442 error_log( "Media Cleaner: Failed to delete the file." );
443 }
444 $wpdb->query( $wpdb->prepare( "DELETE FROM $table_name WHERE id = %d", $id ) );
445 $this->wpmc_clean_dir( dirname( $trashPath ) );
446 return true;
447 }
448 }
449
450 if ( $issue->type == 1 ) {
451 if ( $issue->deleted == 0 && MEDIA_TRASH ) {
452 // Move Media to trash
453 // Let's copy the images to the trash so that it can be recovered.
454 $fullpath = get_attached_file( $issue->postId );
455 $mainfile = $this->wpmc_clean_uploaded_filename( $fullpath );
456 $baseUp = pathinfo( $mainfile );
457 $baseUp = $baseUp['dirname'];
458 $file = $this->wpmc_clean_uploaded_filename( $fullpath );
459 if ( !$this->wpmc_trash_file( $file ) ) {
460 $this->log( "Could not trash $file." );
461 error_log( "Media Cleaner: Could not trash $file." );
462 return false;
463 }
464
465 // If images, check the other files as well
466 $meta = wp_get_attachment_metadata( $issue->postId );
467 $isImage = isset( $meta, $meta['width'], $meta['height'] );
468 $sizes = $this->wpmc_get_image_sizes();
469 if ( $isImage && isset( $meta['sizes'] ) ) {
470 foreach ( $meta['sizes'] as $name => $attr ) {
471 if ( isset( $attr['file'] ) ) {
472 $filepath = wp_upload_dir();
473 $filepath = $filepath['basedir'];
474 $filepath = trailingslashit( $filepath ) . trailingslashit( $baseUp ) . $attr['file'];
475 $file = $this->wpmc_clean_uploaded_filename( $filepath );
476 if ( !$this->wpmc_trash_file( $file ) ) {
477 $this->log( "Could not trash $file." );
478 error_log( "Media Cleaner: Could not trash $file." );
479 }
480 }
481 }
482 }
483 wp_delete_attachment( $issue->postId, false );
484 $wpdb->query( $wpdb->prepare( "UPDATE $table_name SET deleted = 1, ignored = 0 WHERE id = %d", $id ) );
485 return true;
486 }
487 else {
488 // Trash Media definitely by recovering it (to be like a normal Media) and remove it through the
489 // standard WordPress workflow
490 if ( MEDIA_TRASH )
491 $this->wpmc_recover( $id );
492 wp_delete_attachment( $issue->postId, true );
493 $wpdb->query( $wpdb->prepare( "DELETE FROM $table_name WHERE id = %d", $id ) );
494 return true;
495 }
496 }
497 return false;
498 }
499
500 /**
501 *
502 * SCANNING / RESET
503 *
504 */
505
506 function add_reference_url( $urlOrUrls, $type, $origin = null, $extra = null ) {
507 $urlOrUrls = !is_array( $urlOrUrls ) ? array( $urlOrUrls ) : $urlOrUrls;
508 foreach ( $urlOrUrls as $url ) {
509 // With files, we need both filename without resolution and filename with resolution, it's important
510 // to make sure the original file is not deleted if a size exists for it.
511 // With media, all URLs should be without resolution to make sure it matches Media.
512 if ( $this->current_method == 'files' )
513 $this->add_reference( null, $url, $type, $origin );
514 $this->add_reference( 0, $this->clean_url_from_resolution( $url ), $type, $origin );
515 }
516 }
517
518 function add_reference_id( $idOrIds, $type, $origin = null, $extra = null ) {
519 $idOrIds = !is_array( $idOrIds ) ? array( $idOrIds ) : $idOrIds;
520 foreach ( $idOrIds as $id )
521 $this->add_reference( $id, "", $type, $origin );
522 }
523
524 private $cached_ids = array();
525 private $cached_urls = array();
526
527 private function add_reference( $id, $url, $type, $origin = null, $extra = null ) {
528 // The references are actually not being added directly in the DB, they are being pushed
529 // into a cache ($this->refcache).
530 if ( !empty( $id ) ) {
531 if ( !in_array( $id, $this->cached_ids ) )
532 array_push( $this->cached_ids, $id );
533 else
534 return;
535 }
536 if ( !empty( $url ) ) {
537 // The URL shouldn't contain http, https, javascript at the beginning (and there are probably many more cases)
538 // The URL must be cleaned before being passed as a reference.
539 if ( substr( $url, 0, 5 ) === "http:" )
540 return;
541 if ( substr( $url, 0, 6 ) === "https:" )
542 return;
543 if ( substr( $url, 0, 11 ) === "javascript:" )
544 return;
545 if ( !in_array( $url, $this->cached_urls ) )
546 array_push( $this->cached_urls, $url );
547 else
548 return;
549 }
550 //
551 array_push( $this->refcache, array( 'id' => $id, 'url' => $url, 'type' => $type, 'origin' => $origin ) );
552
553 // Without cache, the code would be this.
554 // $wpdb->insert( $table_name,
555 // array(
556 // 'time' => current_time('mysql'), 'mediaId' => $id, 'mediaUrl' => $url, 'origin' => $origin, 'originType' => $type )
557 // );
558 }
559
560 // The cache containing the references is wrote to the DB.
561 function write_references() {
562 global $wpdb;
563 $table = $wpdb->prefix . "mclean_refs";
564 $values = array();
565 $place_holders = array();
566 $query = "INSERT INTO $table (mediaId, mediaUrl, originType) VALUES ";
567 foreach( $this->refcache as $key => $value ) {
568 array_push( $values, $value['id'], $value['url'], $value['type'] );
569 if ( get_option( 'wpmc_debuglogs', false ) ) {
570 if ( !empty( $value['id'] ) )
571 $this->log( "* {$value['type']}: Media #{$value['id']}" );
572 if ( !empty( $value['url'] ) )
573 $this->log( "* {$value['type']}: {$value['url']}" );
574 }
575 $place_holders[] = "('%d','%s','%s')";
576 }
577 if ( !empty( $values ) ) {
578 $query .= implode( ', ', $place_holders );
579 $prepared = $wpdb->prepare( "$query ", $values );
580 $wpdb->query( $prepared );
581 }
582 $this->refcache = array();
583 }
584
585 function wpmc_check_is_ignore( $file ) {
586 global $wpdb;
587 $table_name = $wpdb->prefix . "mclean_scan";
588 $count = $wpdb->get_var( "SELECT COUNT(*)
589 FROM $table_name
590 WHERE ignored = 1
591 AND path LIKE '%". esc_sql( $wpdb->esc_like( $file ) ) . "%'" );
592 if ( $count > 0 ) {
593 $this->log( "Could not trash $file." );
594 }
595 return ($count > 0);
596 }
597
598 function wpmc_find_attachment_id_by_file( $file ) {
599 global $wpdb;
600 $postmeta_table_name = $wpdb->prefix . 'postmeta';
601 $file = $this->wpmc_clean_uploaded_filename( $file );
602 $sql = $wpdb->prepare( "SELECT post_id
603 FROM {$postmeta_table_name}
604 WHERE meta_key = '_wp_attached_file'
605 AND meta_value = %s", $file
606 );
607 $ret = $wpdb->get_var( $sql );
608 if ( empty( $ret ) )
609 $this->log( "File $file not found as _wp_attached_file (Library)." );
610 else {
611 $this->log( "File $file found as Media $ret." );
612 }
613 return $ret;
614 }
615
616 function wpmc_get_image_sizes() {
617 $sizes = array();
618 global $_wp_additional_image_sizes;
619 foreach ( get_intermediate_image_sizes() as $s ) {
620 $crop = false;
621 if ( isset( $_wp_additional_image_sizes[$s] ) ) {
622 $width = intval( $_wp_additional_image_sizes[$s]['width'] );
623 $height = intval( $_wp_additional_image_sizes[$s]['height'] );
624 $crop = $_wp_additional_image_sizes[$s]['crop'];
625 } else {
626 $width = get_option( $s.'_size_w' );
627 $height = get_option( $s.'_size_h' );
628 $crop = get_option( $s.'_crop' );
629 }
630 $sizes[$s] = array( 'width' => $width, 'height' => $height, 'crop' => $crop );
631 }
632 return $sizes;
633 }
634
635 function clean_url_from_resolution( $url ) {
636 $pattern = '/[_-]\d+x\d+(?=\.[a-z]{3,4}$)/';
637 $url = preg_replace( $pattern, '', $url );
638 return $url;
639 }
640
641 function clean_url_from_resolution_ref( &$url ) {
642 $url = $this->clean_url_from_resolution( $url );
643 }
644
645 // From a url to the shortened and cleaned url (for example '2013/02/file.png')
646 function wpmc_clean_url( $url ) {
647 $upload_folder = wp_upload_dir();
648 $baseurl = $upload_folder['baseurl'];
649 if ( substr( $url, 0, 2 ) === "//" )
650 $url = "http:" . $url;
651 $baseurl = str_replace( 'https://', 'http://', $baseurl );
652 $baseurl = str_replace( 'http://www.', 'http://', $baseurl );
653 $newurl = str_replace( 'https://', 'http://', $url );
654 $newurl = str_replace( 'http://www.', 'http://', $newurl );
655 $newurl = str_replace( $baseurl, '', $newurl );
656 $newurl = trim( $newurl, "/" );
657 return $newurl;
658 }
659
660 // From a fullpath to the shortened and cleaned path (for example '2013/02/file.png')
661 function wpmc_clean_uploaded_filename( $fullpath ) {
662 $upload_folder = wp_upload_dir();
663 $basedir = $upload_folder['basedir'];
664 $file = str_replace( $basedir, '', $fullpath );
665 $file = str_replace( "./", "", $file );
666 $file = trim( $file, "/" );
667 return $file;
668 }
669
670 function wpmc_check_media( $attachmentId, $checkOnly = false ) {
671
672 $this->last_analysis = "N/A";
673
674 // Is it an image?
675 $meta = wp_get_attachment_metadata( $attachmentId );
676 $isImage = isset( $meta, $meta['width'], $meta['height'] );
677
678 // Get the main file
679 global $wpdb;
680 $fullpath = get_attached_file( $attachmentId );
681 $mainfile = $this->wpmc_clean_uploaded_filename( $fullpath );
682 $baseUp = pathinfo( $mainfile );
683 $baseUp = $baseUp['dirname'];
684 $size = 0;
685 $countfiles = 0;
686 $issue = 'NO_CONTENT';
687 if ( file_exists( $fullpath ) ) {
688
689 // Special scan: Broken only!
690 $check_postmeta = get_option( 'wpmc_postmeta', false );
691 $check_posts = get_option( 'wpmc_posts', false );
692 $check_widgets = get_option( 'wpmc_widgets', false );
693 if ( !$check_postmeta && !$check_posts && !$check_widgets )
694 return true;
695
696 $size = filesize( $fullpath );
697
698 // Analysis
699 $this->last_analysis = "NONE";
700 $this->log( "Checking Media #{$attachmentId}: {$mainfile}" );
701 if ( $this->wpmc_check_is_ignore( $mainfile, $attachmentId ) ) {
702 $this->last_analysis = "IGNORED";
703 return true;
704 }
705 if ( $this->checkers->check( $mainfile, $attachmentId ) )
706 return true;
707
708 // If images, check the other files as well
709 $countfiles = 0;
710 $sizes = $this->wpmc_get_image_sizes();
711 if ( $isImage && isset( $meta['sizes'] ) ) {
712 foreach ( $meta['sizes'] as $name => $attr ) {
713 if ( isset( $attr['file'] ) ) {
714 $filepath = wp_upload_dir();
715 $filepath = $filepath['basedir'];
716 $filepath = trailingslashit( $filepath ) . trailingslashit( $baseUp ) . $attr['file'];
717 if ( file_exists( $filepath ) )
718 $size += filesize( $filepath );
719 $file = $this->wpmc_clean_uploaded_filename( $filepath );
720 $countfiles++;
721 // Analysis
722 $this->log( "Checking Media #{$attachmentId}: {$file}" );
723 if ( $this->checkers->check( $mainfile, $attachmentId ) )
724 return true;
725 }
726 }
727 }
728 } else {
729 $this->log( "File {$fullpath} does not exist." );
730 $issue = 'ORPHAN_MEDIA';
731 }
732
733 if ( !$checkOnly ) {
734 $table_name = $wpdb->prefix . "mclean_scan";
735 $wpdb->insert( $table_name,
736 array(
737 'time' => current_time('mysql'),
738 'type' => 1,
739 'size' => $size,
740 'path' => $mainfile . ( $countfiles > 0 ? ( " (+ " . $countfiles . " files)" ) : "" ),
741 'postId' => $attachmentId,
742 'issue' => $issue
743 )
744 );
745 }
746 return false;
747 }
748
749 // Delete all issues
750 function wpmc_reset_issues( $includingIgnored = false ) {
751 global $wpdb;
752 $table_name = $wpdb->prefix . "mclean_scan";
753 if ( $includingIgnored ) {
754 $wpdb->query( "DELETE FROM $table_name WHERE deleted = 0" );
755 }
756 else {
757 $wpdb->query( "DELETE FROM $table_name WHERE ignored = 0 AND deleted = 0" );
758 }
759 if ( file_exists( plugin_dir_path( __FILE__ ) . '/media-cleaner.log' ) ) {
760 file_put_contents( plugin_dir_path( __FILE__ ) . '/media-cleaner.log', '' );
761 }
762 $table_name = $wpdb->prefix . "mclean_refs";
763 $wpdb->query("TRUNCATE $table_name");
764
765 // Delete the old transient. Let's delete those lines later.
766 delete_transient( "wpmc_theme_ids" );
767 delete_transient( "wpmc_theme_urls" );
768 delete_transient( "wpmc_widgets_ids" );
769 delete_transient( "wpmc_widgets_urls" );
770 delete_transient( "wpmc_posts_images_urls" );
771 delete_transient( "wpmc_posts_images_ids" );
772 delete_transient( "wpmc_postmeta_images_urls" );
773 delete_transient( "wpmc_postmeta_images_ids" );
774 delete_transient( "wpmc_postmeta_images_acf_urls" );
775 delete_transient( "wpmc_postmeta_images_acf_ids" );
776 delete_transient( "wpmc_posts_images_visualcomposer" );
777 delete_transient( "wpmc_galleries_images_visualcomposer" );
778 delete_transient( "wpmc_galleries_images_fusionbuilder" );
779 delete_transient( "wpmc_galleries_images_woocommerce" );
780 delete_transient( "wpmc_galleries_images_divi" );
781 delete_transient( "wpmc_galleries_images_urls" );
782 }
783
784 function echo_issue( $issue ) {
785 if ( $issue == 'NO_CONTENT' ) {
786 _e( "Seems not in use.", 'media-cleaner' );
787 }
788 else if ( $issue == 'NO_MEDIA' ) {
789 _e( "Not in Media Library.", 'media-cleaner' );
790 }
791 else if ( $issue == 'ORPHAN_RETINA' ) {
792 _e( "Orphan retina.", 'media-cleaner' );
793 }
794 else if ( $issue == 'ORPHAN_MEDIA' ) {
795 _e( "File not found.", 'media-cleaner' );
796 }
797 else {
798 echo $issue;
799 }
800 }
801 }
802
803
804 /*
805 INSTALL / UNINSTALL
806 */
807
808 function wpmc_init( $mainfile ) {
809 register_activation_hook( $mainfile, 'wpmc_reset' );
810 register_deactivation_hook( $mainfile, 'wpmc_uninstall' );
811 register_uninstall_hook( $mainfile, 'wpmc_uninstall' );
812 }
813
814 function wpmc_reset () {
815 wpmc_uninstall();
816 wpmc_install();
817 $upload_folder = wp_upload_dir();
818 $basedir = $upload_folder['basedir'];
819 if ( !is_writable( $basedir ) ) {
820 echo '<div class="error"><p>' . __( 'The directory for uploads is not writable. Media Cleaner will only be able to scan.', 'media-cleaner' ) . '</p></div>';
821 }
822
823 }
824
825 function wpmc_install() {
826 global $wpdb;
827 $table_name = $wpdb->prefix . "mclean_scan";
828 $charset_collate = $wpdb->get_charset_collate();
829 $sql = "CREATE TABLE $table_name (
830 id BIGINT(20) NOT NULL AUTO_INCREMENT,
831 time DATETIME DEFAULT '0000-00-00 00:00:00' NOT NULL,
832 type TINYINT(1) NOT NULL,
833 postId BIGINT(20) NULL,
834 path TINYTEXT NULL,
835 size INT(9) NULL,
836 ignored TINYINT(1) NOT NULL DEFAULT 0,
837 deleted TINYINT(1) NOT NULL DEFAULT 0,
838 issue TINYTEXT NOT NULL,
839 PRIMARY KEY (id)
840 ) " . $charset_collate . ";";
841 require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
842 dbDelta( $sql );
843 $table_name = $wpdb->prefix . "mclean_refs";
844 $charset_collate = $wpdb->get_charset_collate();
845 // This key doesn't work on too many installs because of the 'Specified key was too long' issue
846 // KEY mediaLookUp (mediaId, mediaUrl)
847 $sql = "CREATE TABLE $table_name (
848 id BIGINT(20) NOT NULL AUTO_INCREMENT,
849 mediaId BIGINT(20) NULL,
850 mediaUrl VARBINARY(256) NULL,
851 originType VARBINARY(32) NOT NULL,
852 PRIMARY KEY (id)
853 ) " . $charset_collate . ";";
854 require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
855 dbDelta( $sql );
856 }
857
858 function wpmc_uninstall () {
859 global $wpdb;
860 $table_name = $wpdb->prefix . "wpmcleaner";
861 $wpdb->query("DROP TABLE IF EXISTS $table_name");
862 $table_name = $wpdb->prefix . "mclean_scan";
863 $wpdb->query("DROP TABLE IF EXISTS $table_name");
864 $table_name = $wpdb->prefix . "mclean_refs";
865 $wpdb->query("DROP TABLE IF EXISTS $table_name");
866 }
867