PluginProbe ʕ •ᴥ•ʔ
Admin Help Docs / 2.0.1.1
Admin Help Docs v2.0.1.1
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 1 month ago docs 1 month ago img 1 month ago js 1 month ago tabs 1 month ago api.php 1 month ago cleanup.php 1 month ago colors.php 1 month ago deprecated.php 1 month ago header.php 1 month ago helpers.php 1 month ago index.php 1 month ago menu.php 1 month ago plugins-page.php 1 month ago post-type-help-doc-imports.php 1 month ago post-type-help-docs.php 1 month ago shortcodes.php 1 month ago taxonomy-folders.php 1 month 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();