PluginProbe
Posts Table with Search & Sort / 1.3.3
Posts Table with Search & Sort v1.3.3
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.3.3, at src/Simple_Posts_Table.php

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