PluginProbe
Woody Code Snippets – Insert PHP, CSS, JS, and Header/Footer Scripts / trunk
Woody Code Snippets – Insert PHP, CSS, JS, and Header/Footer Scripts vtrunk
2.7.7 2.7.6 2.7.5 2.7.4 trunk 1.3 2.0.4 2.0.6 2.1.91 2.2.4 2.2.7 2.2.9 2.3.1 2.3.10 2.4.10 2.4.2 2.4.4 2.4.5 2.4.6 2.4.7 2.4.8 2.4.9 2.6.0 2.6.1 2.7.0 All 28 releases
← All changes | admin/includes/class.snippets.table.php +634 -572 2.3.1trunk View file →
@@ -1,572 +1,634 @@
1 -<?php
2 -/**
3 - * This class is implemented page: snippet table
4 - *
5 - * @author Webcraftic <wordpress.webraftic@gmail.com>
6 - * @since 1.0.0
7 - * @package core
8 - * @copyright (c) 2019, OnePress Ltd
9 - * s
10 - */
11 -
12 -// Exit if accessed directly
13 -if ( ! defined( 'ABSPATH' ) ) {
14 - exit;
15 -}
16 -
17 -if ( ! class_exists( 'WP_List_Table' ) ) {
18 - require_once( ABSPATH . 'wp-admin/includes/class-wp-list-table.php' );
19 -}
20 -
21 -/************************** CREATE A PACKAGE CLASS *****************************
22 - *******************************************************************************
23 - * Create a new list table package that extends the core WP_List_Table class.
24 - * WP_List_Table contains most of the framework for generating the table, but we
25 - * need to define and override some methods so that our data can be displayed
26 - * exactly the way we need it to be.
27 - *
28 - * To display this example on a page, you will first need to instantiate the class,
29 - * then call $yourInstance->prepare_items() to handle any data manipulation, then
30 - * finally call $yourInstance->display() to render the table to the page.
31 - *
32 - * Our theme for this list table is going to be movies.
33 - */
34 -class WINP_Snippet_Library_Table extends WP_List_Table {
35 -
36 - /** ************************************************************************
37 - * Normally we would be querying data from a database and manipulating that
38 - * for use in your list table. For this example, we're going to simplify it
39 - * slightly and create a pre-built array. Think of this as the data that might
40 - * be returned by $wpdb->query()
41 - *
42 - * In a real-world scenario, you would make your own custom query inside
43 - * this class' prepare_items() method.
44 - *
45 - * @var array
46 - **************************************************************************/
47 - var $example_data = [];
48 -
49 - /**
50 - * Is modal window
51 - *
52 - * @var bool
53 - */
54 - private $modal;
55 -
56 - /**
57 - * Если true, то выводить общие сниппеты без привязки к пользователю
58 - *
59 - * @var bool
60 - */
61 - private $common;
62 -
63 - /**
64 - * @var array
65 - *
66 - * Array contains slug columns that you want hidden
67 - *
68 - */
69 - private $hidden_columns = [
70 - 'id',
71 - ];
72 -
73 - /**
74 - * @var integer
75 - */
76 - private $per_page = 10;
77 -
78 - /** ************************************************************************
79 - * REQUIRED. Set up a constructor that references the parent constructor. We
80 - * use the parent reference to set some default configs.
81 - ***************************************************************************/
82 - /**
83 - * WINP_Snippet_Library_Table constructor.
84 - *
85 - * @param bool $modal
86 - */
87 - function __construct( $modal = false ) {
88 - global $status, $page;
89 - add_thickbox();
90 - $this->modal = $modal;
91 - $this->common = true;
92 -
93 - // Set parent defaults
94 - parent::__construct( [
95 - 'singular' => 'snippet', // singular name of the listed records
96 - 'plural' => 'snippets', // plural name of the listed records
97 - 'ajax' => true, // does this table support ajax?
98 - ] );
99 - }
100 -
101 - /** ************************************************************************
102 - * Recommended. This method is called when the parent class can't find a method
103 - * specifically build for a given column. Generally, it's recommended to include
104 - * one method for each column you want to render, keeping your package class
105 - * neat and organized. For example, if the class needs to process a column
106 - * named 'title', it would first see if a method named $this->column_title()
107 - * exists - if it does, that method will be used. If it doesn't, this one will
108 - * be used. Generally, you should try to use custom column methods as much as
109 - * possible.
110 - *
111 - * Since we have defined a column_title() method later on, this method doesn't
112 - * need to concern itself with any column with a name of 'title'. Instead, it
113 - * needs to handle everything else.
114 - *
115 - * For more detailed insight into how columns are handled, take a look at
116 - * WP_List_Table::single_row_columns()
117 - *
118 - * @param array $item A singular item (one full row's worth of data)
119 - * @param string $column_name The name/slug of the column to be processed
120 - *
121 - * @return string Text or HTML to be placed inside the column <td>
122 - **************************************************************************/
123 - public function column_default( $item, $column_name ) {
124 - switch ( $column_name ) {
125 - case 'type':
126 - $class = 'wbcr-inp-type-' . esc_attr( $item[ $column_name ] );
127 - $type = 'universal' == $item[ $column_name ] ? 'uni' : esc_attr( $item[ $column_name ] );
128 -
129 - return '<div class="wbcr-inp-snippet-type-label ' . $class . '">' . esc_html( $type ) . '</div>';
130 - case 'desc':
131 - $desc = strlen( $item[ $column_name ] ) > 500 ? substr( $item[ $column_name ], 0, 500 ) : $item[ $column_name ];
132 -
133 - return '<div class="wbcr-inp-snippet-description" title="' . esc_attr( $desc ) . '">' . esc_html( $desc ) . '</div>';
134 - case 'preview':
135 - case 'datetime':
136 - case 'insert':
137 - case 'delete':
138 - return $item[ $column_name ];
139 - default:
140 - return print_r( $item, true ); // Show the whole array for troubleshooting purposes
141 - }
142 - }
143 -
144 - /** ************************************************************************
145 - * Recommended. This is a custom column method and is responsible for what
146 - * is rendered in any column with a name/slug of 'title'. Every time the class
147 - * needs to render a column, it first looks for a method named
148 - * column_{$column_title} - if it exists, that method is run. If it doesn't
149 - * exist, column_default() is called instead.
150 - *
151 - * This example also illustrates how to implement rollover actions. Actions
152 - * should be an associative array formatted as 'slug'=>'link html' - and you
153 - * will need to generate the URLs yourself. You could even ensure the links
154 - *
155 - *
156 - * @param array $item A singular item (one full row's worth of data)
157 - *
158 - * @return string Text to be placed inside the column <td> (movie title only)
159 - **************************************************************************@see WP_List_Table::::single_row_columns()
160 - */
161 - public function column_title( $item ) {
162 - //Build row actions
163 - $actions = [/*'edit' => sprintf( '<a href="?page=%s&action=%s&movie=%s">Edit</a>', $_REQUEST['page'], 'edit', $item['ID'] ),
164 - 'delete' => sprintf( '<a href="?page=%s&action=%s&movie=%s">Delete</a>', $_REQUEST['page'], 'delete', $item['ID'] ),*/
165 - ];
166 -
167 - $url = admin_url() . 'post-new.php?post_type=' . WINP_SNIPPETS_POST_TYPE . '&winp_item=' . $item['type'] . '&snippet_id=' . $item['ID'] . ( $this->common ? '&common=1' : '' );
168 -
169 - //Return the title contents
170 - return sprintf( '<a href="%1$s"><b>%2$s</b></a>%3$s', /*$1%s*/ esc_url( $url ), /*$2%s*/ esc_html( $item['title'] ), /*$3%s*/ $this->row_actions( $actions ) );
171 - }
172 -
173 - /** ************************************************************************
174 - * REQUIRED if displaying checkboxes or using bulk actions! The 'cb' column
175 - * is given special treatment when columns are processed. It ALWAYS needs to
176 - * have it's own method.
177 - *
178 - * @param array $item A singular item (one full row's worth of data)
179 - *
180 - * @return string Text to be placed inside the column <td> (movie title only)
181 - **************************************************************************@see WP_List_Table::::single_row_columns()
182 - */
183 - public function column_cb( $item ) {
184 - return sprintf( '<input type="checkbox" name="%1$s[]" value="%2$s" />', /*$1%s*/ esc_attr( $this->_args['singular'] ), //Let's simply repurpose the table's singular label ("movie")
185 - /*$2%s*/ esc_attr( $item['ID'] ) //The value of the checkbox should be the record's id
186 - );
187 - }
188 -
189 - /** ************************************************************************
190 - * REQUIRED! This method dictates the table's columns and titles. This should
191 - * return an array where the key is the column slug (and class) and the value
192 - * is the column's title text. If you need a checkbox for bulk actions, refer
193 - * to the $columns array below.
194 - *
195 - * The 'cb' column is treated differently than the rest. If including a checkbox
196 - * column in your table you must create a column_cb() method. If you don't need
197 - * bulk actions or checkboxes, simply leave the 'cb' entry out of your array.
198 - *
199 - * @return array An associative array containing column information: 'slugs'=>'Visible Titles'
200 - **************************************************************************@see WP_List_Table::::single_row_columns()
201 - */
202 - public function get_columns() {
203 - $columns = [// 'cb' => '<input type="checkbox" />', //Render a checkbox instead of text
204 - ];
205 - $columns['type'] = __( 'Type', 'insert-php' );
206 - $columns['title'] = __( 'Title', 'insert-php' );
207 -
208 - if ( ! $this->modal && $this->common ) {
209 - $columns['preview'] = __( 'Preview', 'insert-php' );
210 - }
211 -
212 - $columns['desc'] = __( 'Description', 'insert-php' );
213 - $columns['datetime'] = __( 'Date', 'insert-php' );
214 - $columns['insert'] = __( 'Insert', 'insert-php' );
215 -
216 - if ( ! $this->modal && ! $this->common ) {
217 - $columns['delete'] = __( 'Delete', 'insert-php' );
218 - }
219 -
220 - return $columns;
221 - }
222 -
223 - /** ************************************************************************
224 - * Optional. If you want one or more columns to be sortable (ASC/DESC toggle),
225 - * you will need to register it here. This should return an array where the
226 - * key is the column that needs to be sortable, and the value is db column to
227 - * sort by. Often, the key and value will be the same, but this is not always
228 - * the case (as the value is a column name from the database, not the list table).
229 - *
230 - * This method merely defines which columns should be sortable and makes them
231 - * clickable - it does not handle the actual sorting. You still need to detect
232 - * the ORDERBY and ORDER querystring variables within prepare_items() and sort
233 - * your data accordingly (usually by modifying your query).
234 - *
235 - * @return array An associative array containing all the columns that should be sortable: 'slugs'=>array('data_values',bool)
236 - **************************************************************************/
237 - public function get_sortable_columns() {
238 - $sortable_columns = [
239 - 'title' => [ 'title', false ], //true means it's already sorted
240 - 'type' => [ 'type', false ],
241 - 'datetime' => [ 'datetime', false ],
242 - ];
243 -
244 - return $sortable_columns;
245 - }
246 -
247 - /** ************************************************************************
248 - * Optional. If you need to include bulk actions in your list table, this is
249 - * the place to define them. Bulk actions are an associative array in the format
250 - * 'slug'=>'Visible Title'
251 - *
252 - * If this method returns an empty value, no bulk action will be rendered. If
253 - * you specify any bulk actions, the bulk actions box will be rendered with
254 - * the table automatically on display().
255 - *
256 - * Also note that list tables are not automatically wrapped in <form> elements,
257 - * so you will need to create those manually in order for bulk actions to function.
258 - *
259 - * @return array An associative array containing all the bulk actions: 'slugs'=>'Visible Titles'
260 - **************************************************************************/
261 - public function get_bulk_actions() {
262 - $actions = [//'sync' => __( 'Synchronization', 'insert-php' ),
263 - ];
264 -
265 - return $actions;
266 - }
267 -
268 - /** ************************************************************************
269 - * Optional. You can handle your bulk actions anywhere or anyhow you prefer.
270 - * For this example package, we will handle it in the class to keep things
271 - * clean and organized.
272 - *
273 - * @see $this->prepare_items()
274 - **************************************************************************/
275 - public function process_bulk_action() {
276 -
277 - //Detect when a bulk action is being triggered...
278 - /*if ( 'sync' === $this->current_action() ) {
279 - wp_die( 'Synchronization' );
280 - }*/
281 - }
282 -
283 - /**
284 - * Возвращает id youtube видео
285 - *
286 - * @param $video_link - ссылка на видео
287 - *
288 - * @return bool|string - если id сниппета не найден, то вернёт false
289 - */
290 - private function get_video_id( $video_link ) {
291 - // youtube regex
292 - preg_match( "#([\/|\?|&]vi?[\/|=]|youtu\.be\/|embed\/)([a-zA-Z0-9_-]+)#", $video_link, $matches );
293 -
294 - return ! empty( $matches ) ? end( $matches ) : false;
295 - }
296 -
297 - /**
298 - * Get snippets data
299 - *
300 - * @return array
301 - */
302 - public function get_data() {
303 - $data = [];
304 - $saved_data = [];
305 -
306 - $orderby = WINP_Plugin::app()->request->request( 'orderby', 'datetime', true );
307 - $order = WINP_Plugin::app()->request->request( 'order', 'desc', true );
308 - $paged = WINP_Plugin::app()->request->request( 'paged', 1, 'intval' );
309 -
310 - $order_tags = [
311 - 'title' => 'title',
312 - 'type' => 'type_id',
313 - 'datetime' => 'updated_at',
314 - ];
315 -
316 - $args = [
317 - 'per-page=' . $this->per_page,
318 - 'page=' . $paged,
319 - 'sort=' . ( 'asc' == $order ? '' : '-' ) . ( $order_tags[ $orderby ] ),
320 - ];
321 -
322 - $snippets = WINP_Plugin::app()->get_api_object()->get_all_snippets( $this->common, $args );
323 -
324 - if ( ! empty( $snippets ) ) {
325 - foreach ( (array) $snippets as $snippet ) {
326 - $_data = [
327 - 'ID' => (int) $snippet->id,
328 - 'title' => esc_html( $snippet->title ),
329 - 'desc' => esc_html( $snippet->description ),
330 - 'type' => $snippet->type->title,
331 - 'datetime' => date( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ), $snippet->updated_at ),
332 - 'insert' => '<a class="wbcr-inp-enable-snippet-button button" data-snippet="' . esc_attr( $snippet->id ) . '" data-common="' . ( $this->common ? 1 : 0 ) . '" href="javascript: void(0)"><span class="dashicons dashicons-plus"></span></a>',
333 - 'delete' => '<a class="wbcr-inp-delete-snippet-button button" data-snippet="' . esc_attr( $snippet->id ) . '" href="javascript: void(0)"><span class="dashicons dashicons-no"></span></a>',
334 - ];
335 -
336 - if ( $this->common ) {
337 - $_data['preview'] = '';
338 - }
339 -
340 - $video_id = $this->get_video_id( $snippet->video_link );
341 -
342 - if ( $video_id ) {
343 - $_data['preview'] = '<a class="thickbox" href="https://www.youtube.com/embed/' . esc_attr( $video_id ) . '?autoplay=1&rel=0&TB_iframe=true">' . '<img src="' . WINP_PLUGIN_URL . '/admin/assets/img/video.png" class="winp-library-image-preview" data-videoid="' . esc_attr( $video_id ) . '" alt="' . __( 'View the video', 'insert-php' ) . '">' . '</a>';
344 - }
345 -
346 - $data[] = $_data;
347 -
348 - $saved_data[ $snippet->id ] = [
349 - 'title' => esc_html( $snippet->title ),
350 - 'desc' => esc_html( $snippet->description ),
351 - 'type' => $snippet->type->slug,
352 - 'content' => $snippet->content,
353 - 'type_id' => (int) $snippet->type_id,
354 - 'scope' => $snippet->execute_everywhere ? 'evrywhere' : 'shortcode',
355 - ];
356 - }
357 -
358 - update_user_meta( get_current_user_id(), WINP_Plugin::app()->getPrefix() . 'current_snippets', $saved_data );
359 - }
360 -
361 - return $data;
362 - }
363 -
364 - /**
365 - * Get total items for last query
366 - *
367 - * @return int
368 - */
369 - public function get_total_items() {
370 - return WINP_Plugin::app()->get_api_object()->get_total_items();
371 - }
372 -
373 - /** ************************************************************************
374 - * REQUIRED! This is where you prepare your data for display. This method will
375 - * usually be used to query the database, sort and filter the data, and generally
376 - * get it ready to be displayed. At a minimum, we should set $this->items and
377 - * $this->set_pagination_args(), although the following properties and methods
378 - * are frequently interacted with here...
379 - *
380 - * @param bool $common - если true, то выводить общие сниппеты без привязки к пользователю
381 - *
382 - * @global WPDB $wpdb
383 - * @uses $this->_column_headers
384 - * @uses $this->items
385 - * @uses $this->get_columns()
386 - * @uses $this->get_sortable_columns()
387 - * @uses $this->get_pagenum()
388 - * @uses $this->set_pagination_args()
389 - * *************************************************************************/
390 - public function prepare_items( $common = false ) {
391 - /**
392 - * First, lets decide how many records per page to show
393 - */
394 - $this->per_page = 10;
395 -
396 - /**
397 - * @param bool $common - если true, то выводить общие сниппеты без привязки к пользователю
398 - */
399 - $this->common = $common;
400 -
401 - /**
402 - * REQUIRED. Now we need to define our column headers. This includes a complete
403 - * array of columns to be displayed (slugs & titles), a list of columns
404 - * to keep hidden, and a list of columns that are sortable. Each of these
405 - * can be defined in another method (as we've done here) before being
406 - * used to build the value for our _column_headers property.
407 - */
408 - $columns = $this->get_columns();
409 - $hidden = $this->hidden_columns;
410 - $sortable = $this->get_sortable_columns();
411 -
412 - /**
413 - * REQUIRED. Finally, we build an array to be used by the class for column
414 - * headers. The $this->_column_headers property takes an array which contains
415 - * 3 other arrays. One for all columns, one for hidden columns, and one
416 - * for sortable columns.
417 - */
418 - $this->_column_headers = [ $columns, $hidden, $sortable ];
419 -
420 - /**
421 - * Optional. You can handle your bulk actions however you see fit. In this
422 - * case, we'll handle them within our package just to keep things clean.
423 - */
424 - $this->process_bulk_action();
425 -
426 - /**
427 - * Instead of querying a database, we're going to fetch the example data
428 - * property we created for use in this plugin. This makes this example
429 - * package slightly different than one you might build on your own. In
430 - * this example, we'll be using array manipulation to sort and paginate
431 - * our data. In a real-world implementation, you will probably want to
432 - * use sort and pagination data to build a custom query instead, as you'll
433 - * be able to use your precisely-queried data immediately.
434 - */
435 - $data = $this->get_data();
436 -
437 - /**
438 - * This checks for sorting input and sorts the data in our array accordingly.
439 - *
440 - * In a real-world situation involving a database, you would probably want
441 - * to handle sorting by passing the 'orderby' and 'order' values directly
442 - * to a custom query. The returned data will be pre-sorted, and this array
443 - * sorting technique would be unnecessary.
444 - *
445 - * @param $a
446 - * @param $b
447 - *
448 - * @return int
449 - */ /*function usort_reorder( $a, $b ) {
450 - $orderby = ( ! empty( $_REQUEST['orderby'] ) ) ? $_REQUEST['orderby'] : 'title'; // If no sort, default to title
451 - $order = ( ! empty( $_REQUEST['order'] ) ) ? $_REQUEST['order'] : 'asc'; // If no order, default to asc
452 - $result = strcmp( $a[ $orderby ], $b[ $orderby ] ); // Determine sort order
453 -
454 - return ( 'asc' === $order ) ? $result : - $result; // Send final sort direction to usort
455 - }
456 -
457 - usort( $data, 'usort_reorder' );*/
458 -
459 - /***********************************************************************
460 - * ---------------------------------------------------------------------
461 - * vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
462 - *
463 - * In a real-world situation, this is where you would place your query.
464 - *
465 - * For information on making queries in WordPress, see this Codex entry:
466 - * http://codex.wordpress.org/Class_Reference/wpdb
467 - *
468 - * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
469 - * ---------------------------------------------------------------------
470 - **********************************************************************/
471 -
472 - /**
473 - * REQUIRED for pagination. Let's figure out what page the user is currently
474 - * looking at. We'll need this later, so you should always include it in
475 - * your own package classes.
476 - */ // $current_page = $this->get_current_page();
477 -
478 - /**
479 - * REQUIRED for pagination. Let's check how many items are in our data array.
480 - * In real-world use, this would be the total number of items in your database,
481 - * without filtering. We'll need this later, so you should always include it
482 - * in your own package classes.
483 - */
484 - $total_items = $this->get_total_items();
485 -
486 - /**
487 - * The WP_List_Table class does not handle pagination for us, so we need
488 - * to ensure that the data is trimmed to only the current page. We can use
489 - * array_slice() to
490 - */ // $data = array_slice( $data, ( ( $current_page - 1 ) * $this->per_page ), $this->per_page );
491 -
492 - /**
493 - * REQUIRED. Now we can add our *sorted* data to the items property, where
494 - * it can be used by the rest of the class.
495 - */
496 - $this->items = $data;
497 -
498 - /**
499 - * REQUIRED. We also have to register our pagination options & calculations.
500 - */
501 - $this->set_pagination_args( [
502 - 'total_items' => $total_items,
503 - 'per_page' => $this->per_page,
504 - 'total_pages' => ceil( $total_items / $this->per_page ),
505 - 'orderby' => WINP_Plugin::app()->request->request( 'orderby', 'title', true ),
506 - 'order' => WINP_Plugin::app()->request->request( 'order', 'asc', true ),
507 - ] );
508 - }
509 -
510 - /**
511 - * @Override of display method
512 - */
513 - public function display() {
514 - /**
515 - * Adds a nonce field
516 - */
517 - wp_nonce_field( 'winp-ajax-custom-list-nonce', 'winp_ajax_custom_list_nonce' );
518 -
519 - if ( ! empty( $this->items ) && ! $this->common ) {
520 - foreach ( $this->items as $item ) {
521 - wp_nonce_field( 'winp-ajax-snippet-delete-' . $item['ID'], 'winp_ajax_snippet_delete_' . $item['ID'] );
522 - }
523 - }
524 -
525 - /**
526 - * Adds field order and orderby
527 - */
528 - echo '<input type="hidden" id="order" name="order" value="' . $this->_pagination_args['order'] . '" />';
529 - echo '<input type="hidden" id="orderby" name="orderby" value="' . $this->_pagination_args['orderby'] . '" />';
530 - parent::display();
531 - }
532 -
533 - /**
534 - * @Override ajax_response method
535 - */
536 - public function ajax_response() {
537 -
538 - $this->prepare_items();
539 - extract( $this->_args );
540 - extract( $this->_pagination_args, EXTR_SKIP );
541 - ob_start();
542 - $no_placeholder = WINP_Plugin::app()->request->request( 'no_placeholder', '' );
543 - if ( ! empty( $no_placeholder ) ) {
544 - $this->display_rows();
545 - } else {
546 - $this->display_rows_or_placeholder();
547 - }
548 - $rows = ob_get_clean();
549 - ob_start();
550 - $this->print_column_headers();
551 - $headers = ob_get_clean();
552 - ob_start();
553 - $this->pagination( 'top' );
554 - $pagination_top = ob_get_clean();
555 - ob_start();
556 - $this->pagination( 'bottom' );
557 - $pagination_bottom = ob_get_clean();
558 - $response = [ 'rows' => $rows ];
559 - $response['pagination']['top'] = $pagination_top;
560 - $response['pagination']['bottom'] = $pagination_bottom;
561 - $response['column_headers'] = $headers;
562 - if ( isset( $total_items ) ) {
563 - $response['total_items_i18n'] = sprintf( _n( '1 item', '%s items', $total_items ), number_format_i18n( $total_items ) );
564 - }
565 - if ( isset( $total_pages ) ) {
566 - $response['total_pages'] = $total_pages;
567 - $response['total_pages_i18n'] = number_format_i18n( $total_pages );
568 - }
569 - die( json_encode( $response ) );
570 - }
571 -
572 -}
1 +<?php
2 +/**
3 + * This class is implemented page: snippet table
4 + *
5 + * @since 1.0.0
6 + * @package core
7 + */
8 +
9 +// Exit if accessed directly
10 +if ( ! defined( 'ABSPATH' ) ) {
11 + exit;
12 +}
13 +
14 +if ( ! class_exists( 'WP_List_Table' ) ) {
15 + require_once ABSPATH . 'wp-admin/includes/class-wp-list-table.php';
16 +}
17 +
18 +/************************** CREATE A PACKAGE CLASS *****************************
19 + * ******************************************************************************
20 + * Create a new list table package that extends the core WP_List_Table class.
21 + * WP_List_Table contains most of the framework for generating the table, but we
22 + * need to define and override some methods so that our data can be displayed
23 + * exactly the way we need it to be.
24 + *
25 + * To display this example on a page, you will first need to instantiate the class,
26 + * then call $yourInstance->prepare_items() to handle any data manipulation, then
27 + * finally call $yourInstance->display() to render the table to the page.
28 + *
29 + * Our theme for this list table is going to be movies.
30 + */
31 +class WINP_Snippet_Library_Table extends WP_List_Table {
32 +
33 + /** ************************************************************************
34 + * Normally we would be querying data from a database and manipulating that
35 + * for use in your list table. For this example, we're going to simplify it
36 + * slightly and create a pre-built array. Think of this as the data that might
37 + * be returned by $wpdb->query()
38 + *
39 + * In a real-world scenario, you would make your own custom query inside
40 + * this class' prepare_items() method.
41 + *
42 + * @var array
43 + **************************************************************************/
44 + var $example_data = [];
45 +
46 + /**
47 + * Is modal window
48 + *
49 + * @var bool
50 + */
51 + private $modal;
52 +
53 + /**
54 + * Если true, то выводить общие сниппеты без привязки к пользователю
55 + *
56 + * @var bool
57 + */
58 + private $common;
59 +
60 + /**
61 + * @var array
62 + *
63 + * Array contains slug columns that you want hidden
64 + */
65 + private $hidden_columns = [
66 + 'id',
67 + ];
68 +
69 + /**
70 + * @var integer
71 + */
72 + private $per_page = 10;
73 +
74 + /** ************************************************************************
75 + * REQUIRED. Set up a constructor that references the parent constructor. We
76 + * use the parent reference to set some default configs.
77 + ***************************************************************************/
78 + /**
79 + * WINP_Snippet_Library_Table constructor.
80 + *
81 + * @param bool $modal
82 + */
83 + function __construct( $modal = false ) {
84 + global $status, $page;
85 + add_thickbox();
86 + $this->modal = $modal;
87 + $this->common = true;
88 +
89 + // Set parent defaults
90 + parent::__construct(
91 + [
92 + 'singular' => 'snippet', // singular name of the listed records
93 + 'plural' => 'snippets', // plural name of the listed records
94 + 'ajax' => true, // does this table support ajax?
95 + ]
96 + );
97 + }
98 +
99 + /** ************************************************************************
100 + * Recommended. This method is called when the parent class can't find a method
101 + * specifically build for a given column. Generally, it's recommended to include
102 + * one method for each column you want to render, keeping your package class
103 + * neat and organized. For example, if the class needs to process a column
104 + * named 'title', it would first see if a method named $this->column_title()
105 + * exists - if it does, that method will be used. If it doesn't, this one will
106 + * be used. Generally, you should try to use custom column methods as much as
107 + * possible.
108 + *
109 + * Since we have defined a column_title() method later on, this method doesn't
110 + * need to concern itself with any column with a name of 'title'. Instead, it
111 + * needs to handle everything else.
112 + *
113 + * For more detailed insight into how columns are handled, take a look at
114 + * WP_List_Table::single_row_columns()
115 + *
116 + * @param array $item A singular item (one full row's worth of data)
117 + * @param string $column_name The name/slug of the column to be processed
118 + *
119 + * @return string Text or HTML to be placed inside the column <td>
120 + **************************************************************************/
121 + public function column_default( $item, $column_name ) {
122 + switch ( $column_name ) {
123 + case 'type':
124 + $class = 'wbcr-inp-type-' . esc_attr( $item[ $column_name ] );
125 + $type = 'universal' == $item[ $column_name ] ? 'uni' : esc_attr( $item[ $column_name ] );
126 +
127 + return '<div class="wbcr-inp-snippet-type-label ' . $class . '">' . esc_html( $type ) . '</div>';
128 + case 'desc':
129 + $desc = strlen( $item[ $column_name ] ) > 500 ? substr( $item[ $column_name ], 0, 500 ) : $item[ $column_name ];
130 +
131 + return '<div class="wbcr-inp-snippet-description" title="' . esc_attr( $desc ) . '">' . esc_html( $desc ) . '</div>';
132 + case 'preview':
133 + case 'datetime':
134 + case 'insert':
135 + case 'delete':
136 + return $item[ $column_name ];
137 + default:
138 + return print_r( $item, true ); // Show the whole array for troubleshooting purposes
139 + }
140 + }
141 +
142 + /** ************************************************************************
143 + * Recommended. This is a custom column method and is responsible for what
144 + * is rendered in any column with a name/slug of 'title'. Every time the class
145 + * needs to render a column, it first looks for a method named
146 + * column_{$column_title} - if it exists, that method is run. If it doesn't
147 + * exist, column_default() is called instead.
148 + *
149 + * This example also illustrates how to implement rollover actions. Actions
150 + * should be an associative array formatted as 'slug'=>'link html' - and you
151 + * will need to generate the URLs yourself. You could even ensure the links
152 + *
153 + * @param array $item A singular item (one full row's worth of data)
154 + *
155 + * @return string Text to be placed inside the column <td> (movie title only)
156 + * *************************************************************************@see WP_List_Table::::single_row_columns()
157 + */
158 + public function column_title( $item ) {
159 + // Build row actions
160 + $actions = [/*
161 + 'edit' => sprintf( '<a href="?page=%s&action=%s&movie=%s">Edit</a>', $_REQUEST['page'], 'edit', $item['ID'] ),
162 + 'delete' => sprintf( '<a href="?page=%s&action=%s&movie=%s">Delete</a>', $_REQUEST['page'], 'delete', $item['ID'] ),*/
163 + ];
164 +
165 + $url = admin_url() . 'post-new.php?post_type=' . WINP_SNIPPETS_POST_TYPE . '&winp_item=' . $item['type'] . '&snippet_id=' . $item['ID'] . ( $this->common ? '&common=1' : '' );
166 +
167 + // Add premium badge if snippet is locked.
168 + $premium_badge = '';
169 + $is_locked = ! empty( $item['is_premium_locked'] );
170 +
171 + if ( $is_locked ) {
172 + $premium_badge = ' <span style="display:inline-block;background:#6366f1;color:#fff;font-size:11px;padding:2px 8px;border-radius:3px;font-weight:600;margin-left:6px;">PRO</span>';
173 + }
174 +
175 + // Return the title contents - no link for locked premium snippets.
176 + if ( $is_locked ) {
177 + return sprintf( '<b>%1$s</b>%2$s%3$s', /*$1%s*/ esc_html( $item['title'] ), /*$2%s*/ $premium_badge, /*$3%s*/ $this->row_actions( $actions ) );
178 + }
179 +
180 + return sprintf( '<a href="%1$s"><b>%2$s</b></a>%3$s%4$s', /*$1%s*/ esc_url( $url ), /*$2%s*/ esc_html( $item['title'] ), /*$3%s*/ $premium_badge, /*$4%s*/ $this->row_actions( $actions ) );
181 + }
182 +
183 + /** ************************************************************************
184 + * REQUIRED if displaying checkboxes or using bulk actions! The 'cb' column
185 + * is given special treatment when columns are processed. It ALWAYS needs to
186 + * have it's own method.
187 + *
188 + * @param array $item A singular item (one full row's worth of data)
189 + *
190 + * @return string Text to be placed inside the column <td> (movie title only)
191 + * *************************************************************************@see WP_List_Table::::single_row_columns()
192 + */
193 + public function column_cb( $item ) {
194 + return sprintf(
195 + '<input type="checkbox" name="%1$s[]" value="%2$s" />', /*$1%s*/
196 + esc_attr( $this->_args['singular'] ), // Let's simply repurpose the table's singular label ("movie")
197 + /*$2%s*/ esc_attr( $item['ID'] ) // The value of the checkbox should be the record's id
198 + );
199 + }
200 +
201 + /** ************************************************************************
202 + * REQUIRED! This method dictates the table's columns and titles. This should
203 + * return an array where the key is the column slug (and class) and the value
204 + * is the column's title text. If you need a checkbox for bulk actions, refer
205 + * to the $columns array below.
206 + *
207 + * The 'cb' column is treated differently than the rest. If including a checkbox
208 + * column in your table you must create a column_cb() method. If you don't need
209 + * bulk actions or checkboxes, simply leave the 'cb' entry out of your array.
210 + *
211 + * @return array An associative array containing column information: 'slugs'=>'Visible Titles'
212 + * *************************************************************************@see WP_List_Table::::single_row_columns()
213 + */
214 + public function get_columns() {
215 + $columns = [// 'cb' => '<input type="checkbox" />', //Render a checkbox instead of text
216 + ];
217 + $columns['type'] = __( 'Type', 'insert-php' );
218 + $columns['title'] = __( 'Title', 'insert-php' );
219 +
220 + if ( ! $this->modal && $this->common ) {
221 + $columns['preview'] = __( 'Preview', 'insert-php' );
222 + }
223 +
224 + $columns['desc'] = __( 'Description', 'insert-php' );
225 + $columns['datetime'] = __( 'Date', 'insert-php' );
226 + $columns['insert'] = __( 'Insert', 'insert-php' );
227 +
228 + if ( ! $this->modal && ! $this->common ) {
229 + $columns['delete'] = __( 'Delete', 'insert-php' );
230 + }
231 +
232 + return $columns;
233 + }
234 +
235 + /** ************************************************************************
236 + * Optional. If you want one or more columns to be sortable (ASC/DESC toggle),
237 + * you will need to register it here. This should return an array where the
238 + * key is the column that needs to be sortable, and the value is db column to
239 + * sort by. Often, the key and value will be the same, but this is not always
240 + * the case (as the value is a column name from the database, not the list table).
241 + *
242 + * This method merely defines which columns should be sortable and makes them
243 + * clickable - it does not handle the actual sorting. You still need to detect
244 + * the ORDERBY and ORDER querystring variables within prepare_items() and sort
245 + * your data accordingly (usually by modifying your query).
246 + *
247 + * @return array An associative array containing all the columns that should be sortable: 'slugs'=>array('data_values',bool)
248 + **************************************************************************/
249 + public function get_sortable_columns() {
250 + $sortable_columns = [
251 + 'title' => [ 'title', false ], // true means it's already sorted
252 + 'type' => [ 'type', false ],
253 + 'datetime' => [ 'datetime', false ],
254 + ];
255 +
256 + return $sortable_columns;
257 + }
258 +
259 + /** ************************************************************************
260 + * Optional. If you need to include bulk actions in your list table, this is
261 + * the place to define them. Bulk actions are an associative array in the format
262 + * 'slug'=>'Visible Title'
263 + *
264 + * If this method returns an empty value, no bulk action will be rendered. If
265 + * you specify any bulk actions, the bulk actions box will be rendered with
266 + * the table automatically on display().
267 + *
268 + * Also note that list tables are not automatically wrapped in <form> elements,
269 + * so you will need to create those manually in order for bulk actions to function.
270 + *
271 + * @return array An associative array containing all the bulk actions: 'slugs'=>'Visible Titles'
272 + **************************************************************************/
273 + public function get_bulk_actions() {
274 + $actions = [// 'sync' => __( 'Synchronization', 'insert-php' ),
275 + ];
276 +
277 + return $actions;
278 + }
279 +
280 + /** ************************************************************************
281 + * Optional. You can handle your bulk actions anywhere or anyhow you prefer.
282 + * For this example package, we will handle it in the class to keep things
283 + * clean and organized.
284 + *
285 + * @see $this->prepare_items()
286 + **************************************************************************/
287 + public function process_bulk_action() {
288 +
289 + // Detect when a bulk action is being triggered...
290 + /*
291 + if ( 'sync' === $this->current_action() ) {
292 + wp_die( 'Synchronization' );
293 + }*/
294 + }
295 +
296 + /**
297 + * Возвращает id youtube видео
298 + *
299 + * @param $video_link - ссылка на видео
300 + *
301 + * @return bool|string - если id сниппета не найден, то вернёт false
302 + */
303 + private function get_video_id( $video_link ) {
304 + // youtube regex
305 + preg_match( '#([\/|\?|&]vi?[\/|=]|youtu\.be\/|embed\/)([a-zA-Z0-9_-]+)#', $video_link, $matches );
306 +
307 + return ! empty( $matches ) ? end( $matches ) : false;
308 + }
309 +
310 + /**
311 + * Get snippets data
312 + *
313 + * @return array
314 + */
315 + public function get_data() {
316 + $data = [];
317 + $saved_data = [];
318 +
319 + $orderby = WINP_HTTP::request( 'orderby', 'datetime', true );
320 + $order = WINP_HTTP::request( 'order', 'desc', true );
321 + $paged = WINP_HTTP::request( 'paged', 1, 'intval' );
322 +
323 + $order_tags = [
324 + 'title' => 'title',
325 + 'type' => 'type_id',
326 + 'datetime' => 'updated_at',
327 + ];
328 +
329 + $args = [
330 + 'per-page=' . $this->per_page,
331 + 'page=' . $paged,
332 + 'sort=' . ( 'asc' == $order ? '' : '-' ) . ( $order_tags[ $orderby ] ),
333 + ];
334 +
335 + $snippets = WINP_Plugin::app()->get_api_object()->get_all_snippets( $this->common, $args );
336 +
337 + if ( ! empty( $snippets ) ) {
338 + foreach ( (array) $snippets as $snippet ) {
339 + // Check if snippet is premium and user doesn't have license.
340 + $is_premium_locked = $this->common &&
341 + isset( $snippet->is_premium ) &&
342 + $snippet->is_premium &&
343 + ! WINP_Plugin::app()->get_api_object()->is_key();
344 +
345 + // Build Insert button - use different class for premium locked snippets.
346 + if ( $is_premium_locked ) {
347 + $insert_button = '<a class="wbcr-inp-premium-snippet-button button" href="javascript: void(0)" style="display:inline-flex;align-items:center;justify-content:center;"><span class="dashicons dashicons-plus"></span></a>';
348 + } else {
349 + $insert_button = '<a class="wbcr-inp-enable-snippet-button button" data-snippet="' . esc_attr( $snippet->id ) . '" data-common="' . ( $this->common ? 1 : 0 ) . '" href="javascript: void(0)" style="display:inline-flex;align-items:center;justify-content:center;"><span class="dashicons dashicons-plus"></span></a>';
350 + }
351 +
352 + $_data = [
353 + 'ID' => (int) $snippet->id,
354 + 'title' => esc_html( $snippet->title ),
355 + 'desc' => esc_html( $snippet->description ),
356 + 'type' => $snippet->type->title,
357 + 'datetime' => gmdate( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ), $snippet->updated_at ),
358 + 'insert' => $insert_button,
359 + 'delete' => '<a class="wbcr-inp-delete-snippet-button button" data-snippet="' . esc_attr( $snippet->id ) . '" href="javascript: void(0)"><span class="dashicons dashicons-no"></span></a>',
360 + 'is_premium_locked' => $is_premium_locked,
361 + ];
362 +
363 + if ( $this->common ) {
364 + $_data['preview'] = '';
365 + }
366 +
367 + $video_id = $this->get_video_id( $snippet->video_link );
368 +
369 + if ( $video_id ) {
370 + $_data['preview'] = '<a class="thickbox" href="https://www.youtube.com/embed/' . esc_attr( $video_id ) . '?autoplay=1&rel=0&TB_iframe=true"><img src="' . WINP_PLUGIN_URL . '/admin/assets/img/video.png" class="winp-library-image-preview" data-videoid="' . esc_attr( $video_id ) . '" alt="' . __( 'Watch Tutorial Video', 'insert-php' ) . '"></a>';
371 + }
372 +
373 + $data[] = $_data;
374 +
375 + $saved_data[ $snippet->id ] = [
376 + 'title' => esc_html( $snippet->title ),
377 + 'desc' => esc_html( $snippet->description ),
378 + 'type' => $snippet->type->slug,
379 + 'content' => $snippet->content,
380 + 'type_id' => (int) $snippet->type_id,
381 + 'scope' => $snippet->execute_everywhere ? 'evrywhere' : 'shortcode',
382 + ];
383 + }
384 +
385 + update_user_meta( get_current_user_id(), 'wbcr_inp_current_snippets', $saved_data );
386 + }
387 +
388 + return $data;
389 + }
390 +
391 + /**
392 + * Get total items for last query
393 + *
394 + * @return int
395 + */
396 + public function get_total_items() {
397 + return WINP_Plugin::app()->get_api_object()->get_total_items();
398 + }
399 +
400 + /** ************************************************************************
401 + * REQUIRED! This is where you prepare your data for display. This method will
402 + * usually be used to query the database, sort and filter the data, and generally
403 + * get it ready to be displayed. At a minimum, we should set $this->items and
404 + * $this->set_pagination_args(), although the following properties and methods
405 + * are frequently interacted with here...
406 + *
407 + * @param bool $common - если true, то выводить общие сниппеты без привязки к пользователю
408 + *
409 + * @global WPDB $wpdb
410 + * @uses $this->_column_headers
411 + * @uses $this->items
412 + * @uses $this->get_columns()
413 + * @uses $this->get_sortable_columns()
414 + * @uses $this->get_pagenum()
415 + * @uses $this->set_pagination_args()
416 + * *************************************************************************/
417 + public function prepare_items( $common = false ) {
418 + /**
419 + * First, lets decide how many records per page to show
420 + */
421 + $this->per_page = 10;
422 +
423 + /**
424 + * @param bool $common - если true, то выводить общие сниппеты без привязки к пользователю
425 + */
426 + $this->common = $common;
427 +
428 + /**
429 + * REQUIRED. Now we need to define our column headers. This includes a complete
430 + * array of columns to be displayed (slugs & titles), a list of columns
431 + * to keep hidden, and a list of columns that are sortable. Each of these
432 + * can be defined in another method (as we've done here) before being
433 + * used to build the value for our _column_headers property.
434 + */
435 + $columns = $this->get_columns();
436 + $hidden = $this->hidden_columns;
437 + $sortable = $this->get_sortable_columns();
438 +
439 + /**
440 + * REQUIRED. Finally, we build an array to be used by the class for column
441 + * headers. The $this->_column_headers property takes an array which contains
442 + * 3 other arrays. One for all columns, one for hidden columns, and one
443 + * for sortable columns.
444 + */
445 + $this->_column_headers = [ $columns, $hidden, $sortable ];
446 +
447 + /**
448 + * Optional. You can handle your bulk actions however you see fit. In this
449 + * case, we'll handle them within our package just to keep things clean.
450 + */
451 + $this->process_bulk_action();
452 +
453 + /**
454 + * Instead of querying a database, we're going to fetch the example data
455 + * property we created for use in this plugin. This makes this example
456 + * package slightly different than one you might build on your own. In
457 + * this example, we'll be using array manipulation to sort and paginate
458 + * our data. In a real-world implementation, you will probably want to
459 + * use sort and pagination data to build a custom query instead, as you'll
460 + * be able to use your precisely-queried data immediately.
461 + */
462 + $data = $this->get_data();
463 +
464 + /**
465 + * This checks for sorting input and sorts the data in our array accordingly.
466 + *
467 + * In a real-world situation involving a database, you would probably want
468 + * to handle sorting by passing the 'orderby' and 'order' values directly
469 + * to a custom query. The returned data will be pre-sorted, and this array
470 + * sorting technique would be unnecessary.
471 + *
472 + * @param $a
473 + * @param $b
474 + *
475 + * @return int
476 + */ /*
477 + function usort_reorder( $a, $b ) {
478 + $orderby = ( ! empty( $_REQUEST['orderby'] ) ) ? $_REQUEST['orderby'] : 'title'; // If no sort, default to title
479 + $order = ( ! empty( $_REQUEST['order'] ) ) ? $_REQUEST['order'] : 'asc'; // If no order, default to asc
480 + $result = strcmp( $a[ $orderby ], $b[ $orderby ] ); // Determine sort order
481 +
482 + return ( 'asc' === $order ) ? $result : - $result; // Send final sort direction to usort
483 + }
484 +
485 + usort( $data, 'usort_reorder' );*/
486 +
487 + /***********************************************************************
488 + * ---------------------------------------------------------------------
489 + * vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
490 + *
491 + * In a real-world situation, this is where you would place your query.
492 + *
493 + * For information on making queries in WordPress, see this Codex entry:
494 + * http://codex.wordpress.org/Class_Reference/wpdb
495 + *
496 + * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
497 + * ---------------------------------------------------------------------
498 + */
499 +
500 + /**
501 + * REQUIRED for pagination. Let's figure out what page the user is currently
502 + * looking at. We'll need this later, so you should always include it in
503 + * your own package classes.
504 + */ // $current_page = $this->get_current_page();
505 +
506 + /**
507 + * REQUIRED for pagination. Let's check how many items are in our data array.
508 + * In real-world use, this would be the total number of items in your database,
509 + * without filtering. We'll need this later, so you should always include it
510 + * in your own package classes.
511 + */
512 + $total_items = $this->get_total_items();
513 +
514 + /**
515 + * The WP_List_Table class does not handle pagination for us, so we need
516 + * to ensure that the data is trimmed to only the current page. We can use
517 + * array_slice() to
518 + */ // $data = array_slice( $data, ( ( $current_page - 1 ) * $this->per_page ), $this->per_page );
519 +
520 + /**
521 + * REQUIRED. Now we can add our *sorted* data to the items property, where
522 + * it can be used by the rest of the class.
523 + */
524 + $this->items = $data;
525 +
526 + /**
527 + * REQUIRED. We also have to register our pagination options & calculations.
528 + */
529 + $this->set_pagination_args(
530 + [
531 + 'total_items' => $total_items,
532 + 'per_page' => $this->per_page,
533 + 'total_pages' => ceil( $total_items / $this->per_page ),
534 + 'orderby' => WINP_HTTP::request( 'orderby', 'title', true ),
535 + 'order' => WINP_HTTP::request( 'order', 'asc', true ),
536 + ]
537 + );
538 + }
539 +
540 + /**
541 + * @Override of display method
542 + */
543 + public function display() {
544 + /**
545 + * Adds a nonce field
546 + */
547 + wp_nonce_field( 'winp-ajax-custom-list-nonce', 'winp_ajax_custom_list_nonce' );
548 +
549 + if ( ! empty( $this->items ) && ! $this->common ) {
550 + foreach ( $this->items as $item ) {
551 + wp_nonce_field( 'winp-ajax-snippet-delete-' . $item['ID'], 'winp_ajax_snippet_delete_' . $item['ID'] );
552 + }
553 + }
554 +
555 + /**
556 + * Adds field order and orderby
557 + */
558 + echo '<input type="hidden" id="order" name="order" value="' . $this->_pagination_args['order'] . '" />';
559 + echo '<input type="hidden" id="orderby" name="orderby" value="' . $this->_pagination_args['orderby'] . '" />';
560 + parent::display();
561 +
562 + // Add premium upsell modal.
563 + if ( $this->common ) {
564 + ?>
565 + <div id="winp-premium-snippet-modal" style="display:none;">
566 + <div style="position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.7);z-index:100000;display:flex;align-items:center;justify-content:center;">
567 + <div style="background:#fff;border-radius:8px;padding:0;max-width:500px;width:90%;position:relative;">
568 + <button id="winp-modal-close" style="position:absolute;top:15px;right:15px;background:none;border:none;font-size:24px;cursor:pointer;color:#666;line-height:1;padding:0;width:30px;height:30px;">&times;</button>
569 + <div class="winp-upsell-container" style="margin:0;padding:0;">
570 + <div class="winp-upsell-card" style="box-shadow:none;">
571 + <div class="winp-upsell-icon">
572 + <span class="dashicons dashicons-star-filled"></span>
573 + </div>
574 + <div class="winp-upsell-title">
575 + <?php esc_html_e( 'Premium Snippet', 'insert-php' ); ?>
576 + </div>
577 + <p class="winp-upsell-badge">
578 + <?php esc_html_e( 'Pro feature', 'insert-php' ); ?>
579 + </p>
580 + <p class="winp-upsell-description">
581 + <?php esc_html_e( 'This is a premium snippet available only in the Pro version. Upgrade to unlock access to all premium code snippets and advanced features.', 'insert-php' ); ?>
582 + </p>
583 + <a href="<?php echo esc_url( tsdk_utmify( WINP_UPGRADE, 'snippet_library', 'premium_snippet_upsell' ) ); ?>" class="button button-primary button-large winp-upsell-button" target="_blank">
584 + <?php esc_html_e( 'Upgrade to Pro', 'insert-php' ); ?>
585 + </a>
586 + </div>
587 + </div>
588 + </div>
589 + </div>
590 + </div>
591 + <?php
592 + }
593 + }
594 +
595 + /**
596 + * @Override ajax_response method
597 + */
598 + public function ajax_response() {
599 +
600 + $this->prepare_items();
601 + extract( $this->_args );
602 + extract( $this->_pagination_args, EXTR_SKIP );
603 + ob_start();
604 + $no_placeholder = WINP_HTTP::request( 'no_placeholder', '' );
605 + if ( ! empty( $no_placeholder ) ) {
606 + $this->display_rows();
607 + } else {
608 + $this->display_rows_or_placeholder();
609 + }
610 + $rows = ob_get_clean();
611 + ob_start();
612 + $this->print_column_headers();
613 + $headers = ob_get_clean();
614 + ob_start();
615 + $this->pagination( 'top' );
616 + $pagination_top = ob_get_clean();
617 + ob_start();
618 + $this->pagination( 'bottom' );
619 + $pagination_bottom = ob_get_clean();
620 + $response = [ 'rows' => $rows ];
621 + $response['pagination']['top'] = $pagination_top;
622 + $response['pagination']['bottom'] = $pagination_bottom;
623 + $response['column_headers'] = $headers;
624 + if ( isset( $total_items ) ) {
625 + /* translators: %s: Number of items */
626 + $response['total_items_i18n'] = sprintf( _n( '%s item', '%s items', $total_items, 'insert-php' ), number_format_i18n( $total_items ) );
627 + }
628 + if ( isset( $total_pages ) ) {
629 + $response['total_pages'] = $total_pages;
630 + $response['total_pages_i18n'] = number_format_i18n( $total_pages );
631 + }
632 + die( json_encode( $response ) );
633 + }
634 +}