PluginProbe
TablePress – Tables in WordPress made easy / 3.4
TablePress – Tables in WordPress made easy v3.4
3.4 3.3.4 3.3.3 3.3.2 3.3.1 trunk 1.12 1.14 1.9.2 2.0.4 2.1.7 2.1.8 2.2 2.2.1 2.2.2 2.2.3 2.2.4 2.2.5 2.3 2.3.1 2.3.2 2.4 2.4.1 2.4.2 2.4.3 All 45 releases
tablepress / classes / class-view.php

class-view.php in TablePress – Tables in WordPress made easy 3.4, at classes/class-view.php

664 lines 24.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * TablePress Base View with members and methods for all views
4 *
5 * @package TablePress
6 * @subpackage Views
7 * @author Tobias Bäthge
8 * @since 1.0.0
9 */
10
11 declare(strict_types=1);
12
13 // Prohibit direct script loading.
14 defined( 'ABSPATH' ) || die( 'No direct script access allowed!' );
15
16 /**
17 * TablePress Base View class
18 *
19 * @package TablePress
20 * @subpackage Views
21 * @author Tobias Bäthge
22 * @since 1.0.0
23 */
24 abstract class TablePress_View {
25
26 /**
27 * Data for the view.
28 *
29 * @since 1.0.0
30 * @var array<string, mixed>
31 */
32 protected array $data = array();
33
34 /**
35 * Number of screen columns for post boxes.
36 *
37 * @since 1.0.0
38 */
39 protected int $screen_columns = 0;
40
41 /**
42 * User action for this screen.
43 *
44 * @since 1.0.0
45 */
46 protected string $action = '';
47
48 /**
49 * List of text boxes (similar to post boxes, but just with text and without extra functionality).
50 *
51 * @since 1.0.0
52 * @var array<string, array<string, array<string, mixed>>>
53 */
54 protected array $textboxes = array();
55
56 /**
57 * List of messages that are to be displayed as boxes below the page title.
58 *
59 * @since 1.0.0
60 * @var string[]
61 */
62 protected array $header_messages = array();
63
64 /**
65 * Whether there are post boxes registered for this screen,
66 * is automatically set to true, when a meta box is added.
67 *
68 * @since 1.0.0
69 */
70 protected bool $has_meta_boxes = false;
71
72 /**
73 * List of WP feature pointers for this view.
74 *
75 * @since 1.0.0
76 * @var string[]
77 */
78 protected array $wp_pointers = array();
79
80 /**
81 * Initializes the View class, by setting the correct screen columns and adding help texts.
82 *
83 * @since 1.0.0
84 */
85 public function __construct() {
86 $screen = get_current_screen();
87 if ( 0 !== $this->screen_columns ) {
88 $screen->add_option( 'layout_columns', array( 'max' => $this->screen_columns ) ); // @phpstan-ignore method.nonObject
89 }
90 // Enable two column layout.
91 add_filter( "get_user_option_screen_layout_{$screen->id}", array( $this, 'set_current_screen_layout_columns' ) ); // @phpstan-ignore property.nonObject
92
93 /* translators: %1$s: URL to TablePress website, %2$s: URL to WordPress Plugin Directory */
94 $common_content = '<p>' . sprintf( __( 'More information about TablePress can be found on the <a href="%1$s">plugin website</a> or on its page in the <a href="%2$s">WordPress Plugin Directory</a>.', 'tablepress' ), 'https://tablepress.org/', 'https://wordpress.org/plugins/tablepress/' ) . '</p>';
95 /* translators: %s: URL to Documentation page */
96 $common_content .= '<p>' . sprintf( __( 'For technical information, please see the <a href="%s">Documentation</a>.', 'tablepress' ), 'https://tablepress.org/documentation/' ) . ' ';
97 /* translators: %s: URL to FAQ page */
98 $common_content .= sprintf( __( 'Common questions are answered in the <a href="%s">FAQ</a>.', 'tablepress' ), 'https://tablepress.org/faq/' ) . '</p>';
99
100 if ( tb_tp_fs()->is_free_plan() ) {
101 $common_content .= '<p>'
102 . sprintf( __( '<a href="%1$s">Support</a> is provided through the <a href="%2$s">WordPress Support Forums</a>.', 'tablepress' ), 'https://tablepress.org/support/', 'https://wordpress.org/support/plugin/tablepress/' )
103 . ' '
104 . sprintf( __( 'Before asking for support, please carefully read the <a href="%s">Frequently Asked Questions</a>, where you will find answers to the most common questions, and search through the forums.', 'tablepress' ), 'https://tablepress.org/faq/' )
105 . '</p>';
106 $common_content .= '<p><strong>' . sprintf( __( 'More great features for you and your site’s visitors and priority email support are available with a Premium license plan of TablePress. <a href="%s">Go check them out!</a>', 'tablepress' ), 'https://tablepress.org/premium/?utm_source=plugin&utm_medium=textlink&utm_content=help-tab' ) . '</strong></p>';
107 }
108
109 $screen->add_help_tab( array( // @phpstan-ignore method.nonObject
110 'id' => 'tablepress-help', // This should be unique for the screen.
111 'title' => __( 'TablePress Help', 'tablepress' ),
112 'content' => '<p>' . $this->help_tab_content() . '</p>' . $common_content,
113 ) );
114 // "Sidebar" in the help tab.
115 $screen->set_help_sidebar( // @phpstan-ignore method.nonObject
116 '<p><strong>' . __( 'For more information:', 'tablepress' ) . '</strong></p>'
117 . '<p><a href="https://tablepress.org/">TablePress Website</a></p>'
118 . '<p><a href="https://tablepress.org/faq/">TablePress FAQ</a></p>'
119 . '<p><a href="https://tablepress.org/documentation/">TablePress Documentation</a></p>'
120 . '<p><a href="https://tablepress.org/support/">TablePress Support</a></p>'
121 );
122 }
123
124 /**
125 * Changes the value of the user option "screen_layout_{$screen->id}" through a filter.
126 *
127 * @since 1.0.0
128 *
129 * @param string|int|false $result Current value of the user option.
130 * @return int New value for the user option.
131 */
132 public function set_current_screen_layout_columns( /* string|int|false */ $result ): int {
133 if ( false === $result ) {
134 // The user option does not yet exist.
135 return $this->screen_columns;
136 }
137
138 $result = (int) $result;
139 if ( $result > $this->screen_columns ) {
140 // The value of the user option is bigger than what is possible on this screen (e.g. because the number of columns was reduced in an update).
141 $result = $this->screen_columns;
142 }
143
144 return $result;
145 }
146
147 /**
148 * Sets up the view with data and do things that are necessary for all views.
149 *
150 * @since 1.0.0
151 *
152 * @param string $action Action for this view.
153 * @param array<string, mixed> $data Data for this view.
154 */
155 public function setup( /* string */ $action, array $data ) /* : void */ {
156 // Don't use type hints (except array $data) in method declaration, as the method is extended in some TablePress Extensions which are no longer updated.
157
158 $this->action = $action;
159 $this->data = $data;
160
161 // Set page title.
162 $GLOBALS['title'] = sprintf( __( '%1$s &lsaquo; %2$s', 'tablepress' ), $this->data['view_actions'][ $this->action ]['page_title'], 'TablePress' ); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
163
164 add_filter( 'admin_footer_text', array( $this, 'add_admin_footer_text' ) );
165
166 TablePress::enqueue_style( 'common', array( 'wp-components' ) );
167 // RTL styles for the admin interface.
168 if ( is_rtl() ) {
169 TablePress::enqueue_style( 'common-rtl', array( 'tablepress-common' ) );
170 }
171
172 // Initialize WP feature pointers for TablePress.
173 $this->_init_wp_pointers();
174
175 // Necessary fields for all views.
176 $this->add_text_box( 'default_nonce_fields', array( $this, 'default_nonce_fields' ), 'header', false );
177 $this->add_text_box( 'action_nonce_field', array( $this, 'action_nonce_field' ), 'header', false );
178 $this->add_text_box( 'action_field', array( $this, 'action_field' ), 'header', false );
179 }
180
181 /**
182 * Registers a header message for the view.
183 *
184 * @since 1.0.0
185 *
186 * @param string $text Text for the header message.
187 * @param string $css_class Optional. Additional CSS class for the header message.
188 * @param string $title Optional. Text for the header title.
189 */
190 protected function add_header_message( string $text, string $css_class = 'is-success notice-success', string $title = '' ): void {
191 if ( ! str_contains( $css_class, 'not-dismissible' ) ) {
192 $css_class .= ' is-dismissible';
193 }
194 if ( '' !== $title ) {
195 $title = "<h3>{$title}</h3>";
196 }
197 // Wrap the message text in HTML <p> tags if it does not already start with one (potentially with attributes), indicating custom message HTML.
198 if ( '' !== $text && ! str_starts_with( $text, '<p' ) ) {
199 $text = "<p>{$text}</p>";
200 }
201 $this->header_messages[] = "<div class=\"notice components-notice {$css_class}\"><div class=\"components-notice__content\">{$title}{$text}</div></div>\n";
202 }
203
204 /**
205 * Processes header action messages, i.e. check if a message should be added to the page.
206 *
207 * @since 1.0.0
208 *
209 * @param array<string, string> $action_messages Action messages for the screen.
210 */
211 protected function process_action_messages( array $action_messages ): void {
212 if ( $this->data['message'] && isset( $action_messages[ $this->data['message'] ] ) ) {
213 $class = ( str_starts_with( $this->data['message'], 'error' ) ) ? 'is-error notice-error' : 'is-success notice-success';
214
215 if ( '' !== $this->data['error_details'] ) {
216 $this->data['error_details'] = '</p><p>' . sprintf( __( 'Error code: %s', 'tablepress' ), '<code>' . esc_html( $this->data['error_details'] ) . '</code>' );
217 }
218
219 $this->add_header_message( "<strong>{$action_messages[ $this->data['message'] ]}</strong>{$this->data['error_details']}", $class );
220 }
221 }
222
223 /**
224 * Registers a text box for the view.
225 *
226 * @since 1.0.0
227 *
228 * @param string $id Unique HTML ID for the text box container (only visible with $wrap = true).
229 * @param callable $callback Callback that prints the contents of the text box.
230 * @param string $context Optional. Context/position of the text box (normal, side, additional, header, submit).
231 * @param bool $wrap Whether the content of the text box shall be wrapped in a <div> container.
232 */
233 protected function add_text_box( string $id, callable $callback, string $context = 'normal', bool $wrap = false ): void {
234 if ( ! isset( $this->textboxes[ $context ] ) ) {
235 $this->textboxes[ $context ] = array();
236 }
237
238 $long_id = "tablepress_{$this->action}-{$id}";
239 $this->textboxes[ $context ][ $id ] = array(
240 'id' => $long_id,
241 'callback' => $callback,
242 'context' => $context,
243 'wrap' => $wrap,
244 );
245 }
246
247 /**
248 * Registers a post meta box for the view, that is drag/droppable with WordPress functionality.
249 *
250 * @since 1.0.0
251 *
252 * @param string $id Unique ID for the meta box.
253 * @param string $title Title for the meta box.
254 * @param callable $callback Callback that prints the contents of the post meta box.
255 * @param 'normal'|'side'|'additional' $context Optional. Context/position of the post meta box (normal, side, additional).
256 * @param 'core'|'default'|'high'|'low' $priority Optional. Order of the post meta box for the $context position (high, default, low).
257 * @param mixed[]|null $callback_args Optional. Additional data for the callback function (e.g. useful when in different class).
258 */
259 protected function add_meta_box( string $id, string $title, callable $callback, string $context = 'normal', string $priority = 'default', ?array $callback_args = null ): void {
260 $this->has_meta_boxes = true;
261 add_meta_box( "tablepress_{$this->action}-{$id}", $title, $callback, null, $context, $priority, $callback_args );
262 }
263
264 /**
265 * Renders all text boxes for the given context.
266 *
267 * @since 1.0.0
268 *
269 * @param string $context Context (normal, side, additional, header, submit) for which registered text boxes shall be rendered.
270 */
271 protected function do_text_boxes( string $context ): void {
272 if ( empty( $this->textboxes[ $context ] ) ) {
273 return;
274 }
275
276 foreach ( $this->textboxes[ $context ] as $box ) {
277 if ( $box['wrap'] ) {
278 echo "<div id=\"{$box['id']}\" class=\"textbox\">\n";
279 }
280 call_user_func( $box['callback'], $this->data, $box );
281 if ( $box['wrap'] ) {
282 echo "</div>\n";
283 }
284 }
285 }
286
287 /**
288 * Renders all post meta boxes for the given context, if there are post meta boxes.
289 *
290 * @since 1.0.0
291 *
292 * @param string $context Context (normal, side, additional) for which registered post meta boxes shall be rendered.
293 */
294 protected function do_meta_boxes( string $context ): void {
295 if ( $this->has_meta_boxes ) {
296 do_meta_boxes( get_current_screen(), $context, $this->data ); // @phpstan-ignore argument.type
297 }
298 }
299
300 /**
301 * Prints hidden fields with nonces for post meta box AJAX handling, if there are post meta boxes on the screen.
302 *
303 * The check is possible as this function is executed after post meta boxes have to be registered.
304 *
305 * @since 1.0.0
306 *
307 * @param array<string, mixed> $data Data for this screen.
308 * @param array<string, mixed> $box Information about the text box.
309 */
310 protected function default_nonce_fields( array $data, array $box ): void {
311 if ( ! $this->has_meta_boxes ) {
312 return;
313 }
314 wp_nonce_field( 'closedpostboxes', 'closedpostboxesnonce', false );
315 echo "\n";
316 wp_nonce_field( 'meta-box-order', 'meta-box-order-nonce', false );
317 echo "\n";
318 }
319
320 /**
321 * Prints hidden field with a nonce for the screen's action, to be transmitted in HTTP requests.
322 *
323 * @since 1.0.0
324 *
325 * @param array<string, mixed> $data Data for this screen.
326 * @param array<string, mixed> $box Information about the text box.
327 */
328 protected function action_nonce_field( array $data, array $box ): void {
329 wp_nonce_field( TablePress::nonce( $this->action ) );
330 echo "\n";
331 }
332
333 /**
334 * Prints hidden field with the screen action.
335 *
336 * @since 1.0.0
337 *
338 * @param array<string, mixed> $data Data for this screen.
339 * @param array<string, mixed> $box Information about the text box.
340 */
341 protected function action_field( array $data, array $box ): void {
342 echo "<input type=\"hidden\" name=\"action\" value=\"tablepress_{$this->action}\">\n";
343 }
344
345 /**
346 * Renders the current view.
347 *
348 * @since 1.0.0
349 */
350 public function render(): void {
351 ?>
352 <div id="tablepress-page" class="wrap">
353 <?php
354 $this->print_nav_tab_menu();
355 ?>
356 <div id="tablepress-body">
357 <hr class="wp-header-end">
358 <script>document.querySelectorAll('.notice:has(~#tablepress-page)').forEach(e=>document.querySelector('.wp-header-end').after(e));</script>
359 <?php
360 // Print all header messages.
361 foreach ( $this->header_messages as $message ) {
362 echo $message;
363 }
364 // "Import" screen has file upload.
365 $enctype = ( 'import' === $this->action ) ? ' enctype="multipart/form-data"' : '';
366 ?>
367 <form action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>" method="post"<?php echo $enctype; ?> id="tablepress-page-form">
368 <?php
369 $this->do_text_boxes( 'header' );
370 $hide_if_no_js = ( in_array( $this->action, array( 'export', 'import' ), true ) ) ? ' class="hide-if-no-js"' : '';
371 ?>
372 <div id="poststuff"<?php echo $hide_if_no_js; ?>>
373 <div id="post-body" class="metabox-holder columns-<?php echo ( isset( $GLOBALS['screen_layout_columns'] ) && ( 2 === $GLOBALS['screen_layout_columns'] ) ) ? '2' : '1'; ?>">
374 <div id="postbox-container-2" class="postbox-container">
375 <?php
376 $this->do_text_boxes( 'normal' );
377 $this->do_meta_boxes( 'normal' );
378
379 $this->do_text_boxes( 'additional' );
380 $this->do_meta_boxes( 'additional' );
381
382 // Print all submit buttons.
383 $this->do_text_boxes( 'submit' );
384 ?>
385 </div>
386 <div id="postbox-container-1" class="postbox-container">
387 <?php
388 // Print all boxes in the sidebar.
389 $this->do_text_boxes( 'side' );
390 $this->do_meta_boxes( 'side' );
391 ?>
392 </div>
393 </div>
394 <br class="clear">
395 </div>
396 </form>
397 </div>
398 </div>
399 <?php
400 }
401
402 /**
403 * Renders the navigation menu with links to the possible actions, highlighting the current one.
404 *
405 * @since 1.0.0
406 */
407 protected function print_nav_tab_menu(): void {
408 $milestone_period_ended = ( strtotime( '2026-10-10' ) < strtotime( 'today' ) );
409 $name = __( 'TablePress', 'tablepress' );
410 $filename = 'admin/img/tablepress.svg';
411 ?>
412 <div id="tablepress-header" class="header">
413 <h1 class="name">
414 <img src="<?php echo plugins_url( $filename, TABLEPRESS__FILE__ ); ?>" alt="<?php esc_attr_e( 'TablePress plugin logo', 'tablepress' ); ?>">
415 <span class="screen-reader-text"><?php echo $name; ?></span>
416 </h1>
417 <?php if ( 'list' !== $this->action || ! $this->data['messages']['plugin_update'] || $milestone_period_ended ) : ?>
418 <?php if ( ! TABLEPRESS_IS_PLAYGROUND_PREVIEW && tb_tp_fs()->is_free_plan() ) : ?>
419 <div class="buttons">
420 <a href="<?php echo $milestone_period_ended ? esc_url( tb_tp_fs()->pricing_url( WP_FS__PERIOD_ANNUALLY, false ) ) : 'https://tablepress.org/premium/?utm_source=plugin&utm_medium=header&utm_campaign=upgrade-message340&utm_content=button'; ?>" class="tablepress-button">
421 <span>
422 <?php
423 if ( $milestone_period_ended ) {
424 _e( 'Upgrade to Premium', 'tablepress' );
425 } else {
426 echo in_array( get_user_locale(), array( 'de_DE', 'de_AT', 'de_CH' ), true ) ? 'Premium: Spare jetzt 25&#8239;%' : 'Premium: Get 25% off now';
427 }
428 ?>
429 </span>
430 <span class="dashicons dashicons-arrow-right-alt"></span>
431 </a>
432 </div>
433 <?php endif; ?>
434 <?php endif; ?>
435 </div>
436 <nav id="tablepress-nav">
437 <ul class="nav-menu">
438 <?php
439 foreach ( $this->data['view_actions'] as $action => $entry ) {
440 if ( '' === $entry['nav_tab_title'] ) {
441 continue;
442 }
443 if ( ! current_user_can( $entry['required_cap'] ) ) {
444 continue;
445 }
446
447 $url = esc_url( TablePress::url( array( 'action' => $action ) ) );
448 $active = ( $action === $this->action ) ? ' active' : '';
449 $separator = ( 'export' === $action ) ? ' separator' : ''; // Make the "Export" entry a separator, for some spacing.
450 echo "<li class=\"nav-item\"><a id=\"tablepress-nav-item-{$action}\" class=\"nav-link{$active}{$separator}\" href=\"{$url}\">{$entry['nav_tab_title']}</a></li>";
451 }
452 ?>
453 </ul>
454 </nav>
455 <?php
456 }
457
458 /**
459 * Prints a notification about JavaScript not being activated in the browser.
460 *
461 * @since 2.0.0
462 *
463 * @param array<string, mixed> $data Data for this screen.
464 * @param array<string, mixed> $box Information about the text box.
465 */
466 public function textbox_no_javascript( array $data, array $box ): void {
467 ?>
468 <div class="notice components-notice is-error notice-error hide-if-js">
469 <div class="components-notice__content">
470 <h3><em>
471 <?php _e( 'Attention: Unfortunately, there is a problem!', 'tablepress' ); ?>
472 </em></h3>
473 <p style="font-size:14px">
474 <strong><?php _e( 'This screen requires JavaScript. Please enable JavaScript in your browser settings.', 'tablepress' ); ?></strong><br>
475 <?php _e( 'For help, please follow <a href="https://www.enable-javascript.com/">the instructions on how to enable JavaScript in your browser</a>.', 'tablepress' ); ?>
476 </p>
477 <p>
478 <?php echo '<a href="' . esc_url( TablePress::url( array( 'action' => 'list' ) ) ) . '">' . __( 'Back to the List of Tables', 'tablepress' ) . '</a>'; ?>
479 </p>
480 </div>
481 </div>
482 <?php
483 }
484
485 /**
486 * Prints a submit button (only done when function is used as a callback for a text box).
487 *
488 * This method is soft-deprecated. It's no longer used in TablePress, but e.g. in the "TablePress Debug Extension".
489 *
490 * @since 1.0.0
491 *
492 * @param array<string, mixed> $data Data for this screen.
493 * @param array<string, mixed> $box Information about the text box.
494 */
495 protected function textbox_submit_button( array $data, array $box ): void {
496 ?>
497 <p class="submit"><input type="submit" class="components-button is-primary button-save-changes" value="<?php esc_attr_e( 'Save Changes', 'tablepress' ); ?>"></p>
498 <?php
499 }
500
501 /**
502 * Returns a safe JSON representation of a variable for printing inside of JavaScript code.
503 *
504 * @since 2.0.0
505 *
506 * @param mixed $data Variable to convert to JSON.
507 * @return string Safe JSON representation of a variable for printing inside of JavaScript code.
508 */
509 public function convert_to_json_parse_output( /* string|array|bool|int|float|null */ $data ): string {
510 $json = wp_json_encode( $data, JSON_HEX_TAG | JSON_UNESCAPED_SLASHES );
511 if ( false === $json ) {
512 // JSON encoding failed, return an error object. Use a prefixed "_error" key to avoid conflicts with intentionally added "error" keys.
513 $json = '{ "_error": "The data could not be encoded to JSON!" }';
514 }
515 // Print the JSON data inside a `JSON.parse()` call in JS for speed gains, with necessary escaping of `\` and `'`.
516 $json = str_replace( array( '\\', "'" ), array( '\\\\', "\'" ), $json );
517 return "JSON.parse( '{$json}' )";
518 }
519
520 /**
521 * Prints JavaScript variables for the screen.
522 *
523 * @since 3.1.0
524 *
525 * @param string $variable Name of the JavaScript variable.
526 * @param array<string, mixed> $data Information about the text box.
527 */
528 protected function print_script_data_json( string $variable, array $data ): void {
529 echo "<script>\n";
530 echo "window.tp = window.tp || {};\n";
531 echo "tp.{$variable} = {\n";
532 foreach ( $data as $key => $value ) {
533 $value = $this->convert_to_json_parse_output( $value );
534 echo "\t{$key}: {$value},\n";
535 }
536 echo "};\n";
537 echo "</script>\n";
538 }
539
540 /**
541 * Returns the content for the help tab for this screen.
542 *
543 * Has to be implemented for every view that is visible in the WP Dashboard!
544 *
545 * @since 1.0.0
546 *
547 * @return string Help tab content for the view.
548 */
549 protected function help_tab_content(): string {
550 // Has to be implemented for every view that is visible in the WP Dashboard!
551 return '';
552 }
553
554 /**
555 * Adds a TablePress "Thank You" message to the admin footer content.
556 *
557 * @since 1.0.0
558 * @since 3.3.0 This method was moved from the now-removed `TablePress_Admin_Page` class.
559 *
560 * @param string $content Current admin footer content.
561 * @return string New admin footer content.
562 */
563 public function add_admin_footer_text( /* string */ $content ): string {
564 // Don't use a type hint in the method declaration as many WordPress plugins use the `admin_footer_text` filter without returning a string.
565
566 // Protect against other plugins not returning a string in their filter callbacks.
567 if ( ! is_string( $content ) ) { // @phpstan-ignore function.alreadyNarrowedType (The `is_string()` check is needed as the input is coming from a filter hook.)
568 $content = '';
569 }
570
571 /* translators: %s: URL to TablePress website */
572 $content .= ' &bull; ' . sprintf( __( 'Thank you for using <a href="%s">TablePress</a>.', 'tablepress' ), 'https://tablepress.org/' );
573 if ( tb_tp_fs()->is_free_plan() ) {
574 /* translators: %s: URL to TablePress premium features */
575 $content .= ' ' . sprintf( __( 'Take a look at the <a href="%s">Premium features</a>!', 'tablepress' ), 'https://tablepress.org/premium/?utm_source=plugin&utm_medium=textlink&utm_content=admin-footer' );
576 }
577 return $content;
578 }
579
580 /**
581 * Initializes the WP feature pointers for TablePress.
582 *
583 * @since 1.0.0
584 */
585 protected function _init_wp_pointers(): void {
586 // Check if there are WP pointers for this view.
587 if ( empty( $this->wp_pointers ) ) {
588 return;
589 }
590
591 // Get dismissed pointers.
592 $dismissed = explode( ',', (string) get_user_meta( get_current_user_id(), 'dismissed_wp_pointers', true ) );
593
594 $pointers_on_page = false;
595 foreach ( array_diff( $this->wp_pointers, $dismissed ) as $pointer ) {
596 // Bind pointer print function.
597 add_action( "admin_footer-{$GLOBALS['hook_suffix']}", array( $this, 'wp_pointer_' . $pointer ) ); // @phpstan-ignore argument.type
598 $pointers_on_page = true;
599 }
600
601 if ( $pointers_on_page ) {
602 wp_enqueue_style( 'wp-pointer' );
603 wp_enqueue_script( 'wp-pointer' );
604 }
605 }
606
607 /**
608 * Prints the JavaScript code for a WP feature pointer.
609 *
610 * @since 1.0.0
611 * @since 3.3.0 This method was moved from the now-removed `TablePress_Admin_Page` class.
612 *
613 * @param string $pointer_id The pointer ID.
614 * @param string $selector The HTML elements, on which the pointer should be attached.
615 * @param array<string, mixed> $args Arguments to be passed to the pointer JS (see wp-pointer.js).
616 */
617 public function print_wp_pointer_js( string $pointer_id, string $selector, array $args ): void {
618 if ( empty( $pointer_id ) || empty( $selector ) || empty( $args['content'] ) ) {
619 return;
620 }
621
622 $keyboard_shortcut = '';
623 if ( 'tp33_edit_quick_navigation' === $pointer_id ) {
624 $keyboard_shortcut = <<<JS
625 content: options.content.replace( /%metaKey%/g, window?.navigator?.platform?.includes( 'Mac' ) ? wp.i18n._x( '⌘', 'keyboard shortcut modifier key on a Mac keyboard', 'tablepress' ) : wp.i18n._x( 'Ctrl+', 'keyboard shortcut modifier key on a non-Mac keyboard', 'tablepress' ) ),\n
626 JS;
627 }
628
629 /*
630 * Print JS code for the feature pointers, extended with event handling for opened/closed "Screen Options", so that pointers can
631 * be repositioned. 210 ms is slightly slower than jQuery's "fast" value, to allow all elements to reach their original position.
632 */
633 ?>
634 <script>
635 ( ( $ ) => {
636 let options = <?php echo wp_json_encode( $args, JSON_HEX_TAG | JSON_UNESCAPED_SLASHES ); ?>;
637 if ( ! options ) {
638 return;
639 }
640
641 options = {
642 ...options,
643 <?php echo $keyboard_shortcut; ?>
644 close() {
645 $.post( ajaxurl, {
646 pointer: '<?php echo $pointer_id; ?>',
647 action: 'dismiss-wp-pointer'
648 } );
649 $( this ).pointer( { 'disabled': true } );
650 },
651 };
652
653 $( () => setTimeout( () => $( '<?php echo $selector; ?>' ).pointer( options ).pointer( 'open' ), 1 ) );
654
655 $( document ).on( 'screen:options:open screen:options:close', () => {
656 setTimeout( () => $( '<?php echo $selector; ?>' ).pointer( 'reposition' ), 210 );
657 } );
658 } )( jQuery );
659 </script>
660 <?php
661 }
662
663 } // class TablePress_View
664