| @@ -57,8 +57,237 @@ | ||
| 57 | 57 | $this->network_wide = $network_wide; |
| 58 | 58 | } |
| 59 | 59 | |
| 60 | 60 | /** |
| 61 | + * Get the fixed IDs reserved for site-wide tracking contexts. | |
| 62 | + * | |
| 63 | + * The class only exists in the Pro build. Keeping this lookup optional lets | |
| 64 | + * the shared statistics table continue to work in the free plugin. | |
| 65 | + * | |
| 66 | + * @return array<int,int> Reserved context IDs. | |
| 67 | + */ | |
| 68 | + protected function get_sitewide_context_ids() { | |
| 69 | + if ( ! class_exists( 'WebberZone\\Top_Ten\\Pro\\Sitewide_Database' ) || ! \WebberZone\Top_Ten\Pro\Sitewide_Database::is_available() ) { | |
| 70 | + return array(); | |
| 71 | + } | |
| 72 | + | |
| 73 | + return \WebberZone\Top_Ten\Pro\Sitewide_Database::get_context_ids(); | |
| 74 | + } | |
| 75 | + | |
| 76 | + /** | |
| 77 | + * Build a SQL condition matching any reserved site-wide context ID. | |
| 78 | + * | |
| 79 | + * @param string $alias Count-table alias. | |
| 80 | + * @return string SQL condition, or an always-false condition when Pro is not loaded. | |
| 81 | + */ | |
| 82 | + protected function get_sitewide_context_condition( $alias = 'ttt' ) { | |
| 83 | + $ids = $this->get_sitewide_context_ids(); | |
| 84 | + if ( empty( $ids ) ) { | |
| 85 | + return '0=1'; | |
| 86 | + } | |
| 87 | + | |
| 88 | + return sprintf( '%s.postnumber IN (%s)', $alias, implode( ',', array_map( 'intval', $ids ) ) ); | |
| 89 | + } | |
| 90 | + | |
| 91 | + /** | |
| 92 | + * Get the site-wide context key for a result row. | |
| 93 | + * | |
| 94 | + * @param array $item Result row. | |
| 95 | + * @return string Context key, or an empty string for a post row. | |
| 96 | + */ | |
| 97 | + protected function get_item_context_key( $item ) { | |
| 98 | + if ( ! isset( $item['ID'] ) || ! class_exists( 'WebberZone\\Top_Ten\\Pro\\Sitewide_Database' ) || ! \WebberZone\Top_Ten\Pro\Sitewide_Database::is_available() ) { | |
| 99 | + return ''; | |
| 100 | + } | |
| 101 | + | |
| 102 | + return \WebberZone\Top_Ten\Pro\Sitewide_Database::get_context_key( $item['ID'], $item['blog_id'] ?? null ); | |
| 103 | + } | |
| 104 | + | |
| 105 | + /** | |
| 106 | + * Get a translated label for a site-wide context key. | |
| 107 | + * | |
| 108 | + * @param string $context_key Context key. | |
| 109 | + * @return string Context label. | |
| 110 | + */ | |
| 111 | + protected function get_sitewide_context_label( $context_key ) { | |
| 112 | + if ( '' === $context_key || ! class_exists( 'WebberZone\\Top_Ten\\Pro\\Sitewide_Database' ) || ! \WebberZone\Top_Ten\Pro\Sitewide_Database::is_available() ) { | |
| 113 | + return ''; | |
| 114 | + } | |
| 115 | + | |
| 116 | + return \WebberZone\Top_Ten\Pro\Sitewide_Database::get_context_label( $context_key ); | |
| 117 | + } | |
| 118 | + | |
| 119 | + /** | |
| 120 | + * Build the search condition for site-wide context labels and keys. | |
| 121 | + * | |
| 122 | + * @param string $search Search text. | |
| 123 | + * @return string SQL condition, or an always-false condition when Pro is not loaded. | |
| 124 | + */ | |
| 125 | + protected function get_sitewide_context_search_condition( $search ) { | |
| 126 | + if ( ! class_exists( 'WebberZone\\Top_Ten\\Pro\\Sitewide_Database' ) || ! \WebberZone\Top_Ten\Pro\Sitewide_Database::is_available() ) { | |
| 127 | + return '0=1'; | |
| 128 | + } | |
| 129 | + | |
| 130 | + global $wpdb; | |
| 131 | + | |
| 132 | + $like = '%' . $wpdb->esc_like( $search ) . '%'; | |
| 133 | + $conditions = array(); | |
| 134 | + foreach ( \WebberZone\Top_Ten\Pro\Sitewide_Database::CONTEXT_IDS as $context_key => $context_id ) { | |
| 135 | + $searchable = $context_key . ' ' . $this->get_sitewide_context_label( $context_key ); | |
| 136 | + $conditions[] = $wpdb->prepare( 'ttt.postnumber = %d AND %s LIKE %s', $context_id, $searchable, $like ); | |
| 137 | + } | |
| 138 | + | |
| 139 | + return '(' . implode( ' OR ', $conditions ) . ')'; | |
| 140 | + } | |
| 141 | + | |
| 142 | + /** | |
| 143 | + * Resolve a safe ordering clause for statistics queries. | |
| 144 | + * | |
| 145 | + * @since 4.5.0 | |
| 146 | + * | |
| 147 | + * @param array $args Query arguments. | |
| 148 | + * @return array{orderby:string,order:string} Ordering fields. | |
| 149 | + */ | |
| 150 | + protected function get_ordering( $args ) { | |
| 151 | + $orderby = ''; | |
| 152 | + if ( ! empty( $_REQUEST['orderby'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended | |
| 153 | + $orderby = sanitize_text_field( wp_unslash( $_REQUEST['orderby'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended | |
| 154 | + } elseif ( ! empty( $args['orderby'] ) ) { | |
| 155 | + $orderby = $args['orderby']; | |
| 156 | + } | |
| 157 | + | |
| 158 | + if ( ! in_array( $orderby, array( 'title', 'daily_count', 'total_count' ), true ) ) { | |
| 159 | + $orderby = 'total_count'; | |
| 160 | + } | |
| 161 | + | |
| 162 | + $order = ''; | |
| 163 | + if ( ! empty( $_REQUEST['order'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended | |
| 164 | + $order = sanitize_text_field( wp_unslash( $_REQUEST['order'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended | |
| 165 | + } elseif ( ! empty( $args['order'] ) ) { | |
| 166 | + $order = $args['order']; | |
| 167 | + } | |
| 168 | + | |
| 169 | + if ( ! in_array( $order, array( 'asc', 'ASC', 'desc', 'DESC' ), true ) ) { | |
| 170 | + $order = 'DESC'; | |
| 171 | + } | |
| 172 | + | |
| 173 | + return array( | |
| 174 | + 'orderby' => $orderby, | |
| 175 | + 'order' => strtoupper( $order ), | |
| 176 | + ); | |
| 177 | + } | |
| 178 | + | |
| 179 | + /** | |
| 180 | + * Fetch network-wide popular posts in two phases. | |
| 181 | + * | |
| 182 | + * The first query selects only the requested page in the sort order. The | |
| 183 | + * second query fills in the other count using exact post/blog pairs, which | |
| 184 | + * avoids joining and grouping the entire network's count tables. | |
| 185 | + * | |
| 186 | + * @since 4.5.0 | |
| 187 | + * | |
| 188 | + * @param int $per_page Posts per page. | |
| 189 | + * @param int $page_number Page number. | |
| 190 | + * @param array $args Query arguments. | |
| 191 | + * @param string $from_date Inclusive lower date boundary. | |
| 192 | + * @param string $to_date Exclusive upper date boundary. | |
| 193 | + * @return array Array of popular posts. | |
| 194 | + */ | |
| 195 | + protected function get_network_popular_posts( $per_page, $page_number, $args, $from_date, $to_date ) { | |
| 196 | + global $wpdb; | |
| 197 | + | |
| 198 | + $ordering = $this->get_ordering( $args ); | |
| 199 | + $orderby = 'daily_count' === $ordering['orderby'] ? 'daily_count' : 'total_count'; | |
| 200 | + $offset = max( 0, ( (int) $page_number - 1 ) * (int) $per_page ); | |
| 201 | + $limit = max( 0, (int) $per_page ); | |
| 202 | + | |
| 203 | + if ( 0 === $limit ) { | |
| 204 | + return array(); | |
| 205 | + } | |
| 206 | + | |
| 207 | + $daily_table = $wpdb->base_prefix . 'top_ten_daily'; | |
| 208 | + $total_table = $wpdb->base_prefix . 'top_ten'; | |
| 209 | + | |
| 210 | + // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared | |
| 211 | + if ( 'daily_count' === $orderby ) { | |
| 212 | + // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared | |
| 213 | + $sql = $wpdb->prepare( | |
| 214 | + "SELECT postnumber AS ID, blog_id, SUM(cntaccess) AS daily_count | |
| 215 | + FROM {$daily_table} | |
| 216 | + WHERE dp_date >= %s AND dp_date < %s | |
| 217 | + GROUP BY postnumber, blog_id | |
| 218 | + ORDER BY daily_count {$ordering['order']}, postnumber ASC, blog_id ASC | |
| 219 | + LIMIT %d, %d", | |
| 220 | + $from_date, | |
| 221 | + $to_date, | |
| 222 | + $offset, | |
| 223 | + $limit | |
| 224 | + ); | |
| 225 | + } else { | |
| 226 | + // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared | |
| 227 | + $sql = $wpdb->prepare( | |
| 228 | + "SELECT postnumber AS ID, blog_id, cntaccess AS total_count | |
| 229 | + FROM {$total_table} | |
| 230 | + ORDER BY cntaccess {$ordering['order']}, postnumber ASC, blog_id ASC | |
| 231 | + LIMIT %d, %d", | |
| 232 | + $offset, | |
| 233 | + $limit | |
| 234 | + ); | |
| 235 | + } | |
| 236 | + | |
| 237 | + $results = $wpdb->get_results( $sql, ARRAY_A ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared | |
| 238 | + if ( empty( $results ) ) { | |
| 239 | + return array(); | |
| 240 | + } | |
| 241 | + | |
| 242 | + $pair_conditions = array(); | |
| 243 | + foreach ( $results as $result ) { | |
| 244 | + $pair_conditions[] = $wpdb->prepare( '(postnumber = %d AND blog_id = %d)', $result['ID'], $result['blog_id'] ); | |
| 245 | + } | |
| 246 | + $pairs = implode( ' OR ', $pair_conditions ); | |
| 247 | + | |
| 248 | + if ( 'daily_count' === $orderby ) { | |
| 249 | + // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared | |
| 250 | + $other_results = $wpdb->get_results( | |
| 251 | + "SELECT postnumber AS ID, blog_id, cntaccess AS total_count FROM {$total_table} WHERE {$pairs}", | |
| 252 | + ARRAY_A | |
| 253 | + ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.NotPrepared | |
| 254 | + } else { | |
| 255 | + // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared | |
| 256 | + $other_results = $wpdb->get_results( | |
| 257 | + $wpdb->prepare( | |
| 258 | + "SELECT postnumber AS ID, blog_id, SUM(cntaccess) AS daily_count | |
| 259 | + FROM {$daily_table} | |
| 260 | + WHERE dp_date >= %s AND dp_date < %s AND ({$pairs}) | |
| 261 | + GROUP BY postnumber, blog_id", | |
| 262 | + $from_date, | |
| 263 | + $to_date | |
| 264 | + ), | |
| 265 | + ARRAY_A | |
| 266 | + ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.NotPrepared | |
| 267 | + } | |
| 268 | + | |
| 269 | + $other_counts = array(); | |
| 270 | + foreach ( $other_results as $result ) { | |
| 271 | + $key = (int) $result['ID'] . ':' . (int) $result['blog_id']; | |
| 272 | + $other_counts[ $key ] = $result[ 'daily_count' === $orderby ? 'total_count' : 'daily_count' ]; | |
| 273 | + } | |
| 274 | + | |
| 275 | + foreach ( $results as &$result ) { | |
| 276 | + $key = (int) $result['ID'] . ':' . (int) $result['blog_id']; | |
| 277 | + if ( 'daily_count' === $orderby ) { | |
| 278 | + $result['total_count'] = isset( $other_counts[ $key ] ) ? $other_counts[ $key ] : 0; | |
| 279 | + } else { | |
| 280 | + $result['daily_count'] = isset( $other_counts[ $key ] ) ? $other_counts[ $key ] : 0; | |
| 281 | + } | |
| 282 | + } | |
| 283 | + unset( $result ); | |
| 284 | + | |
| 285 | + // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared | |
| 286 | + return $results; | |
| 287 | + } | |
| 288 | + | |
| 289 | + /** | |
| 61 | 290 | * Retrieve the Top 10 posts |
| 62 | 291 | * |
| 63 | 292 | * @param int $per_page Posts per page. |
| 64 | 293 | * @param int $page_number Page number. |
| @@ -68,8 +297,9 @@ | ||
| 68 | 297 | */ |
| 69 | 298 | public function get_popular_posts( $per_page = 20, $page_number = 1, $args = null ) { |
| 70 | 299 | |
| 71 | 300 | global $wpdb; |
| 301 | + $args = is_array( $args ) ? $args : array(); | |
| 72 | 302 | |
| 73 | 303 | // Initialise some variables. |
| 74 | 304 | $fields = array(); |
| 75 | 305 | $where = ''; |
| @@ -84,102 +314,91 @@ | ||
| 84 | 314 | $from_date = isset( $args['post-date-filter-from'] ) ? $args['post-date-filter-from'] : current_time( 'd M Y' ); |
| 85 | 315 | $from_date = gmdate( 'Y-m-d', strtotime( $from_date ) ); |
| 86 | 316 | $to_date = isset( $args['post-date-filter-to'] ) ? $args['post-date-filter-to'] : current_time( 'd M Y' ); |
| 87 | 317 | $to_date = gmdate( 'Y-m-d', strtotime( $to_date ) ); |
| 318 | + $from_date = $from_date . ' 00:00:00'; | |
| 319 | + $to_date = gmdate( 'Y-m-d 00:00:00', strtotime( $to_date . ' +1 day' ) ); | |
| 88 | 320 | |
| 321 | + if ( $this->network_wide ) { | |
| 322 | + $result = $this->get_network_popular_posts( $per_page, $page_number, $args, $from_date, $to_date ); | |
| 323 | + | |
| 324 | + foreach ( $result as &$row ) { | |
| 325 | + $row['context_key'] = $this->get_item_context_key( $row ); | |
| 326 | + if ( '' !== $row['context_key'] ) { | |
| 327 | + $row['title'] = $this->get_sitewide_context_label( $row['context_key'] ); | |
| 328 | + $row['post_type'] = __( 'Site-wide', 'top-10' ); | |
| 329 | + $row['post_date'] = ''; | |
| 330 | + $row['post_author'] = 0; | |
| 331 | + } | |
| 332 | + } | |
| 333 | + unset( $row ); | |
| 334 | + | |
| 335 | + return $result; | |
| 336 | + } | |
| 337 | + | |
| 89 | 338 | /* Start creating the SQL */ |
| 90 | - $table_name_daily = $wpdb->base_prefix . 'top_ten_daily AS ttd'; | |
| 339 | + $table_name_daily = $wpdb->base_prefix . 'top_ten_daily'; | |
| 91 | 340 | $table_name = $wpdb->base_prefix . 'top_ten AS ttt'; |
| 341 | + $sitewide_sql = $this->get_sitewide_context_condition(); | |
| 92 | 342 | |
| 93 | 343 | // Fields to return. |
| 94 | - if ( ! $this->network_wide ) { | |
| 95 | - $fields[] = "{$wpdb->posts}.post_title as title"; | |
| 96 | - $fields[] = "{$wpdb->posts}.post_type"; | |
| 97 | - $fields[] = "{$wpdb->posts}.post_date"; | |
| 98 | - $fields[] = "{$wpdb->posts}.post_author"; | |
| 99 | - } | |
| 344 | + $fields[] = "{$wpdb->posts}.post_title as title"; | |
| 345 | + $fields[] = "{$wpdb->posts}.post_type"; | |
| 346 | + $fields[] = "{$wpdb->posts}.post_date"; | |
| 347 | + $fields[] = "{$wpdb->posts}.post_author"; | |
| 100 | 348 | |
| 101 | 349 | $fields[] = 'ttt.postnumber as ID'; |
| 102 | 350 | $fields[] = 'ttt.cntaccess as total_count'; |
| 103 | - $fields[] = 'SUM(ttd.cntaccess) as daily_count'; | |
| 351 | + $fields[] = 'SUM(ttd.daily_count) as daily_count'; | |
| 104 | 352 | $fields[] = 'ttt.blog_id as blog_id'; |
| 105 | 353 | |
| 106 | 354 | $fields = implode( ', ', $fields ); |
| 107 | 355 | |
| 108 | 356 | // Create the JOIN clause. |
| 109 | - if ( ! $this->network_wide ) { | |
| 110 | - $join .= " LEFT JOIN {$wpdb->posts} ON ttt.postnumber={$wpdb->posts}.ID "; | |
| 111 | - } | |
| 357 | + $join .= " LEFT JOIN {$wpdb->posts} ON ttt.postnumber={$wpdb->posts}.ID "; | |
| 112 | 358 | $join .= $wpdb->prepare( |
| 113 | 359 | " LEFT JOIN ( |
| 114 | - SELECT * FROM {$table_name_daily} " . // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared | |
| 115 | - 'WHERE DATE(ttd.dp_date) >= DATE(%s) AND DATE(ttd.dp_date) <= DATE(%s) | |
| 360 | + SELECT postnumber, blog_id, SUM(cntaccess) AS daily_count FROM {$table_name_daily} " . // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared | |
| 361 | + 'WHERE dp_date >= %s AND dp_date < %s AND blog_id = %d | |
| 362 | + GROUP BY postnumber, blog_id | |
| 116 | 363 | ) AS ttd |
| 117 | 364 | ON ttt.postnumber=ttd.postnumber AND ttt.blog_id=ttd.blog_id |
| 118 | 365 | ', |
| 119 | 366 | $from_date, |
| 120 | - $to_date | |
| 367 | + $to_date, | |
| 368 | + $blog_id | |
| 121 | 369 | ); |
| 122 | 370 | |
| 123 | 371 | // Create the base WHERE clause. |
| 124 | - if ( ! $this->network_wide ) { | |
| 125 | - $where .= $wpdb->prepare( ' AND ttt.blog_id = %d ', $blog_id ); // Posts need to be from the current blog only. | |
| 126 | - $where .= " AND ($wpdb->posts.post_status = 'publish' OR $wpdb->posts.post_status = 'inherit') "; // Show published posts and attachments. | |
| 372 | + $where .= $wpdb->prepare( ' AND ttt.blog_id = %d ', $blog_id ); // Posts need to be from the current blog only. | |
| 373 | + $where .= " AND (($wpdb->posts.post_status = 'publish' OR $wpdb->posts.post_status = 'inherit') OR {$sitewide_sql}) "; // Show published posts, attachments and site-wide contexts. | |
| 127 | 374 | |
| 128 | - // If search argument is set, do a search for it. | |
| 129 | - if ( ! empty( $args['search'] ) ) { | |
| 130 | - $where .= $wpdb->prepare( " AND $wpdb->posts.post_title LIKE %s ", '%' . $wpdb->esc_like( $args['search'] ) . '%' ); | |
| 131 | - } | |
| 132 | - | |
| 133 | - // If post filter argument is set, do a search for it. | |
| 134 | - if ( isset( $args['post-type-filter'] ) && $this->all_post_type['all'] !== $args['post-type-filter'] ) { | |
| 135 | - $where .= $wpdb->prepare( " AND $wpdb->posts.post_type = %s ", $args['post-type-filter'] ); | |
| 136 | - } else { | |
| 137 | - $post_types = get_post_types( | |
| 138 | - array( | |
| 139 | - 'public' => true, | |
| 140 | - ) | |
| 141 | - ); | |
| 142 | - $where .= " AND $wpdb->posts.post_type IN ('" . join( "', '", $post_types ) . "') "; | |
| 143 | - } | |
| 375 | + // If search argument is set, do a search for it. | |
| 376 | + if ( ! empty( $args['search'] ) ) { | |
| 377 | + $post_search = $wpdb->prepare( "$wpdb->posts.post_title LIKE %s", '%' . $wpdb->esc_like( $args['search'] ) . '%' ); | |
| 378 | + $context_search = $this->get_sitewide_context_search_condition( $args['search'] ); | |
| 379 | + $where .= " AND ({$post_search} OR {$context_search}) "; | |
| 144 | 380 | } |
| 145 | 381 | |
| 146 | - // Create the base GROUP BY clause. | |
| 147 | - $groupby = ' ttt.postnumber, ttt.blog_id '; | |
| 148 | - | |
| 149 | - // Create the ORDER BY clause. | |
| 150 | - $orderby = ''; | |
| 151 | - if ( ! empty( $_REQUEST['orderby'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended | |
| 152 | - $orderby = sanitize_text_field( wp_unslash( $_REQUEST['orderby'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended | |
| 153 | - } elseif ( ! empty( $args['orderby'] ) ) { | |
| 154 | - $orderby = $args['orderby']; | |
| 382 | + // If post filter argument is set, do a search for it. | |
| 383 | + if ( isset( $args['post-type-filter'] ) && $this->all_post_type['all'] !== $args['post-type-filter'] ) { | |
| 384 | + $where .= $wpdb->prepare( " AND $wpdb->posts.post_type = %s ", $args['post-type-filter'] ); | |
| 385 | + } else { | |
| 386 | + $post_types = get_post_types( | |
| 387 | + array( | |
| 388 | + 'public' => true, | |
| 389 | + ) | |
| 390 | + ); | |
| 391 | + $where .= " AND ($wpdb->posts.post_type IN ('" . join( "', '", $post_types ) . "') OR {$sitewide_sql}) "; | |
| 155 | 392 | } |
| 156 | 393 | |
| 157 | - if ( $orderby ) { | |
| 158 | - if ( ! in_array( $orderby, array( 'title', 'daily_count', 'total_count' ) ) ) { //phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict | |
| 159 | - $orderby = ' total_count '; | |
| 160 | - } | |
| 394 | + $ordering = $this->get_ordering( $args ); | |
| 395 | + $orderby = $ordering['orderby'] . ' ' . $ordering['order']; | |
| 161 | 396 | |
| 162 | - $order = ''; | |
| 163 | - if ( ! empty( $_REQUEST['order'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended | |
| 164 | - $order = sanitize_text_field( wp_unslash( $_REQUEST['order'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended | |
| 165 | - } elseif ( ! empty( $args['order'] ) ) { | |
| 166 | - $order = $args['order']; | |
| 167 | - } | |
| 168 | - | |
| 169 | - if ( $order && in_array( $order, array( 'asc', 'ASC', 'desc', 'DESC' ), true ) ) { | |
| 170 | - $orderby .= " {$order}"; | |
| 171 | - } else { | |
| 172 | - $orderby .= ' DESC'; | |
| 173 | - } | |
| 174 | - } else { | |
| 175 | - $orderby = ' total_count DESC '; | |
| 176 | - } | |
| 177 | - | |
| 178 | 397 | // Create the base LIMITS clause. |
| 179 | 398 | $limits = $wpdb->prepare( ' LIMIT %d, %d ', ( $page_number - 1 ) * $per_page, $per_page ); |
| 180 | 399 | |
| 181 | - $groupby = " GROUP BY {$groupby} "; | |
| 400 | + $groupby = ' GROUP BY ttt.postnumber, ttt.blog_id '; | |
| 182 | 401 | $orderby = " ORDER BY {$orderby} "; |
| 183 | 402 | |
| 184 | 403 | $sql = "SELECT $fields FROM {$table_name} $join WHERE 1=1 $where $groupby $orderby $limits"; |
| 185 | 404 | |
| @@ -184,8 +403,19 @@ | ||
| 184 | 403 | $sql = "SELECT $fields FROM {$table_name} $join WHERE 1=1 $where $groupby $orderby $limits"; |
| 185 | 404 | |
| 186 | 405 | $result = $wpdb->get_results( $sql, 'ARRAY_A' ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared |
| 187 | 406 | |
| 407 | + foreach ( $result as &$row ) { | |
| 408 | + $row['context_key'] = $this->get_item_context_key( $row ); | |
| 409 | + if ( '' !== $row['context_key'] ) { | |
| 410 | + $row['title'] = $this->get_sitewide_context_label( $row['context_key'] ); | |
| 411 | + $row['post_type'] = __( 'Site-wide', 'top-10' ); | |
| 412 | + $row['post_date'] = ''; | |
| 413 | + $row['post_author'] = 0; | |
| 414 | + } | |
| 415 | + } | |
| 416 | + unset( $row ); | |
| 417 | + | |
| 188 | 418 | return $result; |
| 189 | 419 | } |
| 190 | 420 | |
| 191 | 421 | /** |
| @@ -196,32 +426,52 @@ | ||
| 196 | 426 | */ |
| 197 | 427 | public function record_count( $args = null ) { |
| 198 | 428 | |
| 199 | 429 | global $wpdb; |
| 430 | + $args = is_array( $args ) ? $args : array(); | |
| 200 | 431 | |
| 432 | + if ( $this->network_wide ) { | |
| 433 | + $cache_key = 'tptn_network_popular_posts_count'; | |
| 434 | + $cached = get_site_transient( $cache_key ); | |
| 435 | + if ( false !== $cached ) { | |
| 436 | + return (string) (int) $cached; | |
| 437 | + } | |
| 438 | + | |
| 439 | + $count = (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->base_prefix}top_ten" ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared | |
| 440 | + /** This filter is documented in includes/admin/class-dashboard-widgets.php */ | |
| 441 | + $ttl = max( 0, (int) apply_filters( 'tptn_network_dashboard_cache_ttl', 15 * MINUTE_IN_SECONDS ) ); | |
| 442 | + if ( $ttl > 0 ) { | |
| 443 | + set_site_transient( $cache_key, $count, $ttl ); | |
| 444 | + } | |
| 445 | + | |
| 446 | + return (string) $count; | |
| 447 | + } | |
| 448 | + | |
| 201 | 449 | $where = ''; |
| 202 | 450 | $join = ''; |
| 203 | 451 | |
| 204 | 452 | $sql = "SELECT COUNT(*) FROM {$wpdb->base_prefix}top_ten as ttt"; |
| 205 | 453 | |
| 206 | - if ( ! $this->network_wide ) { | |
| 207 | - $join = "INNER JOIN {$wpdb->posts} ON ttt.postnumber=ID"; | |
| 208 | - $where .= $wpdb->prepare( ' AND blog_id = %d ', get_current_blog_id() ); | |
| 454 | + $join = "LEFT JOIN {$wpdb->posts} ON ttt.postnumber={$wpdb->posts}.ID"; | |
| 455 | + $sitewide_sql = $this->get_sitewide_context_condition(); | |
| 456 | + $where .= $wpdb->prepare( ' AND ttt.blog_id = %d ', get_current_blog_id() ); | |
| 457 | + $where .= " AND (($wpdb->posts.post_status = 'publish' OR $wpdb->posts.post_status = 'inherit') OR {$sitewide_sql}) "; | |
| 209 | 458 | |
| 210 | - if ( ! empty( $args['search'] ) ) { | |
| 211 | - $where .= $wpdb->prepare( " AND {$wpdb->posts}.post_title LIKE %s ", '%' . $wpdb->esc_like( $args['search'] ) . '%' ); | |
| 212 | - } | |
| 459 | + if ( ! empty( $args['search'] ) ) { | |
| 460 | + $post_search = $wpdb->prepare( "{$wpdb->posts}.post_title LIKE %s", '%' . $wpdb->esc_like( $args['search'] ) . '%' ); | |
| 461 | + $context_search = $this->get_sitewide_context_search_condition( $args['search'] ); | |
| 462 | + $where .= " AND ({$post_search} OR {$context_search}) "; | |
| 463 | + } | |
| 213 | 464 | |
| 214 | - if ( isset( $args['post-type-filter'] ) && $this->all_post_type['all'] !== $args['post-type-filter'] ) { | |
| 215 | - $where .= $wpdb->prepare( " AND {$wpdb->posts}.post_type = %s ", $args['post-type-filter'] ); | |
| 216 | - } else { | |
| 217 | - $post_types = get_post_types( | |
| 218 | - array( | |
| 219 | - 'public' => true, | |
| 220 | - ) | |
| 221 | - ); | |
| 222 | - $where .= " AND $wpdb->posts.post_type IN ('" . join( "', '", $post_types ) . "') "; | |
| 223 | - } | |
| 465 | + if ( isset( $args['post-type-filter'] ) && $this->all_post_type['all'] !== $args['post-type-filter'] ) { | |
| 466 | + $where .= $wpdb->prepare( " AND {$wpdb->posts}.post_type = %s ", $args['post-type-filter'] ); | |
| 467 | + } else { | |
| 468 | + $post_types = get_post_types( | |
| 469 | + array( | |
| 470 | + 'public' => true, | |
| 471 | + ) | |
| 472 | + ); | |
| 473 | + $where .= " AND ($wpdb->posts.post_type IN ('" . join( "', '", $post_types ) . "') OR {$sitewide_sql}) "; | |
| 224 | 474 | } |
| 225 | 475 | return $wpdb->get_var( "$sql $join WHERE 1=1 $where" ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.PreparedSQL.InterpolatedNotPrepared |
| 226 | 476 | } |
| 227 | 477 | |
| @@ -283,8 +533,17 @@ | ||
| 283 | 533 | * @param array $item Current item. |
| 284 | 534 | * @return string |
| 285 | 535 | */ |
| 286 | 536 | public function column_title( $item ) { |
| 537 | + $context_key = $this->get_item_context_key( $item ); | |
| 538 | + if ( '' !== $context_key ) { | |
| 539 | + return sprintf( | |
| 540 | + '<strong>%1$s</strong> <span style="color:grey">(%2$s, id:%3$s)</span>', | |
| 541 | + esc_html( $this->get_sitewide_context_label( $context_key ) ), | |
| 542 | + esc_html__( 'site-wide', 'top-10' ), | |
| 543 | + esc_html( $item['ID'] ) | |
| 544 | + ); | |
| 545 | + } | |
| 287 | 546 | |
| 288 | 547 | $delete_nonce = wp_create_nonce( 'tptn_delete_entry' ); |
| 289 | 548 | $page = isset( $_REQUEST['page'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['page'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 290 | 549 | $post = $this->network_wide ? get_blog_post( $item['blog_id'], $item['ID'] ) : get_post( $item['ID'] ); |
| @@ -327,8 +586,11 @@ | ||
| 327 | 586 | * @param array $item Current item. |
| 328 | 587 | * @return string Post date. |
| 329 | 588 | */ |
| 330 | 589 | public function column_date( $item ) { |
| 590 | + if ( '' !== $this->get_item_context_key( $item ) ) { | |
| 591 | + return ''; | |
| 592 | + } | |
| 331 | 593 | |
| 332 | 594 | $post = is_multisite() ? get_blog_post( $item['blog_id'], $item['ID'] ) : get_post( $item['ID'] ); |
| 333 | 595 | |
| 334 | 596 | if ( $post ) { |
| @@ -349,8 +611,11 @@ | ||
| 349 | 611 | * @param array $item Current item. |
| 350 | 612 | * @return string Post Type. |
| 351 | 613 | */ |
| 352 | 614 | public function column_post_type( $item ) { |
| 615 | + if ( '' !== $this->get_item_context_key( $item ) ) { | |
| 616 | + return esc_html__( 'Site-wide', 'top-10' ); | |
| 617 | + } | |
| 353 | 618 | |
| 354 | 619 | $post = is_multisite() ? get_blog_post( $item['blog_id'], $item['ID'] ) : get_post( $item['ID'] ); |
| 355 | 620 | |
| 356 | 621 | if ( $post ) { |
| @@ -370,8 +635,11 @@ | ||
| 370 | 635 | * @param array $item Current item. |
| 371 | 636 | * @return string Post Author. |
| 372 | 637 | */ |
| 373 | 638 | public function column_author( $item ) { |
| 639 | + if ( '' !== $this->get_item_context_key( $item ) ) { | |
| 640 | + return ''; | |
| 641 | + } | |
| 374 | 642 | |
| 375 | 643 | $post = is_multisite() ? get_blog_post( $item['blog_id'], $item['ID'] ) : get_post( $item['ID'] ); |
| 376 | 644 | if ( ! $post ) { |
| 377 | 645 | return ''; |
| @@ -377,9 +645,9 @@ | ||
| 377 | 645 | return ''; |
| 378 | 646 | } |
| 379 | 647 | |
| 380 | 648 | $author_info = get_userdata( (int) $post->post_author ); |
| 381 | - $author_name = ( false === $author_info ) ? '' : ucwords( trim( stripslashes( $author_info->display_name ) ) ); | |
| 649 | + $author_name = ( false === $author_info ) ? '' : ucwords( trim( stripslashes( $author_info->display_name ), " \t\n\r\0\x0B" ) ); | |
| 382 | 650 | |
| 383 | 651 | return sprintf( |
| 384 | 652 | '<a href="%s">%s</a>', |
| 385 | 653 | esc_url( |
| @@ -401,8 +669,12 @@ | ||
| 401 | 669 | * @param array $item Current item. |
| 402 | 670 | * @return string |
| 403 | 671 | */ |
| 404 | 672 | public function column_total_count( $item ) { |
| 673 | + if ( '' !== $this->get_item_context_key( $item ) ) { | |
| 674 | + return \WebberZone\Top_Ten\Util\Helpers::number_format_i18n( absint( $item['total_count'] ) ); | |
| 675 | + } | |
| 676 | + | |
| 405 | 677 | return sprintf( |
| 406 | 678 | '<div contentEditable="true" class="live_edit" id="total_count_%1$s" data-wp-post-id="%1$s" data-wp-count="%2$s"%3$s>%4$s</div>', |
| 407 | 679 | $item['ID'], |
| 408 | 680 | $item['total_count'], |