Media Cleaner - Version 5.4.5

Version Description

Download this release

Release Info

Developer TigrouMeow
Plugin Icon 128x128 Media Cleaner
Version 5.4.5
Comparing to
See all releases

Code changes from version 5.4.4 to 5.4.5

admin.php CHANGED
@@ -156,17 +156,15 @@ class Meow_WPMC_Admin extends MeowApps_Admin {
156
  add_settings_field( 'wpmc_delay', "Delay (in ms)",
157
  array( $this, 'admin_delay_callback' ),
158
  'wpmc_advanced_settings-menu', 'wpmc_advanced_settings' );
 
 
 
159
 
160
  // SETTINGS
161
  register_setting( 'wpmc_settings', 'wpmc_method' );
162
  register_setting( 'wpmc_settings', 'wpmc_content' );
163
  register_setting( 'wpmc_settings', 'wpmc_live_content' );
164
- // register_setting( 'wpmc_settings', 'wpmc_posts' );
165
- // register_setting( 'wpmc_settings', 'wpmc_shortcode' );
166
- // register_setting( 'wpmc_settings', 'wpmc_background' );
167
- // register_setting( 'wpmc_settings', 'wpmc_widgets' );
168
  register_setting( 'wpmc_settings', 'wpmc_media_library' );
169
- // register_setting( 'wpmc_settings', 'wpmc_postmeta' );
170
  register_setting( 'wpmc_settings', 'wpmc_debuglogs' );
171
 
172
  register_setting( 'wpmc_filters_settings', 'wpmc_thumbnails_only' );
@@ -181,6 +179,7 @@ class Meow_WPMC_Admin extends MeowApps_Admin {
181
  register_setting( 'wpmc_advanced_settings', 'wpmc_posts_buffer' );
182
  register_setting( 'wpmc_advanced_settings', 'wpmc_analysis_buffer' );
183
  register_setting( 'wpmc_advanced_settings', 'wpmc_delay' );
 
184
  }
185
 
186
  function admin_medias_buffer_callback( $args ) {
@@ -404,29 +403,13 @@ class Meow_WPMC_Admin extends MeowApps_Admin {
404
  echo $html;
405
  }
406
 
407
- // function admin_posts_callback( $args ) {
408
- // $value = get_option( 'wpmc_posts', true );
409
- // $html = '<input type="checkbox" id="wpmc_posts" name="wpmc_posts" value="1" ' .
410
- // checked( 1, get_option( 'wpmc_posts' ), false ) . '/>';
411
- // $html .= '<label>Analyze</label><br /><small>Check if the media/file is used by any post types.</small>';
412
- // echo $html;
413
- // }
414
-
415
- // function admin_postmeta_callback( $args ) {
416
- // $value = get_option( 'wpmc_postmeta', true );
417
- // $html = '<input type="checkbox" id="wpmc_postmeta" name="wpmc_postmeta" value="1" ' .
418
- // checked( 1, get_option( 'wpmc_postmeta' ), false ) . '/>';
419
- // $html .= '<label>Analyze</label><br /><small>Checks if the media/file is used in the meta.</small>';
420
- // echo $html;
421
- // }
422
-
423
- // function admin_widgets_callback( $args ) {
424
- // $value = get_option( 'wpmc_widgets', false );
425
- // $html = '<input type="checkbox" id="wpmc_widgets" name="wpmc_widgets" value="1" ' .
426
- // checked( 1, get_option( 'wpmc_widgets' ), false ) . '/>';
427
- // $html .= '<label>Analyze</label><br /><small>Checks if the media/file is used by any widget.</small>';
428
- // echo $html;
429
- // }
430
 
431
  function admin_hide_thumbnails_callback( $args ) {
432
  $value = get_option( 'wpmc_hide_thumbnails', null );
156
  add_settings_field( 'wpmc_delay', "Delay (in ms)",
157
  array( $this, 'admin_delay_callback' ),
158
  'wpmc_advanced_settings-menu', 'wpmc_advanced_settings' );
159
+ add_settings_field( 'wpmc_shortcodes_disabled', "Shortcodes",
160
+ array( $this, 'admin_shortcodes_disabled_callback' ),
161
+ 'wpmc_advanced_settings-menu', 'wpmc_advanced_settings' );
162
 
163
  // SETTINGS
164
  register_setting( 'wpmc_settings', 'wpmc_method' );
165
  register_setting( 'wpmc_settings', 'wpmc_content' );
166
  register_setting( 'wpmc_settings', 'wpmc_live_content' );
 
 
 
 
167
  register_setting( 'wpmc_settings', 'wpmc_media_library' );
 
168
  register_setting( 'wpmc_settings', 'wpmc_debuglogs' );
169
 
170
  register_setting( 'wpmc_filters_settings', 'wpmc_thumbnails_only' );
179
  register_setting( 'wpmc_advanced_settings', 'wpmc_posts_buffer' );
180
  register_setting( 'wpmc_advanced_settings', 'wpmc_analysis_buffer' );
181
  register_setting( 'wpmc_advanced_settings', 'wpmc_delay' );
182
+ register_setting( 'wpmc_advanced_settings', 'wpmc_shortcodes_disabled' );
183
  }
184
 
185
  function admin_medias_buffer_callback( $args ) {
403
  echo $html;
404
  }
405
 
406
+ function admin_shortcodes_disabled_callback( $args ) {
407
+ $value = get_option( 'wpmc_shortcodes_disabled', null );
408
+ $html = '<input type="checkbox" id="wpmc_shortcodes_disabled" name="wpmc_shortcodes_disabled" value="1" ' .
409
+ checked( 1, get_option( 'wpmc_shortcodes_disabled' ), false ) . '/>';
410
+ $html .= '<label>Disable Analysis</label><br /><small>Resolving shortcodes increase accuracy, but makes the process slower and takes more memory.</small>';
411
+ echo $html;
412
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
413
 
414
  function admin_hide_thumbnails_callback( $args ) {
415
  $value = get_option( 'wpmc_hide_thumbnails', null );
core.php CHANGED
@@ -156,7 +156,8 @@ class Meow_WPMC_Core {
156
  $html = mb_convert_encoding( $html, 'HTML-ENTITIES', 'UTF-8' );
157
 
158
  // Resolve src-set and shortcodes
159
- $html = do_shortcode( $html );
 
160
  $html = wp_make_content_images_responsive( $html );
161
 
162
  // Create the DOM Document
156
  $html = mb_convert_encoding( $html, 'HTML-ENTITIES', 'UTF-8' );
157
 
158
  // Resolve src-set and shortcodes
159
+ if ( !get_option( 'wpmc_shortcodes_disabled', false ) )
160
+ $html = do_shortcode( $html );
161
  $html = wp_make_content_images_responsive( $html );
162
 
163
  // Create the DOM Document
trunk/admin.php ADDED
@@ -0,0 +1,485 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ include "common/admin.php";
4
+
5
+ class Meow_WPMC_Admin extends MeowApps_Admin {
6
+
7
+ public function __construct( $prefix, $mainfile, $domain ) {
8
+ parent::__construct( $prefix, $mainfile, $domain );
9
+ add_action( 'admin_menu', array( $this, 'app_menu' ) );
10
+ add_action( 'admin_notices', array( $this, 'admin_notices' ) );
11
+ add_filter( 'pre_update_option', array( $this, 'pre_update_option' ), 10, 3 );
12
+ }
13
+
14
+ /**
15
+ * Filters and performs validation for certain options
16
+ * @param mixed $value Option value
17
+ * @param string $option Option name
18
+ * @param mixed $old_value The current value of the option
19
+ * @return mixed The actual value to be stored
20
+ */
21
+ function pre_update_option( $value, $option, $old_value ) {
22
+ if ( strpos( $option, 'wpmc_' ) !== 0 ) return $value; // Never touch extraneous options
23
+ $validated = $this->validate_option( $option, $value );
24
+ if ( $validated instanceof WP_Error ) {
25
+ // TODO: Show warning for invalid option value
26
+ return $old_value;
27
+ }
28
+ return $validated;
29
+ }
30
+
31
+ /**
32
+ * Validates certain option values
33
+ * @param string $option Option name
34
+ * @param mixed $value Option value
35
+ * @return mixed|WP_Error Validated value if no problem
36
+ */
37
+ function validate_option( $option, $value ) {
38
+ switch ( $option ) {
39
+ case 'wpmc_dirs_filter':
40
+ case 'wpmc_files_filter':
41
+ if ( $value && @preg_match( $value, '' ) === false ) return new WP_Error( 'invalid_option', __( "Invalid Regular-Expression", 'media-cleaner' ) );
42
+ break;
43
+ }
44
+ return $value;
45
+ }
46
+
47
+ function admin_notices() {
48
+
49
+ $mediasBuffer = get_option( 'wpmc_medias_buffer', null );
50
+ $postsBuffer = get_option( 'wpmc_posts_buffer', null );
51
+ $analysisBuffer = get_option( 'wpmc_analysis_buffer', null );
52
+ $delay = get_option( 'wpmc_delay', null );
53
+
54
+ if ( !is_numeric( $mediasBuffer ) || $mediasBuffer < 1 )
55
+ update_option( 'wpmc_medias_buffer', 100 );
56
+ if ( !is_numeric( $postsBuffer ) || $postsBuffer < 1 )
57
+ update_option( 'wpmc_posts_buffer', 5 );
58
+ if ( !is_numeric( $analysisBuffer ) || $analysisBuffer < 1 )
59
+ update_option( 'wpmc_analysis_buffer', 100 );
60
+ if ( !is_numeric( $delay ) )
61
+ update_option( 'wpmc_delay', 100 );
62
+
63
+ if ( !$this->is_registered() && get_option( 'wpmc_method', 'media' ) == 'files' ) {
64
+ _e( "<div class='error'><p>The Pro version is required to scan files. You can <a target='_blank' href='http://meowapps.com/media-cleaner'>get a serial for the Pro version here</a>.</p></div>", 'media-cleaner' );
65
+ }
66
+ }
67
+
68
+ function common_url( $file ) {
69
+ return trailingslashit( plugin_dir_url( __FILE__ ) ) . 'common/' . $file;
70
+ }
71
+
72
+ function app_menu() {
73
+
74
+ // SUBMENU > Settings
75
+ add_submenu_page( 'meowapps-main-menu', 'Media Cleaner', 'Media Cleaner', 'manage_options',
76
+ 'wpmc_settings-menu', array( $this, 'admin_settings' ) );
77
+
78
+ // SUBMENU > Settings > Settings (Scanning)
79
+ add_settings_section( 'wpmc_settings', null, null, 'wpmc_settings-menu' );
80
+ add_settings_field( 'wpmc_method', "Method",
81
+ array( $this, 'admin_method_callback' ),
82
+ 'wpmc_settings-menu', 'wpmc_settings' );
83
+ add_settings_field( 'wpmc_content', "Content",
84
+ array( $this, 'admin_content_callback' ),
85
+ 'wpmc_settings-menu', 'wpmc_settings' );
86
+ add_settings_field( 'wpmc_media_library', "Media Library",
87
+ array( $this, 'admin_media_library_callback' ),
88
+ 'wpmc_settings-menu', 'wpmc_settings' );
89
+ add_settings_field( 'wpmc_live_content', "Live Content<br />(Pro)",
90
+ array( $this, 'admin_live_content_callback' ),
91
+ 'wpmc_settings-menu', 'wpmc_settings' );
92
+ // add_settings_field( 'wpmc_posts', "Posts",
93
+ // array( $this, 'admin_posts_callback' ),
94
+ // 'wpmc_settings-menu', 'wpmc_settings' );
95
+ // add_settings_field( 'wpmc_postmeta', "Post Meta",
96
+ // array( $this, 'admin_postmeta_callback' ),
97
+ // 'wpmc_settings-menu', 'wpmc_settings' );
98
+ // add_settings_field( 'wpmc_widgets', "Widgets",
99
+ // array( $this, 'admin_widgets_callback' ),
100
+ // 'wpmc_settings-menu', 'wpmc_settings' );
101
+ // add_settings_field( 'wpmc_shortcode', "Shortcodes<br />(Pro)",
102
+ // array( $this, 'admin_shortcode_callback' ),
103
+ // 'wpmc_settings-menu', 'wpmc_settings' );
104
+ // add_settings_field( 'wpmc_background', "Background CSS<br />(Pro)",
105
+ // array( $this, 'admin_background_callback' ),
106
+ // 'wpmc_settings-menu', 'wpmc_settings' );
107
+ add_settings_field( 'wpmc_debuglogs', "Logs",
108
+ array( $this, 'admin_debuglogs_callback' ),
109
+ 'wpmc_settings-menu', 'wpmc_settings', array( "Enable" ) );
110
+
111
+ // SUBMENU > Settings > Filters
112
+ add_settings_section( 'wpmc_filters_settings', null, null, 'wpmc_filters_settings-menu' );
113
+ add_settings_field( 'wpmc_thumbnails_only', "Thumbnails Only",
114
+ array( $this, 'admin_thumbnails_only_callback' ),
115
+ 'wpmc_filters_settings-menu', 'wpmc_filters_settings' );
116
+
117
+ add_settings_field(
118
+ 'wpmc_dirs_filter',
119
+ 'Directories Filter',
120
+ array( $this, 'admin_dirs_filter_callback' ),
121
+ 'wpmc_filters_settings-menu',
122
+ 'wpmc_filters_settings'
123
+ );
124
+
125
+ add_settings_field(
126
+ 'wpmc_files_filter',
127
+ 'Files Filter',
128
+ array( $this, 'admin_files_filter_callback' ),
129
+ 'wpmc_filters_settings-menu',
130
+ 'wpmc_filters_settings'
131
+ );
132
+
133
+ // SUBMENU > Settings > UI
134
+ add_settings_section( 'wpmc_ui_settings', null, null, 'wpmc_ui_settings-menu' );
135
+ add_settings_field( 'wpmc_hide_thumbnails', "Thumbnails",
136
+ array( $this, 'admin_hide_thumbnails_callback' ),
137
+ 'wpmc_ui_settings-menu', 'wpmc_ui_settings' );
138
+ add_settings_field( 'wpmc_hide_warning', "Warning Message",
139
+ array( $this, 'admin_hide_warning_callback' ),
140
+ 'wpmc_ui_settings-menu', 'wpmc_ui_settings' );
141
+ add_settings_field( 'wpmc_results_per_page', "Results Per Page",
142
+ array( $this, 'admin_results_per_page' ),
143
+ 'wpmc_ui_settings-menu', 'wpmc_ui_settings' );
144
+
145
+ // SUBMENU > Settings > Advanced
146
+ add_settings_section( 'wpmc_advanced_settings', null, null, 'wpmc_advanced_settings-menu' );
147
+ add_settings_field( 'wpmc_medias_buffer', "Medias Buffer",
148
+ array( $this, 'admin_medias_buffer_callback' ),
149
+ 'wpmc_advanced_settings-menu', 'wpmc_advanced_settings' );
150
+ add_settings_field( 'wpmc_posts_buffer', "Posts Buffer",
151
+ array( $this, 'admin_posts_buffer_callback' ),
152
+ 'wpmc_advanced_settings-menu', 'wpmc_advanced_settings' );
153
+ add_settings_field( 'wpmc_analysis_buffer', "Analysis Buffer",
154
+ array( $this, 'admin_analysis_buffer_callback' ),
155
+ 'wpmc_advanced_settings-menu', 'wpmc_advanced_settings' );
156
+ add_settings_field( 'wpmc_delay', "Delay (in ms)",
157
+ array( $this, 'admin_delay_callback' ),
158
+ 'wpmc_advanced_settings-menu', 'wpmc_advanced_settings' );
159
+ add_settings_field( 'wpmc_shortcodes_disabled', "Shortcodes",
160
+ array( $this, 'admin_shortcodes_disabled_callback' ),
161
+ 'wpmc_advanced_settings-menu', 'wpmc_advanced_settings' );
162
+
163
+ // SETTINGS
164
+ register_setting( 'wpmc_settings', 'wpmc_method' );
165
+ register_setting( 'wpmc_settings', 'wpmc_content' );
166
+ register_setting( 'wpmc_settings', 'wpmc_live_content' );
167
+ register_setting( 'wpmc_settings', 'wpmc_media_library' );
168
+ register_setting( 'wpmc_settings', 'wpmc_debuglogs' );
169
+
170
+ register_setting( 'wpmc_filters_settings', 'wpmc_thumbnails_only' );
171
+ register_setting( 'wpmc_filters_settings', 'wpmc_dirs_filter' );
172
+ register_setting( 'wpmc_filters_settings', 'wpmc_files_filter' );
173
+
174
+ register_setting( 'wpmc_ui_settings', 'wpmc_hide_thumbnails' );
175
+ register_setting( 'wpmc_ui_settings', 'wpmc_hide_warning' );
176
+ register_setting( 'wpmc_ui_settings', 'wpmc_results_per_page' );
177
+
178
+ register_setting( 'wpmc_advanced_settings', 'wpmc_medias_buffer' );
179
+ register_setting( 'wpmc_advanced_settings', 'wpmc_posts_buffer' );
180
+ register_setting( 'wpmc_advanced_settings', 'wpmc_analysis_buffer' );
181
+ register_setting( 'wpmc_advanced_settings', 'wpmc_delay' );
182
+ register_setting( 'wpmc_advanced_settings', 'wpmc_shortcodes_disabled' );
183
+ }
184
+
185
+ function admin_medias_buffer_callback( $args ) {
186
+ $value = get_option( 'wpmc_medias_buffer', 100 );
187
+ $html = '<input type="number" style="width: 100%;" id="wpmc_medias_buffer" name="wpmc_medias_buffer" value="' . $value . '" />';
188
+ $html .= '<br /><span class="description">The number of media entries to read at a time. This is fast, so the value should be between 50 and 1000.</label>';
189
+ echo $html;
190
+ }
191
+
192
+ function admin_posts_buffer_callback( $args ) {
193
+ $value = get_option( 'wpmc_posts_buffer', 5 );
194
+ $html = '<input type="number" style="width: 100%;" id="wpmc_posts_buffer" name="wpmc_posts_buffer" value="' . $value . '" />';
195
+ $html .= '<br /><span class="description">The number of posts (and any other post types) to analyze at a time. This is the most intense part of the process. Recommended value is between 1 (slow server) and 20 (excellent server).</label>';
196
+ echo $html;
197
+ }
198
+
199
+ function admin_analysis_buffer_callback( $args ) {
200
+ $value = get_option( 'wpmc_analysis_buffer', 100 );
201
+ $html = '<input type="number" style="width: 100%;" id="wpmc_analysis_buffer" name="wpmc_analysis_buffer" value="' . $value . '" />';
202
+ $html .= '<br /><span class="description">The number of media entries or files to analyze at a time. This is the main part of the process, but is is much faster than analyzing each post. Recommended value is between 20 (slow server) and 1000 (excellent server).</label>';
203
+ echo $html;
204
+ }
205
+
206
+ function admin_delay_callback( $args ) {
207
+ $value = get_option( 'wpmc_delay', 100 );
208
+ $html = '<input type="number" style="width: 100%;" id="wpmc_delay" name="wpmc_delay" value="' . $value . '" />';
209
+ $html .= '<br /><span class="description">Time to wait between each request (in milliseconds). The overall process is intensive so this gives the chance to your server to chill out a bit. A very good server doesn\'t need it, but a slow/shared hosting might even reject requests if they are too fast and frequent. Recommended value is actually 0, 100 for safety, 2000 or 5000 if your hosting is kind of cheap.</label>';
210
+ echo $html;
211
+ }
212
+
213
+ function admin_settings() {
214
+ ?>
215
+ <div class="wrap">
216
+ <?php
217
+ echo $this->display_title( "Media Cleaner" );
218
+ ?>
219
+ <div class="meow-section meow-group">
220
+ <div class="meow-box meow-col meow-span_2_of_2">
221
+ <h3>How to use</h3>
222
+ <div class="inside">
223
+ <?php echo _e( "You can choose two kind of methods. Usually, users like to analyze their Media Library for images which are not in used (Media Library Method + Content Check), and then, their Filesystem for images which aren't registered in the Media Library (Filesystem Method + Media Library Check). Check the <a target=\"_blank\" href=\"https://meowapps.com/media-cleaner-tutorial/\">tutorial</a> for more information.", 'media-cleaner' ); ?>
224
+ <p class="submit">
225
+ <a class="button button-primary" href="upload.php?page=media-cleaner"><?php echo _e( "Access Media Cleaner Dashboard", 'media-cleaner' ); ?></a>
226
+ </p>
227
+ </div>
228
+ </div>
229
+ </div>
230
+
231
+ <div class="meow-section meow-group">
232
+
233
+ <div class="meow-col meow-span_1_of_2">
234
+
235
+ <div class="meow-box">
236
+ <h3>Scanning</h3>
237
+ <div class="inside">
238
+ <form method="post" action="options.php">
239
+ <?php settings_fields( 'wpmc_settings' ); ?>
240
+ <?php do_settings_sections( 'wpmc_settings-menu' ); ?>
241
+ <?php submit_button(); ?>
242
+ </form>
243
+ </div>
244
+ </div>
245
+
246
+ <div class="meow-box">
247
+ <h3>Filters</h3>
248
+ <div class="inside">
249
+ <form method="post" action="options.php">
250
+ <?php settings_fields( 'wpmc_filters_settings' ); ?>
251
+ <?php do_settings_sections( 'wpmc_filters_settings-menu' ); ?>
252
+ <?php submit_button(); ?>
253
+ </form>
254
+ </div>
255
+
256
+ </div>
257
+
258
+ </div>
259
+
260
+ <div class="meow-col meow-span_1_of_2">
261
+ <?php $this->display_serialkey_box( "https://meowapps.com/media-cleaner/" ); ?>
262
+
263
+ <div class="meow-box">
264
+ <h3>UI</h3>
265
+ <div class="inside">
266
+ <form method="post" action="options.php">
267
+ <?php settings_fields( 'wpmc_ui_settings' ); ?>
268
+ <?php do_settings_sections( 'wpmc_ui_settings-menu' ); ?>
269
+ <?php submit_button(); ?>
270
+ </form>
271
+ </div>
272
+ </div>
273
+
274
+ <div class="meow-box">
275
+ <h3>Advanced</h3>
276
+ <div class="inside">
277
+ <form method="post" action="options.php">
278
+ <?php settings_fields( 'wpmc_advanced_settings' ); ?>
279
+ <?php do_settings_sections( 'wpmc_advanced_settings-menu' ); ?>
280
+ <?php submit_button(); ?>
281
+ </form>
282
+ </div>
283
+ </div>
284
+
285
+ <!--
286
+ <?php if ( get_option( 'wpmc_shortcode', false ) ): ?>
287
+ <div class="meow-box">
288
+ <h3>Shortcodes</h3>
289
+ <div class="inside"><small>
290
+ <p>Here are the shortcodes registered in your WordPress by your theme and other plugins.</p>
291
+ <?php
292
+ global $shortcode_tags;
293
+ try {
294
+ if ( is_array( $shortcode_tags ) ) {
295
+ $my_shortcodes = array();
296
+ foreach ( $shortcode_tags as $sc )
297
+ if ( $sc != '__return_false' ) {
298
+ if ( is_string( $sc ) )
299
+ array_push( $my_shortcodes, str_replace( '_shortcode', '', (string)$sc ) );
300
+ }
301
+ $my_shortcodes = implode( ', ', $my_shortcodes );
302
+ }
303
+ }
304
+ catch (Exception $e) {
305
+ $my_shortcodes = "";
306
+ }
307
+ echo $my_shortcodes;
308
+ ?>
309
+ </small></div>
310
+ </div>
311
+ <?php endif; ?>
312
+ -->
313
+
314
+ </div>
315
+
316
+ </div>
317
+ </div>
318
+ <?php
319
+ }
320
+
321
+
322
+
323
+ /*
324
+ OPTIONS CALLBACKS
325
+ */
326
+
327
+ function admin_method_callback( $args ) {
328
+ $value = get_option( 'wpmc_method', 'media' );
329
+ $html = '<select id="wpmc_method" name="wpmc_method">
330
+ <option ' . selected( 'media', $value, false ) . 'value="media">Media Library</option>
331
+ <option ' . disabled( $this->is_registered(), false, false ) . ' ' . selected( 'files', $value, false ) . 'value="files">Filesystem (Pro)</option>
332
+ </select><small><br />' . __( 'Check the <a target="_blank" href="https://meowapps.com/media-cleaner-tutorial/">tutorial</a> for more information.', 'media-cleaner' ) . '</small>';
333
+
334
+ // TODO: This is temporary
335
+ // $html .= '<br /><br /><small><b style="color: red;">Notice: </b><b> Those settings will be simplified soon. Have a look <a href="https://trello.com/c/qrCuITg8/55-cleaner-simplification-of-the-settings" target="_blank">here</a> and let me know in the comments if you like the idea or not.</b></small>';
336
+
337
+ echo $html;
338
+ }
339
+
340
+
341
+ // function admin_shortcode_callback( $args ) {
342
+ // $value = get_option( 'wpmc_shortcode', null );
343
+ // $html = '<input ' . disabled( $this->is_registered(), false, false ) . ' type="checkbox" id="wpmc_shortcode" name="wpmc_shortcode" value="1" ' .
344
+ // checked( 1, get_option( 'wpmc_shortcode' ), false ) . '/>';
345
+ // $html .= '<label>Resolve</label><br /><small>The shortcodes you are using in your <b>posts</b> and/or <b>widgets</b> (depending on your options) will be resolved and analyzed. You don\'t need to have this option enabled for the WP Gallery (as it is covered by the Galleries option).</small>';
346
+ // echo $html;
347
+ // }
348
+
349
+ // function admin_background_callback( $args ) {
350
+ // $value = get_option( 'wpmc_background', null );
351
+ // $html = '<input ' . disabled( $this->is_registered(), false, false ) . ' type="checkbox" id="wpmc_background" name="wpmc_background" value="1" ' .
352
+ // checked( 1, get_option( 'wpmc_background' ), false ) . '/>';
353
+ // $html .= '<label>Analyze</label><br /><small>When parsing HTML, the CSS inline background will also be analyzed. A few page builders are using this.</small>';
354
+ // echo $html;
355
+ // }
356
+
357
+ function admin_debuglogs_callback( $args ) {
358
+ global $wpmc;
359
+ $debuglogs = get_option( 'wpmc_debuglogs' );
360
+ $clearlogs = isset ( $_GET[ 'clearlogs' ] ) ? $_GET[ 'clearlogs' ] : 0;
361
+ if ( $clearlogs && file_exists( plugin_dir_path( __FILE__ ) . '/media-cleaner.log' ) ) {
362
+ unlink( plugin_dir_path( __FILE__ ) . '/media-cleaner.log' );
363
+ }
364
+ $html = '<input type="checkbox" id="wpmc_debuglogs" name="wpmc_debuglogs" value="1" ' .
365
+ checked( 1, $debuglogs, false ) . '/>';
366
+ $html .= '<label for="wpmc_debuglogs"> ' . $args[0] . '</label><br>';
367
+ $html .= '<small>' . __( 'Creates an internal log file, for debugging purposes.', 'media-cleaner' );
368
+ if ( $debuglogs && !file_exists( plugin_dir_path( __FILE__ ) . '/media-cleaner.log' ) ) {
369
+ if ( !$wpmc->log( "Testing the logging feature. It works!" ) ) {
370
+ $html .= sprintf( __( '<br /><b>Cannot create the logging file. Logging will not work. The plugin as a whole might not be able to work neither.</b>', 'media-cleaner' ), plugin_dir_url( __FILE__ ) );
371
+ }
372
+ }
373
+ if ( file_exists( plugin_dir_path( __FILE__ ) . '/media-cleaner.log' ) ) {
374
+ $html .= sprintf( __( '<br />The <a target="_blank" href="%smedia-cleaner.log">log file</a> is available. You can also <a href="?page=wpmc_settings-menu&clearlogs=true">clear</a> it.', 'media-cleaner' ), plugin_dir_url( __FILE__ ) );
375
+ }
376
+ $html .= '</small>';
377
+ echo $html;
378
+ }
379
+
380
+ function admin_media_library_callback( $args ) {
381
+ $value = get_option( 'wpmc_media_library', true );
382
+ $html = '<input type="checkbox" id="wpmc_media_library" name="wpmc_media_library" value="1" ' .
383
+ disabled( get_option( 'wpmc_method', 'media' ) == 'files', false, false ) . ' ' .
384
+ checked( 1, $value, false ) . '/>';
385
+ $html .= '<label>Check</label><br /><small>Checks if the file is linked to a media. <i>Only matters to the Filesystem Method.</i></small>';
386
+ echo $html;
387
+ }
388
+
389
+ function admin_content_callback( $args ) {
390
+ $value = get_option( 'wpmc_content', true );
391
+ $html = '<input type="checkbox" id="wpmc_content" name="wpmc_content" value="1" ' .
392
+ checked( 1, $value, false ) . '/>';
393
+ $html .= '<label>Check</label><br /><small>Check if the media/file is used in the content, such as Posts, Pages (and other Post Types), Metadata, Widgets, etc.</small>';
394
+ echo $html;
395
+ }
396
+
397
+ function admin_live_content_callback( $args ) {
398
+ $value = get_option( 'wpmc_live_content', false );
399
+ $html = '<input ' . disabled( $this->is_registered(), false, false ) .
400
+ ' type="checkbox" id="wpmc_content" name="wpmc_live_content" value="1" ' .
401
+ checked( 1, $value, false ) . '/>';
402
+ $html .= '<label>Check</label><br /><small>The live version of the website will be also analyzed (as if a visitor was loading it). <i>This will increase the accuracy of the results.</i></small>';
403
+ echo $html;
404
+ }
405
+
406
+ function admin_shortcodes_disabled_callback( $args ) {
407
+ $value = get_option( 'wpmc_shortcodes_disabled', null );
408
+ $html = '<input type="checkbox" id="wpmc_shortcodes_disabled" name="wpmc_shortcodes_disabled" value="1" ' .
409
+ checked( 1, get_option( 'wpmc_shortcodes_disabled' ), false ) . '/>';
410
+ $html .= '<label>Disable Analysis</label><br /><small>Resolving shortcodes increase accuracy, but makes the process slower and takes more memory.</small>';
411
+ echo $html;
412
+ }
413
+
414
+ function admin_hide_thumbnails_callback( $args ) {
415
+ $value = get_option( 'wpmc_hide_thumbnails', null );
416
+ $html = '<input type="checkbox" id="wpmc_hide_thumbnails" name="wpmc_hide_thumbnails" value="1" ' .
417
+ checked( 1, get_option( 'wpmc_hide_thumbnails' ), false ) . '/>';
418
+ $html .= '<label>Hide</label><br /><small>If you prefer not to see the thumbnails.</small>';
419
+ echo $html;
420
+ }
421
+
422
+ function admin_hide_warning_callback( $args ) {
423
+ $value = get_option( 'wpmc_hide_warning', null );
424
+ $html = '<input type="checkbox" id="wpmc_hide_warning" name="wpmc_hide_warning" value="1" ' .
425
+ checked( 1, get_option( 'wpmc_hide_warning' ), false ) . '/>';
426
+ $html .= '<label>Hide</label><br /><small>Have you read it twice? If yes, hide it :)</small>';
427
+ echo $html;
428
+ }
429
+
430
+ function admin_results_per_page( $args ) {
431
+ $value = get_option( 'wpmc_results_per_page', 20 );
432
+ $html = <<< HTML
433
+ <input step="1" min="1" max="999" name="wpmc_results_per_page" id="wpmc_results_per_page" maxlength="3" value="{$value}" type="number">
434
+ HTML;
435
+ echo $html;
436
+ }
437
+
438
+ function admin_thumbnails_only_callback( $args ) {
439
+ $value = get_option( 'wpmc_thumbnails_only', false );
440
+ $html = '<input type="checkbox" id="wpmc_thumbnails_only" name="wpmc_thumbnails_only" value="1" ' .
441
+ disabled( get_option( 'wpmc_method', 'media' ) == 'files', false, false ) . ' ' .
442
+ checked( 1, get_option( 'wpmc_thumbnails_only' ), false ) . '/>';
443
+ $html .= '<label>Enable</label><br /><small>Restrict the filesystem scan to thumbnails (files containing the resolution). If none of the checks above are selected, you will get the list of all the thumbnails and be able to remove them.</small>';
444
+ echo $html;
445
+ }
446
+
447
+ function admin_dirs_filter_callback( $args ) {
448
+ $value = get_option( 'wpmc_dirs_filter', '' );
449
+ $invalid = @preg_match( $value, '' ) === false;
450
+ ?>
451
+ <input type="text" id="wpmc_dirs_filter" name="wpmc_dirs_filter" value="<?php echo $value; ?>" placeholder="/regex/" autocomplete="off" data-needs-validation style="font-family: monospace;">
452
+ <?php
453
+ }
454
+
455
+ function admin_files_filter_callback( $args ) {
456
+ $value = get_option( 'wpmc_files_filter', '' );
457
+ $invalid = @preg_match( $value, '' ) === false;
458
+ ?>
459
+ <input type="text" id="wpmc_files_filter" name="wpmc_files_filter" value="<?php echo $value; ?>" placeholder="/regex/" autocomplete="off" data-needs-validation style="font-family: monospace;">
460
+ <?php
461
+ }
462
+
463
+ /**
464
+ *
465
+ * GET / SET OPTIONS (TO REMOVE)
466
+ *
467
+ */
468
+
469
+ function old_getoption( $option, $section, $default = '' ) {
470
+ $options = get_option( $section );
471
+ if ( isset( $options[$option] ) ) {
472
+ if ( $options[$option] == "off" ) {
473
+ return false;
474
+ }
475
+ if ( $options[$option] == "on" ) {
476
+ return true;
477
+ }
478
+ return $options[$option];
479
+ }
480
+ return $default;
481
+ }
482
+
483
+ }
484
+
485
+ ?>
trunk/api.php ADDED
@@ -0,0 +1,234 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class Meow_WPMC_API {
4
+
5
+ function __construct( $core, $admin, $engine ) {
6
+ $this->core = $core;
7
+ $this->engine = $engine;
8
+ $this->admin = $admin;
9
+ add_action( 'wp_ajax_wpmc_prepare_do', array( $this, 'wp_ajax_wpmc_prepare_do' ) );
10
+ add_action( 'wp_ajax_wpmc_scan', array( $this, 'wp_ajax_wpmc_scan' ) );
11
+ add_action( 'wp_ajax_wpmc_scan_do', array( $this, 'wp_ajax_wpmc_scan_do' ) );
12
+ add_action( 'wp_ajax_wpmc_get_all_issues', array( $this, 'wp_ajax_wpmc_get_all_issues' ) );
13
+ add_action( 'wp_ajax_wpmc_get_all_deleted', array( $this, 'wp_ajax_wpmc_get_all_deleted' ) );
14
+ add_action( 'wp_ajax_wpmc_delete_do', array( $this, 'wp_ajax_wpmc_delete_do' ) );
15
+ add_action( 'wp_ajax_wpmc_ignore_do', array( $this, 'wp_ajax_wpmc_ignore_do' ) );
16
+ add_action( 'wp_ajax_wpmc_recover_do', array( $this, 'wp_ajax_wpmc_recover_do' ) );
17
+ add_action( 'wp_ajax_wpmc_validate_option', array( $this, 'wp_ajax_wpmc_validate_option' ) );
18
+ }
19
+
20
+ /*******************************************************************************
21
+ * ASYNCHRONOUS AJAX FUNCTIONS
22
+ ******************************************************************************/
23
+
24
+ function wp_ajax_wpmc_prepare_do() {
25
+ $limit = isset( $_POST['limit'] ) ? $_POST['limit'] : 0;
26
+ $limitsize = get_option( 'wpmc_posts_buffer', 5 );
27
+ $finished = $this->engine->parse( $limit, $limitsize, $message ); // $message is set by run()
28
+ echo json_encode(
29
+ array(
30
+ 'success' => true,
31
+ 'finished' => $finished,
32
+ 'limit' => $limit + $limitsize,
33
+ 'message' => $message )
34
+ );
35
+ die();
36
+ }
37
+
38
+ function wp_ajax_wpmc_scan() {
39
+ global $wpdb;
40
+
41
+ $method = get_option( 'wpmc_method', 'media' );
42
+ if ( !$this->admin->is_registered() )
43
+ $method = 'media';
44
+
45
+ if ( $method == 'files' ) {
46
+ $output = null;
47
+ $path = isset( $_POST['path'] ) ? $_POST['path'] : null;
48
+ $files = $this->engine->get_files( $path );
49
+
50
+ if ( $files === null ) {
51
+ $output = array(
52
+ 'results' => array(),
53
+ 'success' => true,
54
+ 'message' => __( "No files for this path ($path).", 'media-cleaner' )
55
+ );
56
+ }
57
+ else {
58
+ $output = array(
59
+ 'results' => $files,
60
+ 'success' => true,
61
+ 'message' => __( "Files retrieved.", 'media-cleaner' )
62
+ );
63
+ }
64
+ echo json_encode( $output );
65
+ die();
66
+ }
67
+
68
+ if ( $method == 'media' ) {
69
+ $limit = isset( $_POST['limit'] ) ? $_POST['limit'] : 0;
70
+ $limitsize = get_option( 'wpmc_medias_buffer', 100 );
71
+ $results = $this->engine->get_media_entries( $limit, $limitsize );
72
+ $finished = count( $results ) < $limitsize;
73
+ echo json_encode(
74
+ array(
75
+ 'results' => $results,
76
+ 'success' => true,
77
+ 'finished' => $finished,
78
+ 'limit' => $limit + $limitsize,
79
+ 'message' => __( "Medias retrieved.", 'media-cleaner' ) )
80
+ );
81
+ die();
82
+ }
83
+
84
+ // No task.
85
+ echo json_encode( array( 'success' => false, 'message' => __( "No task.", 'media-cleaner' ) ) );
86
+ die();
87
+ }
88
+
89
+ function wp_ajax_wpmc_scan_do() {
90
+ // For debug, to pretend there is a timeout
91
+ //$this->core->deepsleep(10);
92
+ //header("HTTP/1.0 408 Request Timeout");
93
+ //exit;
94
+
95
+ ob_start();
96
+ $type = $_POST['type'];
97
+ $data = $_POST['data'];
98
+ $this->core->timeout_check_start( count( $data ) );
99
+ $success = 0;
100
+ if ( $type == 'file' ) {
101
+ do_action( 'wpmc_check_file_init' ); // Build_CroppedFile_Cache() in pro core.php
102
+ }
103
+ foreach ( $data as $piece ) {
104
+ $this->core->timeout_check();
105
+ if ( $type == 'file' ) {
106
+ $this->core->log( "Check File: {$piece}" );
107
+
108
+ $result = ( $this->engine->check_file( $piece ) ? 1 : 0 );
109
+
110
+ if ( $result )
111
+ $success += $result;
112
+ }
113
+ else if ( $type == 'media' ) {
114
+ $this->core->log( "Checking Media #{$piece}" );
115
+ $result = ( $this->engine->check_media( $piece ) ? 1 : 0 );
116
+ if ( $result )
117
+ $success += $result;
118
+ }
119
+ $this->core->log();
120
+ $this->core->timeout_check_additem();
121
+ }
122
+ ob_end_clean();
123
+ echo json_encode(
124
+ array(
125
+ 'success' => true,
126
+ 'result' => array( 'type' => $type, 'data' => $data, 'success' => $success ),
127
+ 'message' => __( "Items checked.", 'media-cleaner' )
128
+ )
129
+ );
130
+ die();
131
+ }
132
+
133
+ function wp_ajax_wpmc_get_all_issues() {
134
+ global $wpdb;
135
+ $isTrash = ( isset( $_POST['isTrash'] ) && $_POST['isTrash'] == 1 ) ? true : false;
136
+ $table_name = $wpdb->prefix . "mclean_scan";
137
+ $q = "SELECT id FROM $table_name WHERE ignored = 0 AND deleted = " . ( $isTrash ? 1 : 0 );
138
+ if ( $search = ( isset( $_POST['s'] ) && $_POST['s'] ) ? sanitize_text_field( $_POST['s'] ) : '' )
139
+ $q = $wpdb->prepare( $q . ' AND path LIKE %s', '%' . $wpdb->esc_like( $search ) . '%' );
140
+ $ids = $wpdb->get_col( $q );
141
+
142
+ echo json_encode(
143
+ array(
144
+ 'results' => array( 'ids' => $ids ),
145
+ 'success' => true,
146
+ 'message' => __( "List generated.", 'media-cleaner' )
147
+ )
148
+ );
149
+ die;
150
+ }
151
+
152
+ function wp_ajax_wpmc_get_all_deleted() {
153
+ global $wpdb;
154
+ $table_name = $wpdb->prefix . "mclean_scan";
155
+ $ids = $wpdb->get_col( "SELECT id FROM $table_name WHERE ignored = 0 AND deleted = 1" );
156
+ echo json_encode(
157
+ array(
158
+ 'results' => array( 'ids' => $ids ),
159
+ 'success' => true,
160
+ 'message' => __( "List generated.", 'media-cleaner' )
161
+ )
162
+ );
163
+ die;
164
+ }
165
+
166
+ function wp_ajax_wpmc_delete_do() {
167
+ ob_start();
168
+ $data = $_POST['data'];
169
+ $success = 0;
170
+ foreach ( $data as $piece ) {
171
+ $success += ( $this->core->delete( $piece ) ? 1 : 0 );
172
+ }
173
+ ob_end_clean();
174
+ echo json_encode(
175
+ array(
176
+ 'success' => true,
177
+ 'result' => array( 'data' => $data, 'success' => $success ),
178
+ 'message' => __( "Status unknown.", 'media-cleaner' )
179
+ )
180
+ );
181
+ die();
182
+ }
183
+
184
+ function wp_ajax_wpmc_ignore_do() {
185
+ ob_start();
186
+ $data = $_POST['data'];
187
+ $success = 0;
188
+ foreach ( $data as $piece ) {
189
+ $success += ( $this->core->ignore( $piece ) ? 1 : 0 );
190
+ }
191
+ ob_end_clean();
192
+ echo json_encode(
193
+ array(
194
+ 'success' => true,
195
+ 'result' => array( 'data' => $data, 'success' => $success ),
196
+ 'message' => __( "Status unknown.", 'media-cleaner' )
197
+ )
198
+ );
199
+ die();
200
+ }
201
+
202
+ function wp_ajax_wpmc_recover_do() {
203
+ ob_start();
204
+ $data = $_POST['data'];
205
+ $success = 0;
206
+ foreach ( $data as $piece ) {
207
+ $success += ( $this->core->recover( $piece ) ? 1 : 0 );
208
+ }
209
+ ob_end_clean();
210
+ echo json_encode(
211
+ array(
212
+ 'success' => true,
213
+ 'result' => array( 'data' => $data, 'success' => $success ),
214
+ 'message' => __( "Status unknown.", 'media-cleaner' )
215
+ )
216
+ );
217
+ die();
218
+ }
219
+
220
+ function wp_ajax_wpmc_validate_option() {
221
+ $name = $_POST['name']; // Option Name
222
+ $value = $_POST['value']; // Option Value
223
+ $value = wp_unslash( $value ); // Unescape backslashes
224
+ $validated = $this->admin->validate_option( $name, $value );
225
+ if ( $validated instanceof WP_Error ) { // Invalid value
226
+ $error = array (
227
+ 'code' => $validated->get_error_code() ?: 'invalid_option',
228
+ 'message' => $validated->get_error_message() ?: __( "Invalid Option Value", 'media-cleaner' )
229
+ );
230
+ wp_send_json_error( $error );
231
+ }
232
+ wp_send_json_success();
233
+ }
234
+ }
trunk/common/admin.css ADDED
@@ -0,0 +1,405 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* MEOW-TABS */
2
+
3
+ .meow-tabs {
4
+ display: flex;
5
+ flex-wrap: wrap;
6
+ box-sizing: border-box;
7
+ }
8
+
9
+ .meow-tabs * {
10
+ box-sizing: border-box;
11
+ }
12
+
13
+ .meow-tabs .meow-tabs-input {
14
+ position: absolute;
15
+ opacity: 0;
16
+ }
17
+
18
+ .meow-tabs .meow-tabs-label {
19
+ width: auto;
20
+ padding: 4px 12px;
21
+ background: #3c3c3c;
22
+ cursor: pointer;
23
+ font-weight: bold;
24
+ font-size: 13px;
25
+ text-transform: uppercase;
26
+ color: white;
27
+ transition: background 0.1s, color 0.1s;
28
+ }
29
+
30
+ .meow-tabs .inside {
31
+ display: none;
32
+ width: 100%;
33
+ }
34
+
35
+ .meow-tabs .meow-tabs-label:hover {
36
+ background: #36495f;
37
+ }
38
+
39
+ .meow-tabs .meow-tabs-label:active {
40
+ background: #3C82C7;
41
+ }
42
+
43
+ .meow-tabs .meow-tabs-input:focus + .meow-tabs-label {
44
+ box-shadow: inset 0px 0px 0px 3px #2aa1c0;
45
+ z-index: 1;
46
+ }
47
+
48
+ .meow-tabs .meow-tabs-input:checked + .meow-tabs-label {
49
+ background: #3C82C7 !important;
50
+ }
51
+
52
+ .meow-tabs-input:checked + .meow-tabs-label + .inside {
53
+ display: block;
54
+ order: 99;
55
+ }
56
+
57
+ .meow-tabs .inside {
58
+ background: #fff;
59
+ }
60
+
61
+ /* MEOW-BOX */
62
+
63
+ .meow-box {
64
+ box-sizing: border-box;
65
+ border: 1px solid #e5e5e5;
66
+ box-shadow: 2px 2px 1px rgba(0,0,0,.02);
67
+ background: #fff;
68
+ color: #444;
69
+ margin-bottom: 15px;
70
+ font-size: 13px !important;
71
+ border-top-right-radius: 8px;
72
+ }
73
+
74
+ .meow-box input, .meow-box th, .meow-box label, .meow-box select {
75
+ font-size: 13px !important;
76
+ }
77
+
78
+ .meow-box small {
79
+ font-size: 12px !important;
80
+ }
81
+
82
+ .meow-box h3 {
83
+ font-size: 13px;
84
+ padding: 4px 12px;
85
+ margin: 0;
86
+ background: #3c3c3c;
87
+ color: #ffffff;
88
+ text-transform: uppercase;
89
+ border-top-right-radius: 8px;
90
+ /*border-bottom: 1px solid #eee;*/
91
+ }
92
+
93
+ .meow-box h3 .dashicons {
94
+ position: relative;
95
+ top: 0px;
96
+ margin-right: 5px;
97
+ }
98
+
99
+ .meow-box .pro_info {
100
+ padding: 5px;
101
+ margin: 10px -10px 5px -10px;
102
+ font-size: 11px;
103
+ line-height: 13px;
104
+ }
105
+
106
+ .meow-box .pro_info.enabled {
107
+ background: #4482d2;
108
+ border-left: 5px solid #4482d2;
109
+ color: white;
110
+ }
111
+
112
+ .meow-box .pro_info.disabled {
113
+ background: #96555b;
114
+ border-left: 5px solid #632329;
115
+ color: white;
116
+ }
117
+
118
+ .meow-box .pro_info.disabled a {
119
+ background: #96555b;
120
+ color: red;
121
+ text-decoration: none;
122
+ }
123
+
124
+ .meow-box .inside {
125
+ margin: 10px;
126
+ }
127
+
128
+ .meow-box th {
129
+ padding: 10px 10px 10px 0px;
130
+ width: 22%;
131
+ }
132
+
133
+ .meow-box td {
134
+ padding: 10px 10px;
135
+ }
136
+
137
+ .meow-box p.submit, .meow-box div.submit {
138
+ text-align: right;
139
+ margin: 10px -10px -10px -10px;
140
+ padding: 7px 10px 10px 0px !important;
141
+ border-top: 1px solid #eee !important;
142
+ max-width: inherit;
143
+ background: rgba(125, 125, 125, 0.04);
144
+ border-radius: 0px;
145
+ }
146
+
147
+ .meow-box [type="text"], .meow-box select {
148
+ width: 100%;
149
+ }
150
+
151
+ /* CONTROLS INSIDE A COLUMN (LABEL ON THE LEFT, VALUE ON THE RIGHT) */
152
+ .meow-box td [type="text"], .meow-box td [type="checkbox"], .meow-box td select {
153
+ margin-top: -3px;
154
+ }
155
+
156
+ .meow-header-ad {
157
+ float: right;
158
+ }
159
+
160
+ /* BUTTONS */
161
+
162
+ .meow-button-xs {
163
+ font-size: 10px !important;
164
+ height: 20px !important;
165
+ line-height: 18px !important;
166
+ position: relative !important;
167
+ top: 1px !important;
168
+ text-align: center !important
169
+ }
170
+
171
+ /* MODAL */
172
+
173
+ #meow-modal-info-backdrop {
174
+ background: rgba(0, 0, 0, 0.75);
175
+ position: fixed;
176
+ top: 0px;
177
+ bottom: 0px;
178
+ right: 0px;
179
+ left: 0px;
180
+ z-index: 10000;
181
+ }
182
+
183
+ #meow-modal-info {
184
+ background: white;
185
+ box-shadow: 0px 0px 5px black;
186
+ padding: 15px;
187
+ overflow-y: scroll;
188
+ position: fixed;
189
+ z-index: 10000;
190
+ left: 100px;
191
+ right: 100px;
192
+ top: 50px;
193
+ bottom: 50px;
194
+ }
195
+
196
+ #meow-modal-info h3 {
197
+ height: 25px;
198
+ border-bottom: 2px solid #808080;
199
+ }
200
+
201
+ #meow-modal-info td {
202
+ text-align: center;
203
+ font-size: 11px;
204
+ border: 1px solid #D3DCFF;
205
+ padding: 10px 15px;
206
+ background: #EFF8FF;
207
+ }
208
+
209
+ #meow-modal-info .close {
210
+ float: right;
211
+ font-size: 18px;
212
+ font-weight: bold;
213
+ font-family: Verdana;
214
+ cursor: pointer;
215
+ }
216
+
217
+ #meow-modal-info .loading {
218
+ background-color: #F2F2F2;
219
+ text-align: center;
220
+ padding-top: 10px;
221
+ background-size: 32px 32px;
222
+ }
223
+
224
+ #meow-modal-info .meow-sized-image {
225
+ width: 19px;
226
+ height: 19px;
227
+ margin-right: 10px;
228
+ }
229
+
230
+ /* IMAGE SIZES RELATED INFO */
231
+
232
+ .meow-sized-images {
233
+ margin-top: 0px;
234
+ cursor: pointer;
235
+ }
236
+
237
+ .meow-sized-images li, #meow-modal-info .meow-sized-image {
238
+ display: block;
239
+ color: white;
240
+ padding: 5px 0px;
241
+ font-size: 10px;
242
+ text-align: center;
243
+ width: 18px;
244
+ height: 18px;
245
+ line-height: 8px;
246
+ background: black;
247
+ float: left;
248
+ margin-right: 1px;
249
+ box-sizing: border-box;
250
+ margin-bottom: 2px;
251
+ }
252
+
253
+ .meow-sized-images:after {
254
+ clear: both;
255
+ content:""; display:table;
256
+ }
257
+
258
+ .meow-sized-images + span {
259
+ position: relative;
260
+ top: -12px;
261
+ }
262
+
263
+ /* MEOW COLORS */
264
+
265
+ .meow-bk-blue {
266
+ background: #3C82C7 !important;
267
+ color: white;
268
+ }
269
+
270
+ .meow-bk-orange {
271
+ background: #f1900e !important;
272
+ color: white;
273
+ }
274
+
275
+ .meow-bk-red {
276
+ background: #c53a47 !important;
277
+ color: white;
278
+ }
279
+
280
+ .meow-bk-gray {
281
+ background: gray !important;
282
+ color: white;
283
+ }
284
+
285
+ .meow-bk-green {
286
+ background: #2b9463 !important;
287
+ color: white;
288
+ }
289
+
290
+ .meow-bk-black {
291
+ background: #3c3c3c !important;
292
+ color: white;
293
+ }
294
+
295
+ .meow-bk-purple {
296
+ background: #984c96 !important;
297
+ color: white;
298
+ }
299
+
300
+ /* DASHBOARD */
301
+
302
+ .meow-dashboard {
303
+ box-sizing: border-box;
304
+ }
305
+
306
+ .meow-dashboard .meow-box ul {
307
+ padding: 0px;
308
+ margin: 0px;
309
+ }
310
+
311
+ .meow-featured-plugins .meow-box li {
312
+ min-height: 80px;
313
+ }
314
+
315
+ .meow-dashboard .meow-box li {
316
+ border-bottom: 1px solid #eee;
317
+ margin: 0px;
318
+ padding: 10px;
319
+ box-sizing: border-box;
320
+ }
321
+
322
+ .meow-dashboard .meow-box li img {
323
+ width: 80px;
324
+ height: 80px;
325
+ float: left;
326
+ margin: -10px 10px -10px -10px;
327
+
328
+ }
329
+
330
+ .meow-dashboard .meow-box li:last-child {
331
+ border: none;
332
+ padding-bottom: 0px;
333
+ }
334
+
335
+ .meow-button-xs .dashicons {
336
+ font-size: 18px !important;
337
+ }
338
+
339
+ .meow-button-xs.updating-message:before {
340
+ margin-top: 0px !important;
341
+ }
342
+
343
+ .meow-button-xs.updating-message * {
344
+ display: none;
345
+ }
346
+
347
+ #phpinfo {
348
+ font-size: 11px;
349
+ }
350
+
351
+ #phpinfo .e {
352
+ padding: 5px;
353
+ background: lightgray;
354
+ }
355
+
356
+ #phpinfo .h th {
357
+ padding: 5px;
358
+ color: white;
359
+ background: gray;
360
+ text-align: left;
361
+ font-size: 14px !important;
362
+ }
363
+
364
+ #phpinfo h1 {
365
+ padding: 10px 0px;
366
+ font-weight: bold;
367
+ }
368
+
369
+ #phpinfo h2 {
370
+ padding: 10px 0px;
371
+ font-weight: bold;
372
+ font-size: 20px;
373
+ }
374
+
375
+ #error_log {
376
+ font-size: 12px;
377
+ }
378
+
379
+ /* ROWS AND COLUMNS */
380
+ .meow-col { display: block; float:left; margin: 1% 0 1% 1.6%; }
381
+ .meow-col:first-child { margin-left: 0; }
382
+
383
+ /* GROUPING */
384
+ .meow-row { clear: both; padding: 0px; margin: 0px; }
385
+ .meow-row:before,
386
+ .meow-row:after { content:""; display:table; }
387
+ .meow-row:after { clear:both; }
388
+ .meow-row { zoom:1; /* For IE 6/7 */ }
389
+
390
+ /* GRID OF THREE */
391
+ .meow-span_3_of_3 { width: 100%; }
392
+ .meow-span_2_of_3 { width: 66.1%; }
393
+ .meow-span_1_of_3 { width: 32.2%; }
394
+
395
+ /* GRID OF TWO */
396
+ .meow-span_2_of_2 { width: 100%; }
397
+ .meow-span_1_of_2 { width: 49.2%; }
398
+
399
+ @media only screen and (max-width: 480px) {
400
+ .meow-col { margin: 0 0 0px 0;}
401
+ .meow-span_3_of_3, .meow-span_2_of_3, .meow-span_1_of_3 { width: 100%; }
402
+ .meow-span_2_of_2, .meow-span_1_of_2 { width: 100%; }
403
+ .meow-header-ad { display: none !important; }
404
+ .meow-box [type="text"] { width: 100% !important; }
405
+ }
trunk/common/admin.php ADDED
@@ -0,0 +1,476 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ if ( !class_exists( 'MeowApps_Admin' ) ) {
4
+
5
+ class MeowApps_Admin {
6
+
7
+ public static $logo = 'data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxIiB2aWV3Qm94PSIwIDAgMTY1IDE2NSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KICA8c3R5bGU+CiAgICAuc3Qye2ZpbGw6IzgwNDYyNX0uc3Qze2ZpbGw6I2ZkYTk2MH0KICA8L3N0eWxlPgogIDxwYXRoIGQ9Ik03MiA3YTc2IDc2IDAgMCAxIDg0IDkxQTc1IDc1IDAgMSAxIDcyIDd6IiBmaWxsPSIjNGE2YjhjIi8+CiAgPHBhdGggZD0iTTQ4IDQ4YzIgNSAyIDEwIDUgMTQgNSA4IDEzIDE3IDIyIDIwbDEtMTBjMS0yIDMtMyA1LTNoMTNjMiAwIDQgMSA1IDNsMyA5IDQtMTBjMi0zIDYtMiA5LTJoMTFjMyAyIDMgNSAzIDhsMiAzN2MwIDMtMSA3LTQgOGgtMTJjLTIgMC0zLTItNS00LTEgMS0yIDMtNCAzLTUgMS05IDEtMTMtMS0zIDItNSAyLTkgMnMtOSAxLTEwLTNjLTItNC0xLTggMC0xMi04LTMtMTUtNy0yMi0xMi03LTctMTUtMTQtMjAtMjMtMy00LTUtOC01LTEzIDEtNCAzLTEwIDYtMTMgNC0zIDEyLTIgMTUgMnoiIGZpbGw9IiMxMDEwMTAiLz4KICA8cGF0aCBjbGFzcz0ic3QyIiBkPSJNNDMgNTFsNCAxMS02IDVoLTZjLTMtNS0zLTExIDAtMTYgMi0yIDYtMyA4IDB6Ii8+CiAgPHBhdGggY2xhc3M9InN0MyIgZD0iTTQ3IDYybDMgNmMwIDMgMCA0LTIgNnMtNCAyLTcgMmwtNi05aDZsNi01eiIvPgogIDxwYXRoIGNsYXNzPSJzdDIiIGQ9Ik01MCA2OGw4IDljLTMgMy01IDYtOSA4bC04LTljMyAwIDUgMCA3LTJzMy0zIDItNnoiLz4KICA8cGF0aCBkPSJNODIgNzRoMTJsNSAxOCAzIDExIDgtMjloMTNsMiA0MmgtOGwtMS0yLTEtMzEtMTAgMzItNyAxLTktMzMtMSAyOS0xIDRoLThsMy00MnoiIGZpbGw9IiNmZmYiLz4KICA8cGF0aCBjbGFzcz0ic3QzIiBkPSJNNTggNzdsNSA1Yy0xIDQtMiA4LTcgOGwtNy01YzQtMiA2LTUgOS04eiIvPgogIDxwYXRoIGNsYXNzPSJzdDIiIGQ9Ik02MyA4Mmw5IDUtNiA5LTEwLTZjNSAwIDYtNCA3LTh6Ii8+CiAgPHBhdGggY2xhc3M9InN0MyIgZD0iTTcyIDg3bDMgMS0xIDExLTgtMyA2LTEweiIvPgo8L3N2Zz4K';
8
+
9
+ public static $loaded = false;
10
+ public static $admin_version = "2.0";
11
+
12
+ public $prefix; // prefix used for actions, filters (mfrh)
13
+ public $mainfile; // plugin main file (media-file-renamer.php)
14
+ public $domain; // domain used for translation (media-file-renamer)
15
+
16
+ public function __construct( $prefix, $mainfile, $domain, $disableReview = false ) {
17
+
18
+ // Core Admin (used by all Meow Apps plugins)
19
+ if ( !MeowApps_Admin::$loaded ) {
20
+ if ( is_admin() ) {
21
+ add_action( 'admin_menu', array( $this, 'admin_menu_start' ) );
22
+ add_action( 'admin_enqueue_scripts', array( $this, 'admin_enqueue_scripts' ) );
23
+ add_action( 'plugins_loaded', array( $this, 'plugins_loaded' ) );
24
+ }
25
+ MeowApps_Admin::$loaded = true;
26
+ }
27
+
28
+ // Variables for this plugin
29
+ $this->prefix = $prefix;
30
+ $this->mainfile = $mainfile;
31
+ $this->domain = $domain;
32
+ $this->is_theme = ( strpos( $this->mainfile, '-theme' ) !== false );
33
+
34
+ // If there is no mainfile, it's either a Pro only Plugin (with no Free version available) or a Theme.
35
+ if ( !$this->is_theme ) {
36
+ register_activation_hook( $mainfile, array( $this, 'show_meowapps_create_rating_date' ) );
37
+ if ( is_admin() ) {
38
+ $license = get_option( $this->prefix . '_license', "" );
39
+ if ( ( !empty( $license ) ) && !file_exists( plugin_dir_path( $this->mainfile ) . 'common/meowapps/admin.php' ) ) {
40
+ add_action( 'admin_notices', array( $this, 'admin_notices_licensed_free' ) );
41
+ }
42
+ if ( !$disableReview ) {
43
+ $rating_date = $this->create_rating_date();
44
+ if ( time() > $rating_date ) {
45
+ add_action( 'admin_notices', array( $this, 'admin_notices_rating' ) );
46
+ }
47
+ }
48
+ }
49
+ }
50
+
51
+ add_filter( 'edd_sl_api_request_verify_ssl', array( $this, 'request_verify_ssl' ), 10, 0 );
52
+ }
53
+
54
+ function request_verify_ssl() {
55
+ return get_option( 'force_sslverify', false );
56
+ }
57
+
58
+ function show_meowapps_create_rating_date() {
59
+ delete_option( 'meowapps_hide_meowapps' );
60
+ $this->create_rating_date();
61
+ }
62
+
63
+ function create_rating_date() {
64
+ $rating_date = get_option( $this->prefix . '_rating_date' );
65
+ if ( empty( $rating_date ) ) {
66
+ $two_months = strtotime( '+2 months' );
67
+ $six_months = strtotime( '+4 months' );
68
+ $rating_date = mt_rand( $two_months, $six_months );
69
+ update_option( $this->prefix . '_rating_date', $rating_date, false );
70
+ }
71
+ return $rating_date;
72
+ }
73
+
74
+ function admin_notices_rating() {
75
+ if ( isset( $_POST[$this->prefix . '_remind_me'] ) ) {
76
+ $two_weeks = strtotime( '+2 weeks' );
77
+ $six_weeks = strtotime( '+6 weeks' );
78
+ $future_date = mt_rand( $two_weeks, $six_weeks );
79
+ update_option( $this->prefix . '_rating_date', $future_date, false );
80
+ return;
81
+ }
82
+ else if ( isset( $_POST[$this->prefix . '_never_remind_me'] ) ) {
83
+ $twenty_years = strtotime( '+5 years' );
84
+ update_option( $this->prefix . '_rating_date', $twenty_years, false );
85
+ return;
86
+ }
87
+ else if ( isset( $_POST[$this->prefix . '_did_it'] ) ) {
88
+ $twenty_years = strtotime( '+10 years' );
89
+ update_option( $this->prefix . '_rating_date', $twenty_years, false );
90
+ return;
91
+ }
92
+ $rating_date = get_option( $this->prefix . '_rating_date' );
93
+ echo '<div class="notice notice-success" data-rating-date="' . date( 'Y-m-d', $rating_date ) . '">';
94
+ echo '<p style="font-size: 100%;">You have been using <b>' . $this->nice_name_from_file( $this->mainfile ) . '</b> for some time now. Thank you! Could you kindly share your opinion with me, along with, maybe, features you would like to see implemented? Then, please <a style="font-weight: bold; color: #b926ff;" target="_blank" href="https://wordpress.org/support/plugin/' . $this->nice_short_url_from_file( $this->mainfile ) . '/reviews/?rate=5#new-post">write a little review</a>. That will also bring me joy and motivation, and I will get back to you :) <u>In the case you already have written a review</u>, please check again. Many reviews got removed from WordPress recently.';
95
+ echo '<p>
96
+ <form method="post" action="" style="float: right;">
97
+ <input type="hidden" name="' . $this->prefix . '_never_remind_me" value="true">
98
+ <input type="submit" name="submit" id="submit" class="button button-red" value="Never remind me!">
99
+ </form>
100
+ <form method="post" action="" style="float: right; margin-right: 10px;">
101
+ <input type="hidden" name="' . $this->prefix . '_remind_me" value="true">
102
+ <input type="submit" name="submit" id="submit" class="button button-primary" value="Remind me in a few weeks...">
103
+ </form>
104
+ <form method="post" action="" style="float: right; margin-right: 10px;">
105
+ <input type="hidden" name="' . $this->prefix . '_did_it" value="true">
106
+ <input type="submit" name="submit" id="submit" class="button button-primary" value="Yes, I did it!">
107
+ </form>
108
+ <div style="clear: both;"></div>
109
+ </p>
110
+ ';
111
+ echo '</div>';
112
+ }
113
+
114
+ function nice_short_url_from_file( $file ) {
115
+ $info = pathinfo( $file );
116
+ if ( !empty( $info ) ) {
117
+ $info['filename'] = str_replace( '-pro', '', $info['filename'] );
118
+ return $info['filename'];
119
+ }
120
+ return "";
121
+ }
122
+
123
+ function nice_name_from_file( $file ) {
124
+ $info = pathinfo( $file );
125
+ if ( !empty( $info ) ) {
126
+ if ( $info['filename'] == 'wplr-sync' ) {
127
+ return "WP/LR Sync";
128
+ }
129
+ $info['filename'] = str_replace( '-', ' ', $info['filename'] );
130
+ $file = ucwords( $info['filename'] );
131
+ }
132
+ return $file;
133
+ }
134
+
135
+ function admin_notices_licensed_free() {
136
+ if ( isset( $_POST[$this->prefix . '_reset_sub'] ) ) {
137
+ delete_option( $this->prefix . '_pro_serial' );
138
+ delete_option( $this->prefix . '_license' );
139
+ return;
140
+ }
141
+ echo '<div class="error">';
142
+ echo '<p>It looks like you are using the free version of the plugin (<b>' . $this->nice_name_from_file( $this->mainfile ) . '</b>) but a license for the Pro version was also found. The Pro version might have been replaced by the Free version during an update (might be caused by a temporarily issue). If it is the case, <b>please download it again</b> from the <a target="_blank" href="https://store.meowapps.com">Meow Store</a>. If you wish to continue using the free version and clear this message, click on this button.';
143
+ echo '<p>
144
+ <form method="post" action="">
145
+ <input type="hidden" name="' . $this->prefix . '_reset_sub" value="true">
146
+ <input type="submit" name="submit" id="submit" class="button" value="Remove the license">
147
+ </form>
148
+ </p>
149
+ ';
150
+ echo '</div>';
151
+ }
152
+
153
+ function display_ads() {
154
+ return !get_option( 'meowapps_hide_ads', false );
155
+ }
156
+
157
+ function display_title( $title = "Meow Apps",
158
+ $author = "By <a style='text-decoration: none;' href='https://meowapps.com' target='_blank'>Jordy Meow</a>" ) {
159
+ if ( !empty( $this->prefix ) && $title !== "Meow Apps" )
160
+ $title = apply_filters( $this->prefix . '_plugin_title', $title );
161
+ if ( $this->display_ads() ) {
162
+ }
163
+ ?>
164
+ <h1 style="line-height: 16px;">
165
+ <img width="42" style="margin-right: 10px; float: left; position: relative; top: -5px;"
166
+ src="<?php echo MeowApps_Admin::$logo ?>"><?php echo $title; ?><br />
167
+ <span style="font-size: 12px"><?php echo $author; ?></span>
168
+ </h1>
169
+ <div style="clear: both;"></div>
170
+ <?php
171
+ }
172
+
173
+ function admin_enqueue_scripts() {
174
+ wp_register_style( 'meowapps-core-css', $this->common_url( 'admin.css' ) );
175
+ wp_enqueue_style( 'meowapps-core-css' );
176
+ }
177
+
178
+ function admin_menu_start() {
179
+ if ( get_option( 'meowapps_hide_meowapps', false ) ) {
180
+ register_setting( 'general', 'meowapps_hide_meowapps' );
181
+ add_settings_field( 'meowapps_hide_ads', 'Meow Apps Menu', array( $this, 'meowapps_hide_dashboard_callback' ), 'general' );
182
+ return;
183
+ }
184
+
185
+ // Creates standard menu if it does NOT exist
186
+ global $submenu;
187
+ if ( !isset( $submenu[ 'meowapps-main-menu' ] ) ) {
188
+ add_menu_page( 'Meow Apps', '<img style="width: 24px; margin-left: -30px; position: absolute; margin-top: -3px;" src="' . MeowApps_Admin::$logo . '" />Meow Apps', 'manage_options', 'meowapps-main-menu',
189
+ array( $this, 'admin_meow_apps' ), '', 82 );
190
+ add_submenu_page( 'meowapps-main-menu', __( 'Dashboard', 'meowapps' ),
191
+ __( 'Dashboard', 'meowapps' ), 'manage_options',
192
+ 'meowapps-main-menu', array( $this, 'admin_meow_apps' ) );
193
+ }
194
+
195
+ add_settings_section( 'meowapps_common_settings', null, null, 'meowapps_common_settings-menu' );
196
+ add_settings_field( 'meowapps_hide_meowapps', "Main Menu",
197
+ array( $this, 'meowapps_hide_dashboard_callback' ),
198
+ 'meowapps_common_settings-menu', 'meowapps_common_settings' );
199
+ add_settings_field( 'meowapps_force_sslverify', "SSL Verify",
200
+ array( $this, 'meowapps_force_sslverify_callback' ),
201
+ 'meowapps_common_settings-menu', 'meowapps_common_settings' );
202
+ // add_settings_field( 'meowapps_hide_ads', "Ads",
203
+ // array( $this, 'meowapps_hide_ads_callback' ),
204
+ // 'meowapps_common_settings-menu', 'meowapps_common_settings' );
205
+ register_setting( 'meowapps_common_settings', 'force_sslverify' );
206
+ register_setting( 'meowapps_common_settings', 'meowapps_hide_meowapps' );
207
+ register_setting( 'meowapps_common_settings', 'meowapps_hide_ads' );
208
+ }
209
+
210
+ function meowapps_hide_ads_callback() {
211
+ $value = get_option( 'meowapps_hide_ads', null );
212
+ $html = '<input type="checkbox" id="meowapps_hide_ads" name="meowapps_hide_ads" value="1" ' .
213
+ checked( 1, get_option( 'meowapps_hide_ads' ), false ) . '/>';
214
+ $html .= __( '<label>Hide</label><br /><small>Doesn\'t display the ads.</small>', 'meowapps' );
215
+ echo $html;
216
+ }
217
+
218
+ function meowapps_hide_dashboard_callback() {
219
+ $value = get_option( 'meowapps_hide_meowapps', null );
220
+ $html = '<input type="checkbox" id="meowapps_hide_meowapps" name="meowapps_hide_meowapps" value="1" ' .
221
+ checked( 1, get_option( 'meowapps_hide_meowapps' ), false ) . '/>';
222
+ $html .= __( '<label>Hide <b>Meow Apps</b> Menu</label><br /><small>Hide Meow Apps menu and all its components, for a cleaner admin. This option will be reset if a new Meow Apps plugin is installed. <b>Once activated, an option will be added in your General settings to display it again.</b></small>', 'meowapps' );
223
+ echo $html;
224
+ }
225
+
226
+ function meowapps_force_sslverify_callback() {
227
+ $value = get_option( 'force_sslverify', null );
228
+ $html = '<input type="checkbox" id="force_sslverify" name="force_sslverify" value="1" ' .
229
+ checked( 1, get_option( 'force_sslverify' ), false ) . '/>';
230
+ $html .= __( '<label>Force</label><br /><small>Updates and licenses checks are usually made without checking SSL certificates and it is actually fine this way. But if you are intransigent when it comes to SSL matters, this option will force it.</small>', 'meowapps' );
231
+ echo $html;
232
+ }
233
+
234
+ function display_serialkey_box( $url = "https://meowapps.com/" ) {
235
+ $html = '<div class="meow-box">';
236
+ $html .= '<h3 class="' . ( $this->is_registered( $this->prefix ) ? 'meow-bk-blue' : 'meow-bk-red' ) . '">Pro Version ' .
237
+ ( $this->is_registered( $this->prefix ) ? '(enabled)' : '(disabled)' ) . '</h3>';
238
+ $html .= '<div class="inside">';
239
+ echo $html;
240
+ $html = apply_filters( $this->prefix . '_meowapps_license_input', ( 'More information about the Pro version here:
241
+ <a target="_blank" href="' . $url . '">' . $url . '</a>. If you actually bought the Pro version already, please remove the current plugin and download the Pro version from your account at the <a target="_blank" href="https://store.meowapps.com/account/downloads/">Meow Apps Store</a>.' ), $url );
242
+ $html .= '</div>';
243
+ $html .= '</div>';
244
+ echo $html;
245
+ }
246
+
247
+ function is_registered() {
248
+ return apply_filters( $this->prefix . '_meowapps_is_registered', false, $this->prefix );
249
+ }
250
+
251
+ function check_install( $plugin ) {
252
+ $pro = false;
253
+
254
+ $pluginpath = trailingslashit( plugin_dir_path( __FILE__ ) ) . '../../' . $plugin . '-pro';
255
+ if ( !file_exists( $pluginpath ) ) {
256
+ $pluginpath = trailingslashit( plugin_dir_path( __FILE__ ) ) . '../../' . $plugin;
257
+ if ( !file_exists( $pluginpath ) ) {
258
+ $url = wp_nonce_url( "update.php?action=install-plugin&plugin=$plugin", "install-plugin_$plugin" );
259
+ return "<a href='$url'><small><span class='' style='float: right;'>install</span></small></a>";
260
+ }
261
+ }
262
+ else {
263
+ $pro = true;
264
+ $plugin = $plugin . "-pro";
265
+ }
266
+
267
+ $plugin_file = $plugin . '/' . $plugin . '.php';
268
+ if ( is_plugin_active( $plugin_file ) ) {
269
+ if ( $plugin == 'wplr-sync' )
270
+ $pro = true;
271
+ if ( $pro )
272
+ return "<small><span style='float: right;'><span class='dashicons dashicons-heart' style='color: rgba(255, 63, 0, 1); font-size: 30px !important; margin-right: 10px;'></span></span></small>";
273
+ else
274
+ return "<small><span style='float: right;'><span class='dashicons dashicons-yes' style='color: #00b4ff; font-size: 30px !important; margin-right: 10px;'></span></span></small>";
275
+ }
276
+ else {
277
+ $url = wp_nonce_url( self_admin_url( 'plugins.php?action=activate&plugin=' . $plugin_file ),
278
+ 'activate-plugin_' . $plugin_file );
279
+ return '<small><span style="color: black; float: right;">off
280
+ (<a style="color: rgba(30,140,190,1); text-decoration: none;" href="' .
281
+ $url . '">enable</a>)</span></small>';
282
+ }
283
+ }
284
+
285
+ function common_url( $file ) {
286
+ die( "Meow Apps: The function common_url( \$file ) needs to be overriden." );
287
+ // Normally, this should be used:
288
+ // return plugin_dir_url( __FILE__ ) . ( '\/common\/' . $file );
289
+ }
290
+
291
+ function meowapps_logo_url() {
292
+ return $this->common_url( 'img/meowapps.png' );
293
+ }
294
+
295
+ function plugins_loaded() {
296
+ if ( isset( $_GET[ 'tool' ] ) && $_GET[ 'tool' ] == 'error_log' ) {
297
+ $sec = "5";
298
+ header( "Refresh: $sec;" );
299
+ }
300
+ }
301
+
302
+ function admin_meow_apps() {
303
+
304
+ echo '<div class="wrap meow-dashboard">';
305
+ if ( isset( $_GET['tool'] ) && $_GET['tool'] == 'phpinfo' ) {
306
+ echo "<a href=\"javascript:history.go(-1)\">< Go back</a><br /><br />";
307
+ echo '<div id="phpinfo">';
308
+ ob_start();
309
+ phpinfo();
310
+ $pinfo = ob_get_contents();
311
+ ob_end_clean();
312
+ $pinfo = preg_replace( '%^.*<body>(.*)</body>.*$%ms','$1', $pinfo );
313
+ echo $pinfo;
314
+ echo "</div>";
315
+ }
316
+ else if ( isset( $_GET['tool'] ) && $_GET['tool'] == 'error_log' ) {
317
+ $log_msg = '=== MEOW APPS DEBUG (This is not an error) ===';
318
+ if ( isset( $_POST['write_logs'] ) ) {
319
+ error_log( $log_msg );
320
+ }
321
+ $errorpath = ini_get( 'error_log' );
322
+ echo "<a href=\"javascript:history.go(-1)\">< Go back</a><br /><br />";
323
+ echo '
324
+ <form method="post">
325
+ <input type="hidden" name="write_logs" value="true">
326
+ <input class="button button-primary" type="submit" value="Write in the Error Logs">
327
+ </form><br />';
328
+ echo '<div id="error_log">';
329
+ if ( file_exists( $errorpath ) ) {
330
+ echo "Now (auto-reload every 5 seconds): [" . date( "d-M-Y H:i:s", time() ) . " UTC]<br /><br /><h2 style='margin: 0px;'>Errors (order by latest)</h2>";
331
+ $errors = file_get_contents( $errorpath );
332
+ $errors = explode( "\n", $errors );
333
+ $errors = array_reverse( $errors );
334
+ $errors = implode( "<br />", $errors );
335
+ echo $errors;
336
+ }
337
+ else {
338
+ echo "The PHP Error Logs cannot be found. Please ask your hosting service for it.";
339
+ }
340
+ echo "</div>";
341
+
342
+ }
343
+ else {
344
+
345
+ ?>
346
+ <?php $this->display_title( 'Meow Apps' ); ?>
347
+ <p>
348
+ <?php _e( 'Meow Apps is run by Jordy Meow, a photographer and software developer living in Japan (and taking <a target="_blank" href="http://offbeatjapan.org">a lot of photos</a>). Meow Apps is a suite of plugins focusing on photography, imaging, optimization and it teams up with the best players in the community (other themes and plugins developers). For more information, please check <a href="http://meowapps.com" target="_blank">Meow Apps</a>.', 'meowapps' )
349
+ ?>
350
+ </p>
351
+
352
+ <h2 style="margin-bottom: 0px; margin-top: 25px;">Featured Plugins</h2>
353
+ <div class="meow-row meow-featured-plugins">
354
+ <div class="meow-box meow-col meow-span_1_of_2">
355
+ <ul class="">
356
+ <li><img src='<?= $this->common_url( 'img/media-cleaner.jpg' ) ?>' />
357
+ <a href='https://meowapps.com/plugin/media-cleaner/'><b>Media Cleaner</b></a>
358
+ <?php echo $this->check_install( 'media-cleaner' ) ?><br />
359
+ Detect the files which are not in use.</li>
360
+ <li><img src='<?= $this->common_url( 'img/media-file-renamer.jpg' ) ?>' />
361
+ <a href='https://meowapps.com/plugin/media-file-renamer/'><b>Media File Renamer</b></a>
362
+ <?php echo $this->check_install( 'media-file-renamer' ) ?><br />
363
+ For nicer filenames and a better SEO.</li>
364
+ <li><img src='<?= $this->common_url( 'img/default.png' ) ?>' />
365
+ <a href='https://meowapps.com/plugin/contact-form-block/'><b>Contact Form Block</b></a>
366
+ <?php echo $this->check_install( 'contact-form-block' ) ?><br />
367
+ A simpler, nicer, prettier contact form.</li>
368
+ <!--li><img src='<?= $this->common_url( 'img/wp-retina-2x.jpg' ) ?>' />
369
+ <a href='https://meowapps.com/plugin/wp-retina-2x/'><b>WP Retina 2x</b></a>
370
+ <?php echo $this->check_install( 'wp-retina-2x' ) ?><br />
371
+ The famous plugin that adds Retina support.</li-->
372
+
373
+ </ul>
374
+ </div>
375
+ <div class="meow-box meow-col meow-span_1_of_2 ">
376
+ <ul class="">
377
+ <li><img src='<?= $this->common_url( 'img/meow-gallery.jpg' ) ?>' />
378
+ <a href='https://meowapps.com/plugin/meow-gallery/'><b>Meow Gallery</b></a>
379
+ <?php echo $this->check_install( 'meow-gallery' ) ?><br />
380
+ Beautiful but lightweight gallery with many layouts. The only one that allows you to uninstall it without losing anything.</li>
381
+ <li><img src='<?= $this->common_url( 'img/meow-lightbox.jpg' ) ?>' />
382
+ <a href='https://meowapps.com/plugin/meow-lightbox/'><b>Meow Lightbox</b></a>
383
+ <?php echo $this->check_install( 'meow-lightbox' ) ?><br />
384
+ Pretty and ultra-optimized Lightbox which can also display your EXIF data. You will love it.</li>
385
+ <li><img src='<?= $this->common_url( 'img/wplr-sync.jpg' ) ?>' />
386
+ <a href='https://meowapps.com/plugin/wplr-sync/'><b>WP/LR Sync</b></a>
387
+ <?php echo $this->check_install( 'wplr-sync' ) ?><br />
388
+ Synchronize your Lightroom to your WordPress. This plugin is loved by all the photographers using Lightroom and WordPress.</li>
389
+ </ul>
390
+ </div>
391
+ </div>
392
+
393
+ <h2>Recommendations</h2>
394
+ <div style="background: white; padding: 5px 15px 5px 15px; box-shadow: 2px 2px 1px rgba(0,0,0,.02);">
395
+ <p>
396
+ <?php _e( 'Too many WordPress installs are blown-up with useless and/or huge plugins, and bad practices. But that is because most users are overwhelmed by the diversity and immensity of the WordPress jungle. One rule of thumb is to keep your install the simplest as possible, with the least amount of plugins, in number and weight.', 'meowapps' )?>
397
+ </p>
398
+ <p>
399
+ <?php _e( 'Articles are kept being updated on the Meow Apps website, with all the latest recommendations. Please have a look and make your WordPress simpler, faster, better: ', 'meowapps' )?>
400
+ <a href='https://meowapps.com/debugging-wordpress/' target='_blank'>
401
+ How To Debug</a>,
402
+ <a href='https://meowapps.com/seo-optimization/' target='_blank'>
403
+ SEO Checklist & Optimization</a>,
404
+ <a href='https://meowapps.com/clean-optimize-wordpress/' target='_blank'>
405
+ Clean Up and Optimize</a>,
406
+ <a href='https://meowapps.com/optimize-images-cdn/' target='_blank'>
407
+ Optimize Images</a>,
408
+ <a href='https://meowapps.com/best-hosting-services-wordpress/' target='_blank'>
409
+ Best Hosting Services</a>.
410
+ </p>
411
+ </div>
412
+
413
+ <h2 style="margin-bottom: 0px; margin-top: 25px;">Common Options & Tools</h2>
414
+ <div class="meow-row">
415
+ <div class="meow-box meow-col meow-span_2_of_3">
416
+ <h3><span class="dashicons dashicons-admin-tools"></span> Common</h3>
417
+ <div class="inside">
418
+ <form method="post" action="options.php">
419
+ <?php settings_fields( 'meowapps_common_settings' ); ?>
420
+ <?php do_settings_sections( 'meowapps_common_settings-menu' ); ?>
421
+ <?php submit_button(); ?>
422
+ </form>
423
+ </div>
424
+ </div>
425
+
426
+ <div class="meow-box meow-col meow-span_1_of_3">
427
+ <h3><span class="dashicons dashicons-admin-tools"></span> Debug</h3>
428
+ <div class="inside">
429
+ <ul>
430
+ <li><a href="?page=meowapps-main-menu&amp;tool=error_log">Display Error Log</a></li>
431
+ <li><a href="?page=meowapps-main-menu&amp;tool=phpinfo">Display PHP Info</a></li>
432
+ </ul>
433
+ </div>
434
+ </div>
435
+
436
+ <div class="meow-box meow-col meow-span_1_of_3">
437
+ <h3><span class="dashicons dashicons-admin-tools"></span> Post Types (used by this install)</h3>
438
+ <div class="inside">
439
+ <?php
440
+ global $wpdb;
441
+ // Maybe we could avoid to check more post_types.
442
+ // SELECT post_type, COUNT(*) FROM `wp_posts` GROUP BY post_type
443
+ $types = $wpdb->get_results( "SELECT post_type as 'type', COUNT(*) as 'count' FROM $wpdb->posts GROUP BY post_type" );
444
+ $result = array();
445
+ foreach( $types as $type )
446
+ array_push( $result, "{$type->type} ({$type->count})" );
447
+ echo implode( $result, ', ' );
448
+ ?>
449
+ </div>
450
+ </div>
451
+ </div>
452
+
453
+ <?php
454
+ }
455
+ echo "<br /><small style='color: lightgray;'>Meow Admin " . MeowApps_Admin::$admin_version . "</small></div>";
456
+ }
457
+
458
+ // HELPERS
459
+
460
+ static function size_shortname( $name ) {
461
+ $name = preg_split( '[_-]', $name );
462
+ $short = strtoupper( substr( $name[0], 0, 1 ) );
463
+ if ( count( $name ) > 1 )
464
+ $short .= strtoupper( substr( $name[1], 0, 1 ) );
465
+ return $short;
466
+ }
467
+
468
+ }
469
+
470
+ }
471
+
472
+ if ( file_exists( plugin_dir_path( __FILE__ ) . '/meowapps/admin.php' ) ) {
473
+ require( 'meowapps/admin.php' );
474
+ }
475
+
476
+ ?>
trunk/common/img/default.png ADDED
Binary file
trunk/common/img/media-cleaner.jpg ADDED
Binary file
trunk/common/img/media-file-renamer.jpg ADDED
Binary file
trunk/common/img/meow-gallery.jpg ADDED
Binary file
trunk/common/img/meow-lightbox.jpg ADDED
Binary file
trunk/common/img/meowapps.png ADDED
Binary file
trunk/common/img/wp-retina-2x.jpg ADDED
Binary file
trunk/common/img/wplr-sync.jpg ADDED
Binary file
trunk/core.php ADDED
@@ -0,0 +1,1002 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class Meow_WPMC_Core {
4
+
5
+ public $admin = null;
6
+ public $last_analysis = null; //TODO: Is it actually used?
7
+ public $engine = null;
8
+ public $catch_timeout = true; // This will halt the plugin before reaching the PHP timeout.
9
+ private $regex_file = '/[A-Za-z0-9-_,.\(\)\s]+[.]{1}(MIMETYPES)/';
10
+ public $current_method = 'media';
11
+ private $refcache = array();
12
+ public $servername = null;
13
+ public $upload_folder = null;
14
+ public $contentDir = null; // becomes 'wp-content/uploads'
15
+ private $check_content = null;
16
+ private $check_postmeta = null;
17
+ private $check_posts = null;
18
+ private $check_widgets = null;
19
+ private $debug_logs = null;
20
+ public $site_url = null;
21
+
22
+ public function __construct( $admin ) {
23
+ $this->admin = $admin;
24
+ $this->site_url = get_site_url();
25
+ $this->current_method = get_option( 'wpmc_method', 'media' );
26
+ $types = "jpg|jpeg|jpe|gif|png|tiff|bmp|csv|pdf|xls|xlsx|doc|docx|tiff|mp3|mp4|wav|lua";
27
+ $this->regex_file = str_replace( "MIMETYPES", $types, $this->regex_file );
28
+ $this->servername = str_replace( 'http://', '', str_replace( 'https://', '', $this->site_url ) );
29
+ $this->upload_folder = wp_upload_dir();
30
+ $this->contentDir = substr( $this->upload_folder['baseurl'], 1 + strlen( $this->site_url ) );
31
+
32
+ $this->check_content = get_option( 'wpmc_content', true );
33
+ $this->check_postmeta = get_option( 'wpmc_postmeta', false );
34
+ $this->check_posts = get_option( 'wpmc_posts', false );
35
+ $this->check_widgets = get_option( 'wpmc_widgets', false );
36
+
37
+ if ( $this->check_postmeta || $this->check_posts || $this->check_widgets ) {
38
+ delete_option( 'wpmc_postmeta' );
39
+ delete_option( 'wpmc_posts' );
40
+ delete_option( 'wpmc_widgets' );
41
+ }
42
+
43
+ $this->debug_logs = get_option( 'wpmc_debuglogs', false );
44
+ add_action( 'wpmc_initialize_parsers', array( $this, 'initialize_parsers' ), 10, 0 );
45
+
46
+ require __DIR__ . '/engine.php';
47
+ require __DIR__ . '/ui.php';
48
+ require __DIR__ . '/api.php';
49
+ $this->engine = new Meow_WPMC_Engine( $this, $admin );
50
+ new Meow_WPMC_UI( $this, $admin );
51
+ new Meow_WPMC_API( $this, $admin, $this->engine );
52
+ }
53
+
54
+ function initialize_parsers() {
55
+ include_once( 'parsers.php' );
56
+ new MeowApps_WPMC_Parsers();
57
+ }
58
+
59
+ function deepsleep( $seconds ) {
60
+ $start_time = time();
61
+ while( true ) {
62
+ if ( ( time() - $start_time ) > $seconds ) {
63
+ return false;
64
+ }
65
+ get_post( array( 'posts_per_page' => 50 ) );
66
+ }
67
+ }
68
+
69
+ private $start_time;
70
+ private $time_elapsed = 0;
71
+ private $item_scan_avg_time = 0;
72
+ private $wordpress_init_time = 0.5;
73
+ private $max_execution_time;
74
+ private $items_checked = 0;
75
+ private $items_count = 0;
76
+
77
+ function get_max_execution_time() {
78
+ if ( isset( $this->max_execution_time ) )
79
+ return $this->max_execution_time;
80
+
81
+ $this->max_execution_time = ini_get( "max_execution_time" );
82
+ if ( empty( $this->max_execution_time ) || $this->max_execution_time < 5 )
83
+ $this->max_execution_time = 30;
84
+
85
+ return $this->max_execution_time;
86
+ }
87
+
88
+ function timeout_check_start( $count ) {
89
+ $this->start_time = time();
90
+ $this->items_count = $count;
91
+ $this->get_max_execution_time();
92
+ }
93
+
94
+ function timeout_check() {
95
+ $this->time_elapsed = time() - $this->start_time;
96
+ $this->time_remaining = $this->max_execution_time - $this->wordpress_init_time - $this->time_elapsed;
97
+ if ( $this->catch_timeout ) {
98
+ if ( $this->time_remaining - $this->item_scan_avg_time < 0 ) {
99
+ error_log("Media Cleaner Timeout! Check the Media Cleaner logs for more info.");
100
+ $this->log( "Timeout! Some info for debug:" );
101
+ $this->log( "Elapsed time: $this->time_elapsed" );
102
+ $this->log( "WP init time: $this->wordpress_init_time" );
103
+ $this->log( "Remaining time: $this->time_remaining" );
104
+ $this->log( "Scan time per item: $this->item_scan_avg_time" );
105
+ $this->log( "PHP max_execution_time: $this->max_execution_time" );
106
+ header("HTTP/1.0 408 Request Timeout");
107
+ exit;
108
+ }
109
+ }
110
+ }
111
+
112
+ function timeout_check_additem() {
113
+ $this->items_checked++;
114
+ $this->time_elapsed = time() - $this->start_time;
115
+ $this->item_scan_avg_time = ceil( ( $this->time_elapsed / $this->items_checked ) * 10 ) / 10;
116
+ }
117
+
118
+ function array_to_ids_or_urls( &$meta, &$ids, &$urls ) {
119
+ foreach ( $meta as $k => $m ) {
120
+ if ( is_numeric( $m ) ) {
121
+ // Probably a Media ID
122
+ if ( $m > 0 )
123
+ array_push( $ids, $m );
124
+ }
125
+ else if ( is_array( $m ) ) {
126
+ // If it's an array with a width, probably that the index is the Media ID
127
+ if ( isset( $m['width'] ) && is_numeric( $k ) ) {
128
+ if ( $k > 0 )
129
+ array_push( $ids, $k );
130
+ }
131
+ }
132
+ else if ( !empty( $m ) ) {
133
+ // If it's a string, maybe it's a file (with an extension)
134
+ if ( preg_match( $this->regex_file, $m ) )
135
+ array_push( $urls, $m );
136
+ }
137
+ }
138
+ }
139
+
140
+ function get_favicon() {
141
+ // Yoast SEO plugin
142
+ $vals = get_option( 'wpseo_titles' );
143
+ if ( !empty( $vals ) ) {
144
+ $url = $vals['company_logo'];
145
+ if ( $this->is_url( $url ) )
146
+ return $this->clean_url( $url );
147
+ }
148
+ }
149
+
150
+ function get_urls_from_html( $html ) {
151
+ if ( empty( $html ) )
152
+ return array();
153
+
154
+ // Proposal/fix by @copytrans
155
+ // Discussion: https://wordpress.org/support/topic/bug-in-core-php/#post-11647775
156
+ $html = mb_convert_encoding( $html, 'HTML-ENTITIES', 'UTF-8' );
157
+
158
+ // Resolve src-set and shortcodes
159
+ if ( !get_option( 'wpmc_shortcodes_disabled', false ) )
160
+ $html = do_shortcode( $html );
161
+ $html = wp_make_content_images_responsive( $html );
162
+
163
+ // Create the DOM Document
164
+ $dom = new DOMDocument();
165
+ @$dom->loadHTML( $html );
166
+ $results = array();
167
+
168
+ // <meta> tags in <head> area
169
+ $metas = $dom->getElementsByTagName( 'meta' );
170
+ foreach ( $metas as $meta ) {
171
+ $property = $meta->getAttribute( 'property' );
172
+ if ( $property == 'og:image' || $property == 'og:image:secure_url' || $property == 'twitter:image' ) {
173
+ $url = $meta->getAttribute( 'content' );
174
+ if ( $this->is_url( $url ) ) {
175
+ $src = $this->clean_url( $url );
176
+ if ( !empty( $src ) ) {
177
+ array_push( $results, $src );
178
+ }
179
+ }
180
+ }
181
+ }
182
+
183
+ // IFrames (by Mike Meinz)
184
+ $iframes = $dom->getElementsByTagName( 'iframe' );
185
+ foreach( $iframes as $iframe ) {
186
+ $iframe_src = $iframe->getAttribute( 'src' );
187
+ // Ignore if the iframe src is not on this server
188
+ if ( ( strpos( $iframe_src, $this->servername ) !== false) || ( substr( $iframe_src, 0, 1 ) == "/" ) ) {
189
+ // Create a new DOM Document to hold iframe
190
+ $iframe_doc = new DOMDocument();
191
+ // Load the url's contents into the DOM
192
+ libxml_use_internal_errors( true ); // ignore html formatting problems
193
+ $rslt = $iframe_doc->loadHTMLFile( $iframe_src );
194
+ libxml_clear_errors();
195
+ libxml_use_internal_errors( false );
196
+ if ( $rslt ) {
197
+ // Get the resulting html
198
+ $iframe_html = $iframe_doc->saveHTML();
199
+ if ( $iframe_html !== false ) {
200
+ // Scan for links in the iframe
201
+ $iframe_urls = $this->get_urls_from_html( $iframe_html ); // Recursion
202
+ if ( !empty( $iframe_urls ) ) {
203
+ $results = array_merge( $results, $iframe_urls );
204
+ }
205
+ }
206
+ }
207
+ else {
208
+ $err = 'ERROR: Failed to load iframe: ' . $iframe_src;
209
+ error_log( $err );
210
+ $this->log( $err );
211
+ }
212
+ }
213
+ }
214
+
215
+
216
+ // Images, src, srcset
217
+ $imgs = $dom->getElementsByTagName( 'img' );
218
+ foreach ( $imgs as $img ) {
219
+ //error_log($img->getAttribute('src'));
220
+ $src = $this->clean_url( $img->getAttribute('src') );
221
+ array_push( $results, $src );
222
+ $srcset = $img->getAttribute('srcset');
223
+ if ( !empty( $srcset ) ) {
224
+ $setImgs = explode( ',', trim( $srcset ) );
225
+ foreach ( $setImgs as $setImg ) {
226
+ $finalSetImg = explode( ' ', trim( $setImg ) );
227
+ if ( is_array( $finalSetImg ) ) {
228
+ array_push( $results, $this->clean_url( $finalSetImg[0] ) );
229
+ }
230
+ }
231
+ }
232
+ }
233
+
234
+ // Links, href
235
+ $urls = $dom->getElementsByTagName( 'a' );
236
+ foreach ( $urls as $url ) {
237
+ $url_href = $url->getAttribute('href'); // mm change
238
+ if ( $this->is_url( $url_href ) ) { // mm change
239
+ $src = $this->clean_url( $url_href ); // mm change
240
+ if ( !empty( $src ) )
241
+ array_push( $results, $src );
242
+ }
243
+ }
244
+
245
+ // <link> tags in <head> area
246
+ $urls = $dom->getElementsByTagName( 'link' );
247
+ foreach ( $urls as $url ) {
248
+ $url_href = $url->getAttribute( 'href' );
249
+ if ( $this->is_url( $url_href ) ) {
250
+ $src = $this->clean_url( $url_href );
251
+ if ( !empty( $src ) ) {
252
+ array_push( $results, $src );
253
+ }
254
+ }
255
+ }
256
+
257
+ // PDF
258
+ preg_match_all( "/((https?:\/\/)?[^\\&\#\[\] \"\?]+\.pdf)/", $html, $res );
259
+ if ( !empty( $res ) && isset( $res[1] ) && count( $res[1] ) > 0 ) {
260
+ foreach ( $res[1] as $url ) {
261
+ if ( $this->is_url($url) )
262
+ array_push( $results, $this->clean_url( $url ) );
263
+ }
264
+ }
265
+
266
+ // Background images
267
+ preg_match_all( "/url\(\'?\"?((https?:\/\/)?[^\\&\#\[\] \"\?]+\.(jpe?g|gif|png))\'?\"?/", $html, $res );
268
+ if ( !empty( $res ) && isset( $res[1] ) && count( $res[1] ) > 0 ) {
269
+ foreach ( $res[1] as $url ) {
270
+ if ( $this->is_url($url) )
271
+ array_push( $results, $this->clean_url( $url ) );
272
+ }
273
+ }
274
+
275
+ return $results;
276
+ }
277
+
278
+ // Parse a meta, visit all the arrays, look for the attributes, fill $ids and $urls arrays
279
+ function get_from_meta( $meta, $lookFor, &$ids, &$urls ) {
280
+ foreach ( $meta as $key => $value ) {
281
+ if ( is_object( $value ) || is_array( $value ) )
282
+ $this->get_from_meta( $value, $lookFor, $ids, $urls );
283
+ else if ( in_array( $key, $lookFor ) ) {
284
+ if ( empty( $value ) )
285
+ continue;
286
+ else if ( is_numeric( $value ) )
287
+ array_push( $ids, $value );
288
+ else {
289
+ if ( $this->is_url( $value ) )
290
+ array_push( $urls, $this->clean_url( $value ) );
291
+ }
292
+ }
293
+ }
294
+ }
295
+
296
+ function get_images_from_themes( &$ids, &$urls ) {
297
+ // USE CURRENT THEME AND WP API
298
+ $ch = get_custom_header();
299
+ if ( !empty( $ch ) && !empty( $ch->url ) ) {
300
+ array_push( $urls, $this->clean_url( $ch->url ) );
301
+ }
302
+ if ( $this->is_url( $ch->thumbnail_url ) ) {
303
+ array_push( $urls, $this->clean_url( $ch->thumbnail_url ) );
304
+ }
305
+ if ( !empty( $ch ) && !empty( $ch->attachment_id ) ) {
306
+ array_push( $ids, $ch->attachment_id );
307
+ }
308
+ $cl = get_custom_logo();
309
+ if ( $this->is_url( $cl ) ) {
310
+ $urls = array_merge( $this->get_urls_from_html( $cl ), $urls );
311
+ }
312
+ $si = get_site_icon_url();
313
+ if ( $this->is_url( $si ) ) {
314
+ array_push( $urls, $this->clean_url( $si ) );
315
+ }
316
+ $si_id = get_option( 'site_icon' );
317
+ if ( !empty( $si_id ) && is_numeric( $si_id ) ) {
318
+ array_push( $ids, (int)$si_id );
319
+ }
320
+ $cd = get_background_image();
321
+ if ( $this->is_url( $cd ) ) {
322
+ array_push( $urls, $this->clean_url( $cd ) );
323
+ }
324
+ $photography_hero_image = get_theme_mod( 'photography_hero_image' );
325
+ if ( !empty( $photography_hero_image ) ) {
326
+ array_push( $ids, $photography_hero_image );
327
+ }
328
+ $author_profile_picture = get_theme_mod( 'author_profile_picture' );
329
+ if ( !empty( $author_profile_picture ) ) {
330
+ array_push( $ids, $author_profile_picture );
331
+ }
332
+ if ( function_exists ( 'get_uploaded_header_images' ) ) {
333
+ $header_images = get_uploaded_header_images();
334
+ if ( !empty( $header_images ) ) {
335
+ foreach( $header_images as $hi ) {
336
+ if ( !empty ( $hi['attachment_id'] ) ) {
337
+ array_push( $ids, $hi['attachment_id'] );
338
+ }
339
+ }
340
+ }
341
+ }
342
+ }
343
+
344
+ function log( $data = null, $force = false ) {
345
+ if ( !$this->debug_logs && !$force )
346
+ return;
347
+ $fh = @fopen( trailingslashit( dirname(__FILE__) ) . '/media-cleaner.log', 'a' );
348
+ if ( !$fh )
349
+ return false;
350
+ $date = date( "Y-m-d H:i:s" );
351
+ if ( is_null( $data ) )
352
+ fwrite( $fh, "\n" );
353
+ else
354
+ fwrite( $fh, "$date: {$data}\n" );
355
+ fclose( $fh );
356
+ return true;
357
+ }
358
+
359
+ /**
360
+ *
361
+ * HELPERS
362
+ *
363
+ */
364
+
365
+ function get_trashdir() {
366
+ return trailingslashit( $this->upload_folder['basedir'] ) . 'wpmc-trash';
367
+ }
368
+
369
+ /**
370
+ *
371
+ * DELETE / SCANNING / RESET
372
+ *
373
+ */
374
+
375
+ function recover_file( $path ) {
376
+ $originalPath = trailingslashit( $this->upload_folder['basedir'] ) . $path;
377
+ $trashPath = trailingslashit( $this->get_trashdir() ) . $path;
378
+ $path_parts = pathinfo( $originalPath );
379
+ if ( !file_exists( $path_parts['dirname'] ) && !wp_mkdir_p( $path_parts['dirname'] ) ) {
380
+ die( 'Failed to create folder.' );
381
+ }
382
+ if ( !file_exists( $trashPath ) ) {
383
+ $this->log( "The file $originalPath actually does not exist in the trash." );
384
+ return true;
385
+ }
386
+ if ( !rename( $trashPath, $originalPath ) ) {
387
+ die( 'Failed to move the file.' );
388
+ }
389
+ return true;
390
+ }
391
+
392
+ function recover( $id ) {
393
+ global $wpdb;
394
+ $table_name = $wpdb->prefix . "mclean_scan";
395
+ $issue = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $table_name WHERE id = %d", $id ), OBJECT );
396
+ $issue->path = stripslashes( $issue->path );
397
+
398
+ // Files
399
+ if ( $issue->type == 0 ) {
400
+ $this->recover_file( $issue->path );
401
+ $wpdb->query( $wpdb->prepare( "UPDATE $table_name SET deleted = 0 WHERE id = %d", $id ) );
402
+ return true;
403
+ }
404
+ // Media
405
+ else if ( $issue->type == 1 ) {
406
+
407
+ // Copy the main file back
408
+ $fullpath = get_attached_file( $issue->postId );
409
+ if ( empty( $fullpath ) ) {
410
+ error_log( "Media {$issue->postId} does not have attached file anymore." );
411
+ return false;
412
+ }
413
+ $mainfile = $this->clean_uploaded_filename( $fullpath );
414
+ $baseUp = pathinfo( $mainfile );
415
+ $baseUp = $baseUp['dirname'];
416
+ $file = $this->clean_uploaded_filename( $fullpath );
417
+ if ( !$this->recover_file( $file ) ) {
418
+ $this->log( "Could not recover $file." );
419
+ error_log( "Media Cleaner: Could not recover $file." );
420
+ }
421
+
422
+ // If images, copy the other files as well
423
+ $meta = wp_get_attachment_metadata( $issue->postId );
424
+ $isImage = isset( $meta, $meta['width'], $meta['height'] );
425
+ $sizes = $this->get_image_sizes();
426
+ if ( $isImage && isset( $meta['sizes'] ) ) {
427
+ foreach ( $meta['sizes'] as $name => $attr ) {
428
+ if ( isset( $attr['file'] ) ) {
429
+ $filepath = $this->upload_folder['basedir'];
430
+ $filepath = trailingslashit( $filepath ) . trailingslashit( $baseUp ) . $attr['file'];
431
+ $file = $this->clean_uploaded_filename( $filepath );
432
+ if ( !$this->recover_file( $file ) ) {
433
+ $this->log( "Could not recover $file." );
434
+ error_log( "Media Cleaner: Could not recover $file." );
435
+ }
436
+ }
437
+ }
438
+ }
439
+ if ( !wp_untrash_post( $issue->postId ) ) {
440
+ error_log( "Cleaner: Failed to Untrash Post {$issue->postId} (but deleted it from Cleaner DB)." );
441
+ }
442
+ $wpdb->query( $wpdb->prepare( "UPDATE $table_name SET deleted = 0 WHERE id = %d", $id ) );
443
+ return true;
444
+ }
445
+ }
446
+
447
+ function trash_file( $fileIssuePath ) {
448
+ global $wpdb;
449
+ $originalPath = trailingslashit( $this->upload_folder['basedir'] ) . $fileIssuePath;
450
+ $trashPath = trailingslashit( $this->get_trashdir() ) . $fileIssuePath;
451
+ $path_parts = pathinfo( $trashPath );
452
+
453
+ try {
454
+ if ( !file_exists( $path_parts['dirname'] ) && !wp_mkdir_p( $path_parts['dirname'] ) ) {
455
+ $this->log( "Could not create the trash directory for Media Cleaner." );
456
+ error_log( "Media Cleaner: Could not create the trash directory." );
457
+ return false;
458
+ }
459
+ // Rename the file (move). 'is_dir' is just there for security (no way we should move a whole directory)
460
+ if ( is_dir( $originalPath ) ) {
461
+ $this->log( "Attempted to delete a directory instead of a file ($originalPath). Can't do that." );
462
+ error_log( "Media Cleaner: Attempted to delete a directory instead of a file ($originalPath). Can't do that." );
463
+ return false;
464
+ }
465
+ if ( !file_exists( $originalPath ) ) {
466
+ $this->log( "The file $originalPath actually does not exist." );
467
+ return true;
468
+ }
469
+ if ( !@rename( $originalPath, $trashPath ) ) {
470
+ return false;
471
+ }
472
+ }
473
+ catch ( Exception $e ) {
474
+ return false;
475
+ }
476
+ $this->clean_dir( dirname( $originalPath ) );
477
+ return true;
478
+ }
479
+
480
+ function ignore( $id ) {
481
+ global $wpdb;
482
+ $table_name = $wpdb->prefix . "mclean_scan";
483
+ $issue = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $table_name WHERE id = %d", $id ), OBJECT );
484
+ if ( (int) $issue->ignored )
485
+ $wpdb->query( $wpdb->prepare( "UPDATE $table_name SET ignored = 0 WHERE id = %d", $id ) );
486
+ else {
487
+ if ( (int) $issue->deleted ) // If it is in trash, recover it
488
+ $this->recover( $id );
489
+ $wpdb->query( $wpdb->prepare( "UPDATE $table_name SET ignored = 1 WHERE id = %d", $id ) );
490
+ }
491
+ return true;
492
+ }
493
+
494
+ function endsWith( $haystack, $needle )
495
+ {
496
+ $length = strlen( $needle );
497
+ if ( $length == 0 )
498
+ return true;
499
+ return ( substr( $haystack, -$length ) === $needle );
500
+ }
501
+
502
+ function clean_dir( $dir ) {
503
+ if ( !file_exists( $dir ) )
504
+ return;
505
+ else if ( $this->endsWith( $dir, 'uploads' ) )
506
+ return;
507
+ $found = array_diff( scandir( $dir ), array( '.', '..' ) );
508
+ if ( count( $found ) < 1 ) {
509
+ if ( rmdir( $dir ) ) {
510
+ $this->clean_dir( dirname( $dir ) );
511
+ }
512
+ }
513
+ }
514
+
515
+ function delete( $id ) {
516
+ global $wpdb;
517
+ $table_name = $wpdb->prefix . "mclean_scan";
518
+ $issue = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $table_name WHERE id = %d", $id ), OBJECT );
519
+ $regex = "^(.*)(\\s\\(\\+.*)$";
520
+ $issue->path = preg_replace( '/' . $regex . '/i', '$1', $issue->path ); // remove " (+ 6 files)" from path
521
+
522
+ // Make sure there isn't a media DB entry
523
+ if ( $issue->type == 0 ) {
524
+ $attachmentid = $this->find_media_id_from_file( $issue->path, true );
525
+ if ( $attachmentid ) {
526
+ $this->log( "Issue listed as filesystem but Media {$attachmentid} exists." );
527
+ }
528
+ }
529
+
530
+ if ( $issue->type == 0 ) {
531
+
532
+ if ( $issue->deleted == 0 ) {
533
+ // Move file to the trash
534
+ if ( $this->trash_file( $issue->path ) )
535
+ $wpdb->query( $wpdb->prepare( "UPDATE $table_name SET deleted = 1, ignored = 0 WHERE id = %d", $id ) );
536
+ return true;
537
+ }
538
+ else {
539
+ // Delete file from the trash
540
+ $trashPath = trailingslashit( $this->get_trashdir() ) . $issue->path;
541
+ if ( !unlink( $trashPath ) ) {
542
+ $this->log( "Failed to delete the file." );
543
+ error_log( "Media Cleaner: Failed to delete the file." );
544
+ }
545
+ $wpdb->query( $wpdb->prepare( "DELETE FROM $table_name WHERE id = %d", $id ) );
546
+ $this->clean_dir( dirname( $trashPath ) );
547
+ return true;
548
+ }
549
+ }
550
+
551
+ if ( $issue->type == 1 ) {
552
+ if ( $issue->deleted == 0 && MEDIA_TRASH ) {
553
+ // Move Media to trash
554
+ // Let's copy the images to the trash so that it can be recovered.
555
+ $fullpath = get_attached_file( $issue->postId );
556
+ $mainfile = $this->clean_uploaded_filename( $fullpath );
557
+ $baseUp = pathinfo( $mainfile );
558
+ $baseUp = $baseUp['dirname'];
559
+ $file = $this->clean_uploaded_filename( $fullpath );
560
+ if ( !$this->trash_file( $file ) ) {
561
+ $this->log( "Could not trash $file." );
562
+ error_log( "Media Cleaner: Could not trash $file." );
563
+ return false;
564
+ }
565
+
566
+ // If images, check the other files as well
567
+ $meta = wp_get_attachment_metadata( $issue->postId );
568
+ $isImage = isset( $meta, $meta['width'], $meta['height'] );
569
+ $sizes = $this->get_image_sizes();
570
+ if ( $isImage && isset( $meta['sizes'] ) ) {
571
+ foreach ( $meta['sizes'] as $name => $attr ) {
572
+ if ( isset( $attr['file'] ) ) {
573
+ $filepath = $this->upload_folder['basedir'];
574
+ $filepath = trailingslashit( $filepath ) . trailingslashit( $baseUp ) . $attr['file'];
575
+ $file = $this->clean_uploaded_filename( $filepath );
576
+ if ( !$this->trash_file( $file ) ) {
577
+ $this->log( "Could not trash $file." );
578
+ error_log( "Media Cleaner: Could not trash $file." );
579
+ }
580
+ }
581
+ }
582
+ }
583
+ wp_delete_attachment( $issue->postId, false );
584
+ $wpdb->query( $wpdb->prepare( "UPDATE $table_name SET deleted = 1, ignored = 0 WHERE id = %d", $id ) );
585
+ return true;
586
+ }
587
+ else {
588
+ // Trash Media definitely by recovering it (to be like a normal Media) and remove it through the
589
+ // standard WordPress workflow
590
+ if ( MEDIA_TRASH )
591
+ $this->recover( $id );
592
+ wp_delete_attachment( $issue->postId, true );
593
+ $wpdb->query( $wpdb->prepare( "DELETE FROM $table_name WHERE id = %d", $id ) );
594
+ return true;
595
+ }
596
+ }
597
+ return false;
598
+ }
599
+
600
+ /**
601
+ *
602
+ * SCANNING / RESET
603
+ *
604
+ */
605
+
606
+ function add_reference_url( $urlOrUrls, $type, $origin = null, $extra = null ) {
607
+ $urlOrUrls = !is_array( $urlOrUrls ) ? array( $urlOrUrls ) : $urlOrUrls;
608
+ foreach ( $urlOrUrls as $url ) {
609
+ // With files, we need both filename without resolution and filename with resolution, it's important
610
+ // to make sure the original file is not deleted if a size exists for it.
611
+ // With media, all URLs should be without resolution to make sure it matches Media.
612
+ if ( $this->current_method == 'files' )
613
+ $this->add_reference( null, $url, $type, $origin );
614
+ $this->add_reference( 0, $this->clean_url_from_resolution( $url ), $type, $origin );
615
+ }
616
+ }
617
+
618
+ function add_reference_id( $idOrIds, $type, $origin = null, $extra = null ) {
619
+ $idOrIds = !is_array( $idOrIds ) ? array( $idOrIds ) : $idOrIds;
620
+ foreach ( $idOrIds as $id )
621
+ $this->add_reference( $id, "", $type, $origin );
622
+ }
623
+
624
+ private $cached_ids = array();
625
+ private $cached_urls = array();
626
+
627
+ private function add_reference( $id, $url, $type, $origin = null, $extra = null ) {
628
+ // The references are actually not being added directly in the DB, they are being pushed
629
+ // into a cache ($this->refcache).
630
+ if ( !empty( $id ) ) {
631
+ if ( !in_array( $id, $this->cached_ids ) )
632
+ array_push( $this->cached_ids, $id );
633
+ else
634
+ return;
635
+ }
636
+ if ( !empty( $url ) ) {
637
+ // The URL shouldn't contain http, https, javascript at the beginning (and there are probably many more cases)
638
+ // The URL must be cleaned before being passed as a reference.
639
+ if ( substr( $url, 0, 5 ) === "http:" )
640
+ return;
641
+ if ( substr( $url, 0, 6 ) === "https:" )
642
+ return;
643
+ if ( substr( $url, 0, 11 ) === "javascript:" )
644
+ return;
645
+ if ( !in_array( $url, $this->cached_urls ) )
646
+ array_push( $this->cached_urls, $url );
647
+ else
648
+ return;
649
+ }
650
+ //
651
+ array_push( $this->refcache, array( 'id' => $id, 'url' => $url, 'type' => $type, 'origin' => $origin ) );
652
+
653
+ // Without cache, the code would be this.
654
+ // $wpdb->insert( $table_name,
655
+ // array(
656
+ // 'time' => current_time('mysql'), 'mediaId' => $id, 'mediaUrl' => $url, 'origin' => $origin, 'originType' => $type )
657
+ // );
658
+ }
659
+
660
+ // The cache containing the references is wrote to the DB.
661
+ function write_references() {
662
+ global $wpdb;
663
+ $table = $wpdb->prefix . "mclean_refs";
664
+ $values = array();
665
+ $place_holders = array();
666
+ $query = "INSERT INTO $table (mediaId, mediaUrl, originType) VALUES ";
667
+ foreach( $this->refcache as $key => $value ) {
668
+ array_push( $values, $value['id'], $value['url'], $value['type'] );
669
+ if ( $this->debug_logs ) {
670
+ if ( !empty( $value['id'] ) )
671
+ $this->log( "* {$value['type']}: Media #{$value['id']}" );
672
+ if ( !empty( $value['url'] ) )
673
+ $this->log( "* {$value['type']}: {$value['url']}" );
674
+ }
675
+ $place_holders[] = "('%d','%s','%s')";
676
+ }
677
+ if ( !empty( $values ) ) {
678
+ $query .= implode( ', ', $place_holders );
679
+ $prepared = $wpdb->prepare( "$query ", $values );
680
+ $wpdb->query( $prepared );
681
+ }
682
+ $this->refcache = array();
683
+ }
684
+
685
+ function check_is_ignore( $file ) {
686
+ global $wpdb;
687
+ $table_name = $wpdb->prefix . "mclean_scan";
688
+ $count = $wpdb->get_var( "SELECT COUNT(*)
689
+ FROM $table_name
690
+ WHERE ignored = 1
691
+ AND path LIKE '%". esc_sql( $wpdb->esc_like( $file ) ) . "%'" );
692
+ if ( $count > 0 ) {
693
+ $this->log( "Could not trash $file." );
694
+ }
695
+ return ($count > 0);
696
+ }
697
+
698
+ function find_media_id_from_file( $file, $doLog ) {
699
+ global $wpdb;
700
+ $postmeta_table_name = $wpdb->prefix . 'postmeta';
701
+ $file = $this->clean_uploaded_filename( $file );
702
+ $sql = $wpdb->prepare( "SELECT post_id
703
+ FROM {$postmeta_table_name}
704
+ WHERE meta_key = '_wp_attached_file'
705
+ AND meta_value = %s", $file
706
+ );
707
+ $ret = $wpdb->get_var( $sql );
708
+ if ( $doLog ) {
709
+ if ( empty( $ret ) )
710
+ $this->log( "File $file not found as _wp_attached_file (Library)." );
711
+ else {
712
+ $this->log( "File $file found as Media $ret." );
713
+ }
714
+ }
715
+
716
+ return $ret;
717
+ }
718
+
719
+ function get_image_sizes() {
720
+ $sizes = array();
721
+ global $_wp_additional_image_sizes;
722
+ foreach ( get_intermediate_image_sizes() as $s ) {
723
+ $crop = false;
724
+ if ( isset( $_wp_additional_image_sizes[$s] ) ) {
725
+ $width = intval( $_wp_additional_image_sizes[$s]['width'] );
726
+ $height = intval( $_wp_additional_image_sizes[$s]['height'] );
727
+ $crop = $_wp_additional_image_sizes[$s]['crop'];
728
+ } else {
729
+ $width = get_option( $s.'_size_w' );
730
+ $height = get_option( $s.'_size_h' );
731
+ $crop = get_option( $s.'_crop' );
732
+ }
733
+ $sizes[$s] = array( 'width' => $width, 'height' => $height, 'crop' => $crop );
734
+ }
735
+ return $sizes;
736
+ }
737
+
738
+ function clean_url_from_resolution( $url ) {
739
+ $pattern = '/[_-]\d+x\d+(?=\.[a-z]{3,4}$)/';
740
+ $url = preg_replace( $pattern, '', $url );
741
+ return $url;
742
+ }
743
+
744
+ function is_url( $url ) {
745
+ return ( (
746
+ !empty( $url ) ) &&
747
+ is_string( $url ) &&
748
+ strlen( $url ) > 4 && (
749
+ strtolower( substr( $url, 0, 4) ) == 'http' || $url[0] == '/'
750
+ )
751
+ );
752
+ }
753
+
754
+ function clean_url_from_resolution_ref( &$url ) {
755
+ $url = $this->clean_url_from_resolution( $url );
756
+ }
757
+
758
+ // From a url to the shortened and cleaned url (for example '2013/02/file.png')
759
+ function clean_url( $url ) {
760
+ $dirIndex = strpos( $url, $this->contentDir );
761
+ if ( empty( $url ) || $dirIndex == false )
762
+ return null;
763
+ return urldecode( substr( $url, 1 + strlen( $this->contentDir ) + $dirIndex ) );
764
+ }
765
+
766
+ // From a fullpath to the shortened and cleaned path (for example '2013/02/file.png')
767
+ // Original version by Jordy
768
+ // function clean_uploaded_filename( $fullpath ) {
769
+ // $basedir = $this->upload_folder['basedir'];
770
+ // $file = str_replace( $basedir, '', $fullpath );
771
+ // $file = str_replace( "./", "", $file );
772
+ // $file = trim( $file, "/" );
773
+ // return $file;
774
+ // }
775
+
776
+ // From a fullpath to the shortened and cleaned path (for example '2013/02/file.png')
777
+ // Faster version, more difficult to read, by Mike Meinz
778
+ function clean_uploaded_filename( $fullpath ) {
779
+ $dirIndex = strpos( $fullpath, $this->contentDir );
780
+ if ( $dirIndex == false ) {
781
+ $file = $fullpath;
782
+ }
783
+ else {
784
+ // Remove first part of the path leaving yyyy/mm/filename.ext
785
+ $file = substr( $fullpath, 1 + strlen( $this->contentDir ) + $dirIndex );
786
+ }
787
+ if ( substr( $file, 0, 2 ) == './' ) {
788
+ $file = substr( $file, 2 );
789
+ }
790
+ if ( substr( $file, 0, 1 ) == '/' ) {
791
+ $file = substr( $file, 1 );
792
+ }
793
+ return $file;
794
+ }
795
+
796
+ /*
797
+ Check if the file or the Media ID is used in the install.
798
+ That file or ID will be checked against the database of references created by the plugin
799
+ by the parsers.
800
+ */
801
+ public function reference_exists( $file, $mediaId ) {
802
+ global $wpdb;
803
+ $table = $wpdb->prefix . "mclean_refs";
804
+ $row = null;
805
+ if ( !empty( $mediaId ) ) {
806
+ $row = $wpdb->get_row( $wpdb->prepare( "SELECT originType FROM $table WHERE mediaId = %d", $mediaId ) );
807
+ if ( !empty( $row ) ) {
808
+ $this->last_analysis = $row->originType;
809
+ $this->log( "OK! Media #{$mediaId} used by {$row->originType}" );
810
+ return true;
811
+ }
812
+ }
813
+ if ( !empty( $file ) ) {
814
+ $row = $wpdb->get_row( $wpdb->prepare( "SELECT originType FROM $table WHERE mediaUrl = %s", $file ) );
815
+ if ( !empty( $row ) ) {
816
+ $this->last_analysis = $row->originType;
817
+ $this->log( "OK! File {$file} used by {$row->originType}" );
818
+ return true;
819
+ }
820
+ }
821
+ return false;
822
+ }
823
+
824
+ function check_media( $attachmentId, $checkOnly = false ) {
825
+
826
+ $this->last_analysis = "N/A";
827
+
828
+ // Is it an image?
829
+ $meta = wp_get_attachment_metadata( $attachmentId );
830
+ $isImage = isset( $meta, $meta['width'], $meta['height'] );
831
+
832
+ // Get the main file
833
+ global $wpdb;
834
+ $fullpath = get_attached_file( $attachmentId );
835
+ $mainfile = $this->clean_uploaded_filename( $fullpath );
836
+ $baseUp = pathinfo( $mainfile );
837
+ $baseUp = $baseUp['dirname'];
838
+ $size = 0;
839
+ $countfiles = 0;
840
+ $issue = 'NO_CONTENT';
841
+ if ( file_exists( $fullpath ) ) {
842
+
843
+ // Special scan: Broken only!
844
+ if ( !$this->check_content && !$this->check_postmeta && !$this->check_posts && !$this->check_widgets )
845
+ return true;
846
+
847
+ $size = filesize( $fullpath );
848
+
849
+ // Analysis
850
+ $this->last_analysis = "NONE";
851
+ $this->log( "Checking Media #{$attachmentId}: {$mainfile}" );
852
+ if ( $this->check_is_ignore( $mainfile, $attachmentId ) ) {
853
+ $this->last_analysis = "IGNORED";
854
+ return true;
855
+ }
856
+ if ( $this->reference_exists( $mainfile, $attachmentId ) )
857
+ return true;
858
+
859
+ // If images, check the other files as well
860
+ $countfiles = 0;
861
+ $sizes = $this->get_image_sizes();
862
+ if ( $isImage && isset( $meta['sizes'] ) ) {
863
+ foreach ( $meta['sizes'] as $name => $attr ) {
864
+ if ( isset( $attr['file'] ) ) {
865
+ $filepath = $this->upload_folder['basedir'];
866
+ $filepath = trailingslashit( $filepath ) . trailingslashit( $baseUp ) . $attr['file'];
867
+ if ( file_exists( $filepath ) )
868
+ $size += filesize( $filepath );
869
+ $file = $this->clean_uploaded_filename( $filepath );
870
+ $countfiles++;
871
+ // Analysis
872
+ $this->log( "Checking Media #{$attachmentId}: {$file}" );
873
+ if ( $this->reference_exists( $mainfile, $attachmentId ) )
874
+ return true;
875
+ }
876
+ }
877
+ }
878
+ } else {
879
+ $this->log( "File {$fullpath} does not exist." );
880
+ $issue = 'ORPHAN_MEDIA';
881
+ }
882
+
883
+ if ( !$checkOnly ) {
884
+ $table_name = $wpdb->prefix . "mclean_scan";
885
+ $wpdb->insert( $table_name,
886
+ array(
887
+ 'time' => current_time('mysql'),
888
+ 'type' => 1,
889
+ 'size' => $size,
890
+ 'path' => $mainfile . ( $countfiles > 0 ? ( " (+ " . $countfiles . " files)" ) : "" ),
891
+ 'postId' => $attachmentId,
892
+ 'issue' => $issue
893
+ )
894
+ );
895
+ }
896
+ return false;
897
+ }
898
+
899
+ // Delete all issues
900
+ function reset_issues( $includingIgnored = false ) {
901
+ global $wpdb;
902
+ $table_name = $wpdb->prefix . "mclean_scan";
903
+ if ( $includingIgnored ) {
904
+ $wpdb->query( "DELETE FROM $table_name WHERE deleted = 0" );
905
+ }
906
+ else {
907
+ $wpdb->query( "DELETE FROM $table_name WHERE ignored = 0 AND deleted = 0" );
908
+ }
909
+ if ( file_exists( plugin_dir_path( __FILE__ ) . '/media-cleaner.log' ) ) {
910
+ file_put_contents( plugin_dir_path( __FILE__ ) . '/media-cleaner.log', '' );
911
+ }
912
+ $table_name = $wpdb->prefix . "mclean_refs";
913
+ $wpdb->query("TRUNCATE $table_name");
914
+ }
915
+
916
+ function echo_issue( $issue ) {
917
+ if ( $issue == 'NO_CONTENT' ) {
918
+ _e( "Seems not in use.", 'media-cleaner' );
919
+ }
920
+ else if ( $issue == 'NO_MEDIA' ) {
921
+ _e( "Not in Media Library.", 'media-cleaner' );
922
+ }
923
+ else if ( $issue == 'ORPHAN_RETINA' ) {
924
+ _e( "Orphan retina.", 'media-cleaner' );
925
+ }
926
+ else if ( $issue == 'ORPHAN_WEBP' ) {
927
+ _e( "Orphan WebP.", 'media-cleaner' );
928
+ }
929
+ else if ( $issue == 'ORPHAN_MEDIA' ) {
930
+ _e( "File not found.", 'media-cleaner' );
931
+ }
932
+ else {
933
+ echo $issue;
934
+ }
935
+ }
936
+ }
937
+
938
+
939
+ /*
940
+ INSTALL / UNINSTALL
941
+ */
942
+
943
+ function wpmc_init( $mainfile ) {
944
+ //register_activation_hook( $mainfile, 'wpmc_reset' );
945
+ //register_deactivation_hook( $mainfile, 'wpmc_uninstall' );
946
+ register_uninstall_hook( $mainfile, 'wpmc_uninstall' );
947
+ }
948
+
949
+ function wpmc_reset () {
950
+ wpmc_uninstall();
951
+ wpmc_install();
952
+ $upload_folder = wp_upload_dir();
953
+ $basedir = $upload_folder['basedir'];
954
+ if ( !is_writable( $basedir ) ) {
955
+ echo '<div class="error"><p>' . __( 'The directory for uploads is not writable. Media Cleaner will only be able to scan.', 'media-cleaner' ) . '</p></div>';
956
+ }
957
+
958
+ }
959
+
960
+ function wpmc_install() {
961
+ global $wpdb;
962
+ $table_name = $wpdb->prefix . "mclean_scan";
963
+ $charset_collate = $wpdb->get_charset_collate();
964
+ $sql = "CREATE TABLE $table_name (
965
+ id BIGINT(20) NOT NULL AUTO_INCREMENT,
966
+ time DATETIME DEFAULT '0000-00-00 00:00:00' NOT NULL,
967
+ type TINYINT(1) NOT NULL,
968
+ postId BIGINT(20) NULL,
969
+ path TINYTEXT NULL,
970
+ size INT(9) NULL,
971
+ ignored TINYINT(1) NOT NULL DEFAULT 0,
972
+ deleted TINYINT(1) NOT NULL DEFAULT 0,
973
+ issue TINYTEXT NOT NULL,
974
+ PRIMARY KEY (id)
975
+ ) " . $charset_collate . ";" ;
976
+ require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
977
+ dbDelta( $sql );
978
+ $sql="ALTER TABLE $table_name ADD INDEX IgnoredIndex (ignored) USING BTREE;";
979
+ $wpdb->query($sql);
980
+ $table_name = $wpdb->prefix . "mclean_refs";
981
+ $charset_collate = $wpdb->get_charset_collate();
982
+ // This key doesn't work on too many installs because of the 'Specified key was too long' issue
983
+ // KEY mediaLookUp (mediaId, mediaUrl)
984
+ $sql = "CREATE TABLE $table_name (
985
+ id BIGINT(20) NOT NULL AUTO_INCREMENT,
986
+ mediaId BIGINT(20) NULL,
987
+ mediaUrl VARBINARY(256) NULL,
988
+ originType VARBINARY(32) NOT NULL,
989
+ PRIMARY KEY (id)
990
+ ) " . $charset_collate . ";";
991
+ require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
992
+ dbDelta( $sql );
993
+ }
994
+
995
+ function wpmc_uninstall () {
996
+ global $wpdb;
997
+ $table_name1 = $wpdb->prefix . "mclean_scan";
998
+ $table_name2 = $wpdb->prefix . "mclean_refs";
999
+ $table_name3 = $wpdb->prefix . "wpmcleaner";
1000
+ $sql = "DROP TABLE IF EXISTS $table_name1, $table_name2, $table_name3;";
1001
+ $wpdb->query( $sql );
1002
+ }
trunk/engine.php ADDED
@@ -0,0 +1,149 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class Meow_WPMC_Engine {
4
+
5
+ function __construct( $core, $admin ) {
6
+ $this->core = $core;
7
+ $this->admin = $admin;
8
+ }
9
+
10
+ /*
11
+ STEP 1: Parse the content, and look for references
12
+ */
13
+
14
+ // Parse the posts for references (based on $limit and $limitsize for paging the scan)
15
+ function parse( $limit, $limitsize, &$message = '' ) {
16
+ if ( empty( $limit ) )
17
+ $this->core->reset_issues();
18
+
19
+ $method = get_option( 'wpmc_method', 'media' );
20
+ $check_library = get_option(' wpmc_media_library', true );
21
+ $check_content = get_option( 'wpmc_content', true );
22
+ $check_postmeta = get_option( 'wpmc_postmeta', false );
23
+ $check_posts = get_option( 'wpmc_posts', false );
24
+ $check_widgets = get_option( 'wpmc_widgets', false );
25
+
26
+ if ( $method == 'media' && $check_posts && !$check_content && !$check_postmeta && !$check_widgets ) {
27
+ $message = __( "Posts, Meta and Widgets analysis are all off. Done.", 'media-cleaner' );
28
+ return true;
29
+ }
30
+ if ( $method == 'files' && $check_library && !$check_content && !$check_posts && !$check_postmeta && !$check_widgets ) {
31
+ $message = __( "Posts, Meta and Widgets analysis are all off. Done.", 'media-cleaner' );
32
+ return true;
33
+ }
34
+
35
+ // Initialize the parsers
36
+ do_action( 'wpmc_initialize_parsers' );
37
+
38
+ global $wpdb;
39
+ // Maybe we could avoid to check more post_types.
40
+ // SELECT post_type, COUNT(*) FROM `wp_posts` GROUP BY post_type
41
+ $posts = $wpdb->get_col( $wpdb->prepare( "SELECT p.ID FROM $wpdb->posts p
42
+ WHERE p.post_status != 'inherit'
43
+ AND p.post_status != 'trash'
44
+ AND p.post_status != 'auto-draft'
45
+ AND p.post_type != 'attachment'
46
+ AND p.post_type != 'shop_order'
47
+ AND p.post_type != 'shop_order_refund'
48
+ AND p.post_type != 'nav_menu_item'
49
+ AND p.post_type != 'revision'
50
+ AND p.post_type != 'auto-draft'
51
+ AND p.post_type != 'wphb_minify_group'
52
+ AND p.post_type != 'customize_changeset'
53
+ AND p.post_type != 'oembed_cache'
54
+ AND p.post_type NOT LIKE 'ml-slide%'
55
+ AND p.post_type NOT LIKE '%acf-%'
56
+ AND p.post_type NOT LIKE '%edd_%'
57
+ LIMIT %d, %d", $limit, $limitsize
58
+ )
59
+ );
60
+
61
+ // Only at the beginning
62
+ if ( empty( $limit ) ) {
63
+ $this->core->log( "Parsed references:" );
64
+ if ( get_option( 'wpmc_widgets', false ) ) {
65
+
66
+ global $wp_registered_widgets;
67
+ $syswidgets = $wp_registered_widgets;
68
+ $active_widgets = get_option( 'sidebars_widgets' );
69
+ foreach ( $active_widgets as $sidebar_name => $widgets ) {
70
+ if ( $sidebar_name != 'wp_inactive_widgets' && !empty( $widgets ) && is_array( $widgets ) ) {
71
+ foreach ( $widgets as $key => $widget ) {
72
+ do_action( 'wpmc_scan_widget', $syswidgets[$widget] );
73
+ }
74
+ }
75
+ }
76
+
77
+ do_action( 'wpmc_scan_widgets' );
78
+ }
79
+ do_action( 'wpmc_scan_once' );
80
+ }
81
+
82
+ $this->core->timeout_check_start( count( $posts ) );
83
+
84
+ foreach ( $posts as $post ) {
85
+ $this->core->timeout_check();
86
+
87
+ // Check Meta
88
+ if ( $check_content || $check_postmeta )
89
+ do_action( 'wpmc_scan_postmeta', $post );
90
+
91
+ // Check Posts
92
+ if ( $check_content || $check_posts ) {
93
+ // Get HTML for this post
94
+ $html = get_post_field( 'post_content', $post );
95
+ do_action( 'wpmc_scan_post', $html, $post );
96
+ }
97
+
98
+ // Extra scanning methods
99
+ do_action( 'wpmc_scan_extra', $post );
100
+
101
+ $this->core->timeout_check_additem();
102
+ }
103
+
104
+ // Write the references found (and cached) by the parsers
105
+ $this->core->write_references();
106
+
107
+ $finished = count( $posts ) < $limitsize;
108
+ if ( $finished )
109
+ $this->core->log();
110
+ $message = __( "Posts checked.", 'media-cleaner' );
111
+ return $finished;
112
+ }
113
+
114
+ /*
115
+ STEP 2: List the media entries (or files)
116
+ */
117
+
118
+ // Get files in /uploads (if path is null, the root of /uploads is returned)
119
+ function get_files( $path = null ) {
120
+ $files = apply_filters( 'wpmc_list_uploaded_files', null, $path );
121
+ return $files;
122
+ }
123
+
124
+ function get_media_entries( $limit, $limitsize ) {
125
+ global $wpdb;
126
+ $results = $wpdb->get_col( $wpdb->prepare( "SELECT p.ID FROM $wpdb->posts p
127
+ WHERE p.post_status = 'inherit'
128
+ AND p.post_type = 'attachment'
129
+ LIMIT %d, %d", $limit, $limitsize
130
+ )
131
+ );
132
+ return $results;
133
+ }
134
+
135
+ /*
136
+ STEP 3: Check the media entries (or files) against the references
137
+ */
138
+
139
+ function check_media( $media ) {
140
+ return $this->core->check_media( $media );
141
+ }
142
+
143
+ function check_file( $file ) {
144
+ return apply_filters( 'wpmc_check_file', true, $file );
145
+ }
146
+
147
+ }
148
+
149
+ ?>
trunk/media-cleaner.php ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ Plugin Name: Media Cleaner
4
+ Plugin URI: https://meowapps.com
5
+ Description: Clean your Media Library, many options, trash system.
6
+ Version: 5.4.4
7
+ Author: Jordy Meow
8
+ Author URI: https://meowapps.com
9
+ Text Domain: media-cleaner
10
+
11
+ Originally developed for two of my websites:
12
+ - Jordy Meow (http://offbeatjapan.org)
13
+ - Haikyo (http://haikyo.org)
14
+ */
15
+
16
+ if ( class_exists( 'Meow_WPMC_Core' ) ) {
17
+ function wpmc_thanks_admin_notices() {
18
+ echo '<div class="error"><p>Thanks for installing the Pro version of Media Cleaner :) However, the free version is still enabled. Please disable or uninstall it.</p></div>';
19
+ }
20
+ add_action( 'admin_notices', 'wpmc_thanks_admin_notices' );
21
+ return;
22
+ }
23
+
24
+ if ( is_admin() ) {
25
+
26
+ global $wpmc_version;
27
+ global $wpmc;
28
+ $wpmc_version = '5.4.4';
29
+
30
+ require __DIR__ . '/admin.php';
31
+ require __DIR__ . '/core.php';
32
+
33
+ wpmc_init( __FILE__ );
34
+ $admin = new Meow_WPMC_Admin( 'wpmc', __FILE__, 'media-cleaner' );
35
+ $wpmc = new Meow_WPMC_Core( $admin );
36
+ }
37
+
38
+ ?>
trunk/parsers.php ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class MeowApps_WPMC_Parsers {
4
+
5
+ public function __construct() {
6
+ // require_once( ABSPATH . 'wp-admin/includes/plugin.php' ); // mm change
7
+ require_once( 'parsers/common.php' );
8
+ new MeowApps_WPMC_Parser();
9
+
10
+ if ( class_exists( 'WooCommerce' ) )
11
+ require_once( 'parsers/woocommerce.php' );
12
+
13
+ if ( class_exists( 'Attachments' ) ) // mm change
14
+ require_once( 'parsers/attachments.php' );
15
+
16
+ if ( class_exists( 'MetaSliderPlugin' ) || class_exists( 'MetaSliderPro' ) ) // mm change
17
+ require_once( 'parsers/metaslider.php' );
18
+
19
+ if ( function_exists( 'mc_show_sidebar' ) )
20
+ require_once( 'parsers/my-calendar.php' );
21
+
22
+ if ( class_exists( 'Mega_Menu' ) )
23
+ require_once( 'parsers/maxmegamenu.php' );
24
+
25
+ if ( class_exists( 'WPSEO_Options' ) )
26
+ require_once( 'parsers/wpseo.php' );
27
+ }
28
+ }
29
+
30
+ ?>
trunk/parsers/attachments.php ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ // Attachment (https://wordpress.org/plugins/attachments/)
4
+ // Added by Mike Meinz
5
+ // Discussion: https://wordpress.org/support/topic/attachments-plugin/
6
+
7
+ add_action( 'wpmc_scan_postmeta', 'wpmc_scan_postmeta_attachments' );
8
+
9
+ function wpmc_scan_postmeta_attachments($id) {
10
+ global $wpmc;
11
+ $postmeta_images_ids = array();
12
+ $attachments_json = get_post_meta( $id, 'attachments', true ); // meta_key=='attachments'
13
+ $attachments_decoded = is_string( $attachments_json ) ? json_decode( $attachments_json ) : false;
14
+ if ( !empty( $attachments_decoded )) {
15
+ foreach ( $attachments_decoded as $AttachmentData => $TheAttachment ) {
16
+ foreach( $TheAttachment as $AttachmentData => $attachment ) {
17
+ array_push( $postmeta_images_ids, $attachment->id );
18
+ }
19
+ }
20
+ }
21
+ if ( !empty( $postmeta_images_ids ) ) {
22
+ $wpmc->add_reference_id( $postmeta_images_ids, 'ATTACHMENT (ID)' ); // mm change
23
+ }
24
+ }
25
+
26
+ ?>
trunk/parsers/common.php ADDED
@@ -0,0 +1,149 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class MeowApps_WPMC_Parser {
4
+
5
+ private $metakeys = array( '%gallery%', '%ids%' );
6
+
7
+ public function __construct() {
8
+
9
+ // Check theme and favicon
10
+ add_action( 'wpmc_scan_once', array( $this, 'scan_once' ), 10, 0 );
11
+
12
+ // Check widgets for IDs and URLs
13
+ add_action( 'wpmc_scan_widget', array( $this, 'scan_widget' ), 10, 1 );
14
+
15
+ // Detect values in the general (known, based on %like%) Meta Keys
16
+ add_action( 'wpmc_scan_postmeta', array( $this, 'scan_postmeta' ), 10, 1 );
17
+
18
+ // Check URLs, IDs, WP Gallery
19
+ add_action( 'wpmc_scan_post', array( $this, 'scan_post' ), 10, 2 );
20
+ }
21
+
22
+ public function scan_once() {
23
+ global $wpmc;
24
+ $theme_ids = array();
25
+ $theme_urls = array();
26
+ $wpmc->get_images_from_themes( $theme_ids, $theme_urls );
27
+ $wpmc->add_reference_id( $theme_ids, 'THEME' );
28
+ $wpmc->add_reference_url( $theme_urls, 'THEME' );
29
+ $favicon = $wpmc->get_favicon();
30
+ if ( !empty( $favicon ) ) {
31
+ $wpmc->add_reference_url( $favicon, 'SITE ICON' );
32
+ }
33
+ }
34
+
35
+ function get_images_from_widget( $widget, &$ids, &$urls ) {
36
+ global $wpmc;
37
+ $widget_class = $widget['callback'][0]->option_name;
38
+ $instance_id = $widget['params'][0]['number'];
39
+ $widget_data = get_option( $widget_class );
40
+ if ( !empty( $widget_data[$instance_id]['text'] ) ) {
41
+ $html = $widget_data[$instance_id]['text']; // mm change
42
+ $urls = array_merge( $urls, $wpmc->get_urls_from_html( $html ) );
43
+ }
44
+ if ( !empty( $widget_data[$instance_id]['attachment_id'] ) ) {
45
+ $id = $widget_data[$instance_id]['attachment_id'];
46
+ array_push( $ids, $id );
47
+ }
48
+ if ( !empty( $widget_data[$instance_id]['url'] ) ) {
49
+ $url = $widget_data[$instance_id]['url'];
50
+ if ( $wpmc->is_url( $url ) ) {
51
+ $url = $wpmc->clean_url( $url );
52
+ if ( !empty($url) )
53
+ array_push( $urls, $url );
54
+ }
55
+ }
56
+ if ( !empty( $widget_data[$instance_id]['ids'] ) ) {
57
+ $newIds = $widget_data[$instance_id]['ids'];
58
+ $ids = array_merge( $ids, $newIds );
59
+ }
60
+ // Recent Blog Posts
61
+ if ( !empty( $widget_data[$instance_id]['thumbnail'] ) ) {
62
+ $id = $widget_data[$instance_id]['thumbnail'];
63
+ array_push( $ids, $id );
64
+ }
65
+ }
66
+
67
+ public function scan_widget( $widget ) {
68
+ global $wpmc;
69
+ $widgets_ids = array();
70
+ $widgets_urls = array();
71
+ $this->get_images_from_widget( $widget, $widgets_ids, $widgets_urls );
72
+ $wpmc->add_reference_id( $widgets_ids, 'WIDGET' );
73
+ $wpmc->add_reference_url( $widgets_urls, 'WIDGET' );
74
+ }
75
+
76
+ public function scan_post( $html, $id ) {
77
+ global $wpmc;
78
+ $posts_images_urls = array();
79
+ $posts_images_ids = array();
80
+ $galleries_images = array();
81
+
82
+ // Check URLs in HTML
83
+ $new_urls = $wpmc->get_urls_from_html( $html );
84
+ $posts_images_urls = array_merge( $posts_images_urls, $new_urls );
85
+
86
+ // Check URLs in the Excerpt
87
+ $excerpt = get_post_field( 'post_excerpt', $id );
88
+ if ( !empty( $excerpt ) ) {
89
+ $new_urls = $wpmc->get_urls_from_html( $excerpt );
90
+ $posts_images_urls = array_merge( $posts_images_urls, $new_urls );
91
+ }
92
+
93
+ // Check for images IDs through classes in in posts
94
+ preg_match_all( "/wp-image-([0-9]+)/", $html, $res );
95
+ if ( !empty( $res ) && isset( $res[1] ) && count( $res[1] ) > 0 )
96
+ $posts_images_ids = array_merge( $posts_images_ids, $res[1] );
97
+
98
+ // Standard WP Gallery
99
+ $galleries = get_post_galleries_images( $id );
100
+ foreach ( $galleries as $gallery ) {
101
+ foreach ( $gallery as $image ) {
102
+ array_push( $galleries_images, $wpmc->clean_url( $image ) );
103
+ }
104
+ }
105
+
106
+ $wpmc->add_reference_id( $posts_images_ids, 'CONTENT (ID)' );
107
+ $wpmc->add_reference_url( $posts_images_urls, 'CONTENT (URL)' );
108
+ $wpmc->add_reference_url( $galleries_images, 'GALLERY (URL)' );
109
+ }
110
+
111
+ public function scan_postmeta( $id ) {
112
+ global $wpdb, $wpmc;
113
+
114
+ $likes = array ();
115
+ foreach ( $this->metakeys as $metakey ) $likes[] = "OR meta_key LIKE '{$metakey}'";
116
+ $likes = implode( ' ', $likes );
117
+
118
+ $q = <<< SQL
119
+ SELECT meta_value FROM {$wpdb->postmeta}
120
+ WHERE post_id = %d
121
+ AND (meta_key = '_thumbnail_id' {$likes})
122
+ SQL;
123
+ $metas = $wpdb->get_col( $wpdb->prepare( $q, $id ) );
124
+ if ( count( $metas ) > 0 ) {
125
+ $postmeta_images_ids = array();
126
+ $postmeta_images_urls = array();
127
+ foreach ( $metas as $meta ) {
128
+ // Just a number, let's assume it's a Media ID
129
+ if ( is_numeric( $meta ) ) {
130
+ if ( $meta > 0 )
131
+ array_push( $postmeta_images_ids, $meta );
132
+ continue;
133
+ }
134
+ $decoded = @unserialize( $meta );
135
+ if ( is_array( $decoded ) ) {
136
+ $wpmc->array_to_ids_or_urls( $decoded, $postmeta_images_ids, $postmeta_images_urls );
137
+ continue;
138
+ }
139
+ $exploded = explode( ',', $meta );
140
+ if ( is_array( $exploded ) ) {
141
+ $wpmc->array_to_ids_or_urls( $exploded, $postmeta_images_ids, $postmeta_images_urls );
142
+ continue;
143
+ }
144
+ }
145
+ $wpmc->add_reference_id( $postmeta_images_ids, 'META (ID)' );
146
+ $wpmc->add_reference_id( $postmeta_images_urls, 'META (URL)' );
147
+ }
148
+ }
149
+ }
trunk/parsers/maxmegamenu.php ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ // Max Mega Menu (https://wordpress.org/plugins/megamenu/)
4
+ // Added by Mike Meinz
5
+ //
6
+
7
+ add_action('wpmc_scan_widgets', 'wpmc_scan_widgets_maxmegamenu');
8
+
9
+ function wpmc_scan_widgets_maxmegamenu() {
10
+ global $wpmc;
11
+ global $wpdb;
12
+ $urls = array();
13
+ $q = "SELECT meta_value FROM {$wpdb->postmeta} WHERE meta_key = '_menu_item_url' and LENGTH(TRIM(meta_value)) > 0;";
14
+ $rows = $wpdb->get_col($q);
15
+ if ( $wpdb->last_error ) {
16
+ error_log($q . " " . $wpdb->last_error);
17
+ $wpmc->log($q . " " . $wpdb->last_error);
18
+ die($wpdb->last_error);
19
+ }
20
+
21
+ if ( count( $rows ) > 0 ) {
22
+ foreach( $rows as $metavalue ) {
23
+ if ( ( !empty( $metavalue ) ) && $wpmc->is_url( $metavalue ) ) {
24
+ $url = $wpmc->clean_url( $metavalue );
25
+ if ( !empty( $url ) ) {
26
+ array_push( $urls, $url );
27
+ }
28
+ }
29
+ }
30
+ }
31
+
32
+ if ( !empty( $urls ) ) {
33
+ $wpmc->add_reference_url( $urls, 'MAX MEGA MENU (URL)' );
34
+ }
35
+ }
36
+
37
+ ?>
trunk/parsers/metaslider.php ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ add_action( 'wpmc_scan_widgets', 'wpmc_scan_widgets_metaslider' );
4
+
5
+ function wpmc_scan_widgets_metaslider() {
6
+ global $wpdb;
7
+ global $wpmc;
8
+ $q = "SELECT object_id
9
+ FROM {$wpdb->term_relationships}
10
+ WHERE object_id > 0
11
+ AND term_taxonomy_id
12
+ IN (SELECT term_taxonomy_id FROM {$wpdb->term_taxonomy} WHERE taxonomy = 'ml-slider');";
13
+ $imageIds = $wpdb->get_col( $q );
14
+ if ( $wpdb->last_error ) {
15
+ error_log( $q . " " . $wpdb->last_error );
16
+ $wpmc->log( $q . " " . $wpdb->last_error );
17
+ die( $wpdb->last_error );
18
+ }
19
+ if ( count( $imageIds) > 0 ) {
20
+ $wpmc->add_reference_id( $imageIds, 'METASLIDER (ID)' );
21
+ }
22
+ }
23
+ ?>
trunk/parsers/my-calendar.php ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ // My Calendar (https://wordpress.org/plugins/my-calendar/)
4
+ // Added by Mike Meinz
5
+ //
6
+
7
+ add_action( 'wpmc_scan_widgets', 'wpmc_scan_widgets_mycalendar' );
8
+
9
+ function wpmc_scan_widgets_mycalendar() {
10
+ global $wpmc;
11
+ global $wpdb;
12
+ $eventurls = array();
13
+ $q = "SELECT event_desc, event_short, event_link, event_url, event_image FROM " . $wpdb->prefix . "my_calendar WHERE
14
+ (LOWER(event_desc) like '%http%' or
15
+ LOWER(event_short) like '%http%' or
16
+ LOWER(event_link) like 'http%' or
17
+ LOWER(event_image) like 'http%' or
18
+ LOWER(event_url) like 'http%');";
19
+ $rows = $wpdb->get_results( $q, ARRAY_N );
20
+ if ( $wpdb->last_error ) {
21
+ error_log( $q . " " . $wpdb->last_error );
22
+ $wpmc->log( $q . " " . $wpdb->last_error );
23
+ die( $wpdb->last_error );
24
+ }
25
+ if ( count( $rows ) > 0 ) {
26
+ foreach ( $rows as $row ) {
27
+ if ( !empty($row[0]) ) { // event_desc
28
+ $urls = $wpmc->get_urls_from_html( $row[0] );
29
+ $eventurls = array_merge( $eventurls, $urls);
30
+ }
31
+ if ( !empty($row[1]) ) { // event_short
32
+ $urls = $wpmc->get_urls_from_html( $row[1] );
33
+ $eventurls = array_merge( $eventurls, $urls);
34
+ }
35
+ if ( !empty($row[2]) ) { // event_link
36
+ array_push( $eventurls, $wpmc->clean_url( $row[2] ) );
37
+ }
38
+ if ( !empty($row[3]) ) { // event_url
39
+ array_push( $eventurls, $wpmc->clean_url( $row[3] ) );
40
+ }
41
+ if ( !empty($row[4]) ) { // event_image
42
+ array_push( $eventurls, $wpmc->clean_url( $row[4] ) );
43
+ }
44
+
45
+ }
46
+ }
47
+
48
+ if ( !empty( $eventurls ) ) {
49
+ $wpmc->add_reference_url( $eventurls, 'MY CALENDAR (URL)' );
50
+ }
51
+ }
52
+
53
+ ?>
trunk/parsers/woocommerce.php ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ add_action( 'wpmc_scan_once', 'wpmc_scan_once_woocommerce' );
4
+ add_action( 'wpmc_scan_postmeta', 'wpmc_scan_postmeta_woocommerce' );
5
+
6
+ // Only on Start: Analyze WooCommerce Categories Images
7
+ function wpmc_scan_once_woocommerce() {
8
+ global $wpdb, $wpmc;
9
+ $query = "SELECT meta_value
10
+ FROM $wpdb->termmeta
11
+ WHERE meta_key LIKE '%thumbnail_id%'";
12
+ $metas = $wpdb->get_col( $query );
13
+ if ( count( $metas ) > 0 ) {
14
+ $postmeta_images_ids = array();
15
+ foreach ( $metas as $meta )
16
+ if ( is_numeric( $meta ) && $meta > 0 )
17
+ array_push( $postmeta_images_ids, $meta );
18
+ $wpmc->add_reference_id( $postmeta_images_ids, 'WOOCOOMMERCE (ID)' );
19
+ }
20
+
21
+ $placeholder_id = get_option( 'woocommerce_placeholder_image', null, true );
22
+ if ( !empty( $placeholder_id ) )
23
+ $wpmc->add_reference_id( (int)$placeholder_id, 'WOOCOOMMERCE (ID)' );
24
+ }
25
+
26
+ function wpmc_scan_postmeta_woocommerce( $id ) {
27
+ global $wpdb, $wpmc;
28
+ $galleries_images_wc = array();
29
+ $res = $wpdb->get_col( "SELECT meta_value FROM $wpdb->postmeta WHERE post_id = $id
30
+ AND meta_key = '_product_image_gallery'" );
31
+ foreach ( $res as $values ) {
32
+ $ids = explode( ',', $values );
33
+ $galleries_images_wc = array_merge( $galleries_images_wc, $ids );
34
+ }
35
+ $wpmc->add_reference_id( $galleries_images_wc, 'WOOCOOMMERCE (ID)' );
36
+ }
37
+
38
+ ?>
trunk/parsers/wpseo.php ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ add_action( 'wpmc_scan_postmeta', 'wpmc_scan_postmeta_wpseo', 10, 1 );
4
+
5
+ function wpmc_scan_postmeta_wpseo( $id ) {
6
+ global $wpmc;
7
+ $data = get_post_meta( $id, '_yoast_wpseo_opengraph-image', true );
8
+ if ( !empty( $data ) )
9
+ $wpmc->add_reference_url( $data, 'META (URL)' );
10
+ $data = get_post_meta( $id, '_yoast_wpseo_opengraph-image-id', true );
11
+ if ( !empty( $data ) )
12
+ $wpmc->add_reference_id( $data, 'META (ID)' );
13
+ }
14
+
15
+ ?>
trunk/readme.txt ADDED
@@ -0,0 +1,327 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ === Media Cleaner ===
2
+ Contributors: TigrouMeow
3
+ Tags: clean, delete, file, files, images, image, media, library, upload, acf, gutenberg
4
+ Donate link: https://commerce.coinbase.com/checkout/d047546a-77a8-41c8-9ea9-4a950f61832f
5
+ Requires at least: 4.8
6
+ Tested up to: 5.2
7
+ Requires PHP: 7.0
8
+ Stable tag: 5.4.4
9
+
10
+ Clean your WordPress from unused or broken media and files. It has its own trash system and recovery features. Please read the description.
11
+
12
+ == Description ==
13
+
14
+ Media Cleaner cleans your Media Library from the media entries (and files) which aren't used in your website, as well as broken entries. An internal trash allows you to make sure everything works properly before deleting the media entries (and files) permanently. It uses a smart analysis powered by many implementations for specific plugins and themes.
15
+
16
+ **Before using this plugin, make sure you have a proper backup of your install. This step is not optional; it is required. You cannot trust any tools modifying or deleting your files.**
17
+
18
+ For compatibility, Pro version and important information, please read this until the end. Last but not least, it is necessary to read the [tutorial](https://meowapps.com/media-cleaner-tutorial/). It is available on the official website, here: [Media Cleaner](https://meowapps.com/media-cleaner).
19
+
20
+ === VERY IMPORTANT ===
21
+
22
+ This tool is a knife. Do not use it if you don't have any backup, or if you don't understand what it does. This plugin does its best to help you. Learn how to use it and you will get awesome results.
23
+
24
+ === DASHBOARD ===
25
+
26
+ The files detected as not used will be listed in a specific dashboard. It will be up to you to delete them. Then, they will be moved to a trash internal to the plugin. After more testing, you can retrieve those files from the trash, or you can delete them permanently.
27
+
28
+ === COMPATIBILITY ===
29
+
30
+ It works with any kind of media entries, including their retina and/or WebP versions. It is tested on modern WordPress installs (Gutenberg included) as well as older ones, with various themes and by a community of thousands of users. It does support **WooCommerce**. As it requires meticulous a lot of work and testing, **the Pro version might be required if you are using complex plugins to handle the content of your website**. I am constantly increasing the compatibility.
31
+
32
+ === PRO VERSION ===
33
+
34
+ The Pro version adds Filesystem Analysis, extra support for complex plugins, Live Site option and WP-CLI support.
35
+
36
+ With the Filesystem Analysis, [Media Cleaner Pro](https://meowapps.com/media-cleaner) scans your physical /uploads directory, and match it against the Media Library. It also has extra support for complex plugins, such as:
37
+
38
+ - ACF (+ ACF Widgets)
39
+ - Divi Builder
40
+ - Fusion Builder (Avada)
41
+ - Visual Composer (WPBakery)
42
+ - Elementor
43
+ - Beaver Builder
44
+ - Brizy Builder
45
+ - Oxygen Builder
46
+ - And more (Theme X, ZipList Recipe, UberMenu...)
47
+
48
+ The Live Site option will analyze the online version of your website, which might enhance the accuracy of the Cleaner in edge-cases.
49
+
50
+ Last but not least, [Media Cleaner Pro](https://meowapps.com/media-cleaner) has support for WP-CLI. If you have direct access (SSH) to your server, you will be able to run the plugin at a much higher speed or have it ran automatically, if you like.
51
+
52
+ === AGAIN, BE CAREFUL ===
53
+
54
+ Better to be safe than sorry. This plugin deletes files! Therefore, backup is not only important, it is **necessary**. Don't use this plugin if you are not ready. I can't help you if you damaged your install.
55
+
56
+ === SPECIAL THANKS ===
57
+
58
+ - Mike Meinz, an amazing developer from the US who made a thorough debugging of the whole process, load lot of corrections and optimizations, and added support for more plugins.
59
+ - Satoshi Soma, an excellent Japanese developer who helped me when I needed it the most.
60
+
61
+ == Installation ==
62
+
63
+ 1. Upload `media-file-cleaner-pro` to the `/wp-content/plugins/` directory
64
+ 2. Activate the plugin through the 'Plugins' menu in WordPress
65
+ 3. Go in the Settings -> Media Cleaner and check the appropriate options
66
+ 3. Go in Media -> Media Cleaner
67
+
68
+ == Screenshots ==
69
+
70
+ 1. Media -> Media Cleaner
71
+
72
+ == Changelog ==
73
+
74
+ = 5.4.4 =
75
+ * Add: Support for Brizy Builder.
76
+ * Fix: Doesn't trigger the timeout check if WP-CLI is being used.
77
+ * Add: WP-CLI can now delete and trash media entries and files.
78
+
79
+ = 5.4.3 =
80
+ * Add: Support for Yoast SEO and its Facebook Image.
81
+ * Add: Support for Elementor and Oxygen Builder.
82
+ * Add: Support for ACF File Field.
83
+ * Update: Better support for WP CLI.
84
+ * Fix: Make sure the HTML is UTF8 encoded before analyzing it.
85
+ * Update: Removed affiliate links to BlogVault in the Readme as it seems to be against the WordPress guidelines.
86
+
87
+ = 5.4.0 =
88
+ * Add: Support for Uber, Easy Real Estate.
89
+ * Update: Admin CSS and texts.
90
+ * Fix: A rare but wrong call to the log() function was causing the plugin to fail.
91
+ * Update: Clean the options. Now, the Content option replaces Posts/Meta/Widgets (they were useless in a way).
92
+ * Add: Support for WP-CLI (have a look at the how-it-works.txt) in the Pro. Now, scanning can be 100x times faster.
93
+ * Add: Option Live Site in the Pro.
94
+
95
+ = 5.2.4 =
96
+ * Add: Lot of refactoring and optimizations.
97
+ * Add: Support for Theme X, ZipList, and better support for standard websites as well.
98
+ * Add: Yes/No dialog for Reset button.
99
+
100
+ = 5.2.3 =
101
+ * Add: Support for Recent Blog Posts.
102
+ * Add: Additional support for images used by the theme.
103
+
104
+ = 5.2.1 =
105
+ * Add: Support for My Calendar (thanks to Mike Meinz).
106
+ * Add: Support for iFrames (thanks to Mike Meinz).
107
+ * Update: Code cleaning, reorganization and optimization.
108
+
109
+ = 5.2.0 =
110
+ * Update: Many optimizations, modules and big sections of the code are now only loaded when really needed.
111
+ * Fix: Filenames with spaces weren't detected correctly and other.
112
+ * Fix: Make sure that the shortcodes are resolved.
113
+ * Add: Compatibility with more plugins (ACF Widgets, Attachments, Metaslider).
114
+
115
+ = 5.1.3 =
116
+ * Add: Support for WebP.
117
+ * Update: Avoid removing tables when plugin is only disabled.
118
+ * Fix: For some, the tables couldn't be reset.
119
+
120
+ = 5.1.2 =
121
+ * Update: Admin style update and common framework updated.
122
+ * Update: Compatibility with WordPress 5.1.
123
+
124
+ = 5.1.0 =
125
+ * Add: Filters for Filesystem scan. Please have a look at the tutorial (https://meowapps.com/media-cleaner-tutorial/), there is now a section about those filters.
126
+ * Fix: Query for metakey.
127
+ * Fix: Thumbnails matching.
128
+ * Update: Compatibility for WordPress 5 and Gutenberg.
129
+
130
+ = 5.0.1 =
131
+ * Update: Slight code cleaning.
132
+ * Update: Checkboxes are updated dynamically.
133
+ * Info: Media Cleaner is better than ever by going through so many improvements and optimizations this year. The plugin has also been perfectly stable for a few weeks, so I have decided to change its version number. Please help this plugin survive by giving me a nice review, here: https://wordpress.org/support/plugin/meow-lightbox/reviews/?rate=5#new-post. Thank you :)
134
+
135
+ = 4.8.4 =
136
+ * Fix: Issue with ACF Repeater.
137
+ * Fix: Trash and Ignore features resulted in a weird behavior when used together.
138
+ * Add: Now can delete the results of a search.
139
+ * Update: Many UI improvements.
140
+
141
+ = 4.8.0 =
142
+ * Update: Many parts of the UI were rewritten for a better experience. Buttons have a nicer logic.
143
+ * Add: Enhanced error control. From now, when an error occurs during the scan, a popup will appear (asking to try again, or to skip the current item), and errors will be logged to the console.
144
+
145
+ = 4.6.3 =
146
+ * Add: Added an option to only scan the thumbnails and ignore the base files.
147
+ * Add: ACF Repeater support.
148
+ * Update: Improved the code and the performance. Scan is now done differently, using the DB.
149
+ * Fix: Debug logs weren't logging (and enhanced them a bit).
150
+
151
+ = 4.5.5 =
152
+ * Fix: Doesn't remove the Media entry if the files cannot be deleted.
153
+ * Update: Displays a warning if the log file cannot be created.
154
+
155
+ = 4.5.4 =
156
+ * Update: Streamlined the plugin, tutorial has also been rewritten.
157
+ * Update: Simplified the Settings. Removed the Gallery option, as it is part of the Posts or Post Meta.
158
+ * Update: Support for UTF8, Background CSS, and Shortcodes have been moved to the Free version, and are now always enabled. Easier for everyone.
159
+ * Add: Extra support for Page Builders is being added into the Pro version.
160
+
161
+ = 4.5.0 =
162
+ * Add: Support for WooCommerce Short Description.
163
+ * Add: Support for Divi Background.
164
+ * Add: Support for Custom Fields Pro (ACF gallery).
165
+ * Fix: Better support for CSS background.
166
+ * Fix: Avoid detected file to be re-added if already there.
167
+ * Update: Removed UTF-8 option (became useless).
168
+
169
+ = 4.4.7 =
170
+ * Fix: Divi Single Image wasn't always properly detected.
171
+ * Add: Option for CSS background.
172
+ * Update: Code cleaning, slighlty faster now.
173
+ * Info: This plugin is hard work, don't hesitate to review it :) Thank you.
174
+
175
+ = 4.4.6 =
176
+ * Update: Support for ACF (Image Field as Object, URL and ID).
177
+ * Info: This plugin is hard work, don't hesitate to review it :) Thank you.
178
+
179
+ = 4.4.4 =
180
+ * Update: Check DIVI Galleries and Single Images in Beaver Builder.
181
+ * Update: Support for files which aren't images and links (href's).
182
+
183
+ = 4.4.2 =
184
+ * Fix: Too many files were detected as used if WooCommerce was installed.
185
+
186
+ = 4.4.0 =
187
+ * Info: This is a MAJOR UPDATE both in term of optimization and detection. Keep my motivation up and give a good review to the plugin here: https://wordpress.org/support/plugin/media-cleaner/reviews/?rate=5#new-post. That helps me a lot.
188
+ * Update: Meta Data analysis is now cached, so much faster.
189
+ * Update: URL detections became a bit more safer.
190
+ * Update: Detect the images used by the themes more than before.
191
+ * Fix: Images in widgets weren't detected in many cases.
192
+
193
+ = 4.2.5 =
194
+ * Update: Support for WP 4.9.
195
+ * Fix: Could not empty trash if Media was already removed.
196
+
197
+ = 4.2.3 =
198
+ * Fix: Meta search issue.
199
+ * Fix: SQL typo for WooCommerce detection.
200
+ * Fix: Avoid checking the empty arrays.
201
+
202
+ = 4.2.0 =
203
+ * Info: This is a MAJOR UPDATE both in term of optimization and detection. Keep my motivation up and give a good review to the plugin here: https://wordpress.org/support/plugin/media-cleaner/reviews/?rate=5#new-post. That helps me a lot.
204
+ * Add: Support for Fusion Builder (Avada).
205
+ * Add: Cache the results found in posts to analyze them much faster later.
206
+ * Add: Debugging log file (option).
207
+
208
+ = 4.1.0 =
209
+ * Add: Support for WooCommerce Gallery.
210
+ * Add: Support for Visual Composer (Single Image and Gallery).
211
+
212
+ = 4.0.7 =
213
+ * Update: Bulk analyze/prepare galleries, avoid the first request to time out.
214
+ * Add: Many option to make the processing faster or slower depending on the server.
215
+ * Fix: Handle server timeout.
216
+ * Add: Pause button and Retry button.
217
+
218
+ = 4.0.4 =
219
+ * Update: Safest default values.
220
+
221
+ = 4.0.2 =
222
+ * Add: Information about how a certain media is used (Edit Media screen).
223
+ * Fix: Check / Create DB process.
224
+ * Fix: Plugin was not working well with themes using Background/Header.
225
+ * Update: A bit of cleaning.
226
+
227
+ = 4.0.0 =
228
+ * Update: Core was re-organized and cleaned. Ready for nice updates.
229
+
230
+ = 3.7.0 =
231
+ * Fix: Little issue when inserting the serial key for the first time.
232
+ * Update: Compliance with the WordPress.org rules, new licensing system.
233
+ * Update: Moved assets.
234
+ * Info: There will be an important warning showing up during this update. It is an important annoucement.
235
+
236
+ = 3.6.4 =
237
+ * Fix: Plugin was not working properly with broken Media metadata. It now handles it properly.
238
+ * Info: If you want to give me a bit of motivation, write a review on https://wordpress.org/support/plugin/media-cleaner/reviews/?rate=5#new-post.
239
+
240
+ = 3.6.2 =
241
+ * Fix: When over 1 GO, was displaying a lower size value.
242
+ * Fix: Counting wasn't exact with a Filesystem scan.
243
+ * Info: Please read the previous changelog as it didn't appear in WP for some reason.
244
+ * Add: Check Posts also look for the Media ID in the classes (more secure).
245
+ * Info: If you want to give me a bit of motivation, write a review on https://wordpress.org/support/plugin/media-cleaner/reviews/?rate=5#new-post.
246
+
247
+ = 3.6.0 =
248
+ * Add: Now the Media can be recovered! You can remove your Media through the plugin, make sure they are not in use (by testing your website thoroughly) and later delete them definitely from the trash. I think you will find it awesome.
249
+ * Update: Nicer internal icons rather than the old images for the UI.
250
+ * Update: Faster and safer for post_content checks.
251
+ * Update: This is a big one. The plugin is more clear about what it does. You need to choose either to scan the Media or the Filesystem, and also against what exactly. There has also been a few fixes and it will work on more big installs. If it fails, you can remove a few scanning options, and I will continue to work on making it perfect to support huge installs with all the options on.
252
+
253
+ = 3.2.8 =
254
+ * Update: Show a better edit media screen.
255
+ * Update: Will show the same number of items as in the Media Library (before it was fixed to 15 items per page).
256
+ * Fix: Was displaying warning if the number of items per page in the Media page is not set.
257
+
258
+ = 3.2.0 =
259
+ * Fix: HTML adapted to WP 4.5.1.
260
+ * Fix: Doesn't break if there is an error on the server-side. Display an alert and continue.
261
+ * Update: Can select more than one file for non-Pro.
262
+ * Fix: Issue with PHP 7.
263
+
264
+ = 3.0.0 =
265
+ * Add: Option for resolving shortcode during analysis.
266
+ * Update: French translation. Big thanks to Guillaume (and also for all his testing!).
267
+ * Info: New name, fresh start. This plugin changed completely since it very first release :)
268
+
269
+ = 2.5.0 =
270
+ * Add: Delete the unused directories.
271
+ * Add: Doesn't break when there are too many files in the system.
272
+ * Add: Pro version with better support.
273
+ * Update: Improved detection of unused files.
274
+ * Fix: UTF8 filenames skipped by default but can be scanned through an option.
275
+ * Fix: Really many fixes :)
276
+ * Info: Contact me if you have been using the plugin for a long time and love it.
277
+
278
+ = 2.4.2 =
279
+ * Add: Inclusion of gallery post format images.
280
+ * Fix: Better gallery URL matching.
281
+ * Info: Thanks to syntax53 for those improvements via GitHub (https://github.com/tigroumeow/media-file-cleaner/pull/3). Please review Media Cleaner if you like it. The plugin needs reviews to live. Thank you :) (https://wordpress.org/support/view/plugin-reviews/media-file-cleaner)
282
+
283
+ = 2.4.0 =
284
+ * Fix: Cross site scripting vulnerability fixes.
285
+ * Change: Many enhancements and fixes made by Matt (http://www.twistedtek.net/). Please thanks him :)
286
+ * Info: Please perform a "Reset" in the plugin dashboard after installing this new version.
287
+
288
+ = 2.2.6 =
289
+ * Fix: Scan for multisite.
290
+ * Change: options are now all enabled by default.
291
+ * Fix: DB issue avoided trashed files from being deleted permanently.
292
+
293
+ = 2.0.2 =
294
+ * Works with WP 4.
295
+ * Gallery support.
296
+ * Fix: IGNORE function was... ignored by the scanning process.
297
+
298
+ = 1.9.0 =
299
+ * Add: thumbnails.
300
+ * Add: IGNORE function.
301
+ * Change: cosmetic changes.
302
+ * Add: now detects the custom header and custom background.
303
+ * Change: the CSS was updated to fit the new Admin theme.
304
+
305
+ = 1.7.0 =
306
+ * Change: the MEDIA files are now going to the trash but the MEDIA reference in the DB is still removed permanently.
307
+ * Stable release.
308
+ * Change: Readme.txt.
309
+
310
+ = 1.4.0 =
311
+ * Add: check the meta properties.
312
+ * Add: check the 'featured image' properties.
313
+ * Fix: keep the trash information when a new scan is started.
314
+ * Fix: remove the DB on uninstall, not on desactivate.
315
+
316
+ = 1.2.2 =
317
+ * Add: progress %.
318
+ * Fix: issues with apostrophes in filenames.
319
+ * Change: UI cleaning.
320
+
321
+ = 1.2.0 =
322
+ * Add: options (scan files / scan media).
323
+ * Fix: mkdir issues.
324
+ * Change: operations are buffered by 5 (faster).
325
+
326
+ = 0.1.0 =
327
+ * First release.
trunk/scripts/dashboard.js ADDED
@@ -0,0 +1,790 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*
2
+ Plugin Name: Media Cleaner
3
+ Description: Clean your Media Library and Uploads Folder.
4
+ Author: Jordy Meow
5
+ */
6
+
7
+ function wpmc_pop_array(items, count) {
8
+ var newItems = [];
9
+ while ( newItems.length < count && items.length > 0 ) {
10
+ newItems.push( items.pop() );
11
+ }
12
+ return newItems;
13
+ }
14
+
15
+ /**
16
+ *
17
+ * RECOVER
18
+ *
19
+ */
20
+
21
+ function wpmc_recover() {
22
+ var items = [];
23
+ jQuery('#wpmc-table input:checked').each(function (index) {
24
+ if (jQuery(this)[0].value != 'on') {
25
+ items.push(jQuery(this)[0].value);
26
+ }
27
+ });
28
+ wpmc_recover_do(items, items.length);
29
+ }
30
+
31
+ function wpmc_recover_all() {
32
+ var items = [];
33
+ var data = { action: 'wpmc_get_all_deleted' };
34
+ jQuery.post(ajaxurl, data, function (response) {
35
+ reply = jQuery.parseJSON(response);
36
+ if ( !reply.success ) {
37
+ alert( reply.message );
38
+ return;
39
+ }
40
+ wpmc_recover_do(reply.results.ids, reply.results.ids.length);
41
+ });
42
+ }
43
+
44
+ function wpmc_recover_do(items, totalcount) {
45
+ wpmc_update_progress(totalcount - items.length, totalcount);
46
+ if (items.length > 0) {
47
+ newItems = wpmc_pop_array(items, 5);
48
+ data = { action: 'wpmc_recover_do', data: newItems };
49
+ }
50
+ else {
51
+ jQuery('#wpmc_pause').hide();
52
+ jQuery('#wpmc_progression').html("Done. Please <a href='?page=media-cleaner'>refresh</a> this page.");
53
+ return;
54
+ }
55
+ jQuery.post(ajaxurl, data, function (response) {
56
+ reply = jQuery.parseJSON(response);
57
+ if ( !reply.success ) {
58
+ alert( reply.message );
59
+ return;
60
+ }
61
+ wpmc_recover_do(items, totalcount);
62
+ });
63
+ }
64
+
65
+ /**
66
+ *
67
+ * DELETE
68
+ *
69
+ */
70
+
71
+ function wpmc_ignore() {
72
+ var items = [];
73
+ jQuery('#wpmc-table input:checked').each(function (index) {
74
+ if (jQuery(this)[0].value != 'on') {
75
+ items.push(jQuery(this)[0].value);
76
+ }
77
+ });
78
+ wpmc_ignore_do(items, items.length);
79
+ }
80
+
81
+ function wpmc_delete() {
82
+ var items = [];
83
+ jQuery('#wpmc-table input:checked').each(function (index) {
84
+ if (jQuery(this)[0].value != 'on') {
85
+ items.push(jQuery(this)[0].value);
86
+ }
87
+ });
88
+ wpmc_delete_do(items, items.length);
89
+ }
90
+
91
+ function wpmc_delete_all(isTrash, filter = '') {
92
+ var items = [];
93
+ var data = {
94
+ action: 'wpmc_get_all_issues',
95
+ isTrash: isTrash ? 1 : 0,
96
+ s: filter
97
+ };
98
+
99
+ jQuery.post(ajaxurl, data, function (response) {
100
+ reply = jQuery.parseJSON(response);
101
+ if ( !reply.success ) {
102
+ alert( reply.message );
103
+ return;
104
+ }
105
+ wpmc_delete_do(reply.results.ids, reply.results.ids.length);
106
+ });
107
+ }
108
+
109
+ function wpmc_update_progress(current, totalcount, isDeleting) {
110
+ if (isDeleting === undefined)
111
+ isDeleting = false;
112
+ var action = isDeleting ? "Deleting" : "Analyzing";
113
+ jQuery('#wpmc_progression').html('<span class="dashicons dashicons-controls-play"></span> ' + action + ' ' + current + "/" + totalcount + " (" + Math.round(current / totalcount * 100) + "%)");
114
+ }
115
+
116
+ function wpmc_delete_do(items, totalcount) {
117
+ wpmc_update_progress(totalcount - items.length, totalcount, true);
118
+ if (items.length > 0) {
119
+ newItems = wpmc_pop_array(items, 5);
120
+ data = { action: 'wpmc_delete_do', data: newItems };
121
+ }
122
+ else {
123
+ jQuery('#wpmc_progression').html("Done. Please <a href='?page=media-cleaner'>refresh</a> this page.");
124
+ return;
125
+ }
126
+ jQuery.post(ajaxurl, data, function (response) {
127
+ reply = jQuery.parseJSON(response);
128
+ if ( !reply.success ) {
129
+ alert( reply.message );
130
+ return;
131
+ }
132
+ wpmc_delete_do(items, totalcount);
133
+ });
134
+ }
135
+
136
+ function wpmc_ignore_do(items, totalcount) {
137
+ wpmc_update_progress(totalcount - items.length, totalcount);
138
+ if (items.length > 0) {
139
+ newItems = wpmc_pop_array(items, 5);
140
+ data = { action: 'wpmc_ignore_do', data: newItems };
141
+ }
142
+ else {
143
+ jQuery('#wpmc_progression').html("Done. Please <a href='?page=media-cleaner'>refresh</a> this page.");
144
+ return;
145
+ }
146
+ jQuery.post(ajaxurl, data, function (response) {
147
+ reply = jQuery.parseJSON(response);
148
+ if ( !reply.success ) {
149
+ alert( reply.message );
150
+ return;
151
+ }
152
+ wpmc_ignore_do(items, totalcount);
153
+ });
154
+ }
155
+
156
+ /**
157
+ *
158
+ * SCAN
159
+ *
160
+ */
161
+
162
+ var wpmc = wpmc_new_context();
163
+
164
+ /**
165
+ * Creates a context object that preserves various states and variables for scanning
166
+ * @return {object}
167
+ */
168
+ function wpmc_new_context() {
169
+ return {
170
+ dirs: [],
171
+ files: [],
172
+ medias: [],
173
+ current: 0,
174
+ total: 0,
175
+ issues: 0,
176
+ isPause: false,
177
+ isPendingPause: false,
178
+ currentPhase: null,
179
+
180
+ phases: {
181
+ preparePosts: {
182
+ init: function () {
183
+ this.progress = 0; // Scanned posts count
184
+ this.progressPrev = 0;
185
+ return this;
186
+ },
187
+ run: function () {
188
+ wpmc_prepare();
189
+ },
190
+ pause: function () {
191
+ jQuery('#wpmc_progression').html('<span class="dashicons dashicons-controls-pause"></span> Paused at preparing posts (' + this.progress + ' posts)');
192
+ },
193
+ skip: function () {
194
+ this.progress += wpmc_cfg.postsBuffer;
195
+ },
196
+ nextPhase: function () {
197
+ if (wpmc_cfg.scanMedia)
198
+ return wpmc.phases.prepareMedia;
199
+ if (wpmc_cfg.scanFiles)
200
+ return wpmc.phases.prepareFiles;
201
+ console.error('Configuration Error'); // This shouldn't happen
202
+ }
203
+ },
204
+ prepareFiles: {
205
+ init: function () {
206
+ this.progress = null; // The last scanned directory
207
+ this.progressPrev = null;
208
+ return this;
209
+ },
210
+ run: function () {
211
+ wpmc_scan_type('files', this.progress);
212
+ },
213
+ pause: function () {
214
+ jQuery('#wpmc_progression').html('<span class="dashicons dashicons-controls-pause"></span> Paused at preparing files (' + this.progress + ')');
215
+ },
216
+ skip: function () {
217
+ var dir = wpmc.dirs.pop();
218
+ if (dir) {
219
+ wpmc.currentPhase.progressPrev = wpmc.currentPhase.progress;
220
+ wpmc.currentPhase.progress = dir;
221
+ }
222
+ else
223
+ wpmc.currentPhase = this.nextPhase().init();
224
+ },
225
+ nextPhase: function () {
226
+ return wpmc.phases.analyze;
227
+ }
228
+ },
229
+ prepareMedia: {
230
+ init: function () {
231
+ this.progress = 0; // Scanned media count
232
+ this.progressPrev = 0;
233
+ return this;
234
+ },
235
+ run: function () {
236
+ wpmc_scan_type('medias', null, this.progress);
237
+ },
238
+ pause: function () {
239
+ jQuery('#wpmc_progression').html('<span class="dashicons dashicons-controls-pause"></span> Paused at preparing media (' + this.progress + ' media)');
240
+ },
241
+ skip: function () {
242
+ this.progress += wpmc_cfg.mediasBuffer;
243
+ },
244
+ nextPhase: function () {
245
+ return wpmc.phases.analyze;
246
+ }
247
+ },
248
+ analyze: {
249
+ init: function () {
250
+ this.currentFiles = [];
251
+ this.currentMedia = [];
252
+ return this;
253
+ },
254
+ run: function () {
255
+ wpmc_scan_do();
256
+ },
257
+ pause: function () {
258
+ var current = wpmc.total - (wpmc.files.length + wpmc.medias.length);
259
+ var totalcount = wpmc.total;
260
+ jQuery('#wpmc_progression').html('<span class="dashicons dashicons-controls-pause"></span> Paused at ' + current + "/" + totalcount + " (" + Math.round(current / totalcount * 100) + "%)");
261
+ },
262
+ skip: function () {
263
+ if (wpmc.files.length)
264
+ this.currentFiles = wpmc_pop_array(wpmc.files, wpmc_cfg.analysisBuffer);
265
+ if (wpmc.medias.lenght)
266
+ this.currentMedia = wpmc_pop_array(wpmc.medias, wpmc_cfg.analysisBuffer);
267
+ },
268
+ rollback: function () {
269
+ wpmc.files = wpmc.files.concat(this.currentFiles.reverse()); // @see wpmc_pop_array
270
+ this.currentFiles = [];
271
+ wpmc.medias = wpmc.medias.concat(this.currentMedia.reverse()); // @see wpmc_pop_array
272
+ this.currentMedia = [];
273
+ }
274
+ }
275
+ }
276
+ };
277
+ }
278
+
279
+ // WPMC GET INITIAL INFO
280
+
281
+ function wpmc_scan_type_finished() {
282
+
283
+ }
284
+
285
+ function wpmc_scan_type_next(type, path) {
286
+
287
+ }
288
+
289
+ function wpmc_prepare() {
290
+ if (!wpmc.currentPhase) return; // Aborted
291
+
292
+ if (wpmc.isPendingPause)
293
+ return wpmc_update_to_pause();
294
+
295
+ setTimeout(
296
+ function() {
297
+ if (!wpmc.currentPhase) return; // Aborted
298
+
299
+ jQuery('#wpmc_progression').html('<span class="dashicons dashicons-portfolio"></span> Preparing posts (' + wpmc.currentPhase.progress + ' posts)...');
300
+
301
+ jQuery.ajax({
302
+ type: 'POST',
303
+ url: ajaxurl,
304
+ dataType: 'text',
305
+ data: {
306
+ action: 'wpmc_prepare_do',
307
+ limit: wpmc.currentPhase.progress
308
+ },
309
+ timeout: wpmc_cfg.timeout + 5000 // Extra 5sec for fail-safe
310
+
311
+ }).done(function (response) {
312
+ if (!wpmc.currentPhase) return; // Aborted
313
+
314
+ var reply = wpmc_parse_response(response);
315
+
316
+ if (!reply.success)
317
+ return wpmc_handle_error(reply.message);
318
+
319
+ if (!reply.finished) {
320
+ wpmc.currentPhase.progressPrev = wpmc.currentPhase.progress;
321
+ wpmc.currentPhase.progress = reply.limit;
322
+ }
323
+ else wpmc.currentPhase = wpmc.currentPhase.nextPhase().init();
324
+
325
+ return wpmc.currentPhase.run();
326
+
327
+ }).fail(function (e) { // Server Error
328
+ wpmc_handle_error(e.statusText, e.status);
329
+ });
330
+
331
+ }, wpmc_cfg.delay
332
+ );
333
+ }
334
+
335
+ function wpmc_scan_type(type, path = null, limit = 0) {
336
+ if (!wpmc.currentPhase) return; // Aborted
337
+
338
+ if (wpmc.isPendingPause)
339
+ return wpmc_update_to_pause();
340
+
341
+ if (path) {
342
+ elpath = path.replace(/^.*[\\\/]/, '');
343
+ jQuery('#wpmc_progression').html('<span class="dashicons dashicons-portfolio"></span> Preparing files (' + elpath + ')...');
344
+ }
345
+ else if (type === 'medias')
346
+ jQuery('#wpmc_progression').html('<span class="dashicons dashicons-admin-media"></span> Preparing medias (' + limit + ' medias)...');
347
+ else
348
+ jQuery('#wpmc_progression').html('<span class="dashicons dashicons-portfolio"></span> Preparing files...');
349
+
350
+ setTimeout(
351
+ function() {
352
+ jQuery.ajax({
353
+ type: 'POST',
354
+ url: ajaxurl,
355
+ dataType: 'text',
356
+ data: {
357
+ action: 'wpmc_scan',
358
+ medias: type === 'medias',
359
+ files: type === 'files',
360
+ path: path,
361
+ limit: limit
362
+ },
363
+ timeout: wpmc_cfg.timeout + 5000 // Extra 5sec for fail-safe
364
+
365
+ }).done(function (response) {
366
+ if (!wpmc.currentPhase) return; // Aborted
367
+
368
+ var reply = wpmc_parse_response(response);
369
+
370
+ if (!reply.success)
371
+ return wpmc_handle_error(reply.message);
372
+
373
+ // Store results
374
+ for (var i = 0, len = reply.results.length; i < len; i++) {
375
+ var r = reply.results[i];
376
+ if (type === 'files') {
377
+ if ( r.type === 'dir' )
378
+ wpmc.dirs.push( r.path );
379
+ else if ( r.type === 'file' ) {
380
+ wpmc.files.push( r.path );
381
+ wpmc.total++;
382
+ }
383
+ }
384
+ else if (type === 'medias') {
385
+ wpmc.medias.push( r );
386
+ wpmc.total++;
387
+ }
388
+ }
389
+
390
+ // Next query
391
+ if (type === 'medias') {
392
+ if (wpmc_cfg.scanFiles || !reply.finished) {
393
+ wpmc.currentPhase.progressPrev = wpmc.currentPhase.progress;
394
+ wpmc.currentPhase.progress = reply.limit;
395
+ }
396
+ else
397
+ wpmc.currentPhase = wpmc.currentPhase.nextPhase().init();
398
+ }
399
+ else if (type === 'files') {
400
+ var dir = wpmc.dirs.pop();
401
+ if (dir) {
402
+ wpmc.currentPhase.progressPrev = wpmc.currentPhase.progress;
403
+ wpmc.currentPhase.progress = dir;
404
+ }
405
+ else
406
+ wpmc.currentPhase = wpmc.currentPhase.nextPhase().init();
407
+ }
408
+ wpmc.currentPhase.run();
409
+
410
+ }).fail(function (e) { // Server Error
411
+ wpmc_handle_error(e.statusText, e.status);
412
+ });
413
+
414
+ }, wpmc_cfg.delay
415
+ );
416
+ }
417
+
418
+ function wpmc_pause() {
419
+ if (wpmc.isPause) { // Resume
420
+ jQuery('#wpmc_pause').html('<span style="top: 3px; position: relative; left: -5px;" class="dashicons dashicons-controls-pause"></span>Pause');
421
+ wpmc.isPause = false;
422
+ wpmc.currentPhase.run();
423
+ }
424
+ else if (wpmc.isPendingPause) {
425
+ wpmc.isPendingPause = false;
426
+ }
427
+ else {
428
+ jQuery('#wpmc_pause').html('<span style="top: 3px; position: relative; left: -5px;" class="dashicons dashicons-controls-pause"></span>Pausing...');
429
+ wpmc.isPendingPause = true;
430
+ }
431
+ }
432
+
433
+ function wpmc_update_to_pause() {
434
+ if (wpmc.isPendingPause) {
435
+ wpmc.currentPhase.pause();
436
+
437
+ jQuery('#wpmc_pause').html('<span style="top: 3px; position: relative; left: -5px;" class="dashicons dashicons-controls-play"></span>Continue');
438
+ wpmc.isPendingPause = false;
439
+ wpmc.isPause = true;
440
+ }
441
+ }
442
+
443
+ /**
444
+ * Parses a Ajax response into a valid JSON
445
+ * @param {string} response The response content to parse
446
+ * @return {object} The parsed result
447
+ */
448
+ function wpmc_parse_response(response) {
449
+ if (typeof response == 'object') return response;
450
+
451
+ var r;
452
+ try {
453
+ r = jQuery.parseJSON(response);
454
+ } catch (e) {
455
+ r = null;
456
+ }
457
+ if (!r) { // Couldn't parse as a valid JSON
458
+ r = {
459
+ success: false,
460
+ message: 'The reply from the server is broken. The reply will be displayed in your Javascript console. You should also check your PHP Error Logs.'
461
+ };
462
+ console.error('Media File Cleaner got a broken reply from the server:', response);
463
+ }
464
+ return r;
465
+ }
466
+
467
+ /**
468
+ * Pauses the scan and Displays an error dialog
469
+ * @param {string} msg=null The error message
470
+ * @param {int} status=null The actual status code that the server responded
471
+ */
472
+ function wpmc_handle_error(msg = null, status = null) {
473
+ wpmc.isPendingPause = true;
474
+ wpmc_update_to_pause();
475
+
476
+ var errDialog = jQuery('#wpmc-error-dialog');
477
+ if (!msg) msg = 'An error happened'; // Default error message
478
+ if (status) {
479
+ console.error('Media Cleaner got an error from server:', status + ' ' + msg);
480
+ msg = '<span class="error-status">' + status + '</span> ' + msg;
481
+ } else
482
+ console.error(msg);
483
+ errDialog.find('h3').html(msg);
484
+ errDialog.dialog('open');
485
+ }
486
+
487
+ function wpmc_update_to_error() {
488
+ var current = wpmc.total - (wpmc.files.length + wpmc.medias.length);
489
+ var totalcount = wpmc.total;
490
+ jQuery('#wpmc_progression').html('<span class="dashicons dashicons-controls-pause"></span> Error at ' + current + "/" + totalcount + " (" + Math.round(current / totalcount * 100) + "%): " + error);
491
+ jQuery('#wpmc_pause').html('<span style="top: 3px; position: relative; left: -5px;" class="dashicons dashicons-controls-play"></span>Retry');
492
+ wpmc.isPendingPause = false;
493
+ wpmc.isPause = true;
494
+ }
495
+
496
+ function wpmc_scan_do() {
497
+ if (!wpmc.currentPhase) return; // Aborted
498
+
499
+ if (wpmc.isPendingPause)
500
+ return wpmc_update_to_pause();
501
+
502
+ wpmc_update_progress(wpmc.total - (wpmc.files.length + wpmc.medias.length), wpmc.total);
503
+ var data = {};
504
+ var expectedSuccess = 0;
505
+ if (wpmc.files.length > 0) {
506
+ wpmc.currentPhase.currentFiles = wpmc_pop_array(wpmc.files, wpmc_cfg.analysisBuffer);
507
+ expectedSuccess = wpmc.currentPhase.currentFiles.length;
508
+ data = { action: 'wpmc_scan_do', type: 'file', data: wpmc.currentPhase.currentFiles };
509
+ }
510
+ else if (wpmc.medias.length > 0) {
511
+ wpmc.currentPhase.currentMedia = wpmc_pop_array(wpmc.medias, wpmc_cfg.analysisBuffer);
512
+ expectedSuccess = wpmc.currentPhase.currentMedia.length;
513
+ data = { action: 'wpmc_scan_do', type: 'media', data: wpmc.currentPhase.currentMedia };
514
+ }
515
+ else {
516
+ jQuery('#wpmc_progression').html(wpmc.issues + " issue(s) found. <a href='?page=media-cleaner'></span>Refresh</a>.");
517
+
518
+ wpmc = wpmc_new_context(); // Reset the context
519
+ jQuery('#wpmc_pause').hide();
520
+ jQuery('#wpmc_scan').html('<span style="top: 3px; position: relative; left: -5px;" class="dashicons dashicons-search"></span>Start Scan');
521
+ jQuery('#wpmc_actions').trigger('idle');
522
+ return;
523
+ }
524
+
525
+ setTimeout(
526
+ function () {
527
+ jQuery.ajax({
528
+ type: 'POST',
529
+ url: ajaxurl,
530
+ dataType: 'text',
531
+ data: data,
532
+ timeout: wpmc_cfg.timeout + 5000 // Extra 5sec for fail-safe
533
+
534
+ }).done(function (response) {
535
+ var reply = wpmc_parse_response(response);
536
+
537
+ if (!reply.success)
538
+ return wpmc_handle_error(reply.message);
539
+
540
+ if (reply.result)
541
+ wpmc.issues += expectedSuccess - reply.result.success;
542
+
543
+ wpmc_scan_do();
544
+
545
+ }).fail(function (e) { // Server Error
546
+ wpmc_handle_error(e.statusText, e.status);
547
+ });
548
+
549
+ }, wpmc_cfg.delay
550
+ );
551
+ }
552
+
553
+ /**
554
+ * Opens a dialog
555
+ * @param {object} content
556
+ * @param {string} content.title=null The title of the dialog
557
+ * @param {string} content.head=null The heading
558
+ * @param {string} content.body=null The body
559
+ * @param {jQuery} content.prepend=null Additional element to prepend
560
+ * @param {jQuery} content.append=null Additional element to append
561
+ * @return {jQuery} The dialog element
562
+ */
563
+ function wpmc_open_dialog(content) {
564
+ var dialog = jQuery('#wpmc-dialog');
565
+ dialog.html('');
566
+ if (content.title)
567
+ dialog.dialog('option', 'title', content.title);
568
+ if (content.head)
569
+ dialog.append('<h3 class="head">' + content.head + '</h3>');
570
+ if (content.body)
571
+ dialog.append('<div class="body">' + content.body + '</div>');
572
+ if (content.prepend)
573
+ dialog.prepend(content.prepend);
574
+ if (content.append)
575
+ dialog.append(content.append);
576
+ dialog.dialog('open');
577
+ return dialog;
578
+ }
579
+
580
+ /**
581
+ *
582
+ * INIT
583
+ *
584
+ */
585
+ (function ($) {
586
+
587
+ // Bulk Selection
588
+ $('#wpmc-cb-select-all').on('change', function (cb) {
589
+ $('#wpmc-table input').prop('checked', cb.target.checked);
590
+ });
591
+
592
+ // Actions
593
+ (function () {
594
+ var wrap = $('#wpmc_actions');
595
+
596
+ // Events: Busy, Idle
597
+ wrap.on('busy', function () {
598
+ wrap.addClass('busy');
599
+ wrap.find('a.exclusive').addClass('disabled');
600
+ wrap.find('input.exclusive').attr('disabled', true);
601
+
602
+ }).on('idle', function () {
603
+ wrap.find('a.exclusive').removeClass('disabled');
604
+ wrap.find('input.exclusive').attr('disabled', false);
605
+ wrap.removeClass('busy');
606
+ });
607
+
608
+ // Scan Button
609
+ wrap.find('#wpmc_scan').on('click', function (ev) {
610
+ ev.preventDefault();
611
+ if (wpmc.currentPhase) { // Abort scan
612
+ wpmc = wpmc_new_context(); // Reset the current context
613
+ $('#wpmc_pause').hide();
614
+ $('#wpmc_pause').html('<span style="top: 3px; position: relative; left: -5px;" class="dashicons dashicons-controls-pause"></span>Pause');
615
+ $('#wpmc_progression').text('');
616
+ $('#wpmc_scan').html('<span style="top: 3px; position: relative; left: -5px;" class="dashicons dashicons-search"></span>Start Scan');
617
+ wrap.trigger('idle');
618
+ return;
619
+ }
620
+ wrap.trigger('busy');
621
+ $('#wpmc_scan').html('Stop Scan');
622
+ $('#wpmc_pause').show();
623
+ wpmc.currentPhase = wpmc.phases.preparePosts.init();
624
+ wpmc.currentPhase.run();
625
+ });
626
+
627
+ // Delete Button
628
+ wrap.find('#wpmc_delete').on('click', function (ev) {
629
+ ev.preventDefault();
630
+ if ($(this).hasClass('disabled')) return;
631
+ wpmc_delete();
632
+ });
633
+
634
+ // Delete All (Search Results) Button
635
+ wrap.find('#wpmc_delete_all').on('click', function (ev) {
636
+ ev.preventDefault();
637
+ var $this = $(this);
638
+ if ($this.hasClass('disabled')) return;
639
+ var filter = $this.data('filter') || '';
640
+ if (filter && filter != wrap.find('.search-box input[name="s"]').val()) {
641
+ var dialog = wpmc_open_dialog({
642
+ title: 'Continue?',
643
+ body: 'You have modified the search terms and did not click on "Search".<br>The <b>current</b> results will be deleted. Do you want to continue?',
644
+ append: $('<div class="prompt">')
645
+ .append(
646
+ // Cancel Button
647
+ $('<a class="button cancel" href="#">Cancel</a>').on('click', function (ev) {
648
+ ev.preventDefault();
649
+ dialog.dialog('close');
650
+ })
651
+ ).append(
652
+ // Continue Button
653
+ $('<a class="button button-primary continue" href="#">Continue</a>').on('click', function (ev) {
654
+ ev.preventDefault();
655
+ dialog.dialog('close');
656
+ wpmc_delete_all(false, filter);
657
+ })
658
+ )
659
+ });
660
+ return;
661
+ }
662
+ wpmc_delete_all(false, filter);
663
+ });
664
+
665
+ // Ignore Button
666
+ wrap.find('#wpmc_ignore').on('click', function (ev) {
667
+ ev.preventDefault();
668
+ if ($(this).hasClass('disabled')) return;
669
+ wpmc_ignore();
670
+ });
671
+
672
+ // Recover All Button
673
+ wrap.find('#wpmc_recover_all').on('click', function (ev) {
674
+ ev.preventDefault();
675
+ if ($(this).hasClass('disabled')) return;
676
+ wpmc_recover_all();
677
+ });
678
+
679
+ // Empty Trash Button
680
+ wrap.find('#wpmc_empty_trash').on('click', function (ev) {
681
+ ev.preventDefault();
682
+ if ($(this).hasClass('disabled')) return;
683
+ wpmc_delete_all(true);
684
+ });
685
+
686
+ // Reset Button
687
+ wrap.find('#wpmc_reset').on('click', function (ev) {
688
+ ev.preventDefault();
689
+ var $this = $(this);
690
+ var dialog = wpmc_open_dialog({
691
+ title: "Reset",
692
+ body: "This will reset the Media Cleaner database. All the information related to your trash, your latest scan and ignored entries will be lost. Do you want to continue?",
693
+ append: $('<div class="prompt">')
694
+ .append(
695
+ // Cancel Button
696
+ $('<a class="button cancel" href="#">Cancel</a>').on('click', function (ev) {
697
+ ev.preventDefault();
698
+ dialog.dialog('close');
699
+ })
700
+ ).append(
701
+ // Continue Button
702
+ $('<a class="button button-primary continue" href="#">Continue</a>').on('click', function (ev) {
703
+ ev.preventDefault();
704
+ dialog.dialog('close');
705
+ location.href = $this.attr('href');
706
+ })
707
+ )
708
+ });
709
+ });
710
+ })();
711
+
712
+ // Dialog
713
+ (function () {
714
+ var dialog = $('#wpmc-dialog');
715
+
716
+ dialog.dialog({
717
+ dialogClass: 'wp-dialog',
718
+ autoOpen: false,
719
+ draggable: true,
720
+ width: 'auto',
721
+ modal: true,
722
+ resizable: false,
723
+ closeOnEscape: true,
724
+ position: {
725
+ my: "center",
726
+ at: "center",
727
+ of: window
728
+ },
729
+ open: function () {
730
+ // Close dialog by clicking the overlay behind it
731
+ $('.ui-widget-overlay').on('click', function () {
732
+ dialog.dialog('close');
733
+ });
734
+ },
735
+ create: function () {
736
+ // Style fix for WordPress admin
737
+ $('.ui-dialog-titlebar-close').addClass('ui-button');
738
+ }
739
+ });
740
+ })();
741
+
742
+ // Error Dialog
743
+ (function () {
744
+ var errDialog = $('#wpmc-error-dialog');
745
+
746
+ errDialog.dialog({
747
+ title: 'ERROR',
748
+ dialogClass: 'wp-dialog',
749
+ autoOpen: false,
750
+ draggable: true,
751
+ width: 'auto',
752
+ modal: true,
753
+ resizable: false,
754
+ closeOnEscape: true,
755
+ position: {
756
+ my: "center",
757
+ at: "center",
758
+ of: window
759
+ },
760
+ open: function () {
761
+ $('.ui-widget-overlay').on('click', function () {
762
+ errDialog.dialog('close');
763
+ });
764
+ },
765
+ create: function () {
766
+ $('.ui-dialog-titlebar-close').addClass('ui-button');
767
+ }
768
+ });
769
+
770
+ // Retry
771
+ errDialog.find('a.retry').on('click', function (ev) {
772
+ ev.preventDefault();
773
+
774
+ wpmc_pause(); // Resume
775
+ errDialog.dialog('close');
776
+ });
777
+
778
+ // Skip (Ignore error)
779
+ errDialog.find('a.skip').on('click', function (ev) {
780
+ ev.preventDefault();
781
+
782
+ wpmc.currentPhase.skip();
783
+ console.warn('1 error is ignored by you');
784
+
785
+ wpmc_pause(); // Resume
786
+ errDialog.dialog('close');
787
+ });
788
+ })();
789
+
790
+ })(jQuery);
trunk/scripts/settings.js ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Script for settings screen
3
+ */
4
+ (function ($) {
5
+
6
+ /**
7
+ * Validation
8
+ */
9
+ $('form *[data-needs-validation]').on('input', function (ev) {
10
+ var $this = $(this);
11
+ var form = $this.closest('form');
12
+ var submit = form.find('*[type=submit]');
13
+ submit.attr('disabled', true);
14
+
15
+ $.ajax(ajaxurl, {
16
+ method: 'post',
17
+ dataType: 'json',
18
+ data: {
19
+ action: 'wpmc_validate_option',
20
+ name: $this.attr('name'),
21
+ value: $this.val()
22
+ }
23
+
24
+ }).always(function () {
25
+ submit.attr('disabled', false);
26
+
27
+ }).done(function (response) {
28
+ if (response.success) {
29
+ $this[0].setCustomValidity('');
30
+
31
+ } else { // Invalid Data
32
+ $this[0].setCustomValidity(response.data.message);
33
+ }
34
+ });
35
+ });
36
+
37
+ /**
38
+ * Scanning Method
39
+ */
40
+ $('select#wpmc_method').on('change', function (ev) {
41
+ var selected = $(this).val();
42
+ if (selected == 'media') { // Method = "Media Library"
43
+ $('input#wpmc_media_library').attr('disabled', true);
44
+ } else { // Method = Other Else
45
+ $('input#wpmc_media_library').attr('disabled', false);
46
+ }
47
+ });
48
+
49
+ })(jQuery);
trunk/scripts/style.css ADDED
@@ -0,0 +1,92 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .wp-core-ui .button-red {
2
+ background-color: #9B2124;
3
+ background-image: -webkit-gradient(linear, left top, left bottom, from(#C5292E), to(#9B2124));
4
+ background-image: -webkit-linear-gradient(top, #C5292E, #9B2124);
5
+ background-image: -moz-linear-gradient(top, #C5292E, #9B2124);
6
+ background-image: -ms-linear-gradient(top, #C5292E, #9B2124);
7
+ background-image: -o-linear-gradient(top, #C5292E, #9B2124);
8
+ background-image: linear-gradient(to bottom, #C5292E, #9B2124);
9
+ border-color: #9B2124;
10
+ border-bottom-color: #8D1F21;
11
+ -webkit-box-shadow: inset 0 1px 0 rgba(120,200,230,0.5);
12
+ box-shadow: inset 0 1px 0 rgba(120,200,230,0.5);
13
+ color: #fff;
14
+ text-decoration: none;
15
+ text-shadow: 0 1px 0 rgba(0,0,0,0.1);
16
+ float: right;
17
+ }
18
+
19
+ .wp-core-ui .button-red.hover,
20
+ .wp-core-ui .button-red:hover,
21
+ .wp-core-ui .button-red.focus,
22
+ .wp-core-ui .button-red:focus {
23
+ background-color: #B72629;
24
+ background-image: -webkit-gradient(linear, left top, left bottom, from(#D22E30), to(#9B2124));
25
+ background-image: -webkit-linear-gradient(top, #D22E30, #9B2124);
26
+ background-image: -moz-linear-gradient(top, #D22E30, #9B2124);
27
+ background-image: -ms-linear-gradient(top, #D22E30, #9B2124);
28
+ background-image: -o-linear-gradient(top, #D22E30, #9B2124);
29
+ background-image: linear-gradient(to bottom, #D22E30, #9B2124);
30
+ border-color: #7F1C1F;
31
+ -webkit-box-shadow: inset 0 1px 0 rgba(120,200,230,0.6);
32
+ box-shadow: inset 0 1px 0 rgba(120,200,230,0.6);
33
+ color: #fff;
34
+ text-shadow: 0 -1px 0 rgba(0,0,0,0.3);
35
+ }
36
+
37
+ .wp-core-ui .button-red.focus,
38
+ .wp-core-ui .button-red:focus {
39
+ border-color: #500F0E;
40
+ -webkit-box-shadow: inset 0 1px 0 rgba(120,200,230,0.6), 1px 1px 2px rgba(0,0,0,0.4);
41
+ box-shadow: inset 0 1px 0 rgba(120,200,230,0.6), 1px 1px 2px rgba(0,0,0,0.4);
42
+ }
43
+
44
+ .wp-core-ui .button-red.active,
45
+ .wp-core-ui .button-red.active:hover,
46
+ .wp-core-ui .button-red.active:focus,
47
+ .wp-core-ui .button-red:active {
48
+ background: #7F1C1F;
49
+ background-image: -webkit-gradient(linear, left top, left bottom, from(#9B2124), to(#B72629));
50
+ background-image: -webkit-linear-gradient(top, #9B2124, #B72629);
51
+ background-image: -moz-linear-gradient(top, #9B2124, #B72629);
52
+ background-image: -ms-linear-gradient(top, #9B2124, #B72629);
53
+ background-image: -o-linear-gradient(top, #9B2124, #B72629);
54
+ background-image: linear-gradient(to bottom, #9B2124, #B72629);
55
+ border-color: #601312 #AE2426 #AE2426 #AE2426;
56
+ color: rgba(255,255,255,0.95);
57
+ -webkit-box-shadow: inset 0 1px 0 rgba(0,0,0,0.1);
58
+ box-shadow: inset 0 1px 0 rgba(0,0,0,0.1);
59
+ text-shadow: 0 1px 0 rgba(0,0,0,0.1);
60
+ }
61
+
62
+ .wp-core-ui .button-red[disabled],
63
+ .wp-core-ui .button-red:disabled,
64
+ .wp-core-ui .button-red-disabled {
65
+ color: #E79496 !important;
66
+ background: #BA292B !important;
67
+ border-color: #7F1C1F !important;
68
+ -webkit-box-shadow: none !important;
69
+ box-shadow: none !important;
70
+ text-shadow: 0 -1px 0 rgba(0,0,0,0.1) !important;
71
+ cursor: default;
72
+ }
73
+
74
+ /**
75
+ * Dialog
76
+ */
77
+ #wpmc-dialog {
78
+ text-align: center;
79
+ }
80
+ #wpmc-dialog .prompt {
81
+ margin-top: 16px;
82
+ }
83
+ #wpmc-dialog .prompt .button + .button {
84
+ margin-left: 16px;
85
+ }
86
+
87
+ /**
88
+ * Settings
89
+ */
90
+ .meow-box form input:invalid {
91
+ color: red;
92
+ }
trunk/ui.php ADDED
@@ -0,0 +1,155 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class Meow_WPMC_UI {
4
+
5
+ private $core = null;
6
+ private $admin = null;
7
+ private $foundTypes = array(
8
+ "CONTENT" => "Found in content.",
9
+ "CONTENT (ID)" => "Found in content (as an ID).",
10
+ "CONTENT (URL)" => "Found in content (as an URL).",
11
+ "THEME" => "Found in theme.",
12
+ "PAGE BUILDER" => "Found in Page Builder.",
13
+ "PAGE BUILDER (ID)" => "Found in Page Builder (as an ID).",
14
+ "PAGE BUILDER (URL)" => "Found in Page Builder (as an URL).",
15
+ "GALLERY" => "Found in gallery.",
16
+ "META" => "Found in meta.",
17
+ "META (ID)" => "Found in meta (as an ID).",
18
+ "META (URL)" => "Found in meta (as an URL).",
19
+ "META ACF (ID)" => "Found in meta (as an URL).",
20
+ "META ACF (URL)" => "Found in meta (as an URL).",
21
+ "WIDGET" => "Found in widget.",
22
+ "ACF WIDGET (ID)" => "Found in ACF Widget (as an ID).",
23
+ "ACF WIDGET (URL)" => "Found in ACF Widget (as an URL).",
24
+ "ATTACHMENT (ID)" => "Found in Attachment (as an ID).",
25
+ "METASLIDER (ID)" => "Found in MetaSlider (as an ID).",
26
+ "MY CALENDAR (URL)" => "Found in My Calendar (as an URL).",
27
+ "UBERMENU (URL)" => "Found in UberMenu (as an URL).",
28
+ "MAX MEGA MENU (URL)" => "Found in Max Mega Menu (as an URL).",
29
+ "SITE ICON" => "Found in Site Icon."
30
+ );
31
+
32
+ function __construct( $core, $admin ) {
33
+ $this->core = $core;
34
+ $this->admin = $admin;
35
+ add_action( 'admin_menu', array( $this, 'admin_menu' ) );
36
+ add_action( 'admin_enqueue_scripts', array( $this, 'wp_enqueue_scripts' ) );
37
+ add_action( 'admin_print_scripts', array( $this, 'admin_inline_js' ) );
38
+ add_action( 'add_meta_boxes', array( $this, 'add_metabox' ) );
39
+ add_filter( 'media_row_actions', array( $this, 'media_row_actions' ), 10, 2 );
40
+ }
41
+
42
+ /**
43
+ * Renders a view within the views directory.
44
+ * @param string $view The name of the view to render
45
+ * @param array $data
46
+ * An associative array of variables to bind to the view.
47
+ * Each key turns into a variable name.
48
+ * @return string Rendered view
49
+ */
50
+ function render_view( $view, $data = null ) {
51
+ ob_start();
52
+ if ( is_array( $data ) ) extract( $data );
53
+ include( __DIR__ . "/views/$view.php" );
54
+ return ob_get_clean();
55
+ }
56
+
57
+ function admin_menu() {
58
+ //load_plugin_textdomain( 'media-cleaner', false, dirname( plugin_basename( __FILE__ ) ) . '/languages/' );
59
+ add_media_page( 'Media Cleaner', 'Cleaner', 'manage_options', 'media-cleaner', array( $this, 'wpmc_screen' ) );
60
+ }
61
+
62
+ function wpmc_screen() {
63
+ global $wpdb, $wplr;
64
+ echo $this->render_view( 'menu-screen', array(
65
+ 'wpdb' => $wpdb,
66
+ 'wplr' => $wplr,
67
+ 'ui' => $this,
68
+ 'core' => $this->core,
69
+ 'admin' => $this->admin
70
+ ) );
71
+ }
72
+
73
+ function wp_enqueue_scripts() {
74
+ wp_enqueue_style( 'wp-jquery-ui-dialog' );
75
+ wp_enqueue_script( 'jquery-ui-dialog' );
76
+ wp_enqueue_style( 'media-cleaner-css', plugins_url( '/scripts/style.css', __FILE__ ) );
77
+
78
+ $screen = get_current_screen();
79
+ global $wpmc_version;
80
+ switch ( $screen->id ) {
81
+ case 'media_page_media-cleaner': // Media > Cleaner
82
+ wp_enqueue_script( 'media-cleaner', plugins_url( '/scripts/dashboard.js', __FILE__ ), array( 'jquery', 'jquery-ui-dialog' ),
83
+ $wpmc_version, true );
84
+ break;
85
+ case 'meow-apps_page_wpmc_settings-menu': // Meow Apps > Media Cleaner (Settings)
86
+ wp_enqueue_script( 'media-cleaner-settings', plugins_url( '/scripts/settings.js', __FILE__ ), array( 'jquery' ),
87
+ $wpmc_version, true );
88
+ break;
89
+ }
90
+ }
91
+
92
+ /**
93
+ *
94
+ * DASHBOARD
95
+ *
96
+ */
97
+
98
+ function admin_inline_js() {
99
+ echo "<script type='text/javascript'>\n";
100
+ echo 'var wpmc_cfg = {
101
+ timeout: ' . ( (int) $this->core->get_max_execution_time() ) * 1000 . ',
102
+ delay: ' . get_option( 'wpmc_delay', 100 ) . ',
103
+ postsBuffer:' . get_option( 'wpmc_posts_buffer', 5 ) . ',
104
+ mediasBuffer:' . get_option( 'wpmc_medias_buffer', 100 ) . ',
105
+ analysisBuffer: ' . get_option( 'wpmc_analysis_buffer', 50 ) . ',
106
+ isPro: ' . ( $this->admin->is_registered() ? '1' : '0') . ',
107
+ scanFiles: ' . ( ( get_option( 'wpmc_method', 'media' ) == 'files' && $this->admin->is_registered() ) ? '1' : '0' ) . ',
108
+ scanMedia: ' . ( get_option( 'wpmc_method', 'media' ) == 'media' ? '1' : '0' ) . ' };';
109
+ echo "\n</script>";
110
+ }
111
+
112
+ /*******************************************************************************
113
+ * METABOX FOR USAGE
114
+ ******************************************************************************/
115
+
116
+ function add_metabox() {
117
+ add_meta_box( 'mfrh_media_usage_box', 'Media Cleaner', array( $this, 'display_metabox' ), 'attachment', 'side', 'default' );
118
+ }
119
+
120
+ function display_metabox( $post ) {
121
+ $this->core->log( "Media Edit > Checking Media #{$post->ID}" );
122
+ $success = $this->core->check_media( $post->ID, true );
123
+ $this->core->log( "Success $success\n" );
124
+ if ( $success ) {
125
+ if ( array_key_exists( $this->core->last_analysis, $this->foundTypes ) )
126
+ echo $this->foundTypes[ $this->core->last_analysis ];
127
+ else
128
+ echo "It seems to be used as: " . $this->core->last_analysis;
129
+ }
130
+ else {
131
+ echo "Doesn't seem to be used.";
132
+ }
133
+ }
134
+
135
+ function media_row_actions( $actions, $post ) {
136
+ global $current_screen;
137
+ if ( 'upload' != $current_screen->id )
138
+ return $actions;
139
+ global $wpdb;
140
+ $table_name = $wpdb->prefix . "mclean_scan";
141
+ $res = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $table_name WHERE postId = %d", $post->ID ) );
142
+ if ( !empty( $res ) && isset( $actions['delete'] ) )
143
+ $actions['delete'] = "<a href='?page=media-cleaner&view=deleted'>" .
144
+ __( 'Delete with Media Cleaner', 'media-cleaner' ) . "</a>";
145
+ if ( !empty( $res ) && isset( $actions['trash'] ) )
146
+ $actions['trash'] = "<a href='?page=media-cleaner'>" .
147
+ __( 'Trash with Media Cleaner', 'media-cleaner' ) . "</a>";
148
+ if ( !empty( $res ) && isset( $actions['untrash'] ) ) {
149
+ $actions['untrash'] = "<a href='?page=media-cleaner&view=deleted'>" .
150
+ __( 'Restore with Media Cleaner', 'media-cleaner' ) . "</a>";
151
+ }
152
+ return $actions;
153
+ }
154
+
155
+ }
trunk/views/menu-screen.php ADDED
@@ -0,0 +1,371 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <div class='wrap'>
2
+
3
+ <?php
4
+ echo $admin->display_title( "Media Cleaner" );
5
+ $posts_per_page = get_option( 'wpmc_results_per_page', 20 );
6
+ $view = isset ( $_GET[ 'view' ] ) ? sanitize_text_field( $_GET[ 'view' ] ) : "issues";
7
+ $paged = isset ( $_GET[ 'paged' ] ) ? sanitize_text_field( $_GET[ 'paged' ] ) : 1;
8
+ $reset = isset ( $_GET[ 'reset' ] ) ? $_GET[ 'reset' ] : 0;
9
+ if ( $reset ) {
10
+ wpmc_reset();
11
+ $core->reset_issues();
12
+ }
13
+ $s = isset ( $_GET[ 's' ] ) ? sanitize_text_field( $_GET[ 's' ] ) : null;
14
+ $table_name = $wpdb->prefix . "mclean_scan";
15
+ $issues_count = $wpdb->get_var( "SELECT COUNT(*) FROM $table_name WHERE ignored = 0 AND deleted = 0" );
16
+ $total_size = $wpdb->get_var( "SELECT SUM(size) FROM $table_name WHERE ignored = 0 AND deleted = 0" );
17
+ $trash_total_size = $wpdb->get_var( "SELECT SUM(size) FROM $table_name WHERE ignored = 0 AND deleted = 1" );
18
+ $ignored_count = $wpdb->get_var( "SELECT COUNT(*) FROM $table_name WHERE ignored = 1" );
19
+ $deleted_count = $wpdb->get_var( "SELECT COUNT(*) FROM $table_name WHERE deleted = 1" );
20
+
21
+ if ( $view == 'deleted' ) {
22
+ $items_count = $deleted_count;
23
+ $items = $wpdb->get_results( $wpdb->prepare( "SELECT id, type, postId, path, size, ignored, deleted, issue
24
+ FROM $table_name WHERE ignored = 0 AND deleted = 1 AND path LIKE %s
25
+ ORDER BY path, time
26
+ DESC LIMIT %d, %d", '%' . $s . '%', ( $paged - 1 ) * $posts_per_page, $posts_per_page ), OBJECT );
27
+ }
28
+ else if ( $view == 'ignored' ) {
29
+ $items_count = $ignored_count;
30
+ $items = $wpdb->get_results( $wpdb->prepare( "SELECT id, type, postId, path, size, ignored, deleted, issue
31
+ FROM $table_name
32
+ WHERE ignored = 1 AND deleted = 0 AND path LIKE %s
33
+ ORDER BY path, time
34
+ DESC LIMIT %d, %d", '%' . $s . '%', ( $paged - 1 ) * $posts_per_page, $posts_per_page ), OBJECT );
35
+ }
36
+ else {
37
+ $items_count = $issues_count;
38
+ $items = $wpdb->get_results( $wpdb->prepare( "SELECT id, type, postId, path, size, ignored, deleted, issue
39
+ FROM $table_name
40
+ WHERE ignored = 0 AND deleted = 0 AND path LIKE %s
41
+ ORDER BY path, time
42
+ DESC LIMIT %d, %d", '%' . $s . '%', ( $paged - 1 ) * $posts_per_page, $posts_per_page ), OBJECT );
43
+ }
44
+ ?>
45
+
46
+ <style>
47
+ #wpmc-pages {
48
+ float: right;
49
+ position: relative;
50
+ top: 12px;
51
+ }
52
+
53
+ #wpmc-pages a {
54
+ text-decoration: none;
55
+ border: 1px solid black;
56
+ padding: 2px 5px;
57
+ border-radius: 4px;
58
+ background: #E9E9E9;
59
+ color: lightslategrey;
60
+ border-color: #BEBEBE;
61
+ }
62
+
63
+ #wpmc-pages .current {
64
+ font-weight: bold;
65
+ }
66
+ </style>
67
+
68
+ <div id="wpmc_actions" style='margin-top: 0px; background: #FFF; padding: 5px; border-radius: 4px; height: 28px; box-shadow: 0px 0px 6px #C2C2C2;'>
69
+
70
+ <!-- SCAN -->
71
+ <?php if ( $view != 'deleted' ): ?>
72
+ <a id='wpmc_scan' class='button-primary' style='float: left;'><span style="top: 3px; position: relative; left: -5px;" class="dashicons dashicons-search"></span><?php _e("Start Scan", 'media-cleaner'); ?></a>
73
+ <?php endif; ?>
74
+
75
+ <!-- PAUSE -->
76
+ <?php if ( $view != 'deleted' ): ?>
77
+ <a id='wpmc_pause' onclick='wpmc_pause()' class='button' style='float: left; margin-left: 5px; display: none;'><span style="top: 3px; position: relative; left: -5px;" class="dashicons dashicons-controls-pause"></span><?php _e("Pause", 'media-cleaner'); ?></a>
78
+ <?php endif; ?>
79
+
80
+ <!-- DELETE SELECTED -->
81
+ <a id='wpmc_delete' class='button exclusive' style='float: left; margin-left: 5px;'><span style="top: 3px; position: relative; left: -5px;" class="dashicons dashicons-no"></span><?php _e("Delete", 'media-cleaner'); ?></a>
82
+ <?php if ( $view == 'deleted' ): ?>
83
+ <a id='wpmc_recover' onclick='wpmc_recover()' class='button-secondary' style='float: left; margin-left: 5px;'><span style="top: 3px; position: relative; left: -5px;" class="dashicons dashicons-controls-repeat"></span><?php _e( "Recover", 'media-cleaner' ); ?></a>
84
+ <?php endif; ?>
85
+
86
+ <!-- IGNORE SELECTED -->
87
+ <a id='wpmc_ignore' class='button exclusive' style='float: left; margin-left: 5px;'><span style="top: 3px; position: relative; left: -5px;" class="dashicons dashicons-yes"></span><?php
88
+ if ( $view == 'ignored' )
89
+ _e( "Mark as Issue", 'media-cleaner' );
90
+ else
91
+ _e( "Ignore", 'media-cleaner' );
92
+ ?>
93
+ </a>
94
+
95
+ <!-- RESET -->
96
+ <?php if ( $view != 'deleted' ): ?>
97
+ <a id='wpmc_reset' href='?page=media-cleaner&reset=1' class='button-primary' style='float: right; margin-left: 5px;'><span style="top: 3px; position: relative; left: -5px;" class="dashicons dashicons-sos"></span><?php _e("Reset", 'media-cleaner'); ?></a>
98
+ <?php endif; ?>
99
+
100
+ <!-- DELETE ALL -->
101
+ <?php if ( $view == 'deleted' ): // i ?>
102
+ <a id='wpmc_recover_all' class='button-primary exclusive' style='float: right; margin-left: 5px;'><span style="top: 3px; position: relative; left: -5px;" class="dashicons dashicons-controls-repeat"></span><?php _e("Recover all", 'media-cleaner'); ?></a>
103
+ <a id='wpmc_empty_trash' class='button button-red exclusive' style='float: right; margin-left: 5px;'><span style="top: 3px; position: relative; left: -5px;" class="dashicons dashicons-trash"></span><?php _e("Empty trash", 'media-cleaner'); ?></a>
104
+ <?php else: // i ?>
105
+ <?php if ( $s ): // ii ?>
106
+ <a id='wpmc_delete_all' class='button button-red exclusive' data-filter="<?php echo esc_attr( $s ); ?>" style='float: right; margin-left: 5px;'><span style="top: 3px; position: relative; left: -5px;" class="dashicons dashicons-trash"></span><?php _e("Delete all search results", 'media-cleaner'); ?></a>
107
+ <?php else: // ii ?>
108
+ <a id='wpmc_delete_all' class='button button-red exclusive' style='float: right; margin-left: 5px;'><span style="top: 3px; position: relative; left: -5px;" class="dashicons dashicons-trash"></span><?php _e("Delete all", 'media-cleaner'); ?></a>
109
+ <?php endif; // ii ?>
110
+ <?php endif; // i ?>
111
+
112
+ <form id="posts-filter" action="upload.php" method="get" style='float: right;'>
113
+ <p class="search-box" style='margin-left: 5px; float: left;'>
114
+ <input type="search" name="s" class="exclusive" style="width: 120px;" value="<?php echo $s ? esc_attr( $s ) : ""; ?>">
115
+ <input type="hidden" name="page" value="media-cleaner">
116
+ <input type="hidden" name="view" value="<?php echo $view; ?>">
117
+ <input type="hidden" name="paged" value="1">
118
+ <input type="submit" class="button exclusive" value="<?php _e( 'Search', 'media-cleaner' ) ?>"><span style='border-right: #A2A2A2 solid 1px; margin-left: 5px; margin-right: 3px;'>&nbsp;</span>
119
+ </p>
120
+ </form>
121
+
122
+ <!-- PROGRESS -->
123
+ <span style='margin-left: 12px; font-size: 15px; top: 5px; position: relative; color: #747474;' id='wpmc_progression'></span>
124
+
125
+ </div>
126
+
127
+ <p>
128
+ <?php
129
+ $method = "";
130
+ $table_scan = $wpdb->prefix . "mclean_scan";
131
+ $table_refs = $wpdb->prefix . "mclean_refs";
132
+ if ( strtolower( $wpdb->get_var( "SHOW TABLES LIKE '$table_scan'" ) ) != strtolower( $table_scan ) ||
133
+ strtolower( $wpdb->get_var( "SHOW TABLES LIKE '$table_refs'" ) ) != strtolower( $table_refs ) ) {
134
+ _e( "<div class='notice notice-error'><p><b>The database is not ready for Media Cleaner. The scan will not work.</b> Click on the <b>Reset</b> button, it re-creates the tables required by Media Cleaner. If this message still appear, contact the support.</p></div>", 'media-cleaner' );
135
+ }
136
+ else {
137
+ $method = get_option( 'wpmc_method', 'media' );
138
+ if ( !$admin->is_registered() )
139
+ $method = 'media';
140
+
141
+ $hide_warning = get_option( 'wpmc_hide_warning', false );
142
+
143
+ if ( !$hide_warning ) {
144
+ _e( "<div class='notice notice-warning'><p><b style='color: red;'>Important.</b> <b>Backup your DB and your /uploads directory before using Media Cleaner. </b> The deleted files will be temporarily moved to the <b>uploads/wpmc-trash</b> directory. After testing your website, you can check the <a href='?page=media-cleaner&s&view=deleted'>trash</a> to either empty it or recover the media and files. The Media Cleaner does its best to be safe to use. However, WordPress being a very dynamic and pluggable system, it is impossible to predict all the situations in which your files are used. <b style='color: red;'>Again, please backup!</b> If you don't know how, give a try to this: <a href='https://meow.click/blogvault' target='_blank'>BlogVault</a>. <br /><br /><b style='color: red;'>Be thoughtful.</b> Don't blame Media Cleaner if it deleted too many or not enough of your files. It makes cleaning possible and this task is only possible this way; don't post a bad review because it broke your install. <b>If you have a proper backup, there is no risk</b>. Sorry for the lengthy message, but better be safe than sorry. You can disable this big warning in the options if you have a Pro license. Make sure you read this warning twice. Media Cleaner is awesome and always getting better so I hope you will enjoy it. Thank you :)</p></div>", 'media-cleaner' );
145
+ }
146
+
147
+ if ( !MEDIA_TRASH ) {
148
+ _e( "<div class='notice notice-warning'><p>The trash for the Media Library is disabled. Any media removed by the plugin will be <b>permanently deleted</b>. To enable it, modify your wp-config.php file and add this line (preferably at the top): <b>define( 'MEDIA_TRASH', true );</b></p></div>", 'media-cleaner' );
149
+ }
150
+ }
151
+
152
+ if ( !$admin->is_registered() ) {
153
+ echo "<div class='notice notice-info'><p>";
154
+ _e( "<b>This version is not Pro.</b> This plugin is a lot of work so please consider <a target='_blank' href='//meowapps.com/media-cleaner'>Media Cleaner Pro</a> in order to receive support and to contribute in the evolution of it. Also, <a target='_blank' href='//meowapps.com/media-cleaner'>Media Cleaner Pro</a> version will also give you the option <b>to scan the physical files in your /uploads folder</b> and extra checks for the common Page Builders.", 'media-cleaner' );
155
+ echo "</p></div>";
156
+
157
+ if ( class_exists( 'ACF' ) ) {
158
+ echo "<div class='notice notice-warning'><p>";
159
+ _e( "<b>ACF has been detected</b>. The free version might detect the files used by ACF correctly, but its full support is only available in <a target='_blank' href='//meowapps.com/media-cleaner'>Media Cleaner Pro</a>.", 'media-cleaner' );
160
+ echo "</p></div>";
161
+ }
162
+
163
+ if ( function_exists( '_et_core_find_latest' ) ) {
164
+ echo "<div class='notice notice-warning'><p>";
165
+ _e( "<b>Divi has been detected</b>. The free version might detect the files used by Divi correctly, but its full support is only available in <a target='_blank' href='//meowapps.com/media-cleaner'>Media Cleaner Pro</a>.", 'media-cleaner' );
166
+ echo "</p></div>";
167
+ }
168
+
169
+ if ( class_exists( 'Vc_Manager' ) ) {
170
+ echo "<div class='notice notice-warning'><p>";
171
+ _e( "<b>Visual Composer has been detected</b>. The free version might detect the files used by Visual Composer correctly, but its full support is only available in <a target='_blank' href='//meowapps.com/media-cleaner'>Media Cleaner Pro</a>.", 'media-cleaner' );
172
+ echo "</p></div>";
173
+ }
174
+
175
+ if ( function_exists( 'fusion_builder_map' ) ) {
176
+ echo "<div class='notice notice-warning'><p>";
177
+ _e( "<b>Fusion Builder has been detected</b>. The free version might detect the files used by Fusion Builder correctly, but its full support is only available in <a target='_blank' href='//meowapps.com/media-cleaner'>Media Cleaner Pro</a>.", 'media-cleaner' );
178
+ echo "</p></div>";
179
+ }
180
+
181
+ if ( class_exists( 'FLBuilderModel' ) ) {
182
+ echo "<div class='notice notice-warning'><p>";
183
+ _e( "<b>Beaver Builder has been detected</b>. The free version might detect the files used by Beaver Builder correctly, but its full support is only available in <a target='_blank' href='//meowapps.com/media-cleaner'>Media Cleaner Pro</a>.", 'media-cleaner' );
184
+ echo "</p></div>";
185
+ }
186
+
187
+ if ( function_exists( 'elementor_load_plugin_textdomain' ) ) {
188
+ echo "<div class='notice notice-warning'><p>";
189
+ _e( "<b>Elementor has been detected</b>. The free version might detect the files used by Elementor correctly, but its full support is only available in <a target='_blank' href='//meowapps.com/media-cleaner'>Media Cleaner Pro</a>.", 'media-cleaner' );
190
+ echo "</p></div>";
191
+ }
192
+
193
+ if ( class_exists( 'Oxygen_VSB_Dynamic_Shortcodes' ) ) {
194
+ echo "<div class='notice notice-warning'><p>";
195
+ _e( "<b>Oxygen Builder has been detected</b>. The free version might detect the files used by Oxygen Builder correctly, but its full support is only available in <a target='_blank' href='//meowapps.com/media-cleaner'>Media Cleaner Pro</a>.", 'media-cleaner' );
196
+ echo "</p></div>";
197
+ }
198
+
199
+ if ( class_exists( 'Brizy_Editor_Post' ) ) {
200
+ echo "<div class='notice notice-warning'><p>";
201
+ _e( "<b>Brizy has been detected</b>. The free version might detect the files used by Brizy correctly, but its full support is only available in <a target='_blank' href='//meowapps.com/media-cleaner'>Media Cleaner Pro</a>.", 'media-cleaner' );
202
+ echo "</p></div>";
203
+ }
204
+
205
+ if ( function_exists( 'amd_zlrecipe_convert_to_recipe' ) ) {
206
+ echo "<div class='notice notice-warning'><p>";
207
+ _e( "<b>ZipList Recipe has been detected</b>. The free version might detect the files used by ZipList Recipe correctly, but its full support is only available in <a target='_blank' href='//meowapps.com/media-cleaner'>Media Cleaner Pro</a>.", 'media-cleaner' );
208
+ echo "</p></div>";
209
+ }
210
+
211
+ if ( class_exists( 'UberMenu' ) ) {
212
+ echo "<div class='notice notice-warning'><p>";
213
+ _e( "<b>UberMenu has been detected</b>. The free version might detect the files used by UberMenu correctly, but its full support is only available in <a target='_blank' href='//meowapps.com/media-cleaner'>Media Cleaner Pro</a>.", 'media-cleaner' );
214
+ echo "</p></div>";
215
+ }
216
+
217
+ if ( class_exists( 'X_Bootstrap' ) ) {
218
+ echo "<div class='notice notice-warning'><p>";
219
+ _e( "<b>Theme X has been detected</b>. The free version might detect the files used by Theme X correctly, but its full support is only available in <a target='_blank' href='//meowapps.com/media-cleaner'>Media Cleaner Pro</a>.", 'media-cleaner' );
220
+ echo "</p></div>";
221
+ }
222
+
223
+ if ( class_exists( 'SiteOrigin_Panels' ) ) {
224
+ echo "<div class='notice notice-warning'><p>";
225
+ _e( "<b>SiteOrigin Page Builder has been detected</b>. The free version might detect the files used by SiteOrigin Page Builder correctly, but its full support is only available in <a target='_blank' href='//meowapps.com/media-cleaner'>Media Cleaner Pro</a>.", 'media-cleaner' );
226
+ echo "</p></div>";
227
+ }
228
+
229
+ }
230
+
231
+ $anychecks = get_option( 'wpmc_content', true ) || get_option( 'wpmc_posts', false )
232
+ || get_option( 'wpmc_postmeta', false ) || get_option( 'wpmc_widgets', false );
233
+ $check_library = get_option(' wpmc_media_library', true );
234
+
235
+ if ( $method == 'media' ) {
236
+ if ( !$anychecks )
237
+ _e( "<div class='error'><p>Media Cleaner will analyze your Media Library. However, There is <b>NOTHING MARKED TO BE CHECKED</b> in the Settings. Media Cleaner will therefore run a special scan: <b>only the broken medias will be detected as issues.</b></p></div>", 'media-cleaner' );
238
+ else
239
+ _e( "<div class='notice notice-success'><p>Media Cleaner will analyze your Media Library.</p></div>", 'media-cleaner' );
240
+ }
241
+ else if ( $method == 'files' ) {
242
+ if ( !$anychecks && !$check_library )
243
+ _e( "<div class='error'><p>Media Cleaner will analyze your Filesystem. However, There is <b>NOTHING MARKED TO BE CHECKED</b> in the Settings. If you scan now, all the files will be detected as <b>NOT USED</b>.</p></div>", 'media-cleaner' );
244
+ else
245
+ _e( "<div class='notice notice-success'><p>Media Cleaner will analyze your Filesystem.</p></div>", 'media-cleaner' );
246
+ }
247
+
248
+ echo sprintf( __( 'There are <b>%s issue(s)</b> with your files, accounting for <b>%s MB</b>. Your trash contains <b>%s MB.</b>', 'media-cleaner' ), number_format( $issues_count, 0 ), number_format( $total_size / 1000000, 2 ), number_format( $trash_total_size / 1000000, 2 ) );
249
+ ?>
250
+ </p>
251
+
252
+ <div id='wpmc-pages'>
253
+ <?php
254
+ echo paginate_links(array(
255
+ 'base' => '?page=media-cleaner&s=' . urlencode($s) . '&view=' . $view . '%_%',
256
+ 'current' => $paged,
257
+ 'format' => '&paged=%#%',
258
+ 'total' => ceil( $items_count / $posts_per_page ),
259
+ 'prev_next' => false
260
+ ));
261
+ ?>
262
+ </div>
263
+
264
+ <ul class="subsubsub">
265
+ <li class="all"><a <?php if ( $view == 'issues' ) echo "class='current'"; ?> href='?page=media-cleaner&s=<?php echo $s; ?>&view=issues'><?php _e( "Issues", 'media-cleaner' ); ?></a><span class="count">(<?php echo $issues_count; ?>)</span></li> |
266
+ <li class="all"><a <?php if ( $view == 'ignored' ) echo "class='current'"; ?> href='?page=media-cleaner&s=<?php echo $s; ?>&view=ignored'><?php _e( "Ignored", 'media-cleaner' ); ?></a><span class="count">(<?php echo $ignored_count; ?>)</span></li> |
267
+ <li class="all"><a <?php if ( $view == 'deleted' ) echo "class='current'"; ?> href='?page=media-cleaner&s=<?php echo $s; ?>&view=deleted'><?php _e( "Trash", 'media-cleaner' ); ?></a><span class="count">(<?php echo $deleted_count; ?>)</span></li>
268
+ </ul>
269
+
270
+ <table id='wpmc-table' class='wp-list-table widefat fixed media'>
271
+
272
+ <thead>
273
+ <tr>
274
+ <th scope="col" id="cb" class="manage-column column-cb check-column"><input id="wpmc-cb-select-all" type="checkbox"></th>
275
+ <?php if ( !get_option( 'wpmc_hide_thumbnails', false ) ): ?>
276
+ <th style='width: 64px;'><?php _e( 'Thumb', 'media-cleaner' ) ?></th>
277
+ <?php endif; ?>
278
+ <th style='width: 50px;'><?php _e( 'Type', 'media-cleaner' ) ?></th>
279
+ <th style='width: 80px;'><?php _e( 'Origin', 'media-cleaner' ) ?></th>
280
+
281
+ <?php if ( !empty( $wplr ) ): ?>
282
+ <th style='width: 70px;'><?php _e( 'LR ID', 'media-cleaner' ) ?></th>
283
+ <?php endif; ?>
284
+
285
+ <th><?php _e( 'Path', 'media-cleaner' ) ?></th>
286
+ <th style='width: 220px;'><?php _e( 'Issue', 'media-cleaner' ) ?></th>
287
+ <th style='width: 80px; text-align: right;'><?php _e( 'Size', 'media-cleaner' ) ?></th>
288
+ </tr>
289
+ </thead>
290
+
291
+ <tbody>
292
+ <?php
293
+ foreach ( $items as $issue ) {
294
+ $regex = "^(.*)(\\s\\(\\+.*)$";
295
+ $issue->path = preg_replace( '/' .$regex . '/i', '$1', $issue->path );
296
+ ?>
297
+ <tr>
298
+ <td><input type="checkbox" name="id" value="<?php echo $issue->id ?>"></td>
299
+ <?php if ( !get_option( 'wpmc_hide_thumbnails', false ) ): ?>
300
+ <td>
301
+ <?php
302
+ if ( $issue->deleted == 0 ) {
303
+ if ( $issue ->type == 0 ) {
304
+ // FILE
305
+ $upload_dir = wp_upload_dir();
306
+ $url = htmlspecialchars( $upload_dir['baseurl'] . '/' . $issue->path, ENT_QUOTES );
307
+ echo "<a target='_blank' href='" . $url .
308
+ "'><img style='max-width: 48px; max-height: 48px;' src='" . $url . "' /></a>";
309
+ }
310
+ else {
311
+ // MEDIA
312
+ $file = get_attached_file( $issue->postId );
313
+ if ( file_exists( $file ) ) {
314
+ $attachmentsrc = wp_get_attachment_image_src( $issue->postId, 'thumbnail' );
315
+ if ( empty( $attachmentsrc ) )
316
+ echo '<span class="dashicons dashicons-no-alt"></span>';
317
+ else {
318
+ $attachmentsrc_clean = htmlspecialchars( $attachmentsrc[0], ENT_QUOTES );
319
+ echo "<a target='_blank' href='" . $attachmentsrc_clean .
320
+ "'><img style='max-width: 48px; max-height: 48px;' src='" .
321
+ $attachmentsrc_clean . "' />";
322
+ }
323
+ }
324
+ else {
325
+ echo '<span class="dashicons dashicons-no-alt"></span>';
326
+ }
327
+ }
328
+ }
329
+ if ( $issue->deleted == 1 ) {
330
+ $upload_dir = wp_upload_dir();
331
+ $url = htmlspecialchars( $upload_dir['baseurl'] . '/wpmc-trash/' . $issue->path, ENT_QUOTES );
332
+ echo "<a target='_blank' href='" . $url .
333
+ "'><img style='max-width: 48px; max-height: 48px;' src='" . $url . "' /></a>";
334
+ }
335
+ ?>
336
+ </td>
337
+ <?php endif; ?>
338
+ <td><?php echo $issue->type == 0 ? 'FILE' : 'MEDIA'; ?></td>
339
+ <td><?php echo $issue->type == 0 ? 'Filesystem' : ("<a href='post.php?post=" .
340
+ $issue->postId . "&action=edit'>ID " . $issue->postId . "</a>"); ?></td>
341
+ <?php if ( !empty( $wplr ) ) { $info = $wplr->get_sync_info( $issue->postId ); ?>
342
+ <td style='width: 70px;'><?php echo ( !empty( $info ) && $info->lr_id ? $info->lr_id : "" ); ?></td>
343
+ <?php } ?>
344
+ <td><?php echo stripslashes( $issue->path ); ?></td>
345
+ <td><?php $core->echo_issue( $issue->issue ); ?></td>
346
+ <td style='text-align: right;'><?php echo number_format( $issue->size / 1000, 2 ); ?> KB</td>
347
+ </tr>
348
+ <?php } ?>
349
+ </tbody>
350
+
351
+ <tfoot>
352
+ <tr><th></th>
353
+ <?php if ( !get_option( 'hide_thumbnails', false ) ): ?>
354
+ <th></th>
355
+ <?php endif; ?>
356
+ <th><?php _e( 'Type', 'media-cleaner' ) ?></th><th><?php _e( 'Origin', 'media-cleaner' ) ?></th>
357
+ <?php if ( !empty( $wplr ) ): ?>
358
+ <th style='width: 70px;'><?php _e( 'LR ID', 'media-cleaner' ) ?></th>
359
+ <?php endif; ?>
360
+ <th><?php _e( 'Path', 'media-cleaner' ) ?></th><th><?php _e( 'Issue', 'media-cleaner' ) ?></th><th style='width: 80px; text-align: right;'><?php _e( 'Size', 'media-cleaner' ) ?></th></tr>
361
+ </tfoot>
362
+
363
+ </table>
364
+ </div>
365
+
366
+ <div id="wpmc-dialog" class="hidden" style="max-width:800px"></div>
367
+
368
+ <div id="wpmc-error-dialog" class="hidden" style="max-width:800px">
369
+ <h3><!-- The content will be inserted by JS --></h3>
370
+ <p>Please check your logs.<br>Do you want to <a href="#" class="retry">try again</a>, or <a href="#" class="skip">skip this entry</a>?</p>
371
+ </div>