| 1 |
<?php |
| 2 |
|
| 3 |
/** |
| 4 |
* Media Sync |
| 5 |
* |
| 6 |
* This class is used for generating main content and also to import files to database |
| 7 |
* |
| 8 |
* @package MediaSync |
| 9 |
* @license http://opensource.org/licenses/gpl-2.0.php GNU Public License |
| 10 |
* @since 0.1.0 |
| 11 |
* @author Erol Živina |
| 12 |
*/ |
| 13 |
if ( !class_exists( 'MediaSync' ) ) : |
| 14 |
|
| 15 |
class MediaSync |
| 16 |
{ |
| 17 |
|
| 18 |
/** |
| 19 |
* Absolute path to main "uploads" directory. |
| 20 |
* |
| 21 |
* @var null |
| 22 |
*/ |
| 23 |
static $upload_dir_path = null; |
| 24 |
|
| 25 |
/** |
| 26 |
* Files already in Media Library. |
| 27 |
* |
| 28 |
* @var null|array |
| 29 |
*/ |
| 30 |
static $files_in_db = null; |
| 31 |
|
| 32 |
/** |
| 33 |
* For showing passed time when debugging. |
| 34 |
* |
| 35 |
* @since 1.2.0 |
| 36 |
* @var null|string |
| 37 |
*/ |
| 38 |
static $start_time = null; |
| 39 |
|
| 40 |
/** |
| 41 |
* Render main plugin content |
| 42 |
* |
| 43 |
* @since 0.1.0 |
| 44 |
* @return void |
| 45 |
*/ |
| 46 |
static public function media_sync_main_page() |
| 47 |
{ |
| 48 |
// Suppress raw PHP errors on Media Sync page, so we can show cleaner custom message |
| 49 |
@ini_set('display_errors', '0'); |
| 50 |
|
| 51 |
// Register shutdown function to catch fatal errors (e.g. memory exhausted) |
| 52 |
register_shutdown_function(function() { |
| 53 |
$message = self::media_sync_get_fatal_error_message(); |
| 54 |
if ( ! empty( $message ) ) { |
| 55 |
self::media_sync_render_error_message( $message ); |
| 56 |
} |
| 57 |
}); |
| 58 |
|
| 59 |
if(!MediaSync::media_sync_user_has_general_access()) { |
| 60 |
wp_die(__('You do not have sufficient permissions to access this page.', 'media-sync')); |
| 61 |
} |
| 62 |
|
| 63 |
$scan_files = self::filter_input_boolean(INPUT_GET, 'scan_files'); |
| 64 |
$associated_filter = self::sanitize_input_string(INPUT_GET, 'associated-filter'); |
| 65 |
$missing_from_ml = $associated_filter && urldecode($associated_filter) === 'missing_from:media_library'; |
| 66 |
|
| 67 |
$here = esc_url(get_admin_url(null, 'upload.php?page=media-sync-page')); |
| 68 |
|
| 69 |
$upload_dir_path = self::media_sync_get_uploads_basedir(); |
| 70 |
$relative_path = self::media_sync_get_relative_path($upload_dir_path); |
| 71 |
$no_upload_dir_message = __('Scan directory not found. Please check settings.', 'media-sync'); |
| 72 |
?> |
| 73 |
|
| 74 |
<div class="wrap main-media-sync-page" xmlns="http://www.w3.org/1999/html" xmlns="http://www.w3.org/1999/html"> |
| 75 |
<h1><?= __('Media Sync', 'media-sync') ?></h1> |
| 76 |
|
| 77 |
<?php do_action('media_sync_extended_initial_notice'); ?> |
| 78 |
|
| 79 |
<div class="notice notice-error hidden media-sync-global-errors js-media-sync-error-placeholder"></div> |
| 80 |
|
| 81 |
<?php if ($scan_files) : ?> |
| 82 |
<div class="notice notice-error"> |
| 83 |
<p><?= __('Please backup your database! This plugin makes database changes.', 'media-sync') ?></p> |
| 84 |
</div> |
| 85 |
<div class="notice notice-success notice-files-imported"> |
| 86 |
<p><?= sprintf(__('Done! Highlighted files were successfully imported. %s to see changes.', 'media-sync'), |
| 87 |
'<a href="'.add_query_arg('scan_files', 1, $here).'">'.__('Re-scan', 'media-sync').'</a>') ?></p> |
| 88 |
</div> |
| 89 |
<?php endif; ?> |
| 90 |
|
| 91 |
<div class="media-sync-list-files"> |
| 92 |
<form action="<?= $here ?>" method="GET"> |
| 93 |
<input type="hidden" name="page" value="media-sync-page"/> |
| 94 |
<input type="hidden" name="scan_files" value="<?= $scan_files ?>"/> |
| 95 |
<div class="media-sync-buttons-holder"> |
| 96 |
<?php if (!$scan_files) : ?> |
| 97 |
<div class="card"> |
| 98 |
<h2 class="title"><?= __('Sync - uploads directory', 'media-sync') ?></h2> |
| 99 |
|
| 100 |
<?php if($upload_dir_path) : ?> |
| 101 |
<a class="button button-primary" href="<?= add_query_arg('scan_files', 1, $here) ?>"> |
| 102 |
<?= __('Scan Files', 'media-sync') ?> |
| 103 |
</a> |
| 104 |
|
| 105 |
<p class="media-sync-scan-files-message"> |
| 106 |
<?= sprintf(__('Use this to see content of upload dir: %s and import files to Media Library.', 'media-sync'), |
| 107 |
'<code title="'.$upload_dir_path.'">'.$relative_path.'</code>') ?> |
| 108 |
</p> |
| 109 |
<?php else: ?> |
| 110 |
<p><i><?= $no_upload_dir_message ?></i></p> |
| 111 |
<?php endif; ?> |
| 112 |
</div> |
| 113 |
<div class="card"> |
| 114 |
<h2 class="title"><?= __('Sync - Media Library', 'media-sync') ?></h2> |
| 115 |
<a class="button button-primary" href="<?= add_query_arg(array('mode' => 'list', 'media_sync_missing_files' => 'yes'), get_admin_url(null, 'upload.php')) ?>"> |
| 116 |
<?= __('Filter Media Library', 'media-sync') ?> |
| 117 |
</a> |
| 118 |
|
| 119 |
<p class="media-sync-scan-files-message"> |
| 120 |
<?= __('Use this to see Media Library items that are missing actual files. This takes you to Media Library but with custom filter.', 'media-sync') ?> |
| 121 |
</p> |
| 122 |
</div> |
| 123 |
<?php MediaSync::media_sync_render_pro_card(); ?> |
| 124 |
<?php endif; ?> |
| 125 |
|
| 126 |
<?php if ($scan_files) : ?> |
| 127 |
<div class="media-sync-button-holder"> |
| 128 |
<a class="button button-primary" href="<?= add_query_arg('scan_files', 1, $here) ?>"> |
| 129 |
<?= __('Re-scan', 'media-sync') ?> |
| 130 |
</a> |
| 131 |
</div> |
| 132 |
|
| 133 |
<div class="media-sync-button-holder"> |
| 134 |
<button class="button button-primary js-import-selected"><?= __('Import Selected', 'media-sync') ?></button> |
| 135 |
<span class="spinner import-spinner"></span> |
| 136 |
</div> |
| 137 |
|
| 138 |
<div class="import-options"> |
| 139 |
<?php if ( !!get_option( 'ms_sg_use_dry_run', 1 ) ) : ?> |
| 140 |
<div class="media-sync-dry-run-holder"> |
| 141 |
<input type="checkbox" id="dry-run" name="dry_run" checked="checked" /> |
| 142 |
<label for="dry-run"><?= __('Dry Run (test without making database changes)', 'media-sync') ?></label> |
| 143 |
</div> |
| 144 |
<?php else: ?> |
| 145 |
<input type="hidden" name="dry_run" value="" /> |
| 146 |
<?php endif; ?> |
| 147 |
|
| 148 |
<div class="import-option-date-type"> |
| 149 |
<label><?= __('Date/time to set for newly imported files', 'media-sync') ?>:</label> |
| 150 |
<?php self::media_sync_render_post_date_option( 'file_post_date' ) ?> |
| 151 |
</div> |
| 152 |
<div class="import-option-batch-size"> |
| 153 |
<label for="batch-size"> |
| 154 |
<?= __('Batch Size:', 'media-sync') ?> |
| 155 |
<input type="number" value="10" step="1" min="1" class="small-text" name="batch_size" id="batch-size" /> |
| 156 |
</label> |
| 157 |
<p class="description"> |
| 158 |
<?= __('The number of files to process per network request.', 'media-sync') ?> |
| 159 |
<?= __('A lower value might avoid some limits of your server (e.g. max_execution_time). ', 'media-sync') ?> |
| 160 |
<br /> |
| 161 |
<?= __('If you still get errors, please try disabling other plugins related to files/images.', 'media-sync') ?> |
| 162 |
<?= __('To reduce additional actions related to file uploads (e.g. generating additional thumbnails).', 'media-sync') ?> |
| 163 |
</p> |
| 164 |
</div> |
| 165 |
</div> |
| 166 |
<?php endif; ?> |
| 167 |
</div> |
| 168 |
|
| 169 |
<?php if ($scan_files) : ?> |
| 170 |
<p class="media-sync-state-holder"> |
| 171 |
<span class="media-sync-progress-holder"> |
| 172 |
<span class="media-sync-progress"></span> |
| 173 |
</span> |
| 174 |
<span class="media-sync-state"> |
| 175 |
<span class="media-sync-state-text"> |
| 176 |
<?= __('Imported', 'media-sync') ?> |
| 177 |
</span> |
| 178 |
<span class="media-sync-state-number media-sync-imported-count js-media-sync-imported-count">0</span> |
| 179 |
<span class="media-sync-state-text"> |
| 180 |
<?= __('out of', 'media-sync') ?> |
| 181 |
</span> |
| 182 |
<span class="media-sync-state-number media-sync-selected-count js-media-sync-selected-count">0</span> |
| 183 |
<span class="media-sync-state-text"> |
| 184 |
<?= __('selected items', 'media-sync') ?> |
| 185 |
</span> |
| 186 |
</span> |
| 187 |
<span class="media-sync-state media-sync-state-note"> |
| 188 |
<?= __('Files already in Media Library will be skipped during import', 'media-sync') ?> |
| 189 |
</span> |
| 190 |
</p> |
| 191 |
|
| 192 |
<div class="wp-filter"> |
| 193 |
<div class="filter-items"> |
| 194 |
<label for="associated-filter" class="screen-reader-text"><?= __('Filter by type', 'media-sync') ?></label> |
| 195 |
<select class="associated-filters" name="associated-filter" id="associated-filter"> |
| 196 |
<option value=""><?= __('All files', 'media-sync') ?></option> |
| 197 |
<option value="missing_from:media_library"<?= $missing_from_ml ? 'selected="selected"' : '' ?>> |
| 198 |
<?= __('Only files missing from Media Library', 'media-sync') ?> |
| 199 |
</option> |
| 200 |
</select> |
| 201 |
|
| 202 |
<div class="actions"> |
| 203 |
<input type="submit" name="filter_action" id="post-query-submit" class="button" value="<?= __('Filter', 'media-sync') ?>"> |
| 204 |
</div> |
| 205 |
</div> |
| 206 |
</div> |
| 207 |
<?php |
| 208 |
$tree = $upload_dir_path ? self::media_sync_get_list_of_uploads() : array(); |
| 209 |
?> |
| 210 |
<?php if (!empty($tree)) : ?> |
| 211 |
<div class="media-sync-table-holder"> |
| 212 |
<table class="wp-list-table widefat fixed media"> |
| 213 |
<?php self::media_sync_render_thead_tfoot_row('thead') ?> |
| 214 |
<tbody id="the-list"> |
| 215 |
<?php foreach ($tree as $item) : ?> |
| 216 |
<?php self::media_sync_render_row($item) ?> |
| 217 |
<?php endforeach; ?> |
| 218 |
</tbody> |
| 219 |
<?php self::media_sync_render_thead_tfoot_row('tfoot') ?> |
| 220 |
</table> |
| 221 |
<span class="spinner is-active table-spinner"></span> |
| 222 |
</div> |
| 223 |
<?php else : ?> |
| 224 |
<p class="media-sync-no-results"> |
| 225 |
<?php if(!$upload_dir_path) : ?> |
| 226 |
<?= $no_upload_dir_message ?> |
| 227 |
<?php else : ?> |
| 228 |
<?= __('No Results', 'media-sync') ?> |
| 229 |
<?php endif; ?> |
| 230 |
</p> |
| 231 |
<?php endif; ?> |
| 232 |
<?php endif; ?> |
| 233 |
</form> |
| 234 |
</div> |
| 235 |
</div> |
| 236 |
<?php |
| 237 |
} |
| 238 |
|
| 239 |
|
| 240 |
/** |
| 241 |
* Render error message when limits are reached. |
| 242 |
* |
| 243 |
* @param string $message |
| 244 |
* @return void |
| 245 |
*/ |
| 246 |
static private function media_sync_render_error_message($message) |
| 247 |
{ |
| 248 |
?> |
| 249 |
<div class="notice notice-error inline"> |
| 250 |
<p> |
| 251 |
<?= __('Server has reached limits that prevent scanning all files at once.', 'media-sync') ?> |
| 252 |
</p> |
| 253 |
<p> |
| 254 |
<strong><?= __('Caught error:', 'media-sync') ?></strong> |
| 255 |
<?= $message ?> |
| 256 |
</p> |
| 257 |
</div> |
| 258 |
<?php |
| 259 |
MediaSync::media_sync_render_pro_card(); |
| 260 |
} |
| 261 |
|
| 262 |
|
| 263 |
/** |
| 264 |
* Get fatal error message if one occurred. |
| 265 |
* |
| 266 |
* @since 1.4.9 |
| 267 |
* @return string |
| 268 |
*/ |
| 269 |
static private function media_sync_get_fatal_error_message() { |
| 270 |
try { |
| 271 |
$error = error_get_last(); |
| 272 |
// Check if it's a fatal error and if it's related to memory or time |
| 273 |
if ( $error && ( $error['type'] === E_ERROR || $error['type'] === E_USER_ERROR ) ) { |
| 274 |
if ( strpos( $error['message'], 'Allowed memory size' ) !== false ) { |
| 275 |
return sprintf( |
| 276 |
__( 'Memory limit reached (%s used).', 'media-sync' ), |
| 277 |
size_format( memory_get_peak_usage( true ) ) |
| 278 |
); |
| 279 |
} |
| 280 |
if ( strpos( $error['message'], 'Maximum execution time' ) !== false ) { |
| 281 |
return sprintf( |
| 282 |
__( 'Execution time limit reached (%s seconds).', 'media-sync' ), |
| 283 |
ini_get( 'max_execution_time' ) |
| 284 |
); |
| 285 |
} |
| 286 |
} |
| 287 |
} catch ( Exception $e ) { |
| 288 |
} |
| 289 |
|
| 290 |
return ""; |
| 291 |
} |
| 292 |
|
| 293 |
|
| 294 |
/** |
| 295 |
* @since 1.4.9 |
| 296 |
* @return void |
| 297 |
*/ |
| 298 |
static private function media_sync_render_pro_card() { |
| 299 |
?> |
| 300 |
<div class="card"> |
| 301 |
<h2 class="title">Media Sync Pro</h2> |
| 302 |
<a id="purchase-media-sync-pro" class="button button-primary" href="https://checkout.freemius.com/mode/dialog/plugin/14503/plan/24225/?show_monthly_switch=1" target="_blank" rel="nofollow noopener">Upgrade Now</a> |
| 303 |
|
| 304 |
<a class="button button-secondary" href="https://mediasyncplugin.com/?utm_source=base_plugin_banner&utm_medium=init_page&utm_campaign=bip" target="_blank" rel="noopener">Find out more</a> |
| 305 |
|
| 306 |
<h4>Check out our newly revamped pro version with amazing new features.</h4> |
| 307 |
|
| 308 |
<ul style="list-style: circle; margin-left: 15px;"> |
| 309 |
<li><strong>Revised incremental scan</strong>: Allows scanning and importing unlimited number of files.</li> |
| 310 |
<li><strong>Quick single directory rescan</strong>: Easily rescan one directory to find new files or apply a different filter without reloading the whole page.</li> |
| 311 |
<li><strong>Advanced filters</strong>: Find any file by customizing all default filters, search for a specific file type (images, videos, etc.), skip by tailor-made rules, or enter any custom pattern.</li> |
| 312 |
<li><strong>Schedule automatic imports</strong>: Select a desired interval and let the plugin automatically import any new files it finds.</li> |
| 313 |
<li><strong>Import logs</strong>: View the history of manual or scheduled imports.</li> |
| 314 |
<li><strong>Limit plugin access</strong>: Limit plugin access to a specific role.</li> |
| 315 |
</ul> |
| 316 |
</div> |
| 317 |
<?php |
| 318 |
} |
| 319 |
|
| 320 |
|
| 321 |
/** |
| 322 |
* Prepare plugin settings |
| 323 |
* |
| 324 |
* @since 1.1.0 |
| 325 |
* @return void|boolean |
| 326 |
*/ |
| 327 |
static public function media_sync_options_setup() |
| 328 |
{ |
| 329 |
if ( !current_user_can( 'manage_options' ) ) { |
| 330 |
return false; |
| 331 |
} |
| 332 |
|
| 333 |
// From this plugin |
| 334 |
add_option( 'ms_sg_use_dry_run', 1 ); |
| 335 |
add_option( 'ms_sg_file_post_date', 'default' ); |
| 336 |
register_setting( 'media-sync-settings-group', 'ms_sg_use_dry_run' ); |
| 337 |
register_setting( 'media-sync-settings-group', 'ms_sg_file_post_date' ); |
| 338 |
|
| 339 |
// From Add-On |
| 340 |
do_action('media_sync_extended_advanced_options_register'); |
| 341 |
|
| 342 |
// Delete options that are no longer used |
| 343 |
// delete_option('ms_sg_scan_sub_dir'); |
| 344 |
delete_option('ms_sg_use_debug'); |
| 345 |
} |
| 346 |
|
| 347 |
|
| 348 |
/** |
| 349 |
* Render plugin settings |
| 350 |
* |
| 351 |
* @since 1.1.0 |
| 352 |
* @return void|boolean |
| 353 |
*/ |
| 354 |
static public function media_sync_options_page() |
| 355 |
{ |
| 356 |
if ( !current_user_can( 'manage_options' ) ) { |
| 357 |
return false; |
| 358 |
} |
| 359 |
?> |
| 360 |
<div class="wrap media-sync-page-settings"> |
| 361 |
<h1><?= __('Media Sync Settings', 'media-sync') ?></h1> |
| 362 |
|
| 363 |
<form method="post" action="options.php"> |
| 364 |
<?php |
| 365 |
// From this plugin |
| 366 |
settings_fields( 'media-sync-settings-group' ); |
| 367 |
do_settings_sections( 'media-sync-settings-group' ); |
| 368 |
?> |
| 369 |
|
| 370 |
<table class="form-table"> |
| 371 |
<tbody> |
| 372 |
<?php |
| 373 |
// From this plugin |
| 374 |
?> |
| 375 |
<tr> |
| 376 |
<th scope="row"> |
| 377 |
<label><?= __('Dry Run', 'media-sync') ?></label> |
| 378 |
</th> |
| 379 |
<td> |
| 380 |
<fieldset> |
| 381 |
<label for="ms-sg-use-dry-run"> |
| 382 |
<?php $checked = checked( 1, get_option( 'ms_sg_use_dry_run' ), false ); ?> |
| 383 |
<input type="checkbox" name="ms_sg_use_dry_run" id="ms-sg-use-dry-run" value="1" <?= $checked ?>> |
| 384 |
<?= __('Show "Dry Run" option', 'media-sync') ?> |
| 385 |
</label> |
| 386 |
</fieldset> |
| 387 |
</td> |
| 388 |
</tr> |
| 389 |
<tr> |
| 390 |
<th scope="row"> |
| 391 |
<label><?= __('Preselected / default value for file date', 'media-sync') ?></label> |
| 392 |
</th> |
| 393 |
<td> |
| 394 |
<?php self::media_sync_render_post_date_option( 'ms_sg_file_post_date' ) ?> |
| 395 |
</td> |
| 396 |
</tr> |
| 397 |
<?php |
| 398 |
// From Add-On |
| 399 |
do_action('media_sync_extended_advanced_options_render'); |
| 400 |
?> |
| 401 |
</tbody> |
| 402 |
</table> |
| 403 |
|
| 404 |
<?php submit_button(); ?> |
| 405 |
</form> |
| 406 |
</div> |
| 407 |
<?php |
| 408 |
} |
| 409 |
|
| 410 |
|
| 411 |
/** |
| 412 |
* Render option for selecting post date |
| 413 |
* |
| 414 |
* @since 1.1.0 |
| 415 |
* @param string $name Field name |
| 416 |
* @return void |
| 417 |
*/ |
| 418 |
static public function media_sync_render_post_date_option($name) |
| 419 |
{ |
| 420 |
$selected = self::sanitize_input_string(INPUT_GET, 'file_post_date'); |
| 421 |
if (empty($selected)) { |
| 422 |
$selected = get_option( 'ms_sg_file_post_date' ); |
| 423 |
} |
| 424 |
if (empty($selected)) { |
| 425 |
$selected = 'default'; |
| 426 |
} |
| 427 |
?> |
| 428 |
<fieldset> |
| 429 |
<div class="import-option-date-type-item"> |
| 430 |
<label> |
| 431 |
<input type="radio" name="<?= $name ?>" value="default" <?= checked( 'default', $selected ) ?>/> |
| 432 |
<span><?= __('Default', 'media-sync') ?></span> |
| 433 |
</label> |
| 434 |
<p class="description"><?= __('Defaults to what WordPress uses - current time.', 'media-sync') ?></p> |
| 435 |
</div> |
| 436 |
<div class="import-option-date-type-item"> |
| 437 |
<label> |
| 438 |
<input type="radio" name="<?= $name ?>" value="file_time" <?= checked( 'file_time', $selected ) ?>/> |
| 439 |
<span><?= __('File time', 'media-sync') ?></span> |
| 440 |
</label> |
| 441 |
<p class="description"><?= __('File modification timestamp - when it was last modified.', 'media-sync') ?></p> |
| 442 |
</div> |
| 443 |
<div class="import-option-date-type-item"> |
| 444 |
<label> |
| 445 |
<input type="radio" name="<?= $name ?>" value="smart_file_time" <?= checked( 'smart_file_time', $selected ) ?>/> |
| 446 |
<span><?= __('Smart file time', 'media-sync') ?></span> |
| 447 |
</label> |
| 448 |
<p class="description"><?= sprintf(__('If file timestamp does not match the folder in which it is - it will construct the date/time based on the folder: %s.', 'media-sync'), '{year}-{month}-01 00:00:00') ?></p> |
| 449 |
</div> |
| 450 |
</fieldset> |
| 451 |
<?php |
| 452 |
} |
| 453 |
|
| 454 |
|
| 455 |
/** |
| 456 |
* Render table header and footer |
| 457 |
* |
| 458 |
* @since 0.1.0 |
| 459 |
* @param string $tag [thead|tfoot] |
| 460 |
* @return void |
| 461 |
*/ |
| 462 |
static public function media_sync_render_thead_tfoot_row($tag) |
| 463 |
{ |
| 464 |
$cb_id = 'cb-select-all-' . ($tag == 'thead' ? '1' : '2'); |
| 465 |
?> |
| 466 |
<<?= $tag ?>> |
| 467 |
<tr> |
| 468 |
<td class="manage-column check-column check-column-all"<?= $tag == 'thead' ? ' id="cb"':''?>> |
| 469 |
<label class="screen-reader-text" for="<?= $cb_id ?>"><?= __('Select All', 'media-sync') ?></label> |
| 470 |
<input id="<?= $cb_id ?>" type="checkbox"> |
| 471 |
</td> |
| 472 |
<th scope="col" class="manage-column column-title column-primary"<?= $tag == 'thead' ? ' id="title"':''?>> |
| 473 |
<span><?= __('File', 'media-sync') ?></span> |
| 474 |
</th> |
| 475 |
</tr> |
| 476 |
</<?= $tag ?>> |
| 477 |
<?php |
| 478 |
} |
| 479 |
|
| 480 |
|
| 481 |
/** |
| 482 |
* Render table row for each file or directory |
| 483 |
* |
| 484 |
* @since 0.1.0 |
| 485 |
* @param array $item |
| 486 |
* @return void |
| 487 |
*/ |
| 488 |
static public function media_sync_render_row($item) |
| 489 |
{ |
| 490 |
$has_file_id = isset($item['file_id']) && $item['file_id'] !== false; |
| 491 |
$url = $has_file_id ? esc_url(add_query_arg(array('post' => $item['file_id'], 'action' => 'edit'), get_admin_url(null, 'post.php'))) : $item['url']; |
| 492 |
$url_attr = $item['is_dir'] !== true ? ' target="_blank"' : ''; |
| 493 |
$count_children = $item['count_children']; |
| 494 |
|
| 495 |
$cls = 'media-sync-list-file'; |
| 496 |
$cls .= ' is-' . ($item['is_dir'] === true ? 'dir' : 'file'); |
| 497 |
$cls .= ' level-' . $item['level']; |
| 498 |
$cls .= ' is-first-level-' . ($item['level'] === 1 ? 'yes' : 'no'); |
| 499 |
if ($item['is_dir'] !== true) { |
| 500 |
$cls .= ' is-in-db-' . ($has_file_id ? 'yes' : 'no'); |
| 501 |
} |
| 502 |
|
| 503 |
// This can be made optional, for a bit different UI |
| 504 |
$toggle_arrows = true; |
| 505 |
if ($toggle_arrows) { |
| 506 |
$is_link = $item['is_dir'] !== true; |
| 507 |
$cls .= ' toggle-arrows-yes'; |
| 508 |
} else { |
| 509 |
$is_link = $item['is_dir'] !== true || $count_children > 0; |
| 510 |
$url_attr .= ' class="js-toggle-row"'; |
| 511 |
} |
| 512 |
|
| 513 |
$is_trash = isset($item['file_status']) && $item['file_status'] === 'trash'; |
| 514 |
?> |
| 515 |
|
| 516 |
<tr class="<?= $cls ?>" id="<?= $item['row_id'] ?>" data-parent-id="<?= $item['parent_id'] ?>"> |
| 517 |
<th scope="row" class="check-column"> |
| 518 |
<label class="screen-reader-text" for="cb-select-<?= $item['row_id'] ?>"></label> |
| 519 |
<input type="checkbox" class="js-checkbox" id="cb-select-<?= $item['row_id'] ?>" |
| 520 |
value="<?= $item['absolute_path'] ?>" data-row-id="<?= $item['row_id'] ?>"> |
| 521 |
</th> |
| 522 |
<td class="title column-title has-row-actions column-primary" data-colname="<?= __('File', 'media-sync') ?>"> |
| 523 |
<?php if (!empty($item['parents'])) : ?> |
| 524 |
<span class="media-sync-parents"> |
| 525 |
<?php foreach ($item['parents'] as $parent_key => $parent) : ?> |
| 526 |
<?php |
| 527 |
$parent_cls = 'media-sync-parent'; |
| 528 |
$parent_cls .= ' is-first-' . ($parent_key == 0 ? 'yes' : 'no'); |
| 529 |
$parent_cls .= ' is-last-' . ($parent_key + 1 == count($item['parents']) ? 'yes' : 'no'); |
| 530 |
?> |
| 531 |
<span class="<?= $parent_cls ?>"><i></i></span> |
| 532 |
<?php endforeach; ?> |
| 533 |
<span class="clearfix"></span> |
| 534 |
</span> |
| 535 |
<?php endif; ?> |
| 536 |
|
| 537 |
<?php if ($toggle_arrows && $item['is_dir'] === true) : ?> |
| 538 |
<span class="js-toggle-row media-sync-toggle-row dashicons"></span> |
| 539 |
<?php endif; ?> |
| 540 |
|
| 541 |
<?= $is_link ? '<a href="' . $item['url'] . '"' . $url_attr . '>' : '' ?> |
| 542 |
<?php if ($item['is_dir'] === true) : ?> |
| 543 |
<span class="dashicons dashicons-category"></span> |
| 544 |
<?php endif; ?> |
| 545 |
<span class="media-sync-file-name"> |
| 546 |
<?= $item['display_name'] ?> |
| 547 |
</span> |
| 548 |
<?= $is_link ? '</a>' : '' ?> |
| 549 |
|
| 550 |
<?php if ($item['is_dir'] === true && $count_children !== null) : ?> |
| 551 |
<span class="media-sync-num-items"><?= sprintf('(%u %s)', $count_children, $count_children == 1 ? __('item', 'media-sync') : __('items', 'media-sync')) ?></span> |
| 552 |
<?php endif; ?> |
| 553 |
|
| 554 |
<?php if ($has_file_id) : ?> |
| 555 |
<span class="media-sync-already-in-db"> - <?= __('Already in', 'media-sync') ?> |
| 556 |
<a href="<?= $url ?>" class="dashicons dashicons-admin-media" target="_blank"></a> |
| 557 |
<?= $is_trash ? ' (' . __('In Trash', 'media-sync') . ')' : '' ?> |
| 558 |
</span> |
| 559 |
<?php endif; ?> |
| 560 |
</td> |
| 561 |
</tr> |
| 562 |
|
| 563 |
<?php |
| 564 |
if (!empty($item['children'])) : |
| 565 |
foreach ($item['children'] as $child_item) : |
| 566 |
self::media_sync_render_row($child_item); |
| 567 |
endforeach; |
| 568 |
endif; |
| 569 |
} |
| 570 |
|
| 571 |
|
| 572 |
/** |
| 573 |
* Ajax action to import selected files |
| 574 |
* |
| 575 |
* @since 0.1.0 |
| 576 |
* @return string |
| 577 |
*/ |
| 578 |
static public function media_sync_import_files() |
| 579 |
{ |
| 580 |
// Register shutdown function to catch fatal errors (e.g. memory exhausted) |
| 581 |
register_shutdown_function(function() { |
| 582 |
$message = self::media_sync_get_fatal_error_message(); |
| 583 |
if ( ! empty( $message ) ) { |
| 584 |
wp_send_json_error( |
| 585 |
__( 'Server has reached limits that prevented importing all selected files.', 'media-sync' ) . ' ' . |
| 586 |
$message . ' ' . |
| 587 |
sprintf( |
| 588 |
__( '%s solves this by processing files in smaller batches.', 'media-sync' ), |
| 589 |
'<a href="https://mediasyncplugin.com/?utm_source=base_plugin_error&utm_medium=scan_page&utm_campaign=esp" target="_blank">Media Sync Pro</a>' |
| 590 |
) |
| 591 |
); |
| 592 |
} |
| 593 |
}); |
| 594 |
|
| 595 |
if(!MediaSync::media_sync_user_has_general_access()) { |
| 596 |
wp_send_json_error( __( 'You do not have sufficient permissions to access this page.', 'media-sync' ) ); |
| 597 |
} |
| 598 |
|
| 599 |
check_ajax_referer( 'media_sync_import_files', 'security' ); |
| 600 |
|
| 601 |
// Get database stuff |
| 602 |
global $wpdb; |
| 603 |
|
| 604 |
$results = array(); |
| 605 |
|
| 606 |
// We can't apply sanitize_text_field here because file names might contain special characters (e.g. հ� |
| 607 |
վիկ.jpg) |
| 608 |
if(isset($_POST['media_items']) && !empty($_POST['media_items'])) { |
| 609 |
|
| 610 |
// Make sure that this file is included, as wp_generate_attachment_metadata() depends on it. |
| 611 |
require_once( ABSPATH . 'wp-admin' . DIRECTORY_SEPARATOR . 'includes' . DIRECTORY_SEPARATOR . 'image.php' ); |
| 612 |
|
| 613 |
|
| 614 |
$files_in_db = self::media_sync_get_files_in_db(); |
| 615 |
|
| 616 |
$post_date_type = self::sanitize_input_string(INPUT_POST, 'file_post_date'); |
| 617 |
if (empty($post_date_type)) { |
| 618 |
$post_date_type = get_option( 'ms_sg_file_post_date', 'default' ); |
| 619 |
} |
| 620 |
$dry_run = self::filter_input_boolean(INPUT_POST, 'dry_run'); |
| 621 |
|
| 622 |
foreach ($_POST['media_items'] as $media_item) { |
| 623 |
|
| 624 |
if(isset($media_item['file']) && !empty($media_item['file'])) { |
| 625 |
|
| 626 |
// This comes from JS and it's taken from checkbox value, which is $item['absolute_path'] from media_sync_get_list_of_files() |
| 627 |
$absolute_path = urldecode($media_item['file']); |
| 628 |
|
| 629 |
$validated_path = self::media_sync_validate_path($absolute_path); |
| 630 |
if ($validated_path === false) { |
| 631 |
$results[] = array( |
| 632 |
'row_id' => $media_item['row_id'], |
| 633 |
'inserted' => false, |
| 634 |
'errorMessage' => __('Invalid file path.', 'media-sync') |
| 635 |
); |
| 636 |
continue; |
| 637 |
} |
| 638 |
$absolute_path = $validated_path; |
| 639 |
|
| 640 |
$relative_path = self::media_sync_url_encode(self::media_sync_get_relative_path($absolute_path)); |
| 641 |
|
| 642 |
// It's quicker to get all files already in db and check that array, than to do this query for each file |
| 643 |
// $query = "SELECT COUNT(*) FROM {$wpdb->posts} WHERE guid LIKE '%{$relative_path}'"; |
| 644 |
// $is_in_db = intval($wpdb->get_var($query)) > 0; |
| 645 |
|
| 646 |
$is_in_db = isset($files_in_db[$relative_path]) && !empty($files_in_db[$relative_path]); |
| 647 |
|
| 648 |
// Check if file is already in database |
| 649 |
if(!$is_in_db) { |
| 650 |
|
| 651 |
// Prepare data to be saved to `wp_posts` and `wp_postmeta` |
| 652 |
$attachment = self::media_sync_prepare_attachment_data($absolute_path, $relative_path, $post_date_type); |
| 653 |
|
| 654 |
if (!$attachment || isset($attachment['error']) || isset($attachment['errorMessage'])) { |
| 655 |
// It's probably better to just continue importing other files |
| 656 |
// echo json_encode($attachment); |
| 657 |
// wp_die(); |
| 658 |
|
| 659 |
// This will mark this file as failed and show error below file name |
| 660 |
$results[] = array( |
| 661 |
'row_id' => $media_item['row_id'], |
| 662 |
'inserted' => false, |
| 663 |
'error' => isset($attachment['error']) ? $attachment['error'] : null, |
| 664 |
'errorMessage' => isset($attachment['errorMessage']) ? $attachment['errorMessage'] : null |
| 665 |
); |
| 666 |
continue; |
| 667 |
} |
| 668 |
|
| 669 |
// If we're actually importing (creating database records) |
| 670 |
if(!$dry_run) { |
| 671 |
|
| 672 |
// Import file to database (`wp_posts` and `wp_postmeta`) |
| 673 |
$import_response = self::media_sync_update_import_to_database($attachment, $absolute_path); |
| 674 |
|
| 675 |
// Response is an array if there are errors or true if success |
| 676 |
$is_inserted = $import_response === true; |
| 677 |
|
| 678 |
// It's probably better to just continue importing other files |
| 679 |
//if (!$is_inserted) { |
| 680 |
// echo json_encode($import_response); |
| 681 |
// wp_die(); |
| 682 |
//} |
| 683 |
|
| 684 |
$results[] = array( |
| 685 |
'row_id' => $media_item['row_id'], |
| 686 |
'inserted' => $is_inserted, |
| 687 |
'error' => isset($import_response['error']) ? $import_response['error'] : null, |
| 688 |
'errorMessage' => isset($import_response['errorMessage']) ? $import_response['errorMessage'] : null |
| 689 |
); |
| 690 |
} else { |
| 691 |
$results[] = array( |
| 692 |
'row_id' => $media_item['row_id'], |
| 693 |
'inserted' => true |
| 694 |
); |
| 695 |
} |
| 696 |
} |
| 697 |
} |
| 698 |
} |
| 699 |
} |
| 700 |
|
| 701 |
echo json_encode(array( |
| 702 |
'results' => $results |
| 703 |
)); |
| 704 |
|
| 705 |
wp_die(); // Must have for Ajax calls |
| 706 |
} |
| 707 |
|
| 708 |
|
| 709 |
/** |
| 710 |
* Find mime type for file being imported |
| 711 |
* |
| 712 |
* @since 1.2.3 |
| 713 |
* |
| 714 |
* @param string $absolute_path Absolute path to the file being imported |
| 715 |
* @return string|array |
| 716 |
*/ |
| 717 |
static public function media_sync_get_mime_type($absolute_path) |
| 718 |
{ |
| 719 |
try { |
| 720 |
$mime_type = ''; |
| 721 |
if (function_exists('mime_content_type')) { |
| 722 |
$mime_type = mime_content_type($absolute_path); |
| 723 |
} |
| 724 |
|
| 725 |
if (!empty($mime_type)) { |
| 726 |
return $mime_type; |
| 727 |
} |
| 728 |
|
| 729 |
$file_type = wp_check_filetype(basename($absolute_path), null); |
| 730 |
|
| 731 |
if(!$file_type['type']) { |
| 732 |
return array( |
| 733 |
'errorMessage' => sprintf(__('Invalid mime type for file: %s.', 'media-sync'), $absolute_path), |
| 734 |
); |
| 735 |
} |
| 736 |
|
| 737 |
return $file_type['type']; |
| 738 |
|
| 739 |
} catch (Exception $e) { |
| 740 |
return array( |
| 741 |
'errorMessage' => sprintf(__('Error getting mime type for file: %s.', 'media-sync'), $absolute_path), |
| 742 |
'error' => $e->getMessage() |
| 743 |
); |
| 744 |
} |
| 745 |
} |
| 746 |
|
| 747 |
/** |
| 748 |
* Prepare data to be imported to `wp_posts` table |
| 749 |
* |
| 750 |
* @since 1.2.2 |
| 751 |
* |
| 752 |
* @param string $absolute_path Absolute path to the file being imported |
| 753 |
* @param string $relative_path Path relative to the site URL |
| 754 |
* @param string $post_date_type How to generate post date |
| 755 |
* @return array |
| 756 |
*/ |
| 757 |
static public function media_sync_prepare_attachment_data($absolute_path, $relative_path, $post_date_type) { |
| 758 |
try { |
| 759 |
$mime_type = self::media_sync_get_mime_type( $absolute_path ); |
| 760 |
|
| 761 |
if ( isset( $mime_type['error'] ) || isset( $mime_type['errorMessage'] ) ) { |
| 762 |
return $mime_type; |
| 763 |
} |
| 764 |
|
| 765 |
$decoded_relative_path = urldecode( $relative_path ); |
| 766 |
|
| 767 |
$title = self::media_sync_get_file_title( $absolute_path, $mime_type ); |
| 768 |
|
| 769 |
// Prepare an array of post data for the attachment. |
| 770 |
$attachment = array( |
| 771 |
'guid' => get_site_url() . $decoded_relative_path, |
| 772 |
'post_mime_type' => $mime_type, |
| 773 |
'post_title' => $title ?: preg_replace( '/\.[^.]+$/', '', basename( $decoded_relative_path ) ), |
| 774 |
'post_content' => '', |
| 775 |
'post_status' => 'inherit' |
| 776 |
); |
| 777 |
|
| 778 |
// Try to get post date based on settings - "Default" option will not set anything, so WP can use defaults |
| 779 |
$post_date = self::media_sync_post_date( $absolute_path, $post_date_type ); |
| 780 |
|
| 781 |
if ( ! empty( $post_date ) ) { |
| 782 |
$attachment['post_date'] = $post_date; |
| 783 |
$attachment['post_date_gmt'] = $post_date; |
| 784 |
} |
| 785 |
|
| 786 |
return $attachment; |
| 787 |
} catch ( Exception $e ) { |
| 788 |
return array( |
| 789 |
'errorMessage' => sprintf( __( 'Error preparing attachment data for file: %s.', 'media-sync' ), $absolute_path ), |
| 790 |
'error' => $e->getMessage() |
| 791 |
); |
| 792 |
} |
| 793 |
} |
| 794 |
|
| 795 |
|
| 796 |
/** |
| 797 |
* Try to extract an embedded title from a file's metadata. |
| 798 |
* Returns null if no title is found, so the caller can fall back to the filename. |
| 799 |
* |
| 800 |
* @since 1.5.2 |
| 801 |
* @param string $absolute_path Absolute path to the file |
| 802 |
* @param string $mime_type MIME type of the file |
| 803 |
* @return string|null |
| 804 |
*/ |
| 805 |
static private function media_sync_get_file_title($absolute_path, $mime_type) { |
| 806 |
try { |
| 807 |
$is_image = strpos( $mime_type, 'image/' ) === 0; |
| 808 |
if ( $is_image ) { |
| 809 |
return self::media_sync_get_image_title( $absolute_path ); |
| 810 |
} |
| 811 |
if ( $mime_type === 'application/pdf' ) { |
| 812 |
return self::media_sync_get_pdf_title( $absolute_path ); |
| 813 |
} |
| 814 |
} catch ( Exception $e ) { |
| 815 |
} |
| 816 |
|
| 817 |
return null; |
| 818 |
} |
| 819 |
|
| 820 |
|
| 821 |
/** |
| 822 |
* Extract title from image EXIF metadata. |
| 823 |
* Checks XPTitle (Windows, UTF-16LE) then ImageDescription. |
| 824 |
* |
| 825 |
* @since 1.5.2 |
| 826 |
* @param string $path Absolute path to the image file |
| 827 |
* @return string|null |
| 828 |
*/ |
| 829 |
static private function media_sync_get_image_title($path) { |
| 830 |
$exif_available = function_exists( 'exif_read_data' ); |
| 831 |
if ( ! $exif_available ) { |
| 832 |
return null; |
| 833 |
} |
| 834 |
|
| 835 |
$exif = @exif_read_data( $path ); |
| 836 |
$exif_is_valid = is_array( $exif ); |
| 837 |
if ( ! $exif_is_valid ) { |
| 838 |
return null; |
| 839 |
} |
| 840 |
|
| 841 |
$xp_title = ! empty( $exif['XPTitle'] ) ? $exif['XPTitle'] : null; |
| 842 |
if ( $xp_title !== null ) { |
| 843 |
$title = trim( mb_convert_encoding( $xp_title, 'UTF-8', 'UTF-16LE' ) ); |
| 844 |
if ( ! empty( $title ) ) { |
| 845 |
return $title; |
| 846 |
} |
| 847 |
} |
| 848 |
|
| 849 |
$image_description = ! empty( $exif['ImageDescription'] ) ? trim( $exif['ImageDescription'] ) : null; |
| 850 |
if ( ! empty( $image_description ) ) { |
| 851 |
return $image_description; |
| 852 |
} |
| 853 |
|
| 854 |
return null; |
| 855 |
} |
| 856 |
|
| 857 |
|
| 858 |
/** |
| 859 |
* Extract title from PDF XMP metadata or info dictionary. |
| 860 |
* Reads the first 128KB. XMP is tried first as it is more reliably near the start of the file. |
| 861 |
* |
| 862 |
* @since 1.5.2 |
| 863 |
* @param string $path Absolute path to the PDF file |
| 864 |
* @return string|null |
| 865 |
*/ |
| 866 |
static private function media_sync_get_pdf_title($path) { |
| 867 |
$content = @file_get_contents( $path, false, null, 0, 131072 ); |
| 868 |
$content_is_empty = $content === false || $content === ''; |
| 869 |
if ( $content_is_empty ) { |
| 870 |
return null; |
| 871 |
} |
| 872 |
|
| 873 |
// XMP metadata stream: <dc:title> block (checked first more reliably near start of file) |
| 874 |
$has_xmp = preg_match( '/<dc:title>.*?<rdf:li[^>]*>([^<]+)<\/rdf:li>.*?<\/dc:title>/s', $content, $xmp_matches ); |
| 875 |
if ( $has_xmp ) { |
| 876 |
$title = trim( html_entity_decode( $xmp_matches[1], ENT_XML1 | ENT_QUOTES, 'UTF-8' ) ); |
| 877 |
if ( ! empty( $title ) ) { |
| 878 |
return $title; |
| 879 |
} |
| 880 |
} |
| 881 |
|
| 882 |
// Info dictionary literal string: /Title (value) |
| 883 |
$has_literal = preg_match( '/\/Title\s*\(([^)\\\\]*(?:\\\\.[^)\\\\]*)*)\)/', $content, $literal_matches ); |
| 884 |
if ( $has_literal ) { |
| 885 |
$raw = $literal_matches[1]; |
| 886 |
$raw = preg_replace_callback( '/\\\\([nrtbf()\\\\]|[0-7]{1,3})/', function ( $escape_match ) { |
| 887 |
$char = $escape_match[1]; |
| 888 |
$map = [ |
| 889 |
'n' => "\n", |
| 890 |
'r' => "\r", |
| 891 |
't' => "\t", |
| 892 |
'b' => "\x08", |
| 893 |
'f' => "\x0C", |
| 894 |
'(' => '(', |
| 895 |
')' => ')', |
| 896 |
'\\' => '\\' |
| 897 |
]; |
| 898 |
|
| 899 |
return isset( $map[ $char ] ) ? $map[ $char ] : chr( octdec( $char ) ); |
| 900 |
}, $raw ); |
| 901 |
$has_utf16_bom = strlen( $raw ) >= 2 && "\xFE\xFF" === substr( $raw, 0, 2 ); |
| 902 |
if ( $has_utf16_bom ) { |
| 903 |
$raw = mb_convert_encoding( substr( $raw, 2 ), 'UTF-8', 'UTF-16BE' ); |
| 904 |
} |
| 905 |
$title = trim( $raw ); |
| 906 |
if ( ! empty( $title ) ) { |
| 907 |
return $title; |
| 908 |
} |
| 909 |
} |
| 910 |
|
| 911 |
// Info dictionary hex string: /Title <hex> |
| 912 |
$has_hex = preg_match( '/\/Title\s*<([0-9a-fA-F\s]+)>/', $content, $hex_matches ); |
| 913 |
if ( $has_hex ) { |
| 914 |
$hex = preg_replace( '/\s/', '', $hex_matches[1] ); |
| 915 |
$raw = @hex2bin( $hex ); |
| 916 |
if ( $raw ) { |
| 917 |
$has_utf16_bom = strlen( $raw ) >= 2 && "\xFE\xFF" === substr( $raw, 0, 2 ); |
| 918 |
if ( $has_utf16_bom ) { |
| 919 |
$raw = mb_convert_encoding( substr( $raw, 2 ), 'UTF-8', 'UTF-16BE' ); |
| 920 |
} |
| 921 |
$title = trim( $raw ); |
| 922 |
if ( ! empty( $title ) ) { |
| 923 |
return $title; |
| 924 |
} |
| 925 |
} |
| 926 |
} |
| 927 |
|
| 928 |
return null; |
| 929 |
} |
| 930 |
|
| 931 |
|
| 932 |
/** |
| 933 |
* Import file to database (`wp_posts` and `wp_postmeta`) |
| 934 |
* |
| 935 |
* @since 1.2.1 |
| 936 |
* |
| 937 |
* @param array $attachment Data for `wp_posts` table |
| 938 |
* @param string $absolute_path Absolute path to the file being imported. Decoded. |
| 939 |
* @return array|true |
| 940 |
*/ |
| 941 |
static public function media_sync_update_import_to_database($attachment, $absolute_path) |
| 942 |
{ |
| 943 |
if (!stream_resolve_include_path($absolute_path)) { |
| 944 |
return array( |
| 945 |
'errorMessage' => sprintf(__('File to import not found at path: %s.', 'media-sync'), $absolute_path) |
| 946 |
); |
| 947 |
} |
| 948 |
|
| 949 |
// Insert the attachment (`wp_posts` table) |
| 950 |
try { |
| 951 |
$max_execution_time = ini_get('max_execution_time'); |
| 952 |
if ($max_execution_time) { |
| 953 |
// Reset execution time so that each file can be processed |
| 954 |
set_time_limit($max_execution_time); |
| 955 |
} |
| 956 |
|
| 957 |
$attach_id = wp_insert_attachment($attachment, $absolute_path, 0, true); |
| 958 |
} catch (Exception $e) { |
| 959 |
return array( |
| 960 |
'errorMessage' => sprintf(__('Error inserting attachment (`wp_posts` table) for file: %s.', 'media-sync'), $absolute_path), |
| 961 |
'error' => $e->getMessage() |
| 962 |
); |
| 963 |
} |
| 964 |
|
| 965 |
if (!($attach_id > 0)) { |
| 966 |
return array( |
| 967 |
'errorMessage' => sprintf(__('Attach ID not received for inserted attachment (`wp_posts` table) for file: %s.', 'media-sync'), $absolute_path) |
| 968 |
); |
| 969 |
} |
| 970 |
|
| 971 |
try { |
| 972 |
// Create custom meta tag (in `wp_postmeta` table) so that we later know this file was imported using this plugin |
| 973 |
$media_sync_metadata = 1; |
| 974 |
$media_sync_metadata = apply_filters('media_sync_filter_before_update_msc_metadata', $media_sync_metadata, $attach_id, $absolute_path); |
| 975 |
if ($media_sync_metadata) { |
| 976 |
add_post_meta($attach_id, '_msc', $media_sync_metadata, true); |
| 977 |
} |
| 978 |
} catch (Exception $e) {} |
| 979 |
|
| 980 |
try { |
| 981 |
// Generate the metadata for the attachment |
| 982 |
$attach_data = wp_generate_attachment_metadata($attach_id, $absolute_path); |
| 983 |
} catch (Exception $e) { |
| 984 |
return array( |
| 985 |
'errorMessage' => sprintf(__('Error generating attachment metadata (`wp_postmeta` table) for file: %s.', 'media-sync'), $absolute_path), |
| 986 |
'error' => $e->getMessage() |
| 987 |
); |
| 988 |
} |
| 989 |
|
| 990 |
if (!$attach_data || empty($attach_data)) { |
| 991 |
return array( |
| 992 |
'errorMessage' => sprintf(__('Attachment metadata could not be generated from %s.', 'media-sync'), $absolute_path), |
| 993 |
'error' => json_encode($attachment) |
| 994 |
); |
| 995 |
} |
| 996 |
|
| 997 |
try { |
| 998 |
/** |
| 999 |
* Apply this filter to collect additional metadata and/or to run some additional actions, |
| 1000 |
* e.g. to "auto-connect" items to pages, posts, and WooCommerce products. |
| 1001 |
* Returning empty data will skip updating metadata (wp_update_attachment_metadata), |
| 1002 |
* so this filter can also be used to totally overwrite updating attachment metadata. |
| 1003 |
* |
| 1004 |
* This filter can be used in a number of different ways: |
| 1005 |
* 1. to collect additional metadata, |
| 1006 |
* 2. to run some additional custom actions |
| 1007 |
* (e.g. to "auto-connect" items to pages, posts, and WooCommerce products), |
| 1008 |
* 3. to skip or completely overwrite updating the metadata (`wp_update_attachment_metadata` function), |
| 1009 |
* when this filter returns empty data (null). |
| 1010 |
* |
| 1011 |
* NOTE: |
| 1012 |
* Things could break if the filter doesn't return proper attach data |
| 1013 |
* and it hasn't created/updated the `wp_postmeta` table record (meta_key _wp_attachment_metadata). |
| 1014 |
* |
| 1015 |
* @since 1.2.5 |
| 1016 |
* |
| 1017 |
* @param array $attach_data Data received from WP function: wp_generate_attachment_metadata |
| 1018 |
* @param int $attach_id |
| 1019 |
* @return array|null |
| 1020 |
*/ |
| 1021 |
$attach_data = apply_filters('media_sync_filter_before_update_metadata', $attach_data, $attach_id); |
| 1022 |
} catch (Exception $e) { |
| 1023 |
return array( |
| 1024 |
'errorMessage' => sprintf(__('Error processing filter media_sync_filter_before_update_metadata for file: %s.', 'media-sync'), $absolute_path), |
| 1025 |
'error' => $e->getMessage() |
| 1026 |
); |
| 1027 |
} |
| 1028 |
|
| 1029 |
// There is this same validation (if) before "media_sync_filter_before_update_metadata" filter, |
| 1030 |
// so if we end up here, it's because of that filter. Which either already updated metadata, |
| 1031 |
// or doesn't want to update it (basically telling us not to run wp_update_attachment_metadata). |
| 1032 |
if (!$attach_data || empty($attach_data)) { |
| 1033 |
return true; |
| 1034 |
} |
| 1035 |
|
| 1036 |
try { |
| 1037 |
// Update `wp_postmeta` database record |
| 1038 |
wp_update_attachment_metadata($attach_id, $attach_data); |
| 1039 |
} catch (Exception $e) { |
| 1040 |
return array( |
| 1041 |
'errorMessage' => sprintf(__('Error updating attachment metadata (`wp_postmeta` table) for file: %s.', 'media-sync'), $absolute_path), |
| 1042 |
'error' => $e->getMessage() |
| 1043 |
); |
| 1044 |
} |
| 1045 |
|
| 1046 |
return true; |
| 1047 |
} |
| 1048 |
|
| 1049 |
|
| 1050 |
/** |
| 1051 |
* Returns post date for file being imported based on selected option |
| 1052 |
* |
| 1053 |
* @since 1.1.0 |
| 1054 |
* @param string $file_path Absolute path to file being imported |
| 1055 |
* @param string $type Selected option telling which date to find [default|file_time|smart_file_time] |
| 1056 |
* @return string |
| 1057 |
*/ |
| 1058 |
static public function media_sync_post_date($file_path, $type) |
| 1059 |
{ |
| 1060 |
// Default is empty string so that WordPress can set its default value (current date) |
| 1061 |
$post_date = ''; |
| 1062 |
|
| 1063 |
try { |
| 1064 |
|
| 1065 |
// Take "time modified" of file being imported |
| 1066 |
$file_timestamp = filemtime( $file_path ); |
| 1067 |
if($file_timestamp) { |
| 1068 |
|
| 1069 |
// Convert to datetime |
| 1070 |
$file_time = date( 'Y-m-d H:i:s', $file_timestamp ); |
| 1071 |
if($file_time) { |
| 1072 |
|
| 1073 |
// For "File time" option - return file time |
| 1074 |
if($type === 'file_time') { |
| 1075 |
$post_date = $file_time; |
| 1076 |
} |
| 1077 |
|
| 1078 |
// For "Smart file time" option - determine post date by comparing file's modified date with folder in which it is |
| 1079 |
if($type === 'smart_file_time') { |
| 1080 |
// Use file time as a fallback |
| 1081 |
$post_date = $file_time; |
| 1082 |
|
| 1083 |
// Compare the file date with the folder date structure |
| 1084 |
// Try to extract year and month from file path (e.g. "2020/02" or "2020\02") |
| 1085 |
// But this path is now always using forward slash (even on Windows Server), so we can again use just '/(\d{4})\/(\d{2})/' |
| 1086 |
preg_match('/(\d{4})[\/|\\\\](\d{2})/', $file_path, $matches); |
| 1087 |
if ( !empty($matches) ) { |
| 1088 |
$year_and_month = $matches[1] . '-' . $matches[2]; |
| 1089 |
$folder_time = $year_and_month . '-' . '01' . ' 00:00:00'; |
| 1090 |
|
| 1091 |
// If what we found for folder date is a date (double check for regex above) |
| 1092 |
if ( date('Y-m-d H:i:s', strtotime( $folder_time )) == $folder_time ) { |
| 1093 |
|
| 1094 |
// If file's year and month values do not match with folder's year and month |
| 1095 |
if ( date( 'Y-m', $file_timestamp ) != $year_and_month ) { |
| 1096 |
// Use the folder date instead |
| 1097 |
$post_date = $folder_time; |
| 1098 |
} |
| 1099 |
} |
| 1100 |
} |
| 1101 |
} |
| 1102 |
} |
| 1103 |
} |
| 1104 |
|
| 1105 |
|
| 1106 |
// Receive external function for setting custom post date |
| 1107 |
$external = apply_filters('media_sync_filter_post_date', $post_date, $file_path, $type ); |
| 1108 |
|
| 1109 |
// Return received custom post date if it's empty string or a date |
| 1110 |
if( is_string($external) && ($external === '' || ( date('Y-m-d H:i:s', strtotime( $external )) == $external) ) ) { |
| 1111 |
$post_date = $external; |
| 1112 |
} |
| 1113 |
|
| 1114 |
return $post_date; |
| 1115 |
} catch ( Exception $e ) { |
| 1116 |
return $post_date; |
| 1117 |
} |
| 1118 |
} |
| 1119 |
|
| 1120 |
|
| 1121 |
/** |
| 1122 |
* Get path of directory used by this plugin as main import directory (uploads dir). |
| 1123 |
* |
| 1124 |
* Not normalizing dir path anymore (keeping backslashes on Windows Server), |
| 1125 |
* to avoid issues with relative path when importing. |
| 1126 |
* |
| 1127 |
* @since 1.1.3 |
| 1128 |
* @return string|null |
| 1129 |
*/ |
| 1130 |
static private function media_sync_get_uploads_basedir() |
| 1131 |
{ |
| 1132 |
$upload_dir = wp_get_upload_dir(); |
| 1133 |
|
| 1134 |
if(!($upload_dir && isset($upload_dir['basedir']) && !empty($upload_dir['basedir']))) { |
| 1135 |
return null; |
| 1136 |
} |
| 1137 |
|
| 1138 |
return $upload_dir['basedir']; |
| 1139 |
} |
| 1140 |
|
| 1141 |
|
| 1142 |
/** |
| 1143 |
* Validate that a path resolves within the uploads directory. |
| 1144 |
* |
| 1145 |
* @since 1.5.0 |
| 1146 |
* @param string $path Absolute path to validate |
| 1147 |
* @return string|false Resolved canonical path, or false if invalid |
| 1148 |
*/ |
| 1149 |
static private function media_sync_validate_path($path) |
| 1150 |
{ |
| 1151 |
// Reject stream wrappers e.g. ftp://, php:// |
| 1152 |
if (strpos($path, '://') !== false) { |
| 1153 |
return false; |
| 1154 |
} |
| 1155 |
|
| 1156 |
// Reject path traversal sequences |
| 1157 |
if (strpos($path, '..') !== false) { |
| 1158 |
return false; |
| 1159 |
} |
| 1160 |
|
| 1161 |
// Resolve canonical uploads base path |
| 1162 |
$uploads_basedir = realpath(self::media_sync_get_uploads_basedir()); |
| 1163 |
if ($uploads_basedir === false) { |
| 1164 |
return false; |
| 1165 |
} |
| 1166 |
|
| 1167 |
// Resolve canonical path (also confirms the path exists on disk) |
| 1168 |
$resolved = realpath($path); |
| 1169 |
if ($resolved === false) { |
| 1170 |
return false; |
| 1171 |
} |
| 1172 |
|
| 1173 |
// Confirm resolved path is within uploads |
| 1174 |
if (strpos($resolved, $uploads_basedir) !== 0) { |
| 1175 |
return false; |
| 1176 |
} |
| 1177 |
|
| 1178 |
return $path; |
| 1179 |
} |
| 1180 |
|
| 1181 |
|
| 1182 |
/** |
| 1183 |
* Get path absolute to WP root. Always using forward slashes. |
| 1184 |
* |
| 1185 |
* e.g. /var/www/WP/wp-content/uploads -> /wp-content/uploads |
| 1186 |
* e.g. C:/www/WP/wp-content/uploads -> /wp-content/uploads |
| 1187 |
* |
| 1188 |
* @since 1.1.3 |
| 1189 |
* @param string $absolute_path Absolute file path. Will be converted to forward slashes. |
| 1190 |
* @return string |
| 1191 |
*/ |
| 1192 |
static private function media_sync_get_relative_path($absolute_path) |
| 1193 |
{ |
| 1194 |
// Since get_home_path() and WP in general always use forward slashes, we need to convert it as well |
| 1195 |
$absolute_path = wp_normalize_path($absolute_path); |
| 1196 |
|
| 1197 |
// Always using forward slash |
| 1198 |
return str_replace(get_home_path(), '/', $absolute_path); |
| 1199 |
} |
| 1200 |
|
| 1201 |
|
| 1202 |
/** |
| 1203 |
* Scan "uploads" directory and return recursive list of files and directories |
| 1204 |
* |
| 1205 |
* @since 0.1.0 |
| 1206 |
* @return Generator |
| 1207 |
*/ |
| 1208 |
static private function media_sync_get_list_of_uploads() |
| 1209 |
{ |
| 1210 |
self::$start_time = microtime(true); |
| 1211 |
|
| 1212 |
self::$upload_dir_path = self::media_sync_get_uploads_basedir(); |
| 1213 |
if(!self::$upload_dir_path) { |
| 1214 |
return array(); |
| 1215 |
} |
| 1216 |
|
| 1217 |
// Limit scanning to specific sub folder or encoded path (e.g. &sub_dir=2020%2F01) |
| 1218 |
$sub_dir = self::sanitize_input_string(INPUT_GET, 'sub_dir'); |
| 1219 |
if ($sub_dir) { |
| 1220 |
$sub_dir_validated = self::media_sync_validate_path( self::$upload_dir_path . '/' . $sub_dir ); |
| 1221 |
if ( $sub_dir_validated === false ) { |
| 1222 |
return array(); |
| 1223 |
} |
| 1224 |
self::$upload_dir_path = $sub_dir_validated; |
| 1225 |
} |
| 1226 |
|
| 1227 |
if(empty(self::$files_in_db)) { |
| 1228 |
self::$files_in_db = self::media_sync_get_files_in_db(); |
| 1229 |
} |
| 1230 |
|
| 1231 |
$associated_filter = self::sanitize_input_string(INPUT_GET, 'associated-filter'); |
| 1232 |
$is_missing_from_ml_filter = $associated_filter && urldecode($associated_filter) === 'missing_from:media_library'; |
| 1233 |
|
| 1234 |
// Clear cached files (affecting file_exists, stream_resolve_include_path, etc.) |
| 1235 |
clearstatcache(); |
| 1236 |
|
| 1237 |
// Get all files - returning Generator (not Array) |
| 1238 |
return self::media_sync_get_list_of_files(self::$upload_dir_path, self::$files_in_db, $is_missing_from_ml_filter); |
| 1239 |
} |
| 1240 |
|
| 1241 |
|
| 1242 |
/** |
| 1243 |
* Scan directory (passed as first value) and return recursive list of files and directories |
| 1244 |
* |
| 1245 |
* @since 0.1.0 |
| 1246 |
* @param string $current_dir_path Changing recursively for each directory that gets iterated |
| 1247 |
* @param array $files_in_db List of files that are already in database |
| 1248 |
* @param bool $is_missing_from_ml_filter Filter by "association" |
| 1249 |
* @return Generator |
| 1250 |
*/ |
| 1251 |
static private function media_sync_get_list_of_files($current_dir_path, $files_in_db, $is_missing_from_ml_filter) |
| 1252 |
{ |
| 1253 |
if(!self::$upload_dir_path) { |
| 1254 |
yield null; |
| 1255 |
} |
| 1256 |
|
| 1257 |
// "stream_resolve_include_path" is like "file_exists" but should be quicker |
| 1258 |
if( !stream_resolve_include_path($current_dir_path) ) { |
| 1259 |
yield null; |
| 1260 |
} |
| 1261 |
|
| 1262 |
foreach (scandir($current_dir_path) as $key => $file_name) { |
| 1263 |
// Skip ".", "..", etc. |
| 1264 |
if ($file_name == '.' || $file_name == '..' || $file_name == ".DS_Store" || $file_name == ".htaccess" || $file_name == "index.php") { |
| 1265 |
continue; |
| 1266 |
} |
| 1267 |
|
| 1268 |
$full_path = $current_dir_path . '/' . $file_name; |
| 1269 |
|
| 1270 |
$isDir = is_dir($full_path); |
| 1271 |
|
| 1272 |
// Default file skipping rules: |
| 1273 |
// 1. contains image size at the end (e.g. -100x100.jpg), or |
| 1274 |
// 2. ends with "-scaled" (also WP generated), or |
| 1275 |
// 3. retina thumbnail (e.g. -100x100@2x.jpg), or |
| 1276 |
// 4. has .webp after another file type (e.g. .jpg.webp). |
| 1277 |
$is_ignored = preg_match('/(-scaled|[_-]\d+x\d+)|@[2-6]x(?=\.[a-z]{3,4}$)|\.[a-z]{3,4}.webp/im', $file_name) == true; |
| 1278 |
|
| 1279 |
// Receive external rules for skipping files/folders |
| 1280 |
$is_ignored_external = apply_filters('media_sync_filter_is_scan_object_ignored', $is_ignored, $full_path, $file_name); |
| 1281 |
|
| 1282 |
// Take custom rule for skipping files/folders from external hook/filter function |
| 1283 |
if (is_bool($is_ignored_external)) { |
| 1284 |
$is_ignored = $is_ignored_external; |
| 1285 |
} |
| 1286 |
|
| 1287 |
|
| 1288 |
if ($is_ignored) { |
| 1289 |
continue; |
| 1290 |
} |
| 1291 |
|
| 1292 |
|
| 1293 |
$relative_path = self::media_sync_url_encode(self::media_sync_get_relative_path($full_path)); |
| 1294 |
$file_in_db = isset($files_in_db[$relative_path]) && !empty($files_in_db[$relative_path]) ? |
| 1295 |
$files_in_db[$relative_path] : false; |
| 1296 |
$file_id = $file_in_db && !empty($file_in_db['id']) ? $file_in_db['id'] : false; |
| 1297 |
$file_status = $file_in_db && !empty($file_in_db['status']) ? $file_in_db['status'] : false; |
| 1298 |
|
| 1299 |
if (!$isDir && $is_missing_from_ml_filter && $file_id !== false) { |
| 1300 |
continue; |
| 1301 |
} |
| 1302 |
|
| 1303 |
|
| 1304 |
// Ugly, but efficient |
| 1305 |
$children = array(); |
| 1306 |
|
| 1307 |
if ($isDir) { |
| 1308 |
$children = self::media_sync_get_list_of_files($full_path, $files_in_db, $is_missing_from_ml_filter); |
| 1309 |
|
| 1310 |
if (empty($children)) { |
| 1311 |
continue; |
| 1312 |
} |
| 1313 |
} |
| 1314 |
|
| 1315 |
// Get current parents, to get for example: "/2012/03" |
| 1316 |
$parents_path = str_replace(self::$upload_dir_path, '', $current_dir_path); |
| 1317 |
|
| 1318 |
// Trim first slash, to get for example: "2012/03" |
| 1319 |
$parents_path = ltrim($parents_path, '/'); |
| 1320 |
|
| 1321 |
// Since this path is always using forward slashes, that's what we'll use here |
| 1322 |
$parents = !empty($parents_path) ? explode('/', $parents_path) : array(); |
| 1323 |
|
| 1324 |
$item = array( |
| 1325 |
'display_name' => $file_name, |
| 1326 |
'is_dir' => !!$isDir, |
| 1327 |
'level' => count($parents) + 1, |
| 1328 |
'row_id' => self::get_row_id( self::media_sync_url_encode( $parents_path . '/' . $file_name ) ), |
| 1329 |
'parent_id' => self::get_row_id( self::media_sync_url_encode( $parents_path ) ), |
| 1330 |
'parents' => $parents, |
| 1331 |
'absolute_path' => self::media_sync_url_encode($full_path) |
| 1332 |
); |
| 1333 |
|
| 1334 |
if ($isDir) { |
| 1335 |
$item['url'] = 'javascript:;'; |
| 1336 |
$item['children'] = $children; |
| 1337 |
// Not so easy when using generators (yield) |
| 1338 |
$item['count_children'] = null; |
| 1339 |
} else { |
| 1340 |
$item['url'] = get_site_url() . $relative_path; |
| 1341 |
$item['file_id'] = $file_id; |
| 1342 |
$item['file_status'] = $file_status; |
| 1343 |
$item['children'] = []; |
| 1344 |
$item['count_children'] = 0; |
| 1345 |
} |
| 1346 |
|
| 1347 |
yield $item; |
| 1348 |
} |
| 1349 |
} |
| 1350 |
|
| 1351 |
|
| 1352 |
/** |
| 1353 |
* Get row id from path. |
| 1354 |
* Cleanup since this will be used as HTML attribute. |
| 1355 |
* |
| 1356 |
* @param string $encoded_path |
| 1357 |
* @return string |
| 1358 |
* @since 1.4.5 |
| 1359 |
*/ |
| 1360 |
static public function get_row_id(string $encoded_path): string { |
| 1361 |
if ( empty( $encoded_path ) ) { |
| 1362 |
return ""; |
| 1363 |
} |
| 1364 |
|
| 1365 |
return 'msc-' . str_replace( '.', '-', sanitize_file_name( $encoded_path ) ); |
| 1366 |
} |
| 1367 |
|
| 1368 |
|
| 1369 |
/** |
| 1370 |
* Convert special characters to safe characters but keeping forward slash. |
| 1371 |
* |
| 1372 |
* @param $url |
| 1373 |
* @return string |
| 1374 |
* @since 1.2.4 |
| 1375 |
*/ |
| 1376 |
static private function media_sync_url_encode($url) |
| 1377 |
{ |
| 1378 |
return str_replace('%2F', '/', rawurlencode($url)); |
| 1379 |
} |
| 1380 |
|
| 1381 |
|
| 1382 |
/** |
| 1383 |
* Get list of files that are already in database |
| 1384 |
* |
| 1385 |
* Caching does not seem to work, disabled for now |
| 1386 |
* |
| 1387 |
* @since 0.1.0 |
| 1388 |
* @return array |
| 1389 |
*/ |
| 1390 |
static private function media_sync_get_files_in_db() |
| 1391 |
{ |
| 1392 |
$media_query = new WP_Query( array( |
| 1393 |
'post_type' => 'attachment', |
| 1394 |
'post_status' => array( 'inherit', 'trash' ), |
| 1395 |
'posts_per_page' => - 1 |
| 1396 |
) ); |
| 1397 |
|
| 1398 |
$upload_dir_path = self::media_sync_get_uploads_basedir(); |
| 1399 |
$upload_dir_relative_path = self::media_sync_get_relative_path( $upload_dir_path ); |
| 1400 |
|
| 1401 |
$files = array(); |
| 1402 |
foreach ( $media_query->posts as $post ) { |
| 1403 |
|
| 1404 |
$file_path = get_post_meta( $post->ID, '_wp_attached_file', true ); |
| 1405 |
if ( empty( $file_path ) ) { |
| 1406 |
continue; |
| 1407 |
} |
| 1408 |
|
| 1409 |
// e.g. /2012/03/img space.jpg |
| 1410 |
$short_relative_path = '/' . $file_path; |
| 1411 |
|
| 1412 |
// e.g. /wp-content/uploads/2012/03/img%20space.jpg |
| 1413 |
$relative_path = self::media_sync_url_encode( $upload_dir_relative_path . $short_relative_path ); |
| 1414 |
|
| 1415 |
$file = array( |
| 1416 |
'id' => $post->ID, |
| 1417 |
'name' => $post->post_title, |
| 1418 |
'status' => $post->post_status |
| 1419 |
); |
| 1420 |
|
| 1421 |
$files[ $relative_path ] = $file; |
| 1422 |
|
| 1423 |
// Path to current file without file name |
| 1424 |
$base_path = self::get_base_path( $file_path ); |
| 1425 |
|
| 1426 |
// For large images - WordPress creates resized versions ("-scaled" at the end of file) |
| 1427 |
// https://make.wordpress.org/core/2019/10/09/introducing-handling-of-big-images-in-wordpress-5-3/ |
| 1428 |
// So we also need to find and treat original file as "file in db" |
| 1429 |
// |
| 1430 |
// WordPress 7.1 client-side media processing also writes companion basenames into |
| 1431 |
// metadata: the HEIC/HEIF original kept beside its JPEG derivative, and the MP4 and |
| 1432 |
// poster JPEG generated from an animated GIF. All four keys resolve the same way. |
| 1433 |
$meta = wp_get_attachment_metadata( $post->ID ); |
| 1434 |
foreach ( |
| 1435 |
array( |
| 1436 |
'original_image', |
| 1437 |
'source_image', |
| 1438 |
'animated_video', |
| 1439 |
'animated_video_poster' |
| 1440 |
) as $companion_key |
| 1441 |
) { |
| 1442 |
if ( ! empty( $meta[ $companion_key ] ) && is_string( $meta[ $companion_key ] ) ) { |
| 1443 |
$companion_path = self::media_sync_url_encode( $upload_dir_relative_path . $base_path . $meta[ $companion_key ] ); |
| 1444 |
$files[ $companion_path ] = $file; |
| 1445 |
} |
| 1446 |
} |
| 1447 |
|
| 1448 |
// Check if this is an edited image (crop, rotate, etc. in Media Library) and treat all those edits as "files in db" |
| 1449 |
$backup_sizes = get_post_meta( $post->ID, '_wp_attachment_backup_sizes', true ); |
| 1450 |
if ( ! empty( $backup_sizes ) ) { |
| 1451 |
foreach ( $backup_sizes as $backup_size ) { |
| 1452 |
$backup_size_path = self::media_sync_url_encode( $upload_dir_relative_path . $base_path . $backup_size['file'] ); |
| 1453 |
$files[ $backup_size_path ] = $file; |
| 1454 |
} |
| 1455 |
} |
| 1456 |
} |
| 1457 |
|
| 1458 |
return $files; |
| 1459 |
} |
| 1460 |
|
| 1461 |
|
| 1462 |
/** |
| 1463 |
* Take base path from provided file path. |
| 1464 |
* |
| 1465 |
* @param string $file_path |
| 1466 |
* @return string |
| 1467 |
* @since 1.3.3 |
| 1468 |
*/ |
| 1469 |
static private function get_base_path($file_path) |
| 1470 |
{ |
| 1471 |
$base_path = pathinfo($file_path, PATHINFO_DIRNAME); |
| 1472 |
if ($base_path === '.') { |
| 1473 |
return '/'; |
| 1474 |
} else { |
| 1475 |
return '/' . rtrim($base_path, '/') . '/'; |
| 1476 |
} |
| 1477 |
} |
| 1478 |
|
| 1479 |
|
| 1480 |
/** |
| 1481 |
* Parse the memory_limit variable from the php.ini file. |
| 1482 |
* |
| 1483 |
* @since 1.2.0 |
| 1484 |
* @return int |
| 1485 |
*/ |
| 1486 |
static private function media_sync_get_memory_limit() |
| 1487 |
{ |
| 1488 |
$limit_string = ini_get('memory_limit'); |
| 1489 |
$unit = strtolower(mb_substr($limit_string, -1 )); |
| 1490 |
$bytes = intval(mb_substr($limit_string, 0, -1), 10); |
| 1491 |
|
| 1492 |
switch ($unit) |
| 1493 |
{ |
| 1494 |
case 'k': |
| 1495 |
$bytes *= 1024; |
| 1496 |
break 1; |
| 1497 |
|
| 1498 |
case 'm': |
| 1499 |
$bytes *= 1024 * 1024; |
| 1500 |
break 1; |
| 1501 |
|
| 1502 |
case 'g': |
| 1503 |
$bytes *= 1024 * 1024 * 1024; |
| 1504 |
break 1; |
| 1505 |
|
| 1506 |
default: |
| 1507 |
break 1; |
| 1508 |
} |
| 1509 |
|
| 1510 |
return $bytes; |
| 1511 |
} |
| 1512 |
|
| 1513 |
|
| 1514 |
/** |
| 1515 |
* Check if logged in user has access |
| 1516 |
* |
| 1517 |
* @since 1.1.0 |
| 1518 |
* @return boolean |
| 1519 |
*/ |
| 1520 |
static public function media_sync_user_has_general_access() |
| 1521 |
{ |
| 1522 |
if ( !current_user_can( 'upload_files' ) ) { |
| 1523 |
return false; |
| 1524 |
} |
| 1525 |
|
| 1526 |
return true; |
| 1527 |
} |
| 1528 |
|
| 1529 |
|
| 1530 |
/** |
| 1531 |
* Sanitize a string input from GET or POST request. |
| 1532 |
* Be careful not to strip special characters from file or directory name. |
| 1533 |
* |
| 1534 |
* @param int $type The input type (INPUT_GET, INPUT_POST). |
| 1535 |
* @param string $input_name The name of the input to retrieve and sanitize. |
| 1536 |
* @return string The sanitized string. |
| 1537 |
* @since 1.4.1 |
| 1538 |
*/ |
| 1539 |
static public function sanitize_input_string($type, $input_name) |
| 1540 |
{ |
| 1541 |
$value = filter_input( $type, $input_name, FILTER_SANITIZE_FULL_SPECIAL_CHARS ) ?: ''; |
| 1542 |
|
| 1543 |
return ! empty( $value ) ? sanitize_text_field( $value ) : ''; |
| 1544 |
} |
| 1545 |
|
| 1546 |
|
| 1547 |
/** |
| 1548 |
* Sanitize a boolean input from GET or POST request. |
| 1549 |
* |
| 1550 |
* @param int $type The input type (INPUT_GET, INPUT_POST). |
| 1551 |
* @param string $input_name The name of the input to retrieve and sanitize. |
| 1552 |
* @return bool The sanitized boolean. |
| 1553 |
* @since 1.4.1 |
| 1554 |
*/ |
| 1555 |
static public function filter_input_boolean($type, $input_name) |
| 1556 |
{ |
| 1557 |
return (bool) filter_input($type, $input_name, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE); |
| 1558 |
} |
| 1559 |
} |
| 1560 |
endif; // End if class_exists check. |
| 1561 |
?> |