PluginProbe ʕ •ᴥ•ʔ
Admin Help Docs / 2.0.3
Admin Help Docs v2.0.3
2.0.3 2.0.2 2.0.1.1 trunk 1.4.3.2 2.0.0 2.0.0.1 2.0.0.2 2.0.1
admin-help-docs / inc / shortcodes.php
admin-help-docs / inc Last commit date
css 20 hours ago docs 20 hours ago img 20 hours ago js 20 hours ago tabs 20 hours ago api.php 20 hours ago cleanup.php 20 hours ago colors.php 20 hours ago deprecated.php 20 hours ago header.php 20 hours ago helpers.php 20 hours ago index.php 20 hours ago menu.php 20 hours ago plugins-page.php 20 hours ago post-type-help-doc-imports.php 20 hours ago post-type-help-docs.php 20 hours ago shortcodes.php 20 hours ago taxonomy-folders.php 20 hours ago
shortcodes.php
612 lines
1 <?php
2 /**
3 * Shortcodes
4 */
5
6 namespace PluginRx\AdminHelpDocs;
7
8 if ( ! defined( 'ABSPATH' ) ) exit;
9
10 class Shortcodes {
11
12 /**
13 * The single instance of the class
14 *
15 * @var self|null
16 */
17 private static ?Shortcodes $instance = null;
18
19
20 /**
21 * Get the singleton instance
22 *
23 * @return self
24 */
25 public static function instance() : self {
26 return self::$instance ??= new self();
27 } // End instance()
28
29
30 /**
31 * Constructor
32 */
33 private function __construct() {
34
35 // Display shortcodes without executing them
36 add_shortcode( 'dont_do_shortcode', [ $this, 'dont_do_shortcode' ] );
37
38 // Deprecated: Add custom CSS to documents
39 add_shortcode( 'helpdocs_css', [ $this, 'helpdocs_css' ] );
40
41 // Stale posts and and pages
42 add_shortcode( 'helpdocs_stale_content', [ $this, 'stale_content' ] );
43
44 // Display information about the currently logged-in user
45 add_shortcode( 'helpdocs_current_user', [ $this, 'current_user' ] );
46
47 // Display site information like name and URL and other site options
48 add_shortcode( 'helpdocs_site_info', [ $this, 'site_info' ] );
49
50 // List all posts with certain criteria
51 add_shortcode( 'helpdocs_posts', [ $this, 'posts' ] );
52
53 // List all users with certain criteria
54 add_shortcode( 'helpdocs_users', [ $this, 'users' ] );
55
56 } // End __construct()
57
58
59 /**
60 * Display shortcodes without executing them
61 * USAGE: [dont_do_shortcode click_to_copy="false" code="false"][your_shortcode_here][/dont_do_shortcode]
62 *
63 * @param array $atts
64 * @return string
65 */
66 public function dont_do_shortcode( $atts, $content = null ) : string {
67 $atts = shortcode_atts( [
68 'content' => '',
69 'code' => true,
70 'click_to_copy' => true
71 ], $atts );
72
73 $wrapper = ( strtolower( sanitize_text_field( $atts[ 'code' ] ) ) == 'false' ) ? 'span' : 'code';
74 $click_to_copy = ( strtolower( sanitize_text_field( $atts[ 'click_to_copy' ] ) ) == 'false' ) ? false : true;
75
76 // Support legacy method of passing content as an attribute with curly braces instead of square brackets
77 if ( empty( $content ) && ! empty( $atts[ 'content' ] ) ) {
78 $content = $atts[ 'content' ];
79 $content = str_replace( '{', '[[', $content );
80 $content = str_replace( '}', ']]', $content );
81 }
82
83 $click_to_copy_class = $click_to_copy ? ' helpdocs-click-to-copy' : '';
84
85 return '<' . $wrapper . ' class="helpdocs_dont_do_shortcode' . $click_to_copy_class . '">' . $content . '</' . $wrapper . '>';
86 } // End dont_do_shortcode()
87
88
89 /**
90 * Add custom CSS (External or Inline) to the document
91 *
92 * @deprecated Add CSS to main docs page from the settings page now.
93 * @return string
94 */
95 public function helpdocs_css() : string {
96 _deprecated_function( __FUNCTION__, '2.0', 'Add CSS to main docs page from the settings page now.' );
97 return '';
98 } // End helpdocs_css()
99
100
101 /**
102 * Shortcode: List stale posts or pages that haven't been updated in over X time.
103 *
104 * Usage:
105 * [helpdocs_stale_content post_types="post, custom_post_type" since="6 months"]
106 * [helpdocs_stale_content post_types="page" since="1 year"]
107 * [helpdocs_stale_content since="90 days"]
108 * [helpdocs_stale_content since="2 weeks" limit="20"]
109 *
110 * @param array $atts Shortcode attributes.
111 * @return string Rendered HTML output.
112 */
113 public function stale_content( $atts ) {
114 $atts = shortcode_atts(
115 [
116 'since' => '1 year', // e.g. "6 months", "90 days", "2 weeks", "1 year"
117 'limit' => 50,
118 'post_types' => 'post, page',
119 ],
120 $atts,
121 );
122
123 // Build post type array — default to post + page if blank.
124 if ( ! empty( $atts[ 'post_types' ] ) ) {
125 $post_types = array_map( 'sanitize_key', array_map( 'trim', explode( ',', $atts[ 'post_types' ] ) ) );
126 } else {
127 $post_types = [ 'post', 'page' ];
128 }
129
130 // Parse the "since" value into a cutoff date.
131 $cutoff = Helpers::parse_since_to_date( $atts[ 'since' ] );
132
133 if ( ! $cutoff ) {
134 return '<p class="helpdocs-error">'
135 . esc_html__( 'Invalid "since" value. Use a format like "6 months", "90 days", "2 weeks", or "1 year".', 'admin-help-docs' )
136 . '</p>';
137 }
138
139 $query = new \WP_Query( [
140 'post_type' => $post_types,
141 'post_status' => 'publish',
142 'posts_per_page' => (int) $atts[ 'limit' ],
143 'orderby' => 'modified',
144 'order' => 'ASC', // Oldest first — most stale at the top.
145 'date_query' => [
146 [
147 'column' => 'post_modified_gmt',
148 'before' => $cutoff,
149 ],
150 ],
151 ] );
152
153 if ( ! $query->have_posts() ) {
154 return '<p class="helpdocs-table-none">'
155 . esc_html__( 'No content due for review was found.', 'admin-help-docs' )
156 . '</p>';
157 }
158
159 $rows = '';
160 while ( $query->have_posts() ) {
161 $query->the_post();
162
163 $last_modified = get_the_modified_date( 'Y-m-d' );
164
165 $rows .= sprintf(
166 '<tr>
167 <td class="helpdocs-table-title"><a href="%s">%s</a></td>
168 <td class="helpdocs-table-type">%s</td>
169 <td class="helpdocs-table-last-modified">%s</td>
170 <td class="helpdocs-table-age">%s</td>
171 </tr>',
172 esc_url( get_permalink() ),
173 esc_html( get_the_title() ),
174 esc_html( get_post_type_object( get_post_type() )->labels->singular_name ),
175 esc_html( $last_modified ),
176 esc_html( Helpers::time_elapsed_string( $last_modified ) )
177 );
178 }
179 wp_reset_postdata();
180
181 return sprintf(
182 '<table class="helpdocs-table">
183 <thead>
184 <tr>
185 <th class="helpdocs-table-title">%s</th>
186 <th class="helpdocs-table-type">%s</th>
187 <th class="helpdocs-table-last-modified">%s</th>
188 <th class="helpdocs-table-age">%s</th>
189 </tr>
190 </thead>
191 <tbody>%s</tbody>
192 </table>',
193 esc_html__( 'Title', 'admin-help-docs' ),
194 esc_html__( 'Type', 'admin-help-docs' ),
195 esc_html__( 'Last Modified', 'admin-help-docs' ),
196 esc_html__( 'Age', 'admin-help-docs' ),
197 $rows
198 );
199 } // End stale_content()
200
201
202 /**
203 * Shortcode: Display information about the currently logged-in user.
204 *
205 * Usage:
206 * [helpdocs_current_user] — Display name
207 * [helpdocs_current_user field="first_name"] — First name
208 * [helpdocs_current_user field="user_email"] — Email address
209 * [helpdocs_current_user field="my_custom_meta"] — Any custom user meta key
210 *
211 * @param array $atts Shortcode attributes.
212 * @return string The user field value, or empty string if not found.
213 */
214 public function current_user( $atts ) {
215 $atts = shortcode_atts( [
216 'field' => 'display_name',
217 'date_format' => '',
218 ], $atts );
219
220 $user = wp_get_current_user();
221 if ( ! $user || ! $user->exists() ) {
222 return '';
223 }
224
225 $field = sanitize_key( $atts[ 'field' ] );
226 $value = '';
227 if ( isset( $user->$field ) ) {
228 $value = $user->$field;
229 } else {
230 $meta = get_user_meta( $user->ID, $field, true );
231 if ( $meta ) {
232 $value = $meta;
233 }
234 }
235 if ( $value === '' ) {
236 return '';
237 }
238
239 if ( ! empty( $atts[ 'date_format' ] ) ) {
240 $timestamp = is_numeric( $value ) ? (int) $value : strtotime( $value );
241 if ( $timestamp ) {
242 return esc_html( date_i18n( $atts[ 'date_format' ], $timestamp ) );
243 }
244 }
245 return esc_html( $value );
246 } // End current_user()
247
248
249 /**
250 * Shortcode: Display site information like name and URL and other site options.
251 *
252 * Usage:
253 * [helpdocs_site_info field="site_title"] — Site title
254 * [helpdocs_site_info field="admin_email"] — Admin email
255 *
256 * @param array $atts Shortcode attributes.
257 * @return string The requested site information, or empty string if not found.
258 */
259 public function site_info( $atts ) {
260 $atts = shortcode_atts( [
261 'field' => 'site_title',
262 'date_format' => '',
263 ], $atts );
264
265 $map = [
266 'site_title' => 'blogname',
267 'tagline' => 'blogdescription',
268 'admin_email' => 'admin_email',
269 'timezone' => 'timezone_string',
270 'permalink_structure' => 'permalink_structure',
271 'language' => 'WPLANG',
272 'wp_version' => null,
273 'front_page' => null,
274 'posts_page' => null,
275 'date_format' => 'date_format',
276 'time_format' => 'time_format',
277 'uploads_path' => 'upload_path',
278 'site_url' => 'siteurl',
279 'home_url' => 'home',
280 'charset' => 'blog_charset',
281 'posts_per_page' => 'posts_per_page',
282 'anyone_can_register' => 'users_can_register',
283 'default_role' => 'default_role',
284 'comments_open' => 'default_comment_status',
285 ];
286
287 $field = sanitize_key( $atts[ 'field' ] );
288 $value = '';
289 if ( $field === 'wp_version' ) {
290 global $wp_version;
291 $value = $wp_version;
292 } elseif ( $field === 'front_page' ) {
293 $page_id = get_option( 'page_on_front' );
294 $value = $page_id ? get_the_title( $page_id ) : esc_html__( 'Latest Posts', 'admin-help-docs' );
295 } elseif ( $field === 'posts_page' ) {
296 $page_id = get_option( 'page_for_posts' );
297 $value = $page_id ? get_the_title( $page_id ) : esc_html__( 'Not set', 'admin-help-docs' );
298 } elseif ( isset( $map[ $field ] ) ) {
299 $value = get_option( $map[ $field ], '' );
300 } else {
301 $value = get_option( $field, '' );
302 }
303 if ( $value === '' ) {
304 return '';
305 }
306 if ( ! empty( $atts[ 'date_format' ] ) ) {
307 $timestamp = is_numeric( $value ) ? (int) $value : strtotime( $value );
308 if ( $timestamp ) {
309 return esc_html( date_i18n( $atts[ 'date_format' ], $timestamp ) );
310 }
311 }
312 return esc_html( $value );
313 } // End site_info()
314
315
316 /**
317 * Shortcode: List posts with various criteria.
318 *
319 * Usage:
320 * [helpdocs_posts] — Lists recent posts and pages.
321 * [helpdocs_posts post_type="post, page" post_status="publish, draft" limit="10" order="ASC" orderby="title" date_format="F j, Y"]
322 * [helpdocs_posts post_type="custom_post_type" author="admin, editor" category="news, updates" tag="featured"]
323 * [helpdocs_posts meta_key="my_meta_key" meta_value="my_meta_value"]
324 *
325 * @param array $atts Shortcode attributes.
326 * @return string Rendered HTML output.
327 */
328 public function posts( $atts ) {
329 $reserved = [ 'post_type', 'post_status', 'limit', 'order', 'orderby', 'date_format', 'author', 'category', 'tag', 'before', 'after', 'post_password__not', 'post_password' ];
330 $atts = $atts ? $atts : [];
331 $split = function( $value ) {
332 return array_map( 'trim', explode( ',', $value ) );
333 };
334 $post_types = isset( $atts[ 'post_type' ] ) ? $split( $atts[ 'post_type' ] ) : [ 'post', 'page' ];
335 $post_statuses = isset( $atts[ 'post_status' ] ) ? $split( $atts[ 'post_status' ] ) : [ 'publish' ];
336 $order = isset( $atts[ 'order' ] ) ? strtoupper( $atts[ 'order' ] ) : 'DESC';
337 $orderby = isset( $atts[ 'orderby' ] ) ? $atts[ 'orderby' ] : 'date';
338 $limit = isset( $atts[ 'limit' ] ) ? (int) $atts[ 'limit' ] : 50;
339 $date_format = isset( $atts[ 'date_format' ] ) ? $atts[ 'date_format' ] : 'Y-m-d';
340 $show_type = count( $post_types ) > 1;
341 $show_status = count( $post_statuses ) > 1;
342 $query_args = [
343 'post_type' => $post_types,
344 'post_status' => $post_statuses,
345 'posts_per_page' => $limit,
346 'order' => in_array( $order, [ 'ASC', 'DESC' ] ) ? $order : 'DESC',
347 'orderby' => sanitize_key( $orderby ),
348 ];
349 if ( isset( $atts[ 'author' ] ) ) {
350 $authors = $split( $atts[ 'author' ] );
351 $author_ids = [];
352 foreach ( $authors as $author ) {
353 if ( is_numeric( $author ) ) {
354 $author_ids[] = (int) $author;
355 } else {
356 $user = get_user_by( 'login', $author );
357 if ( $user ) {
358 $author_ids[] = $user->ID;
359 }
360 }
361 }
362 if ( $author_ids ) {
363 $query_args[ 'author__in' ] = $author_ids;
364 }
365 }
366 if ( isset( $atts[ 'category' ] ) ) {
367 $cats = $split( $atts[ 'category' ] );
368 $cat_ids = [];
369 foreach ( $cats as $cat ) {
370 if ( is_numeric( $cat ) ) {
371 $cat_ids[] = (int) $cat;
372 } else {
373 $term = get_term_by( 'slug', $cat, 'category' );
374 if ( $term ) {
375 $cat_ids[] = $term->term_id;
376 }
377 }
378 }
379 if ( $cat_ids ) {
380 $query_args[ 'category__in' ] = $cat_ids;
381 }
382 }
383 if ( isset( $atts[ 'tag' ] ) ) {
384 $tags = $split( $atts[ 'tag' ] );
385 $tag_ids = [];
386 foreach ( $tags as $t ) {
387 if ( is_numeric( $t ) ) {
388 $tag_ids[] = (int) $t;
389 } else {
390 $term = get_term_by( 'slug', $t, 'post_tag' );
391 if ( $term ) {
392 $tag_ids[] = $term->term_id;
393 }
394 }
395 }
396 if ( $tag_ids ) {
397 $query_args[ 'tag__in' ] = $tag_ids;
398 }
399 }
400 if ( isset( $atts[ 'post_password__not' ] ) && $atts[ 'post_password__not' ] === '' ) {
401 $query_args[ 'has_password' ] = true;
402 }
403 if ( isset( $atts[ 'post_password' ] ) && $atts[ 'post_password' ] === '' ) {
404 $query_args[ 'has_password' ] = false;
405 }
406 if ( isset( $atts[ 'before' ] ) || isset( $atts[ 'after' ] ) ) {
407 $date_query = [];
408 if ( isset( $atts[ 'after' ] ) ) {
409 $date_query[ 'after' ] = sanitize_text_field( $atts[ 'after' ] );
410 }
411 if ( isset( $atts[ 'before' ] ) ) {
412 $date_query[ 'before' ] = sanitize_text_field( $atts[ 'before' ] );
413 }
414 $date_query[ 'inclusive' ] = true;
415 $query_args[ 'date_query' ] = [ $date_query ];
416 }
417 $meta_query = [];
418 foreach ( $atts as $key => $value ) {
419 if ( in_array( $key, $reserved ) ) {
420 continue;
421 }
422 $negated = str_ends_with( $key, '__not' );
423 $meta_key = $negated ? substr( $key, 0, -5 ) : $key;
424 $values = $split( $value );
425 $compare = $negated ? 'NOT IN' : 'IN';
426 $meta_query[] = [
427 'key' => sanitize_key( $meta_key ),
428 'value' => $values,
429 'compare' => $compare,
430 ];
431 }
432 if ( $meta_query ) {
433 $meta_query[ 'relation' ] = 'AND';
434 $query_args[ 'meta_query' ] = $meta_query;
435 }
436 $query = new \WP_Query( $query_args );
437 if ( ! $query->have_posts() ) {
438 return '<p class="helpdocs-table-none">'
439 . esc_html__( 'No posts found.', 'admin-help-docs' )
440 . '</p>';
441 }
442 $rows = '';
443 while ( $query->have_posts() ) {
444 $query->the_post();
445 $type_cell = $show_type ? '<td class="helpdocs-posts-type">' . esc_html( get_post_type_object( get_post_type() )->labels->singular_name ) . '</td>' : '';
446 $status_label = get_post_status_object( get_post_status() );
447 $status_cell = $show_status ? '<td class="helpdocs-posts-status">' . esc_html( $status_label ? $status_label->label : get_post_status() ) . '</td>' : '';
448 $rows .= sprintf(
449 '<tr>
450 <td class="helpdocs-posts-title"><a href="%s">%s</a></td>
451 %s
452 %s
453 <td class="helpdocs-posts-date">%s</td>
454 </tr>',
455 esc_url( get_permalink() ),
456 esc_html( get_the_title() ),
457 $type_cell,
458 $status_cell,
459 esc_html( date_i18n( $date_format, get_the_date( 'U' ) ) )
460 );
461 }
462 wp_reset_postdata();
463 $type_header = $show_type ? '<th class="helpdocs-posts-type">' . esc_html__( 'Type', 'admin-help-docs' ) . '</th>' : '';
464 $status_header = $show_status ? '<th class="helpdocs-posts-status">' . esc_html__( 'Status', 'admin-help-docs' ) . '</th>' : '';
465 return sprintf(
466 '<table class="helpdocs-table">
467 <thead>
468 <tr>
469 <th class="helpdocs-posts-title">%s</th>
470 %s
471 %s
472 <th class="helpdocs-posts-date">%s</th>
473 </tr>
474 </thead>
475 <tbody>%s</tbody>
476 </table>',
477 esc_html__( 'Title', 'admin-help-docs' ),
478 $type_header,
479 $status_header,
480 esc_html__( 'Published', 'admin-help-docs' ),
481 $rows
482 );
483 } // End posts()
484
485
486 /**
487 * Shortcode: List users with various criteria.
488 *
489 * Usage:
490 * [helpdocs_users] — Lists all users.
491 * [helpdocs_users role="administrator, editor" role__not="subscriber" limit="20" order="ASC" orderby="display_name" date_format="F j, Y" before="2024-01-01" after="2023-01-01"]
492 * [helpdocs_users meta_key="my_meta_key" meta_value="my_meta_value" can_view="administrator, editor"]
493 *
494 * @param array $atts Shortcode attributes.
495 * @return string Rendered HTML output.
496 */
497 public function users( $atts ) {
498 $reserved = [ 'role', 'role__not', 'limit', 'order', 'orderby', 'date_format', 'before', 'after', 'can_view' ];
499 $atts = $atts ? $atts : [];
500 $split = function( $value ) {
501 return array_map( 'trim', explode( ',', $value ) );
502 };
503 $can_view = isset( $atts[ 'can_view' ] ) ? $split( $atts[ 'can_view' ] ) : [ 'administrator' ];
504 $current_user = wp_get_current_user();
505 if ( ! $current_user->exists() ) {
506 return '';
507 }
508 $user_roles = (array) $current_user->roles;
509 $has_access = array_intersect( $user_roles, $can_view );
510 if ( ! $has_access ) {
511 return '';
512 }
513 $order = isset( $atts[ 'order' ] ) ? strtoupper( $atts[ 'order' ] ) : 'ASC';
514 $orderby = isset( $atts[ 'orderby' ] ) ? $atts[ 'orderby' ] : 'display_name';
515 $limit = isset( $atts[ 'limit' ] ) ? (int) $atts[ 'limit' ] : 50;
516 $date_format = isset( $atts[ 'date_format' ] ) ? $atts[ 'date_format' ] : 'Y-m-d';
517 $query_args = [
518 'number' => $limit,
519 'order' => in_array( $order, [ 'ASC', 'DESC' ] ) ? $order : 'ASC',
520 'orderby' => sanitize_key( $orderby ),
521 ];
522 if ( isset( $atts[ 'role' ] ) ) {
523 $query_args[ 'role__in' ] = $split( $atts[ 'role' ] );
524 }
525 if ( isset( $atts[ 'role__not' ] ) ) {
526 $query_args[ 'role__not_in' ] = $split( $atts[ 'role__not' ] );
527 }
528 if ( isset( $atts[ 'before' ] ) || isset( $atts[ 'after' ] ) ) {
529 $date_query = [];
530 if ( isset( $atts[ 'after' ] ) ) {
531 $date_query[ 'after' ] = sanitize_text_field( $atts[ 'after' ] );
532 }
533 if ( isset( $atts[ 'before' ] ) ) {
534 $date_query[ 'before' ] = sanitize_text_field( $atts[ 'before' ] );
535 }
536 $date_query[ 'inclusive' ] = true;
537 $query_args[ 'date_query' ] = [ $date_query ];
538 }
539 $meta_query = [];
540 foreach ( $atts as $key => $value ) {
541 if ( in_array( $key, $reserved ) ) {
542 continue;
543 }
544 $negated = str_ends_with( $key, '__not' );
545 $meta_key = $negated ? substr( $key, 0, -5 ) : $key;
546 $values = $split( $value );
547 $compare = $negated ? 'NOT IN' : 'IN';
548 $meta_query[] = [
549 'key' => sanitize_key( $meta_key ),
550 'value' => $values,
551 'compare' => $compare,
552 ];
553 }
554 if ( $meta_query ) {
555 $meta_query[ 'relation' ] = 'AND';
556 $query_args[ 'meta_query' ] = $meta_query;
557 }
558 $users = get_users( $query_args );
559 if ( empty( $users ) ) {
560 return '<p class="helpdocs-table-none">'
561 . esc_html__( 'No users found.', 'admin-help-docs' )
562 . '</p>';
563 }
564 $rows = '';
565 foreach ( $users as $user ) {
566 $roles = array_map( function( $role ) {
567 $wp_roles = wp_roles();
568 return isset( $wp_roles->roles[ $role ] ) ? $wp_roles->roles[ $role ][ 'name' ] : $role;
569 }, $user->roles );
570 $rows .= sprintf(
571 '<tr>
572 <td class="helpdocs-users-name"><a href="%s">%s</a></td>
573 <td class="helpdocs-users-login">%s</td>
574 <td class="helpdocs-users-email"><a href="mailto:%s">%s</a></td>
575 <td class="helpdocs-users-roles">%s</td>
576 <td class="helpdocs-users-registered">%s</td>
577 </tr>',
578 esc_url( get_edit_user_link( $user->ID ) ),
579 esc_html( $user->display_name ),
580 esc_html( $user->user_login ),
581 esc_attr( $user->user_email ),
582 esc_html( $user->user_email ),
583 esc_html( implode( ', ', $roles ) ),
584 esc_html( date_i18n( $date_format, strtotime( $user->user_registered ) ) )
585 );
586 }
587 return sprintf(
588 '<table class="helpdocs-table">
589 <thead>
590 <tr>
591 <th class="helpdocs-users-name">%s</th>
592 <th class="helpdocs-users-login">%s</th>
593 <th class="helpdocs-users-email">%s</th>
594 <th class="helpdocs-users-roles">%s</th>
595 <th class="helpdocs-users-registered">%s</th>
596 </tr>
597 </thead>
598 <tbody>%s</tbody>
599 </table>',
600 esc_html__( 'Display Name', 'admin-help-docs' ),
601 esc_html__( 'Username', 'admin-help-docs' ),
602 esc_html__( 'Email', 'admin-help-docs' ),
603 esc_html__( 'Role(s)', 'admin-help-docs' ),
604 esc_html__( 'Registered', 'admin-help-docs' ),
605 $rows
606 );
607 } // End users()
608
609 }
610
611
612 Shortcodes::instance();