PluginProbe
Polylang / trunk
Polylang vtrunk
3.8.9 3.8.8 3.8.7 3.8.6 3.8.5 3.8.4 3.8.3 2.7 2.7.0.1 2.7.1 2.7.2 2.7.3 2.7.4 2.8 2.8.1 2.8.2 2.8.3 2.8.4 2.9 2.9.1 2.9.2 3.0 3.0.1 3.0.2 3.0.3 All 233 releases
← All changes | src/crud-posts.php +141 -13 3.8.3 → trunk View file →
@@ -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 }