PluginProbe
Posts Table with Search & Sort / 1.4.7
Posts Table with Search & Sort v1.4.7
trunk 1.0 1.0.1 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.1 1.1.1 1.1.2 1.1.3 1.1.4 1.1.5 1.2 1.3 1.3.1 1.3.1-v2 1.3.2 1.3.3 1.3.4 1.3.5 1.3.6 1.3.7 1.3.8 All 39 releases
posts-data-table / src / Simple_Posts_Table.php

Simple_Posts_Table.php in Posts Table with Search & Sort 1.4.7, at src/Simple_Posts_Table.php

417 lines 12.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Barn2\Plugin\Posts_Table_Search_Sort;
4
5 /**
6 * This class is responsible for generating a HTML table from a list of supplied attributes.
7 *
8 * @package Barn2\posts-table-search-sort
9 * @author Barn2 Plugins <support@barn2.com>
10 * @license GPL-3.0
11 * @copyright Barn2 Media Ltd
12 */
13 class Simple_Posts_Table {
14
15 /**
16 * Stores the number of tables on this page. Used to generate the table ID.
17 *
18 * @var int
19 */
20 private static $table_count = 1;
21
22 /**
23 * The complete list of table attributes, and their defaults.
24 *
25 * @var array
26 */
27 public static $default_args = [
28 'columns' => 'title,content,date,author,categories',
29 'rows_per_page' => 20,
30 'sort_by' => 'date',
31 'sort_order' => '',
32 'category' => '',
33 'tag' => '',
34 'author' => '',
35 'post_status' => '',
36 'date_format' => 'Y/m/d',
37 'search_on_click' => true,
38 'wrap' => true,
39 'content_length' => 15,
40 'scroll_offset' => 15
41 ];
42
43 /**
44 * An array of all possible columns and their default heading, priority, and column width.
45 *
46 * @var array
47 */
48 private static $column_defaults = [];
49
50 /**
51 * An array of all allowed column keys.
52 *
53 * @var array
54 */
55 private static $allowed_columns = [];
56
57 public static function get_defaults() {
58 return wp_parse_args( Settings::get_table_args(), self::$default_args );
59 }
60
61 public static function get_column_defaults() {
62 if ( empty( self::$column_defaults ) ) {
63 /**
64 * Priority values are used to determine visiblity at small screen sizes (1 = highest priority, 6 = lowest priority).
65 * Column widths are automatically calculated by DataTables, but can be overridden by using filter 'posts_data_table_column_defaults'.
66 */
67 self::$column_defaults = [
68 'id' => [
69 'heading' => __( 'ID', 'posts-data-table' ),
70 'priority' => 3,
71 'width' => ''
72 ],
73 'image' => [
74 'heading' => __( 'Image', 'posts-data-table' ),
75 'priority' => 6,
76 'width' => ''
77 ],
78 'title' => [
79 'heading' => __( 'Title', 'posts-data-table' ),
80 'priority' => 1,
81 'width' => ''
82 ],
83 'categories' => [
84 'heading' => __( 'Categories', 'posts-data-table' ),
85 'priority' => 7,
86 'width' => ''
87 ],
88 'tags' => [
89 'heading' => __( 'Tags', 'posts-data-table' ),
90 'priority' => 8,
91 'width' => ''
92 ],
93 'date' => [
94 'heading' => __( 'Date', 'posts-data-table' ),
95 'priority' => 2,
96 'width' => ''
97 ],
98 'author' => [
99 'heading' => __( 'Author', 'posts-data-table' ),
100 'priority' => 4,
101 'width' => ''
102 ],
103 'content' => [
104 'heading' => __( 'Content', 'posts-data-table' ),
105 'priority' => 5,
106 'width' => ''
107 ]
108 ];
109 }
110
111 return self::$column_defaults;
112 }
113
114 public static function get_allowed_columns() {
115 if ( empty( self::$allowed_columns ) ) {
116 self::$allowed_columns = array_keys( self::get_column_defaults() );
117 }
118
119 return self::$allowed_columns;
120 }
121
122 /**
123 * Retrieves a data table containing a list of posts based on the specified arguments.
124 *
125 * @param array $args An array of options used to display the posts table
126 * @return string The posts table HTML output
127 */
128 public function get_table( $args ) {
129 // Load the scripts and styles.
130 if ( apply_filters( 'posts_data_table_load_scripts', true ) ) {
131 wp_enqueue_style( 'posts-data-table' );
132 wp_enqueue_script( 'posts-data-table' );
133 }
134
135 $args = wp_parse_args( $args, self::get_defaults() );
136 $args = $this->back_compat_args( $args );
137
138 if ( empty( $args['columns'] ) ) {
139 $args['columns'] = self::$default_args['columns'];
140 }
141
142 // Get the columns to be used in this table
143 $columns = array_filter( array_map( 'trim', explode( ',', strtolower( $args['columns'] ) ) ) );
144 $columns = array_intersect( $columns, self::get_allowed_columns() );
145
146 if ( empty( $columns ) ) {
147 $columns = explode( ',', self::$default_args['columns'] );
148 }
149
150 $args['rows_per_page'] = filter_var( $args['rows_per_page'], FILTER_VALIDATE_INT );
151
152 if ( $args['rows_per_page'] < 1 || ! $args['rows_per_page'] ) {
153 $args['rows_per_page'] = false;
154 }
155
156 if ( ! in_array( $args['sort_by'], self::get_allowed_columns() ) ) {
157 $args['sort_by'] = self::$default_args['sort_by'];
158 }
159
160 if ( ! in_array( $args['sort_order'], [ 'asc', 'desc' ] ) ) {
161 $args['sort_order'] = self::$default_args['sort_order'];
162 }
163
164 // Set default sort direction
165 if ( ! $args['sort_order'] ) {
166 if ( $args['sort_by'] === 'date' ) {
167 $args['sort_order'] = 'desc';
168 } else {
169 $args['sort_order'] = 'asc';
170 }
171 }
172
173 $args['search_on_click'] = filter_var( $args['search_on_click'], FILTER_VALIDATE_BOOLEAN );
174 $args['wrap'] = filter_var( $args['wrap'], FILTER_VALIDATE_BOOLEAN );
175 $args['content_length'] = filter_var( $args['content_length'], FILTER_VALIDATE_INT );
176 $args['scroll_offset'] = filter_var( $args['scroll_offset'], FILTER_VALIDATE_INT );
177
178 if ( empty( $args['date_format'] ) ) {
179 $args['date_format'] = self::$default_args['date_format'];
180 }
181
182 $output = '';
183 $table_head = '';
184 $table_body = '';
185 $body_row_fmt = '';
186
187 // Start building the args needed for our posts query
188 $post_args = [
189 'post_type' => 'post',
190 'posts_per_page' => apply_filters( 'posts_data_table_post_limit', 1000 ),
191 'post_status' => 'publish',
192 'suppress_filters' => false // Ensure WPML filters run on this query
193 ];
194
195 if ( ! empty( $args['category'] ) ) {
196 if ( is_numeric( $args['category'] ) ) {
197 $post_args['cat'] = $args['category'];
198 } else {
199 $category = get_category_by_slug( $args['category'] );
200
201 if ( $category ) {
202 $post_args['category_name'] = $category->slug;
203 }
204 }
205 }
206
207 if ( ! empty( $args['tag'] ) ) {
208 if ( is_numeric( $args['tag'] ) ) {
209 $post_args['tag_id'] = $args['tag'];
210 } else {
211 $post_args['tag'] = $args['tag'];
212 }
213 }
214
215 if ( ! empty( $args['author'] ) ) {
216 $author = $args['author'];
217
218 if ( is_numeric( $author ) || false !== strpos( $author, ',' ) ) {
219 $post_args['author'] = $author;
220 } else {
221 $post_args['author_name'] = $author;
222 }
223 }
224
225 if ( ! empty( $args['post_status'] ) ) {
226 $post_args['post_status'] = $args['post_status'];
227 }
228
229 // Get all published posts in the current language
230 $all_posts = get_posts( apply_filters( 'posts_data_table_query_args', $post_args, $args ) );
231
232 // Bail early if no posts found
233 if ( ! $all_posts || ! is_array( $all_posts ) ) {
234 return $output;
235 }
236
237 $hidden_columns = [];
238
239 // Set hidden columns and sort indexes
240 $table_sort_index = array_search( $args['sort_by'], $columns );
241 $date_sort_index = false;
242 $hidden_date = in_array( 'date', $columns ) || 'date' === $args['sort_by'];
243
244 if ( $hidden_date ) {
245 $hidden_columns[] = 'timestamp';
246 $date_sort_index = count( $columns );
247
248 // If we're sorting by date, make sure we use this hidden column for the initial table sort
249 if ( 'date' === $args['sort_by'] && false === $table_sort_index ) {
250 $table_sort_index = $date_sort_index;
251 }
252 }
253
254 if ( false === $table_sort_index ) {
255 // Sort column is not in list of displayed columns so we'll add it as a hidden column at end of table
256 $hidden_columns[] = $args['sort_by'];
257
258 // Set the table sort index to the index of the hidden sort column
259 $table_sort_index = $date_sort_index ? $date_sort_index + 1 : \count( $columns );
260 }
261
262 // Allow theme/plugins to override defaults
263 $column_defaults = apply_filters( 'posts_data_table_column_defaults_' . self::$table_count, apply_filters( 'posts_data_table_column_defaults', self::get_column_defaults() ) );
264
265 // Build table header
266 $heading_fmt = '<th data-name="%1$s" data-priority="%2$u" data-width="%3$s"%5$s>%4$s</th>';
267 $cell_fmt = '<td>{%s}</td>';
268
269 foreach ( $columns as $column ) {
270 // Double-check column name is valid
271 if ( ! in_array( $column, self::get_allowed_columns() ) ) {
272 continue;
273 }
274
275 // Do we need to use custom data for ordering this column?
276 $order_data = '';
277
278 if ( 'date' === $column && false !== $date_sort_index ) {
279 $order_data = sprintf( ' data-order-data="%u"', $date_sort_index );
280 }
281
282 // Add heading to table
283 $table_head .= sprintf( $heading_fmt, $column, $column_defaults[ $column ]['priority'], $column_defaults[ $column ]['width'], $column_defaults[ $column ]['heading'], $order_data );
284
285 // Add placeholder to table body format string so that content for this column is included in table output
286 $body_row_fmt .= sprintf( $cell_fmt, $column );
287 }
288
289 foreach ( $hidden_columns as $column ) {
290 $table_head .= sprintf( '<th data-name="%s" data-visible="false"></th>', $column );
291
292 // Make sure data for the hidden column is included in table content
293 $body_row_fmt .= sprintf( $cell_fmt, $column );
294 }
295
296 $table_head = sprintf( '<thead><tr>%s</tr></thead>', $table_head );
297
298 // Build table body
299 $body_row_fmt = '<tr>' . $body_row_fmt . '</tr>';
300
301 // Loop through posts and add a row for each
302 foreach ( (array) $all_posts as $_post ) {
303 setup_postdata( $_post );
304
305 // Format title
306 $title = sprintf( '<a href="%1$s">%2$s</a>', get_permalink( $_post ), get_the_title( $_post ) );
307
308 // Format author
309 $author = sprintf(
310 '<a href="%1$s" title="%2$s" rel="author">%3$s</a>',
311 esc_url( get_author_posts_url( $_post->post_author ) ),
312 /* translators: %s: the author's name */
313 esc_attr( sprintf( __( 'Posts by %s', 'posts-data-table' ), get_the_author() ) ),
314 get_the_author()
315 );
316
317 $post_data_trans = apply_filters(
318 'posts_data_table_row_data_format',
319 [
320 '{id}' => $_post->ID,
321 '{image}' => get_the_post_thumbnail( $_post, apply_filters( 'posts_data_table_image_size', 'thumbnail' ) ),
322 '{title}' => $title,
323 '{categories}' => get_the_category_list( ', ', '', $_post->ID ),
324 '{tags}' => get_the_tag_list( '', ', ', '', $_post->ID ),
325 '{date}' => get_the_date( $args['date_format'], $_post ),
326 '{author}' => $author,
327 '{content}' => $this->get_post_content( $args['content_length'] ),
328 '{timestamp}' => $_post->post_date
329 ]
330 );
331
332 $table_body .= strtr( $body_row_fmt, $post_data_trans );
333 } // foreach post
334
335 wp_reset_postdata();
336
337 $table_body = sprintf( '<tbody>%s</tbody>', $table_body );
338
339 $paging_attr = 'false';
340
341 if ( $args['rows_per_page'] && $args['rows_per_page'] < count( $all_posts ) ) {
342 $paging_attr = 'true';
343 }
344
345 $order_attr = '';
346
347 if ( false !== $table_sort_index ) {
348 // Order attribute should be escaped here rather than in sprintf below as we don't want to escape the double-quotes around "asc" or "desc"
349 $order_attr = sprintf( '[[%u, "%s"]]', esc_attr( $table_sort_index ), esc_attr( $args['sort_order'] ) );
350 }
351
352 $offset_attr = ( $args['scroll_offset'] === false ) ? 'false' : $args['scroll_offset'];
353 $table_class = 'posts-data-table';
354
355 if ( ! $args['wrap'] ) {
356 $table_class .= ' nowrap';
357 }
358
359 $table_attributes = sprintf(
360 'id="posts-table-%1$u" class="%2$s" data-page-length="%3$u" data-paging="%4$s" data-order=\'%5$s\' data-click-filter="%6$s" data-scroll-offset="%7$s" cellspacing="0" width="100%%"',
361 self::$table_count,
362 esc_attr( $table_class ),
363 esc_attr( $args['rows_per_page'] ),
364 esc_attr( $paging_attr ),
365 $order_attr, // escaped above
366 esc_attr( $args['search_on_click'] ? 'true' : 'false' ),
367 esc_attr( $offset_attr )
368 );
369
370 $output = sprintf( '<table %1$s>%2$s%3$s</table>', $table_attributes, $table_head, $table_body );
371
372 // Increment the table count
373 self::$table_count++;
374
375 return apply_filters( 'posts_data_table_html_output', $output, $args );
376 }
377
378 private function back_compat_args( $args ) {
379 if ( ! empty( $args['columns'] ) ) {
380 $columns = array_map( 'trim', explode( ',', $args['columns'] ) );
381
382 if ( false !== ( $index = array_search( 'category', $columns, true ) ) ) {
383 $columns[ $index ] = 'categories';
384 }
385
386 $args['columns'] = implode( ',', $columns );
387 }
388
389 if ( ! empty( $args['sort_by'] ) && 'category' === $args['sort_by'] ) {
390 $args['sort_by'] = 'categories';
391 }
392
393 return $args;
394 }
395
396 /**
397 * Retrieve the post content, truncated to the number of words specified by $num_words.
398 *
399 * Must be called with the Loop or a secondary loop after a call to setup_postdata().
400 *
401 * @param int $num_words The number of words to trim the content to
402 * @return string The (truncated) post content
403 */
404 private function get_post_content( $num_words = 15 ) {
405 $text = get_the_content( '' );
406 $text = strip_shortcodes( $text );
407 $text = apply_filters( 'the_content', $text );
408
409 if ( $num_words > 0 ) {
410 $text = wp_trim_words( $text, $num_words, ' &hellip;' );
411 }
412
413 return $text;
414 }
415
416 }
417