| @@ -72,8 +72,13 @@ | ||
| 72 | 72 | add_action( 'add_attachment', array( $this, 'set_default_language' ) ); |
| 73 | 73 | add_action( 'delete_attachment', array( $this, 'delete_post' ) ); |
| 74 | 74 | add_filter( 'wp_delete_file', array( $this, 'wp_delete_file' ) ); |
| 75 | 75 | } |
| 76 | + | |
| 77 | + // Filter query. | |
| 78 | + add_filter( 'query_vars', array( $this, 'add_query_vars' ) ); | |
| 79 | + add_action( 'parse_query', array( $this, 'parse_query' ) ); | |
| 80 | + add_filter( 'posts_clauses', array( $this, 'posts_clauses' ), 10, 2 ); | |
| 76 | 81 | } |
| 77 | 82 | |
| 78 | 83 | /** |
| 79 | 84 | * Allows to set a language by default for posts if it has no language yet. |
| @@ -268,23 +273,64 @@ | ||
| 268 | 273 | */ |
| 269 | 274 | public function wp_delete_file( $file ) { |
| 270 | 275 | global $wpdb; |
| 271 | 276 | |
| 272 | - $uploadpath = wp_upload_dir(); | |
| 277 | + $basefile = basename( $file ); | |
| 278 | + $upload = wp_upload_dir(); | |
| 279 | + $attached_file = trim( str_replace( $upload['basedir'], '', $file ), '/' ); | |
| 280 | + $subdir = trim( str_replace( $basefile, '', $attached_file ), '/' ); | |
| 281 | + $filetype = wp_check_filetype( $file ); | |
| 273 | 282 | |
| 274 | - // Get the main attached file. | |
| 275 | - $attached_file = substr_replace( $file, '', 0, strlen( trailingslashit( $uploadpath['basedir'] ) ) ); | |
| 276 | - $attached_file = preg_replace( '#-\d+x\d+\.([a-z]+)$#', '.$1', $attached_file ); | |
| 283 | + if ( empty( $filetype['type'] ) || ! str_starts_with( $filetype['type'], 'image/' ) ) { | |
| 284 | + /* | |
| 285 | + * For non-image files, we just have to check the '_wp_attached_file meta'. | |
| 286 | + */ | |
| 287 | + $count = $wpdb->get_var( | |
| 288 | + $wpdb->prepare( | |
| 289 | + "SELECT COUNT(*) FROM $wpdb->postmeta | |
| 290 | + WHERE meta_key = '_wp_attached_file' AND meta_value = %s", | |
| 291 | + $attached_file | |
| 292 | + ) | |
| 293 | + ); | |
| 294 | + } elseif ( ! empty( $subdir ) ) { | |
| 295 | + /* | |
| 296 | + * For images, we must take care to intermediate sizes and backup. | |
| 297 | + * - '_wp_attachment_metadata' stores the base filename without subdir | |
| 298 | + * for intermediate sizes but includes the subdir for the main file. | |
| 299 | + * - '_wp_attachment_backup_sizes' stores the base filename, without the subdir. | |
| 300 | + * - '_wp_attached_file' stores the filename with the subdir. | |
| 301 | + * | |
| 302 | + * For filenames stored without subdir, we get it from '_wp_attached_file' to | |
| 303 | + * avoid a conflict if a file has the same filename in a different subdir. | |
| 304 | + */ | |
| 305 | + $count = $wpdb->get_var( | |
| 306 | + $wpdb->prepare( | |
| 307 | + "SELECT COUNT(*) FROM $wpdb->postmeta AS pm1 | |
| 308 | + JOIN $wpdb->postmeta AS pm2 ON pm1.post_id = pm2.post_id | |
| 309 | + WHERE pm1.meta_key = '_wp_attached_file' AND pm1.meta_value LIKE %s | |
| 310 | + AND pm2.meta_key IN ( '_wp_attachment_metadata', '_wp_attachment_backup_sizes' ) | |
| 311 | + AND ( pm2.meta_value LIKE %s OR pm2.meta_value LIKE %s )", | |
| 312 | + $wpdb->esc_like( $subdir ) . '/%', // The subdir is always present in '_wp_attached_file'. | |
| 313 | + '%"' . $wpdb->esc_like( $basefile ) . '"%', // Intermediate sizes in '_wp_attachment_metadata' + '_wp_attachment_backup_sizes'. | |
| 314 | + '%"' . $wpdb->esc_like( $attached_file ) . '"%' // Main file in '_wp_attachment_metadata'. | |
| 315 | + ) | |
| 316 | + ); | |
| 317 | + } else { | |
| 318 | + /* | |
| 319 | + * The query above doesn't work if there's no subdir for uploads, so we must handle it | |
| 320 | + * as a specific case. | |
| 321 | + */ | |
| 322 | + $count = $wpdb->get_var( | |
| 323 | + $wpdb->prepare( | |
| 324 | + "SELECT COUNT(*) FROM $wpdb->postmeta | |
| 325 | + WHERE meta_key IN ( '_wp_attachment_metadata', '_wp_attachment_backup_sizes' ) | |
| 326 | + AND meta_value LIKE %s", | |
| 327 | + '%"' . $wpdb->esc_like( $basefile ) . '"%' | |
| 328 | + ) | |
| 329 | + ); | |
| 330 | + } | |
| 277 | 331 | |
| 278 | - $ids = $wpdb->get_col( | |
| 279 | - $wpdb->prepare( | |
| 280 | - "SELECT post_id FROM $wpdb->postmeta | |
| 281 | - WHERE meta_key = '_wp_attached_file' AND meta_value = %s", | |
| 282 | - $attached_file | |
| 283 | - ) | |
| 284 | - ); | |
| 285 | - | |
| 286 | - if ( ! empty( $ids ) ) { | |
| 332 | + if ( $count > 0 ) { | |
| 287 | 333 | return ''; // Prevent deleting the file. |
| 288 | 334 | } |
| 289 | 335 | |
| 290 | 336 | return $file; |
| @@ -440,6 +486,88 @@ | ||
| 440 | 486 | |
| 441 | 487 | $this->model->term->save_translations( $term->term_id, $trs ); |
| 442 | 488 | |
| 443 | 489 | return $tr_term_id; |
| 490 | + } | |
| 491 | + | |
| 492 | + /** | |
| 493 | + * Add public query vars. Currently only 'untranslated in'. | |
| 494 | + * | |
| 495 | + * @since 3.9 | |
| 496 | + * | |
| 497 | + * @param string[] $qvars The array of allowed query variable names. | |
| 498 | + * @return string[] | |
| 499 | + */ | |
| 500 | + public function add_query_vars( $qvars ) { | |
| 501 | + $qvars[] = 'untranslated_in'; | |
| 502 | + return $qvars; | |
| 503 | + } | |
| 504 | + | |
| 505 | + /** | |
| 506 | + * Ensures filters are not suppressed when querying untranslated posts. | |
| 507 | + * | |
| 508 | + * @since 3.9 | |
| 509 | + * | |
| 510 | + * @param WP_Query $query The WP_Query instance (passed by reference). | |
| 511 | + * @return void | |
| 512 | + */ | |
| 513 | + public function parse_query( $query ) { | |
| 514 | + if ( ! empty( $query->query['untranslated_in'] ) ) { | |
| 515 | + unset( $query->query['suppress_filters'] ); | |
| 516 | + unset( $query->query_vars['suppress_filters'] ); | |
| 517 | + } | |
| 518 | + } | |
| 519 | + | |
| 520 | + /** | |
| 521 | + * Filters the WP_Query SQL clauses to remove posts: | |
| 522 | + * - translated in the language queried by `unstranslated_in`. | |
| 523 | + * - in the language queried by `unstranslated_in`. | |
| 524 | + * | |
| 525 | + * This second condition is necessary as a post not translated in any language | |
| 526 | + * has no translation group. | |
| 527 | + * | |
| 528 | + * @since 3.9 | |
| 529 | + * | |
| 530 | + * @param string[] $clauses The clauses for the query. | |
| 531 | + * @param WP_Query $query The WP_Query instance (passed by reference). | |
| 532 | + * @return string[] | |
| 533 | + */ | |
| 534 | + public function posts_clauses( $clauses, $query ) { | |
| 535 | + global $wpdb; | |
| 536 | + | |
| 537 | + if ( empty( $query->query['untranslated_in'] ) ) { | |
| 538 | + return $clauses; | |
| 539 | + } | |
| 540 | + | |
| 541 | + $untranslated_in = PLL()->model->languages->get( $query->query['untranslated_in'] ); | |
| 542 | + | |
| 543 | + if ( empty( $untranslated_in ) ) { | |
| 544 | + return $clauses; | |
| 545 | + } | |
| 546 | + | |
| 547 | + $tt_id = $untranslated_in->get_tax_prop( 'language', 'term_taxonomy_id' ); | |
| 548 | + | |
| 549 | + $clauses['where'] .= $wpdb->prepare( | |
| 550 | + " AND {$wpdb->posts}.ID NOT IN ( | |
| 551 | + SELECT pllutr.object_id | |
| 552 | + FROM {$wpdb->term_relationships} AS pllutr | |
| 553 | + WHERE pllutr.term_taxonomy_id = %d | |
| 554 | + )", | |
| 555 | + $tt_id | |
| 556 | + ); | |
| 557 | + | |
| 558 | + $clauses['where'] .= $wpdb->prepare( | |
| 559 | + " AND {$wpdb->posts}.ID NOT IN ( | |
| 560 | + SELECT pllutr1.object_id | |
| 561 | + FROM {$wpdb->term_relationships} AS pllutr1 | |
| 562 | + JOIN {$wpdb->term_taxonomy} AS pllutt ON pllutt.term_taxonomy_id = pllutr1.term_taxonomy_id | |
| 563 | + JOIN {$wpdb->term_relationships} AS pllutr2 ON pllutr2.term_taxonomy_id = pllutt.term_taxonomy_id | |
| 564 | + JOIN {$wpdb->term_relationships} AS pllutr3 ON pllutr3.object_id = pllutr2.object_id | |
| 565 | + WHERE pllutt.taxonomy = 'post_translations' | |
| 566 | + AND pllutr3.term_taxonomy_id = %d | |
| 567 | + )", | |
| 568 | + $tt_id | |
| 569 | + ); | |
| 570 | + | |
| 571 | + return $clauses; | |
| 444 | 572 | } |
| 445 | 573 | } |