← All changes
|
includes/features/bbpc_attachments/code/public.php
+144
-13
2.4.1
→
2.5.1
View file →
| @@ -10,23 +10,124 @@ | ||
| 10 | 10 | * |
| 11 | 11 | * @return array list of attachments objects |
| 12 | 12 | */ |
| 13 | 13 | function forumax_get_post_attachments( $post_id ) { |
| 14 | - $args = [ | |
| 15 | - 'post_type' => 'attachment', | |
| 16 | - 'numberposts' => -1, | |
| 17 | - 'post_status' => null, | |
| 18 | - 'post_parent' => $post_id, | |
| 19 | - 'orderby' => 'ID', | |
| 20 | - 'order' => 'ASC', | |
| 21 | - ]; | |
| 14 | + $post_id = (int) $post_id; | |
| 15 | + $store = &forumax_post_attachments_store(); | |
| 22 | 16 | |
| 23 | - return get_posts( $args ); | |
| 17 | + if ( ! isset( $store[ $post_id ] ) ) { | |
| 18 | + // Prime the whole topic/reply loop in one query instead of one per post. | |
| 19 | + forumax_prime_post_attachments( array_merge( [ $post_id ], forumax_current_loop_post_ids() ) ); | |
| 20 | + } | |
| 21 | + | |
| 22 | + return $store[ $post_id ] ?? []; | |
| 24 | 23 | } |
| 25 | 24 | |
| 26 | 25 | /** |
| 26 | + * Per-request attachment store, keyed by parent post ID. | |
| 27 | + * | |
| 28 | + * @return array | |
| 29 | + */ | |
| 30 | +function &forumax_post_attachments_store() { | |
| 31 | + static $store = []; | |
| 32 | + return $store; | |
| 33 | +} | |
| 34 | + | |
| 35 | +/** | |
| 36 | + * IDs of the topics/replies in the active bbPress loops. | |
| 37 | + * | |
| 38 | + * @return int[] | |
| 39 | + */ | |
| 40 | +function forumax_current_loop_post_ids() { | |
| 41 | + $ids = []; | |
| 42 | + | |
| 43 | + if ( ! function_exists( 'bbpress' ) ) { | |
| 44 | + return $ids; | |
| 45 | + } | |
| 46 | + | |
| 47 | + foreach ( [ 'reply_query', 'topic_query' ] as $prop ) { | |
| 48 | + $query = bbpress()->{$prop} ?? null; | |
| 49 | + if ( $query instanceof WP_Query && ! empty( $query->posts ) ) { | |
| 50 | + $ids = array_merge( $ids, forumax_query_post_ids( $query ) ); | |
| 51 | + } | |
| 52 | + } | |
| 53 | + | |
| 54 | + return $ids; | |
| 55 | +} | |
| 56 | + | |
| 57 | +/** | |
| 58 | + * Post IDs of a query, whether it fetched objects or IDs. | |
| 59 | + * | |
| 60 | + * @param WP_Query $query Query. | |
| 61 | + * @return int[] | |
| 62 | + */ | |
| 63 | +function forumax_query_post_ids( $query ) { | |
| 64 | + return array_map( | |
| 65 | + static function ( $post ) { | |
| 66 | + return is_object( $post ) ? (int) $post->ID : (int) $post; | |
| 67 | + }, | |
| 68 | + (array) $query->posts | |
| 69 | + ); | |
| 70 | +} | |
| 71 | + | |
| 72 | +/** | |
| 73 | + * Fetch attachments for many parent posts in a single query. | |
| 74 | + * | |
| 75 | + * @param int[] $post_ids Topic or reply IDs. | |
| 76 | + * @return void | |
| 77 | + */ | |
| 78 | +function forumax_prime_post_attachments( array $post_ids ) { | |
| 79 | + $store = &forumax_post_attachments_store(); | |
| 80 | + $post_ids = array_values( array_diff( array_unique( array_filter( array_map( 'intval', $post_ids ) ) ), array_keys( $store ) ) ); | |
| 81 | + | |
| 82 | + if ( empty( $post_ids ) ) { | |
| 83 | + return; | |
| 84 | + } | |
| 85 | + | |
| 86 | + foreach ( $post_ids as $id ) { | |
| 87 | + $store[ $id ] = []; | |
| 88 | + } | |
| 89 | + | |
| 90 | + // No row cap: the parent set is already bounded by the current loop, and a | |
| 91 | + // cap would silently drop attachments of the posts past it (each of them | |
| 92 | + // is marked as loaded above). | |
| 93 | + $attachments = get_posts( | |
| 94 | + [ | |
| 95 | + 'post_type' => 'attachment', | |
| 96 | + 'numberposts' => -1, | |
| 97 | + 'post_status' => 'inherit', | |
| 98 | + 'post_parent__in' => $post_ids, | |
| 99 | + 'orderby' => 'ID', | |
| 100 | + 'order' => 'ASC', | |
| 101 | + 'update_post_term_cache' => false, | |
| 102 | + ] | |
| 103 | + ); | |
| 104 | + | |
| 105 | + foreach ( $attachments as $attachment ) { | |
| 106 | + $store[ (int) $attachment->post_parent ][] = $attachment; | |
| 107 | + } | |
| 108 | +} | |
| 109 | + | |
| 110 | +/** | |
| 111 | + * Forget a parent's cached attachments when one is added or removed. | |
| 112 | + * | |
| 113 | + * @param int $attachment_id Attachment ID. | |
| 114 | + * @return void | |
| 115 | + */ | |
| 116 | +function forumax_flush_post_attachments_store( $attachment_id ) { | |
| 117 | + $store = &forumax_post_attachments_store(); | |
| 118 | + $parent = (int) wp_get_post_parent_id( $attachment_id ); | |
| 119 | + unset( $store[ $parent ] ); | |
| 120 | +} | |
| 121 | +add_action( 'add_attachment', 'forumax_flush_post_attachments_store' ); | |
| 122 | +add_action( 'delete_attachment', 'forumax_flush_post_attachments_store' ); | |
| 123 | + | |
| 124 | +/** | |
| 27 | 125 | * Count attachments for the forum topic. It can include topic replies in the count. |
| 28 | 126 | * |
| 127 | + * Counts for every topic in the current loop are fetched together, so the | |
| 128 | + * topic list costs two queries instead of one per topic. | |
| 129 | + * | |
| 29 | 130 | * @param int $topic_id id of the topic to count attachments for |
| 30 | 131 | * @param bool $include_replies true, to include reply attachments |
| 31 | 132 | * |
| 32 | 133 | * @return int number of attachments |
| @@ -32,17 +133,47 @@ | ||
| 32 | 133 | * @return int number of attachments |
| 33 | 134 | */ |
| 34 | 135 | function forumax_topic_attachments_count( $topic_id, $include_replies = false ) { |
| 35 | 136 | global $wpdb; |
| 137 | + static $counts = []; | |
| 36 | 138 | |
| 37 | - $sql = 'select ID from ' . $wpdb->posts . ' where post_parent = ' . $topic_id . " and post_type = 'attachment'"; | |
| 139 | + $topic_id = (int) $topic_id; | |
| 140 | + $key = $include_replies ? 'all' : 'own'; | |
| 38 | 141 | |
| 142 | + if ( $topic_id <= 0 ) { | |
| 143 | + return 0; | |
| 144 | + } | |
| 145 | + | |
| 146 | + if ( isset( $counts[ $key ][ $topic_id ] ) ) { | |
| 147 | + return $counts[ $key ][ $topic_id ]; | |
| 148 | + } | |
| 149 | + | |
| 150 | + $topic_ids = [ $topic_id ]; | |
| 151 | + if ( function_exists( 'bbpress' ) && bbpress()->topic_query instanceof WP_Query ) { | |
| 152 | + $topic_ids = array_merge( $topic_ids, forumax_query_post_ids( bbpress()->topic_query ) ); | |
| 153 | + } | |
| 154 | + $topic_ids = array_values( array_unique( array_filter( $topic_ids ) ) ); | |
| 155 | + | |
| 156 | + foreach ( $topic_ids as $id ) { | |
| 157 | + $counts[ $key ][ $id ] = 0; | |
| 158 | + } | |
| 159 | + | |
| 160 | + $placeholders = implode( ',', array_fill( 0, count( $topic_ids ), '%d' ) ); | |
| 161 | + | |
| 162 | + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared | |
| 163 | + $rows = $wpdb->get_results( $wpdb->prepare( "SELECT post_parent AS topic_id, COUNT(*) AS total FROM {$wpdb->posts} WHERE post_type = 'attachment' AND post_parent IN ($placeholders) GROUP BY post_parent", $topic_ids ) ); | |
| 164 | + | |
| 39 | 165 | if ( $include_replies ) { |
| 40 | - $sql = '(' . $sql . ') union (select ID from ' . $wpdb->posts . ' where post_parent in (select ID from ' . $wpdb->posts . ' where post_parent = ' . $topic_id . " and post_type = 'reply') and post_type = 'attachment')"; | |
| 166 | + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared | |
| 167 | + $reply_rows = $wpdb->get_results( $wpdb->prepare( "SELECT r.post_parent AS topic_id, COUNT(*) AS total FROM {$wpdb->posts} r INNER JOIN {$wpdb->posts} a ON a.post_parent = r.ID AND a.post_type = 'attachment' WHERE r.post_type = 'reply' AND r.post_parent IN ($placeholders) GROUP BY r.post_parent", $topic_ids ) ); | |
| 168 | + $rows = array_merge( $rows, $reply_rows ); | |
| 41 | 169 | } |
| 42 | 170 | |
| 43 | - $attachments = $wpdb->get_results( $sql ); | |
| 44 | - return count( $attachments ); | |
| 171 | + foreach ( $rows as $row ) { | |
| 172 | + $counts[ $key ][ (int) $row->topic_id ] += (int) $row->total; | |
| 173 | + } | |
| 174 | + | |
| 175 | + return $counts[ $key ][ $topic_id ]; | |
| 45 | 176 | } |
| 46 | 177 | |
| 47 | 178 | /** |
| 48 | 179 | * Handle upload file error. |