PluginProbe
Insert Pages / 3.9.0
Insert Pages v3.9.0
3.11.5 trunk 1.4 2.4 2.5 2.6 2.7 2.7.1 2.7.2 2.8 2.9 2.9.1 3.0 3.0.1 3.0.2 3.1 3.1.1 3.1.2 3.1.3 3.1.4 3.1.5 3.1.6 3.1.7 3.1.8 3.1.9 All 79 releases
insert-pages / insert-pages.php

insert-pages.php in Insert Pages 3.9.0, at insert-pages.php

1,868 lines 70.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Plugin Name: Insert Pages
4 * Plugin URI: https://github.com/uhm-coe/insert-pages
5 * Description: Insert Pages lets you embed any WordPress content (e.g., pages, posts, custom post types) into other WordPress content using the Shortcode API.
6 * Author: Paul Ryan
7 * Text Domain: insert-pages
8 * Domain Path: /languages
9 * License: GPL2
10 * Requires at least: 3.3.0
11 * Version: 3.9.0
12 *
13 * @package insert-pages
14 */
15
16 /*
17 Copyright 2011 Paul Ryan (email: prar@hawaii.edu)
18
19 This program is free software; you can redistribute it and/or modify
20 it under the terms of the GNU General Public License, version 2, as
21 published by the Free Software Foundation.
22
23 This program is distributed in the hope that it will be useful,
24 but WITHOUT ANY WARRANTY; without even the implied warranty of
25 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26 GNU General Public License for more details.
27
28 You should have received a copy of the GNU General Public License
29 along with this program; if not, write to the Free Software
30 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
31 */
32
33 /**
34 * Shortcode Format:
35 * [insert page='{slug}|{id}|{url}' display='title|link|excerpt|excerpt-only|content|title-content|post-thumbnail|all|{custom-template.php}' class='any-classes' id='any-id' [inline] [public] querystring='{url-encoded-values}' size='post-thumbnail|thumbnail|medium|large|full|{custom-size}']
36 */
37
38 if ( ! class_exists( 'InsertPagesPlugin' ) ) {
39 /**
40 * Class InsertPagesPlugin
41 */
42 class InsertPagesPlugin {
43 /**
44 * Stack tracking inserted pages (for loop detection).
45 *
46 * @var array Array of page ids inserted.
47 */
48 protected $inserted_page_ids;
49
50 /**
51 * Flag to only render the TinyMCE plugin dialog once.
52 *
53 * @var boolean
54 */
55 private static $link_dialog_printed = false;
56
57 /**
58 * Flag checked when rendering TinyMCE modal to ensure that required scripts
59 * and styles were enqueued (normally done in `admin_init` hook).
60 *
61 * @var boolean
62 */
63 private static $is_admin_initialized = false;
64
65 /**
66 * Singleton plugin instance.
67 *
68 * @var object Plugin instance.
69 */
70 protected static $instance = null;
71
72
73 /**
74 * Access this plugin's working instance.
75 *
76 * @return object Object of this class.
77 */
78 public static function get_instance() {
79 if ( null === self::$instance ) {
80 self::$instance = new self();
81 }
82
83 return self::$instance;
84 }
85
86
87 /**
88 * Disable constructor to enforce a single plugin instance..
89 */
90 protected function __construct() {
91 }
92
93
94 /**
95 * Action hook: WordPress 'init'.
96 *
97 * @return void
98 */
99 public function insert_pages_init() {
100 $options = get_option( 'wpip_settings' );
101
102 // Register the [insert] shortcode.
103 add_shortcode( 'insert', array( $this, 'insert_pages_handle_shortcode_insert' ) );
104
105 // Register the gutenberg block so we can populate it via server side
106 // rendering. Note: only register it once (some plugins, like Advanced
107 // Custom Fields, create a scenario where this init hook gets called
108 // multiple times).
109 if (
110 function_exists( 'register_block_type' ) &&
111 isset( $options['wpip_gutenberg_block'] ) &&
112 'enabled' === $options['wpip_gutenberg_block'] &&
113 class_exists( 'WP_Block_Type_Registry' ) &&
114 ! WP_Block_Type_Registry::get_instance()->is_registered( 'insert-pages/block' )
115 ) {
116 // Automatically load dependencies and version.
117 $asset_file = include plugin_dir_path( __FILE__ ) . 'lib/gutenberg-block/build/index.asset.php';
118
119 wp_register_script(
120 'insert-pages-gutenberg-block',
121 plugins_url( 'lib/gutenberg-block/build/index.js', __FILE__ ),
122 $asset_file['dependencies'],
123 $asset_file['version'],
124 array(
125 'in_footer' => false,
126 )
127 );
128
129 wp_register_style(
130 'insert-pages-gutenberg-block',
131 plugins_url( 'lib/gutenberg-block/build/index.css', __FILE__ ),
132 array(),
133 $asset_file['version']
134 );
135
136 register_block_type(
137 'insert-pages/block',
138 array(
139 'editor_style' => 'insert-pages-gutenberg-block',
140 'editor_script' => 'insert-pages-gutenberg-block',
141 'attributes' => array(
142 'url' => array(
143 'type' => 'string',
144 'default' => '',
145 ),
146 'page' => array(
147 'type' => 'number',
148 'default' => 0,
149 ),
150 'display' => array(
151 'type' => 'string',
152 'default' => 'title',
153 ),
154 'template' => array(
155 'type' => 'string',
156 'default' => '',
157 ),
158 'class' => array(
159 'type' => 'string',
160 'default' => '',
161 ),
162 'id' => array(
163 'type' => 'string',
164 'default' => '',
165 ),
166 'inline' => array(
167 'type' => 'bool',
168 'default' => false,
169 ),
170 'public' => array(
171 'type' => 'bool',
172 'default' => false,
173 ),
174 'querystring' => array(
175 'type' => 'string',
176 'default' => '',
177 ),
178 'size' => array(
179 'type' => 'string',
180 'default' => '',
181 ),
182 ),
183 'render_callback' => array( $this, 'block_render_callback' ),
184 )
185 );
186 }
187 }
188
189
190 /**
191 * Renders the gutenberg block (using legacy server-side rendering).
192 *
193 * @param array $attr Array of block attributes.
194 * @return string Rendered inserted page.
195 */
196 public function block_render_callback( $attr ) {
197 // Display attribute defaults to 'title'; otherwise it is the passed param,
198 // and if the display param is 'custom', it is the value of the 'template'
199 // param.
200 $display = 'title';
201 if ( isset( $attr['display'] ) && strlen( $attr['display'] ) > 0 ) {
202 $display = esc_attr( $attr['display'] );
203 }
204 if ( 'custom' === $display && isset( $attr['template'] ) && strlen( $attr['template'] ) > 0 ) {
205 $display = esc_attr( $attr['template'] );
206 }
207
208 $shortcode = sprintf(
209 '[insert page="%1$s" display="%2$s"%3$s%4$s%5$s%6$s%7$s%8$s]',
210 isset( $attr['page'] ) && strlen( $attr['page'] ) > 0 ? esc_attr( $attr['page'] ) : '0',
211 $display,
212 isset( $attr['class'] ) && strlen( $attr['class'] ) > 0 ? ' class="' . esc_attr( $attr['class'] ) . '"' : '',
213 isset( $attr['id'] ) && strlen( $attr['id'] ) > 0 ? ' id="' . esc_attr( $attr['id'] ) . '"' : '',
214 isset( $attr['querystring'] ) && strlen( $attr['querystring'] ) > 0 ? ' querystring="' . esc_attr( $attr['querystring'] ) . '"' : '',
215 isset( $attr['size'] ) && strlen( $attr['size'] ) > 0 ? ' size="' . esc_attr( $attr['size'] ) . '"' : '',
216 isset( $attr['inline'] ) && 'true' === $attr['inline'] ? ' inline' : '',
217 isset( $attr['public'] ) && 'true' === $attr['public'] ? ' public' : ''
218 );
219
220 $rendered_shortcode = do_shortcode( $shortcode );
221
222 // If we're in the block editor, enqueue any layout styles for blocks
223 // (normally this is done in core but since we're not in the main context,
224 // we need to do so manually). For example, the Grid block uses layout
225 // styles to set the number of columns.
226 // See: https://github.com/WordPress/WordPress/blob/6.6.2/wp-includes/block-supports/layout.php#L539-L551.
227 // See: https://developer.wordpress.org/reference/functions/wp_style_engine_get_stylesheet_from_css_rules/.
228 // See: https://developer.wordpress.org/reference/functions/wp_add_inline_style/.
229 $current_screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null;
230 if (
231 ! empty( $current_screen ) && $current_screen->is_block_editor() &&
232 ! in_array( $display, array( 'title', 'link', 'post-thumbnail' ), true ) &&
233 function_exists( 'wp_style_engine_get_stylesheet_from_context' )
234 ) {
235 $layout_styles = wp_style_engine_get_stylesheet_from_context( 'block-supports' );
236 if ( ! empty( $layout_styles ) ) {
237 wp_add_inline_style( 'wp-block-library', $layout_styles );
238 }
239 }
240
241 return $rendered_shortcode;
242 }
243
244
245 /**
246 * Load gutenberg block resources only when editing (only if Gutenberg block
247 * setting is enabled in Insert Pages settings).
248 *
249 * Action hook: enqueue_block_editor_assets
250 *
251 * @return void
252 */
253 public function insert_pages_enqueue_block_editor_assets() {
254 $options = get_option( 'wpip_settings' );
255 if ( isset( $options['wpip_gutenberg_block'] ) && 'enabled' === $options['wpip_gutenberg_block'] ) {
256 }
257 }
258
259
260 /**
261 * Action hook: WordPress 'admin_init'.
262 *
263 * @return void
264 */
265 public function insert_pages_admin_init() {
266 // Get options set in WordPress dashboard (Settings > Insert Pages).
267 $options = get_option( 'wpip_settings' );
268 if ( false === $options || ! is_array( $options ) || ! array_key_exists( 'wpip_format', $options ) || ! array_key_exists( 'wpip_wrapper', $options ) || ! array_key_exists( 'wpip_insert_method', $options ) || ! array_key_exists( 'wpip_tinymce_filter', $options ) ) {
269 $options = wpip_set_defaults();
270 }
271
272 // Register the TinyMCE toolbar button script.
273 wp_enqueue_script(
274 'wpinsertpages',
275 plugins_url( '/js/wpinsertpages.js', __FILE__ ),
276 array( 'wpdialogs' ),
277 '20221216',
278 false
279 );
280 wp_localize_script(
281 'wpinsertpages',
282 'wpInsertPagesL10n',
283 array(
284 'update' => __( 'Update', 'insert-pages' ),
285 'save' => __( 'Insert Page', 'insert-pages' ),
286 'noTitle' => __( '(no title)', 'insert-pages' ),
287 'noMatchesFound' => __( 'No matches found.', 'insert-pages' ),
288 'l10n_print_after' => 'try{convertEntities(wpInsertPagesL10n);}catch(e){};',
289 'format' => $options['wpip_format'],
290 'private' => __( 'Private', 'insert-pages' ),
291 'tinymce_state' => $this->get_tinymce_state(),
292 )
293 );
294
295 // Register the TinyMCE toolbar button styles.
296 wp_enqueue_style(
297 'wpinsertpagescss',
298 plugins_url( '/css/wpinsertpages.css', __FILE__ ),
299 array( 'wp-jquery-ui-dialog' ),
300 '20221216'
301 );
302
303 /**
304 * Register TinyMCE plugin for the toolbar button in normal mode (register
305 * TinyMCE plugin filters below before plugins_loaded in compatibility
306 * mode, to work around a SiteOrigin PageBuilder bug).
307 *
308 * @see https://wordpress.org/support/topic/button-in-the-toolbar-of-tinymce-disappear-conflict-page-builder/
309 */
310 if ( 'normal' === $options['wpip_tinymce_filter'] ) {
311 add_filter( 'mce_external_plugins', array( $this, 'insert_pages_handle_filter_mce_external_plugins' ) );
312 add_filter( 'mce_buttons', array( $this, 'insert_pages_handle_filter_mce_buttons' ) );
313 }
314
315 // Load the translations.
316 load_plugin_textdomain(
317 'insert-pages',
318 false,
319 plugin_basename( __DIR__ ) . '/languages'
320 );
321
322 self::$is_admin_initialized = true;
323 }
324
325
326 /**
327 * Shortcode hook: Replace the [insert ...] shortcode with the inserted page's content.
328 *
329 * @param array $atts Shortcode attributes.
330 * @param string $content Content to replace shortcode.
331 * @return string Content to replace shortcode.
332 */
333 public function insert_pages_handle_shortcode_insert( $atts, $content = null ) {
334 global $wp_query, $post, $wp_current_filter;
335
336 // Shortcode attributes.
337 $attributes = shortcode_atts(
338 array(
339 'page' => '0',
340 'display' => 'all',
341 'class' => '',
342 'id' => '',
343 'querystring' => '',
344 'size' => '',
345 'inline' => false,
346 'public' => false,
347 ),
348 $atts,
349 'insert'
350 );
351
352 // Validation checks.
353 if ( '0' === $attributes['page'] ) {
354 return $content;
355 }
356
357 // Short circuit if trying to embed same page in itself.
358 if (
359 ( is_object( $post ) && property_exists( $post, 'ID' ) && intval( $attributes['page'] ) === $post->ID ) ||
360 ( is_object( $post ) && property_exists( $post, 'post_name' ) && $attributes['page'] === $post->post_name ) ||
361 ( is_int( $post ) && intval( $attributes['page'] ) === $post )
362 ) {
363 return $content;
364 }
365
366 // Get options set in WordPress dashboard (Settings > Insert Pages).
367 $options = get_option( 'wpip_settings' );
368 if ( false === $options || ! is_array( $options ) || ! array_key_exists( 'wpip_format', $options ) || ! array_key_exists( 'wpip_wrapper', $options ) || ! array_key_exists( 'wpip_insert_method', $options ) || ! array_key_exists( 'wpip_tinymce_filter', $options ) ) {
369 $options = wpip_set_defaults();
370 }
371
372 $attributes['inline'] = ( false !== $attributes['inline'] && 'false' !== $attributes['inline'] ) || array_search( 'inline', $atts, true ) === 0 || ( array_key_exists( 'wpip_wrapper', $options ) && 'inline' === $options['wpip_wrapper'] );
373 /**
374 * Filter the flag indicating whether to wrap the inserted content in inline tags (span).
375 *
376 * @param bool $use_inline_wrapper Indicates whether to wrap the content in span tags.
377 */
378 $attributes['inline'] = apply_filters( 'insert_pages_use_inline_wrapper', $attributes['inline'] );
379 $attributes['wrapper_tag'] = $attributes['inline'] ? 'span' : 'div';
380
381 $attributes['public'] = ( false !== $attributes['public'] && 'false' !== $attributes['public'] ) || array_search( 'public', $atts, true ) === 0 || is_user_logged_in();
382
383 /**
384 * Filter the querystring values applied to every inserted page. Useful
385 * for admins who want to provide the same querystring value to all
386 * inserted pages sitewide.
387 *
388 * @since 3.2.9
389 *
390 * @param string $querystring The querystring value for the inserted page.
391 */
392 $attributes['querystring'] = apply_filters(
393 'insert_pages_override_querystring',
394 str_replace( '{', '[', str_replace( '}', ']', htmlspecialchars_decode( $attributes['querystring'] ) ) )
395 );
396
397 $attributes['should_apply_the_content_filter'] = true;
398 /**
399 * Filter the flag indicating whether to apply the_content filter to post
400 * contents and excerpts that are being inserted.
401 *
402 * @param bool $apply_the_content_filter Indicates whether to apply the_content filter.
403 */
404 $attributes['should_apply_the_content_filter'] = apply_filters( 'insert_pages_apply_the_content_filter', $attributes['should_apply_the_content_filter'] );
405
406 // Disable the_content filter if using inline tags, since wpautop
407 // inserts p tags and we can't have any inside inline elements.
408 if ( $attributes['inline'] ) {
409 $attributes['should_apply_the_content_filter'] = false;
410 }
411
412 /**
413 * Filter the chosen display method, where display can be one of:
414 * title, link, excerpt, excerpt-only, content, post-thumbnail, all, {custom-template.php}
415 * Useful for admins who want to restrict the display sitewide.
416 *
417 * @since 3.2.7
418 *
419 * @param string $display The display method for the inserted page.
420 */
421 $attributes['display'] = apply_filters( 'insert_pages_override_display', $attributes['display'] );
422
423 // If a URL is provided, translate it to a post ID.
424 if ( filter_var( $attributes['page'], FILTER_VALIDATE_URL ) ) {
425 $attributes['page'] = url_to_postid( $attributes['page'] );
426 }
427
428 // Get the WP_Post object from the provided slug, or ID.
429 if ( ! is_numeric( $attributes['page'] ) ) {
430 // Get list of post types that can be inserted (page, post, custom
431 // types), excluding builtin types (nav_menu_item, attachment).
432 $insertable_post_types = array_filter(
433 get_post_types(),
434 array( $this, 'is_post_type_insertable' )
435 );
436 $inserted_page = get_page_by_path( $attributes['page'], OBJECT, $insertable_post_types );
437
438 // If get_page_by_path() didn't find the page, check to see if the slug
439 // was provided instead of the full path (useful for hierarchical pages
440 // that are nested under another page).
441 if ( is_null( $inserted_page ) ) {
442 global $wpdb;
443 $page = $wpdb->get_var( // phpcs:ignore WordPress.DB.DirectDatabaseQuery
444 $wpdb->prepare(
445 "SELECT ID FROM $wpdb->posts WHERE post_name = %s AND (post_status = 'publish' OR post_status = 'private') LIMIT 1",
446 $attributes['page']
447 )
448 );
449 if ( $page ) {
450 $inserted_page = get_post( $page );
451 }
452 }
453
454 $attributes['page'] = $inserted_page ? $inserted_page->ID : $attributes['page'];
455 } else {
456 $inserted_page = get_post( intval( $attributes['page'] ) );
457 }
458
459 // Integration: If WPML is enabled, ensure the inserted page matches the
460 // language of the parent page.
461 if ( is_object( $inserted_page ) && class_exists( 'Sitepress' ) ) {
462 $translated_inserted_page = apply_filters( 'wpml_object_id', $inserted_page->ID, 'any' );
463 if ( ! empty( $translated_inserted_page ) && intval( $translated_inserted_page ) !== $inserted_page->ID ) {
464 $inserted_page = get_post( intval( $translated_inserted_page ) );
465 }
466 }
467
468 // Prevent unprivileged users from inserting private posts from others.
469 if ( is_object( $inserted_page ) && 'publish' !== $inserted_page->post_status ) {
470 $post_type = get_post_type_object( $inserted_page->post_type );
471 $parent_post_author_id = intval( get_the_author_meta( 'ID' ) );
472 if ( ! user_can( $parent_post_author_id, $post_type->cap->read_post, $inserted_page->ID ) ) {
473 $inserted_page = null;
474 }
475 }
476
477 // If inserted page's status is private, don't show to anonymous users
478 // unless 'public' option is set.
479 if ( is_object( $inserted_page ) && 'private' === $inserted_page->post_status && ! $attributes['public'] ) {
480 $inserted_page = null;
481 }
482
483 // Integration: if Simple Membership plugin is used, check that the
484 // current user has permission to see the inserted post.
485 // See: https://simple-membership-plugin.com/simple-membership-miscellaneous-php-tweaks/
486 if ( class_exists( 'SwpmAccessControl' ) ) {
487 $access_ctrl = SwpmAccessControl::get_instance();
488 if ( ! $access_ctrl->can_i_read_post( $inserted_page ) && ! current_user_can( 'edit_files' ) ) {
489 $inserted_page = null;
490 $content = wp_kses_post( $access_ctrl->why() );
491 }
492 }
493
494 // Loop detection: check if the page we are inserting has already been
495 // inserted; if so, short circuit here.
496 if ( ! is_array( $this->inserted_page_ids ) ) {
497 // Initialize stack to the main page that contains inserted page(s).
498 $this->inserted_page_ids = array( get_the_ID() );
499 }
500 if ( isset( $inserted_page->ID ) ) {
501 if ( ! in_array( $inserted_page->ID, $this->inserted_page_ids, true ) ) {
502 // Add the page being inserted to the stack.
503 $this->inserted_page_ids[] = $inserted_page->ID;
504 } else {
505 // Loop detected, so exit without rendering this post.
506 return $content;
507 }
508 }
509
510 // Set any querystring params included in the shortcode.
511 parse_str( $attributes['querystring'], $querystring );
512 $original_get = $_GET; // phpcs:ignore WordPress.Security.NonceVerification
513 $original_request = $_REQUEST; // phpcs:ignore WordPress.Security.NonceVerification
514 foreach ( $querystring as $param => $value ) {
515 $_GET[ $param ] = $value;
516 $_REQUEST[ $param ] = $value;
517 }
518 $original_wp_query_vars = $GLOBALS['wp']->query_vars;
519 if (
520 ! empty( $querystring ) &&
521 isset( $GLOBALS['wp'] ) &&
522 method_exists( $GLOBALS['wp'], 'parse_request' ) &&
523 empty( $GLOBALS['wp']->query_vars['rest_route'] )
524 ) {
525 $GLOBALS['wp']->parse_request( $querystring );
526 }
527
528 // Use "Normal" insert method (get_post).
529 if ( 'legacy' !== $options['wpip_insert_method'] ) {
530
531 // If we couldn't retrieve the page, fire the filter hook showing a not-found message.
532 if ( null === $inserted_page ) {
533 /**
534 * Filter the html that should be displayed if an inserted page was not found.
535 *
536 * @param string $content html to be displayed. Defaults to an empty string.
537 */
538 $content = apply_filters( 'insert_pages_not_found_message', $content );
539
540 // Short-circuit since we didn't find the page.
541 return $content;
542 }
543
544 // Start output buffering so we can save the output to a string.
545 ob_start();
546
547 // If Beaver Builder, SiteOrigin Page Builder, Elementor, or WPBakery
548 // Page Builder (Visual Composer) are enabled, load any cached styles
549 // associated with the inserted page.
550 // Note: Temporarily set the global $post->ID to the inserted page ID,
551 // since both builders rely on the id to load the appropriate styles.
552 if (
553 class_exists( 'UAGB_Post_Assets' ) ||
554 class_exists( 'FLBuilder' ) ||
555 class_exists( 'SiteOrigin_Panels' ) ||
556 class_exists( '\Elementor\Post_CSS_File' ) ||
557 defined( 'VCV_VERSION' ) ||
558 defined( 'WPB_VC_VERSION' )
559 ) {
560 // If we're not in The Loop (i.e., global $post isn't assigned),
561 // temporarily populate it with the post to be inserted so we can
562 // retrieve generated styles for that post. Reset $post to null
563 // after we're done.
564 if ( is_null( $post ) ) {
565 $old_post_id = null;
566 $post = $inserted_page; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
567 } else {
568 $old_post_id = $post->ID;
569 $post->ID = $inserted_page->ID;
570 }
571
572 // Enqueue assets for Ultimate Addons for Gutenberg.
573 // See: https://ultimategutenberg.com/docs/assets-api-third-party-plugins/.
574 if ( class_exists( 'UAGB_Post_Assets' ) ) {
575 $post_assets_instance = new UAGB_Post_Assets( $inserted_page->ID );
576 $post_assets_instance->enqueue_scripts();
577 }
578
579 if ( class_exists( 'FLBuilder' ) ) {
580 FLBuilder::enqueue_layout_styles_scripts( $inserted_page->ID );
581 }
582
583 if ( class_exists( 'SiteOrigin_Panels' ) ) {
584 $renderer = SiteOrigin_Panels::renderer();
585 $renderer->add_inline_css( $inserted_page->ID, $renderer->generate_css( $inserted_page->ID ) );
586 }
587
588 if ( class_exists( '\Elementor\Post_CSS_File' ) ) {
589 $css_file = new \Elementor\Post_CSS_File( $inserted_page->ID );
590 $css_file->enqueue();
591 }
592
593 // Enqueue custom style from WPBakery Page Builder (Visual Composer).
594 if ( defined( 'VCV_VERSION' ) ) {
595 wp_enqueue_style( 'vcv:assets:front:style' );
596 wp_enqueue_script( 'vcv:assets:runtime:script' );
597 wp_enqueue_script( 'vcv:assets:front:script' );
598
599 $bundle_url = get_post_meta( $inserted_page->ID, 'vcvSourceCssFileUrl', true );
600 if ( $bundle_url ) {
601 $version = get_post_meta( $inserted_page->ID, 'vcvSourceCssFileHash', true );
602 if ( ! preg_match( '/^http/', $bundle_url ) ) {
603 if ( ! preg_match( '/assets-bundles/', $bundle_url ) ) {
604 $bundle_url = '/assets-bundles/' . $bundle_url;
605 }
606 }
607 if ( preg_match( '/^http/', $bundle_url ) ) {
608 $bundle_url = set_url_scheme( $bundle_url );
609 } elseif ( defined( 'VCV_TF_ASSETS_IN_UPLOADS' ) && constant( 'VCV_TF_ASSETS_IN_UPLOADS' ) ) {
610 $upload_dir = wp_upload_dir();
611 $bundle_url = set_url_scheme( $upload_dir['baseurl'] . '/' . VCV_PLUGIN_ASSETS_DIRNAME . '/' . ltrim( $bundle_url, '/\\' ) );
612 } elseif ( class_exists( 'VisualComposer\Helpers\AssetsEnqueue' ) ) {
613 // These methods should work for Visual Composer 26.0.
614 // Enqueue custom css/js stored in vcvSourceAssetsFiles postmeta.
615 $vc = new \VisualComposer\Helpers\AssetsEnqueue;
616 if ( method_exists( $vc, 'enqueueAssets' ) ) {
617 $vc->enqueueAssets($inserted_page->ID);
618 }
619 // Enqueue custom CSS stored in vcvSourceCssFileUrl postmeta.
620 $upload_dir = wp_upload_dir();
621 $bundle_url = set_url_scheme( $upload_dir['baseurl'] . '/' . VCV_PLUGIN_ASSETS_DIRNAME . '/' . ltrim( $bundle_url, '/\\' ) );
622 } else {
623 $bundle_url = content_url() . '/' . VCV_PLUGIN_ASSETS_DIRNAME . '/' . ltrim( $bundle_url, '/\\' );
624 }
625 wp_enqueue_style(
626 'vcv:assets:source:main:styles:' . sanitize_title( $bundle_url ),
627 $bundle_url,
628 array(),
629 VCV_VERSION . '.' . $version
630 );
631 }
632 }
633
634 // Visual Composer custom CSS.
635 if ( defined( 'WPB_VC_VERSION' ) ) {
636 // Post custom CSS.
637 $post_custom_css = get_post_meta( $inserted_page->ID, '_wpb_post_custom_css', true );
638 if ( ! empty( $post_custom_css ) ) {
639 echo '<style type="text/css" data-type="vc_custom-css">';
640 echo esc_html( wp_strip_all_tags( $post_custom_css ) );
641 echo '</style>';
642 }
643 // Shortcodes custom CSS.
644 $shortcodes_custom_css = get_post_meta( $inserted_page->ID, '_wpb_shortcodes_custom_css', true );
645 if ( ! empty( $shortcodes_custom_css ) ) {
646 echo '<style type="text/css" data-type="vc_shortcodes-custom-css">';
647 echo esc_html( wp_strip_all_tags( $shortcodes_custom_css ) );
648 echo '</style>';
649 }
650 }
651
652 // GoodLayers page builder content (retrieved from post meta).
653 // See: https://docs.goodlayers.com/add-page-builder-in-product/.
654 do_action( 'gdlr_core_print_page_builder' );
655
656 if ( is_null( $old_post_id ) ) {
657 $post = null; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
658 } else {
659 $post->ID = $old_post_id;
660 }
661 }
662
663 /**
664 * Show either the title, link, content, everything, or everything via a
665 * custom template.
666 *
667 * Note: if the sharing_display filter exists, it means Jetpack is
668 * installed and Sharing is enabled; this plugin conflicts with Sharing,
669 * because Sharing assumes the_content and the_excerpt filters are only
670 * getting called once. The fix here is to disable processing of filters
671 * on the_content in the inserted page.
672 *
673 * @see https://codex.wordpress.org/Function_Reference/the_content#Alternative_Usage
674 */
675 switch ( $attributes['display'] ) {
676 case 'title':
677 $title_tag = $attributes['inline'] ? 'span' : 'h1';
678 echo wp_kses_post( "<$title_tag class='insert-page-title'>" );
679 echo esc_html( get_the_title( $inserted_page->ID ) );
680 echo wp_kses_post( "</$title_tag>" );
681 break;
682
683 case 'title-content':
684 // Title.
685 $title_tag = $attributes['inline'] ? 'span' : 'h1';
686 echo wp_kses_post( "<$title_tag class='insert-page-title'>" );
687 echo esc_html( get_the_title( $inserted_page->ID ) );
688 echo wp_kses_post( "</$title_tag>" );
689 // Content.
690 // If Elementor is installed, try to render the page with it. If there is no Elementor content, fall back to normal rendering.
691 if ( class_exists( '\Elementor\Plugin' ) ) {
692 $elementor_content = \Elementor\Plugin::$instance->frontend->get_builder_content( $inserted_page->ID );
693 if ( strlen( $elementor_content ) > 0 ) {
694 echo $elementor_content;
695 break;
696 }
697 }
698 // Render the content normally.
699 $content = get_post_field( 'post_content', $inserted_page->ID );
700 if ( $attributes['should_apply_the_content_filter'] ) {
701 $content = apply_filters( 'the_content', $content );
702 }
703 echo $content;
704 break;
705
706 case 'link':
707 ?><a href="<?php echo esc_url( get_permalink( $inserted_page->ID ) ); ?>"><?php echo get_the_title( $inserted_page->ID ); ?></a>
708 <?php
709 break;
710
711 case 'excerpt':
712 ?><h1><a href="<?php echo esc_url( get_permalink( $inserted_page->ID ) ); ?>"><?php echo get_the_title( $inserted_page->ID ); ?></a></h1>
713 <?php
714 echo $this->insert_pages_trim_excerpt( get_post_field( 'post_excerpt', $inserted_page->ID ), $inserted_page->ID, $attributes['should_apply_the_content_filter'] );
715 break;
716
717 case 'excerpt-only':
718 echo $this->insert_pages_trim_excerpt( get_post_field( 'post_excerpt', $inserted_page->ID ), $inserted_page->ID, $attributes['should_apply_the_content_filter'] );
719 break;
720
721 case 'content':
722 // If Elementor is installed, try to render the page with it. If there is no Elementor content, fall back to normal rendering.
723 if ( class_exists( '\Elementor\Plugin' ) ) {
724 $elementor_content = \Elementor\Plugin::$instance->frontend->get_builder_content( $inserted_page->ID );
725 if ( strlen( $elementor_content ) > 0 ) {
726 echo $elementor_content;
727 break;
728 }
729 }
730
731 // Render the content normally.
732 $content = get_post_field( 'post_content', $inserted_page->ID );
733 if ( $attributes['should_apply_the_content_filter'] ) {
734 $content = apply_filters( 'the_content', $content );
735 }
736 echo $content;
737 break;
738
739 case 'post-thumbnail':
740 $size = empty( $attributes['size'] ) ? 'post-thumbnail' : $attributes['size'];
741 ?><a href="<?php echo esc_url( get_permalink( $inserted_page->ID ) ); ?>"><?php echo get_the_post_thumbnail( $inserted_page->ID, $size ); ?></a>
742 <?php
743 break;
744
745 case 'all':
746 // Title.
747 $title_tag = $attributes['inline'] ? 'span' : 'h1';
748 echo wp_kses_post( "<$title_tag class='insert-page-title'>" );
749 echo wp_kses_post( get_the_title( $inserted_page->ID ) );
750 echo wp_kses_post( "</$title_tag>" );
751 // Content.
752 $content = get_post_field( 'post_content', $inserted_page->ID );
753 if ( $attributes['should_apply_the_content_filter'] ) {
754 $content = apply_filters( 'the_content', $content );
755 }
756 echo $content;
757 $this->the_meta( $inserted_page->ID );
758 break;
759
760 default: // Display is either invalid, or contains a template file to use.
761 /**
762 * Legacy/compatibility code: In order to use custom templates,
763 * we use query_posts() to provide the template with the global
764 * state it requires for the inserted page (in other words, all
765 * template tags will work with respect to the inserted page
766 * instead of the parent page / main loop). Note that this may
767 * cause some compatibility issues with other plugins.
768 *
769 * @see https://codex.wordpress.org/Function_Reference/query_posts
770 */
771 if ( is_numeric( $attributes['page'] ) ) {
772 $args = array(
773 'p' => intval( $attributes['page'] ),
774 'post_type' => get_post_types(),
775 );
776 } else {
777 $args = array(
778 'name' => esc_attr( $attributes['page'] ),
779 'post_type' => get_post_types(),
780 );
781 }
782 // We save the previous query state here instead of using
783 // wp_reset_query() because wp_reset_query() only has a single stack
784 // variable ($GLOBALS['wp_the_query']). This allows us to support
785 // pages inserted into other pages (multiple nested pages).
786 $old_query = $GLOBALS['wp_query'];
787 $inserted_page = query_posts( $args );
788 if ( have_posts() ) {
789 $template = locate_template( $attributes['display'] );
790 // Only allow templates that don't have any directory traversal in
791 // them (to prevent including php files that aren't in the active
792 // theme directory or the /wp-includes/theme-compat/ directory).
793 $path_in_theme_or_childtheme_or_compat = (
794 // Template is in current theme folder.
795 0 === strpos( realpath( $template ), realpath( get_stylesheet_directory() ) ) ||
796 // Template is in current or parent theme folder.
797 0 === strpos( realpath( $template ), realpath( get_template_directory() ) ) ||
798 // Template is in theme-compat folder.
799 0 === strpos( realpath( $template ), realpath( ABSPATH . WPINC . '/theme-compat/' ) )
800 );
801 if ( strlen( $template ) > 0 && $path_in_theme_or_childtheme_or_compat ) {
802 include $template; // Execute the template code.
803 } else { // Bad path, so fall back to printing a link to the page.
804 the_post();
805 ?><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a>
806 <?php
807 }
808 }
809 // Restore previous query and update the global template variables.
810 $GLOBALS['wp_query'] = $old_query; // phpcs:ignore WordPress.WP.GlobalVariablesOverride
811 wp_reset_postdata();
812 }
813
814 // Save output buffer contents.
815 $content = ob_get_clean();
816
817 } else { // Use "Legacy" insert method (query_posts).
818
819 // Construct query_posts arguments.
820 if ( is_numeric( $attributes['page'] ) ) {
821 $args = array(
822 'p' => intval( $attributes['page'] ),
823 'post_type' => get_post_types(),
824 'post_status' => $attributes['public'] ? array( 'publish', 'private' ) : array( 'publish' ),
825 );
826 } else {
827 $args = array(
828 'name' => esc_attr( $attributes['page'] ),
829 'post_type' => get_post_types(),
830 'post_status' => $attributes['public'] ? array( 'publish', 'private' ) : array( 'publish' ),
831 );
832 }
833
834 // We save the previous query state here instead of using
835 // wp_reset_query() because wp_reset_query() only has a single stack
836 // variable ($GLOBALS['wp_the_query']). This allows us to support
837 // pages inserted into other pages (multiple nested pages).
838 $old_query = $GLOBALS['wp_query'];
839 $posts = query_posts( $args );
840
841 // Prevent unprivileged users from inserting private posts from others.
842 if ( have_posts() ) {
843 $can_read = true;
844 $parent_post_author_id = intval( get_the_author_meta( 'ID' ) );
845 foreach ( $posts as $post ) {
846 if ( is_object( $post ) && 'publish' !== $post->post_status ) {
847 $post_type = get_post_type_object( $post->post_type );
848 if ( ! user_can( $parent_post_author_id, $post_type->cap->read_post, $post->ID ) ) {
849 $can_read = false;
850 }
851 }
852 }
853 if ( ! $can_read ) {
854 // Force an empty query so we don't show any posts.
855 $posts = query_posts( array( 'post__in' => array( 0 ) ) );
856 }
857 }
858
859 if ( have_posts() ) {
860 // Start output buffering so we can save the output to string.
861 ob_start();
862
863 // If Beaver Builder, SiteOrigin Page Builder, Elementor, or WPBakery
864 // Page Builder (Visual Composer) are enabled, load any cached styles
865 // associated with the inserted page.
866 // Note: Temporarily set the global $post->ID to the inserted page ID,
867 // since both builders rely on the id to load the appropriate styles.
868 if (
869 class_exists( 'UAGB_Post_Assets' ) ||
870 class_exists( 'FLBuilder' ) ||
871 class_exists( 'SiteOrigin_Panels' ) ||
872 class_exists( '\Elementor\Post_CSS_File' ) ||
873 defined( 'VCV_VERSION' ) ||
874 defined( 'WPB_VC_VERSION' )
875 ) {
876 // If we're not in The Loop (i.e., global $post isn't assigned),
877 // temporarily populate it with the post to be inserted so we can
878 // retrieve generated styles for that post. Reset $post to null
879 // after we're done.
880 if ( is_null( $post ) ) {
881 $old_post_id = null;
882 $post = $inserted_page; // phpcs:ignore WordPress.WP.GlobalVariablesOverride
883 } else {
884 $old_post_id = $post->ID;
885 $post->ID = $inserted_page->ID;
886 }
887
888 // Enqueue assets for Ultimate Addons for Gutenberg.
889 // See: https://ultimategutenberg.com/docs/assets-api-third-party-plugins/.
890 if ( class_exists( 'UAGB_Post_Assets' ) ) {
891 $post_assets_instance = new UAGB_Post_Assets( $inserted_page->ID );
892 $post_assets_instance->enqueue_scripts();
893 }
894
895 if ( class_exists( 'FLBuilder' ) ) {
896 FLBuilder::enqueue_layout_styles_scripts( $inserted_page->ID );
897 }
898
899 if ( class_exists( 'SiteOrigin_Panels' ) ) {
900 $renderer = SiteOrigin_Panels::renderer();
901 $renderer->add_inline_css( $inserted_page->ID, $renderer->generate_css( $inserted_page->ID ) );
902 }
903
904 if ( class_exists( '\Elementor\Post_CSS_File' ) ) {
905 $css_file = new \Elementor\Post_CSS_File( $inserted_page->ID );
906 $css_file->enqueue();
907 }
908
909 // Enqueue custom style from WPBakery Page Builder (Visual Composer).
910 if ( defined( 'VCV_VERSION' ) ) {
911 wp_enqueue_style( 'vcv:assets:front:style' );
912 wp_enqueue_script( 'vcv:assets:runtime:script' );
913 wp_enqueue_script( 'vcv:assets:front:script' );
914
915 $bundle_url = get_post_meta( $inserted_page->ID, 'vcvSourceCssFileUrl', true );
916 if ( $bundle_url ) {
917 $version = get_post_meta( $inserted_page->ID, 'vcvSourceCssFileHash', true );
918 if ( ! preg_match( '/^http/', $bundle_url ) ) {
919 if ( ! preg_match( '/assets-bundles/', $bundle_url ) ) {
920 $bundle_url = '/assets-bundles/' . $bundle_url;
921 }
922 }
923 if ( preg_match( '/^http/', $bundle_url ) ) {
924 $bundle_url = set_url_scheme( $bundle_url );
925 } elseif ( defined( 'VCV_TF_ASSETS_IN_UPLOADS' ) && constant( 'VCV_TF_ASSETS_IN_UPLOADS' ) ) {
926 $upload_dir = wp_upload_dir();
927 $bundle_url = set_url_scheme( $upload_dir['baseurl'] . '/' . VCV_PLUGIN_ASSETS_DIRNAME . '/' . ltrim( $bundle_url, '/\\' ) );
928 } elseif ( class_exists( 'VisualComposer\Helpers\AssetsEnqueue' ) ) {
929 // These methods should work for Visual Composer 26.0.
930 // Enqueue custom css/js stored in vcvSourceAssetsFiles postmeta.
931 $vc = new \VisualComposer\Helpers\AssetsEnqueue;
932 if ( method_exists( $vc, 'enqueueAssets' ) ) {
933 $vc->enqueueAssets( $inserted_page->ID );
934 }
935 // Enqueue custom CSS stored in vcvSourceCssFileUrl postmeta.
936 $upload_dir = wp_upload_dir();
937 $bundle_url = set_url_scheme( $upload_dir['baseurl'] . '/' . VCV_PLUGIN_ASSETS_DIRNAME . '/' . ltrim( $bundle_url, '/\\' ) );
938 } else {
939 $bundle_url = content_url() . '/' . VCV_PLUGIN_ASSETS_DIRNAME . '/' . ltrim( $bundle_url, '/\\' );
940 }
941 wp_enqueue_style(
942 'vcv:assets:source:main:styles:' . sanitize_title( $bundle_url ),
943 $bundle_url,
944 array(),
945 VCV_VERSION . '.' . $version
946 );
947 }
948 }
949
950 // Visual Composer custom CSS.
951 if ( defined( 'WPB_VC_VERSION' ) ) {
952 // Post custom CSS.
953 $post_custom_css = get_post_meta( $inserted_page->ID, '_wpb_post_custom_css', true );
954 if ( ! empty( $post_custom_css ) ) {
955 $post_custom_css = wp_strip_all_tags( $post_custom_css );
956 echo '<style type="text/css" data-type="vc_custom-css">';
957 echo $post_custom_css;
958 echo '</style>';
959 }
960 // Shortcodes custom CSS.
961 $shortcodes_custom_css = get_post_meta( $inserted_page->ID, '_wpb_shortcodes_custom_css', true );
962 if ( ! empty( $shortcodes_custom_css ) ) {
963 $shortcodes_custom_css = wp_strip_all_tags( $shortcodes_custom_css );
964 echo '<style type="text/css" data-type="vc_shortcodes-custom-css">';
965 echo $shortcodes_custom_css;
966 echo '</style>';
967 }
968 }
969
970 // GoodLayers page builder content (retrieved from post meta).
971 // See: https://docs.goodlayers.com/add-page-builder-in-product/.
972 do_action( 'gdlr_core_print_page_builder' );
973
974 if ( is_null( $old_post_id ) ) {
975 $post = null; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
976 } else {
977 $post->ID = $old_post_id;
978 }
979 }
980
981 /**
982 * Show either the title, link, content, everything, or everything via a
983 * custom template.
984 *
985 * Note: if the sharing_display filter exists, it means Jetpack is
986 * installed and Sharing is enabled; this plugin conflicts with Sharing,
987 * because Sharing assumes the_content and the_excerpt filters are only
988 * getting called once. The fix here is to disable processing of filters
989 * on the_content in the inserted page.
990 *
991 * @see https://codex.wordpress.org/Function_Reference/the_content#Alternative_Usage
992 */
993 switch ( $attributes['display'] ) {
994 case 'title':
995 the_post();
996 $title_tag = $attributes['inline'] ? 'span' : 'h1';
997 echo "<$title_tag class='insert-page-title'>";
998 the_title();
999 echo "</$title_tag>";
1000 break;
1001 case 'title-content':
1002 // Title.
1003 the_post();
1004 $title_tag = $attributes['inline'] ? 'span' : 'h1';
1005 echo "<$title_tag class='insert-page-title'>";
1006 the_title();
1007 echo "</$title_tag>";
1008 // Content.
1009 // If Elementor is installed, try to render the page with it. If there is no Elementor content, fall back to normal rendering.
1010 if ( class_exists( '\Elementor\Plugin' ) ) {
1011 $elementor_content = \Elementor\Plugin::$instance->frontend->get_builder_content( $inserted_page->ID );
1012 if ( strlen( $elementor_content ) > 0 ) {
1013 echo $elementor_content;
1014 break;
1015 }
1016 }
1017 // Render the content normally.
1018 if ( $attributes['should_apply_the_content_filter'] ) {
1019 the_content();
1020 } else {
1021 echo get_the_content();
1022 }
1023 // Render any <!--nextpage--> pagination links.
1024 wp_link_pages(
1025 array(
1026 'before' => '<div class="page-links">' . __( 'Pages:', 'insert-pages' ),
1027 'after' => '</div>',
1028 )
1029 );
1030 break;
1031 case 'link':
1032 the_post();
1033 ?><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a>
1034 <?php
1035 break;
1036 case 'excerpt':
1037 the_post();
1038 ?><h1><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></h1>
1039 <?php
1040 if ( $attributes['should_apply_the_content_filter'] ) {
1041 the_excerpt();
1042 } else {
1043 echo get_the_excerpt();
1044 }
1045 break;
1046 case 'excerpt-only':
1047 the_post();
1048 if ( $attributes['should_apply_the_content_filter'] ) {
1049 the_excerpt();
1050 } else {
1051 echo get_the_excerpt();
1052 }
1053 break;
1054 case 'content':
1055 // If Elementor is installed, try to render the page with it. If there is no Elementor content, fall back to normal rendering.
1056 if ( class_exists( '\Elementor\Plugin' ) ) {
1057 $elementor_content = \Elementor\Plugin::$instance->frontend->get_builder_content( $inserted_page->ID );
1058 if ( strlen( $elementor_content ) > 0 ) {
1059 echo $elementor_content;
1060 break;
1061 }
1062 }
1063 // Render the content normally.
1064 the_post();
1065 if ( $attributes['should_apply_the_content_filter'] ) {
1066 the_content();
1067 } else {
1068 echo get_the_content();
1069 }
1070 // Render any <!--nextpage--> pagination links.
1071 wp_link_pages(
1072 array(
1073 'before' => '<div class="page-links">' . __( 'Pages:', 'insert-pages' ),
1074 'after' => '</div>',
1075 )
1076 );
1077 break;
1078 case 'post-thumbnail':
1079 $size = empty( $attributes['size'] ) ? 'post-thumbnail' : $attributes['size'];
1080 ?><a href="<?php echo esc_url( get_permalink( $inserted_page->ID ) ); ?>"><?php echo get_the_post_thumbnail( $inserted_page->ID, $size ); ?></a>
1081 <?php
1082 break;
1083 case 'all':
1084 the_post();
1085 $title_tag = $attributes['inline'] ? 'span' : 'h1';
1086 echo "<$title_tag class='insert-page-title'>";
1087 the_title();
1088 echo "</$title_tag>";
1089 if ( $attributes['should_apply_the_content_filter'] ) {
1090 the_content();
1091 } else {
1092 echo get_the_content();
1093 }
1094 $this->the_meta();
1095 // Render any <!--nextpage--> pagination links.
1096 wp_link_pages( array(
1097 'before' => '<div class="page-links">' . __( 'Pages:', 'insert-pages' ),
1098 'after' => '</div>',
1099 ) );
1100 break;
1101 default: // Display is either invalid, or contains a template file to use.
1102 $template = locate_template( $attributes['display'] );
1103 // Only allow templates that don't have any directory traversal in
1104 // them (to prevent including php files that aren't in the active
1105 // theme directory or the /wp-includes/theme-compat/ directory).
1106 $path_in_theme_or_childtheme_or_compat = (
1107 // Template is in current theme folder.
1108 0 === strpos( realpath( $template ), realpath( get_stylesheet_directory() ) ) ||
1109 // Template is in current or parent theme folder.
1110 0 === strpos( realpath( $template ), realpath( get_template_directory() ) ) ||
1111 // Template is in theme-compat folder.
1112 0 === strpos( realpath( $template ), realpath( ABSPATH . WPINC . '/theme-compat/' ) )
1113 );
1114 if ( strlen( $template ) > 0 && $path_in_theme_or_childtheme_or_compat ) {
1115 include $template; // Execute the template code.
1116 } else { // Couldn't find template, so fall back to printing a link to the page.
1117 the_post();
1118 ?><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a>
1119 <?php
1120 }
1121 break;
1122 }
1123 // Save output buffer contents.
1124 $content = ob_get_clean();
1125 } else {
1126 /**
1127 * Filter the html that should be displayed if an inserted page was not found.
1128 *
1129 * @param string $content html to be displayed. Defaults to an empty string.
1130 */
1131 $content = apply_filters( 'insert_pages_not_found_message', $content );
1132 }
1133 // Restore previous query and update the global template variables.
1134 $GLOBALS['wp_query'] = $old_query; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
1135 wp_reset_postdata();
1136 }
1137
1138 /**
1139 * Filter the markup generated for the inserted page.
1140 *
1141 * @param string $content The post content of the inserted page.
1142 * @param object $inserted_page The post object returned from querying the inserted page.
1143 * @param array $attributes Extra parameters modifying the inserted page.
1144 * page: Page ID or slug of page to be inserted.
1145 * display: Content to display from inserted page.
1146 * class: Extra classes to add to inserted page wrapper element.
1147 * id: Optional ID for the inserted page wrapper element.
1148 * inline: Boolean indicating wrapper element should be a span.
1149 * public: Boolean indicating anonymous users can see private inserted pages.
1150 * querystring: Extra querystring values provided to the custom template.
1151 * should_apply_the_content_filter: Whether to apply the_content filter to post contents and excerpts.
1152 * wrapper_tag: Tag to use for the wrapper element (e.g., div, span).
1153 */
1154 $content = apply_filters( 'insert_pages_wrap_content', $content, $inserted_page, $attributes );
1155
1156 // Unset any querystring params included in the shortcode.
1157 $_GET = $original_get;
1158 $_REQUEST = $original_request;
1159 $GLOBALS['wp']->query_vars = $original_wp_query_vars;
1160
1161 // Loop detection: remove the page from the stack (so we can still insert
1162 // the same page multiple times on another page, but prevent it from being
1163 // inserted multiple times within the same recursive chain).
1164 if ( isset( $inserted_page->ID ) ) {
1165 foreach ( $this->inserted_page_ids as $key => $page_id ) {
1166 if ( $page_id === $inserted_page->ID ) {
1167 unset( $this->inserted_page_ids[ $key ] );
1168 }
1169 }
1170 } elseif ( is_array( $inserted_page ) && ! empty( $inserted_page ) ) {
1171 // Legacy template code populates $inserted_page with query_posts()
1172 // output. Remove each from the stack (should just be a single page).
1173 foreach ( $inserted_page as $page ) {
1174 foreach ( $this->inserted_page_ids as $key => $page_id ) {
1175 if ( $page_id === $page->ID ) {
1176 unset( $this->inserted_page_ids[ $key ] );
1177 }
1178 }
1179 }
1180 }
1181
1182 return $content;
1183 }
1184
1185 /**
1186 * Default filter for insert_pages_wrap_content.
1187 *
1188 * @param string $content Content of shortcode.
1189 * @param array $posts Post data of inserted page.
1190 * @param array $attributes Shortcode attributes.
1191 * @return string Content to replace shortcode.
1192 */
1193 public function insert_pages_wrap_content( $content, $posts, $attributes ) {
1194 return sprintf(
1195 '<%1$s data-post-id="%2$s" class="insert-page insert-page-%2$s %3$s"%4$s>%5$s</%1$s>',
1196 esc_attr( $attributes['wrapper_tag'] ),
1197 esc_attr( $attributes['page'] ),
1198 esc_attr( $attributes['class'] ),
1199 empty( $attributes['id'] ) ? '' : ' id="' . esc_attr( $attributes['id'] ) . '"',
1200 $content
1201 );
1202 }
1203
1204 /**
1205 * Filter hook: Add a button to the TinyMCE toolbar for our insert page tool.
1206 *
1207 * @param array $buttons TinyMCE buttons.
1208 * @return array TinyMCE buttons with Insert Pages button.
1209 */
1210 public function insert_pages_handle_filter_mce_buttons( $buttons ) {
1211 if ( self::$is_admin_initialized && ! in_array( 'wpInsertPages_button', $buttons, true ) ) {
1212 array_push( $buttons, 'wpInsertPages_button' );
1213 }
1214 return $buttons;
1215 }
1216
1217 /**
1218 * Filter hook: Load the javascript for our custom toolbar button.
1219 *
1220 * @param array $plugins TinyMCE plugins.
1221 * @return array TinyMCE plugins with Insert Pages plugin.
1222 */
1223 public function insert_pages_handle_filter_mce_external_plugins( $plugins ) {
1224 if ( self::$is_admin_initialized && ! array_key_exists( 'wpInsertPages', $plugins ) ) {
1225 $plugins['wpInsertPages'] = plugins_url( '/js/wpinsertpages_plugin.js', __FILE__ );
1226 }
1227 return $plugins;
1228 }
1229
1230 /**
1231 * Helper function to generate an excerpt (outside of the Loop) for a given
1232 * ID (based on wp_trim_excerpt()).
1233 *
1234 * @param string $text Excerpt.
1235 * @param integer $post_id Post ID of excerpt.
1236 * @param boolean $apply_the_content_filter Whether to apply `the_content`.
1237 * @return string Excerpt.
1238 */
1239 public function insert_pages_trim_excerpt( $text = '', $post_id = 0, $apply_the_content_filter = true ) {
1240 $post_id = intval( $post_id );
1241 if ( $post_id < 1 ) {
1242 return '';
1243 }
1244
1245 $raw_excerpt = $text;
1246 if ( '' === $text ) {
1247 $text = get_post_field( 'post_content', $post_id );
1248
1249 $text = strip_shortcodes( $text );
1250
1251 // Look for a <!--more--> quicktag and trim the excerpt there if it exists.
1252 $has_more_quicktag = false;
1253 if ( preg_match( '/<!--more(.*?)?-->/', $text, $matches ) ) {
1254 $has_more_quicktag = true;
1255 $text = explode( $matches[0], $text, 2 );
1256 $text = $text[0];
1257 // Look for a custom <!--crop--> quicktag that will trim any text before
1258 // it out of the excerpt.
1259 if ( preg_match( '/<!--crop-->/', $text, $matches ) ) {
1260 $text = explode( $matches[0], $text, 2 );
1261 $text = $text[1];
1262 }
1263 }
1264
1265 /** This filter is documented in wp-includes/post-template.php */
1266 if ( $apply_the_content_filter ) {
1267 $text = apply_filters( 'the_content', $text );
1268 }
1269 $text = str_replace( ']]>', ']]&gt;', $text );
1270
1271 // Only trim excerpt if there wasn't an existing <!--more--> quicktag.
1272 if ( ! $has_more_quicktag ) {
1273 /**
1274 * Filter the number of words in an excerpt.
1275 *
1276 * @since 2.7.0
1277 *
1278 * @param int $number The number of words. Default 55.
1279 */
1280 $excerpt_length = apply_filters( 'excerpt_length', 55 );
1281
1282 /**
1283 * Filter the string in the "more" link displayed after a trimmed excerpt.
1284 *
1285 * @since 2.9.0
1286 *
1287 * @param string $more_string The string shown within the more link.
1288 */
1289 global $post;
1290 if ( isset( $post->ID ) ) {
1291 $old_post_id = $post->ID;
1292 $post->ID = $post_id;
1293 }
1294 $excerpt_more = apply_filters( 'excerpt_more', ' [&hellip;]' );
1295 if ( isset( $post->ID ) ) {
1296 $post->ID = $old_post_id;
1297 }
1298
1299 $text = wp_trim_words( $text, $excerpt_length, $excerpt_more );
1300 }
1301 }
1302 /**
1303 * Filter the trimmed excerpt string.
1304 *
1305 * @since 2.8.0
1306 *
1307 * @param string $text The trimmed text.
1308 * @param string $raw_excerpt The text prior to trimming.
1309 */
1310 return apply_filters( 'wp_trim_excerpt', $text, $raw_excerpt );
1311 }
1312
1313 /**
1314 * Modified from /wp-includes/class-wp-editor.php, function
1315 * wp_link_dialog().
1316 *
1317 * Dialog for internal linking.
1318 *
1319 * @since 3.1.0
1320 */
1321 public function insert_pages_wp_tinymce_dialog() {
1322 // Don't run if required scripts and styles weren't enqueued.
1323 if ( ! self::$is_admin_initialized ) {
1324 return;
1325 }
1326
1327 // Run once.
1328 if ( self::$link_dialog_printed ) {
1329 return;
1330 }
1331
1332 self::$link_dialog_printed = true;
1333
1334 $formats = array(
1335 'title' => __( 'Title', 'insert-pages' ),
1336 'title-content' => __( 'Title and content', 'insert-pages' ),
1337 'link' => __( 'Link', 'insert-pages' ),
1338 'excerpt' => __( 'Excerpt with title', 'insert-pages' ),
1339 'excerpt-only' => __( 'Excerpt only (no title)', 'insert-pages' ),
1340 'content' => __( 'Content', 'insert-pages' ),
1341 'post-thumbnail' => __( 'Post Thumbnail', 'insert-pages' ),
1342 'all' => __( 'All (includes custom fields)', 'insert-pages' ),
1343 'template' => __( 'Use a custom template', 'insert-pages' ) . ' &raquo;',
1344 );
1345
1346 $templates = array(
1347 'all' => __( 'Default Template', 'insert-pages' ),
1348 );
1349 foreach ( wp_get_theme()->get_page_templates() as $file => $name ) {
1350 $templates[ $file ] = $name;
1351 }
1352
1353 $sizes = function_exists( 'wp_get_registered_image_subsizes' ) ? array_keys( wp_get_registered_image_subsizes() ) : get_intermediate_image_sizes();
1354
1355 /**
1356 * Filter the available templates shown in the template dropdown.
1357 *
1358 * @param array $templates Array of template names keyed by their filename.
1359 */
1360 $templates = apply_filters( 'insert_pages_available_templates', $templates );
1361
1362 // Get default values for the TinyMCE dialog fields. Note: can be
1363 // overridden by the `insert_pages_tinymce_state` filter.
1364 $tinymce_state = $this->get_tinymce_state();
1365
1366 // Get ID of post currently being edited.
1367 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
1368 $post_id = isset( $_REQUEST['post'] ) && intval( $_REQUEST['post'] ) > 0 ? intval( $_REQUEST['post'] ) : '';
1369
1370 // display: none is required here, see #WP27605.
1371 ?>
1372 <div id="wp-insertpage-backdrop" style="display: none"></div>
1373 <div id="wp-insertpage-wrap" class="wp-core-ui<?php echo 1 === intval( get_user_setting( 'wpinsertpage', 0 ) ) ? ' options-panel-visible' : ''; ?><?php echo empty( $tinymce_state['hide_querystring'] ) ? '' : ' querystring-hidden'; ?><?php echo empty( $tinymce_state['hide_public'] ) ? '' : ' public-hidden'; ?>" style="display: none;" role="dialog" aria-labelledby="insertpage-modal-title">
1374 <form id="wp-insertpage" tabindex="-1">
1375 <?php wp_nonce_field( 'internal-inserting', '_ajax_inserting_nonce', false ); ?>
1376 <input type="hidden" id="insertpage-parent-page-id" value="<?php echo esc_attr( $post_id ); ?>" />
1377 <h1 id="insertpage-modal-title"><?php esc_html_e( 'Insert page', 'insert-pages' ); ?></h1>
1378 <button type="button" id="wp-insertpage-close"><span class="screen-reader-text"><?php esc_html_e( 'Close', 'insert-pages' ); ?></span></button>
1379 <div id="insertpage-selector">
1380 <div id="insertpage-search-panel">
1381 <div class="insertpage-search-wrapper">
1382 <label>
1383 <span class="search-label"><?php esc_html_e( 'Search', 'insert-pages' ); ?></span>
1384 <input type="search" id="insertpage-search-field" class="insertpage-search-field" autocomplete="off" />
1385 <span class="spinner"></span>
1386 </label>
1387 </div>
1388 <div id="insertpage-search-results" class="query-results" tabindex="0">
1389 <ul></ul>
1390 <div class="river-waiting">
1391 <span class="spinner"></span>
1392 </div>
1393 </div>
1394 <div id="insertpage-most-recent-results" class="query-results" tabindex="0">
1395 <div class="query-notice" id="insertpage-query-notice-message">
1396 <em class="query-notice-default"><?php esc_html_e( 'No search term specified. Showing recent items.', 'insert-pages' ); ?></em>
1397 <em class="query-notice-hint screen-reader-text"><?php esc_html_e( 'Search or use up and down arrow keys to select an item.', 'insert-pages' ); ?></em>
1398 </div>
1399 <ul></ul>
1400 <div class="river-waiting">
1401 <span class="spinner"></span>
1402 </div>
1403 </div>
1404 </div>
1405 <p class="howto" id="insertpage-options-toggle"><?php esc_html_e( 'Options', 'insert-pages' ); ?></p>
1406 <div id="insertpage-options-panel">
1407 <div class="insertpage-options-wrapper">
1408 <label for="insertpage-slug-field">
1409 <span><?php esc_html_e( 'Slug or ID', 'insert-pages' ); ?></span>
1410 <input id="insertpage-slug-field" type="text" autocomplete="off" />
1411 <input id="insertpage-page-id" type="hidden" />
1412 </label>
1413 </div>
1414 <div class="insertpage-format">
1415 <label for="insertpage-format-select">
1416 <?php esc_html_e( 'Display', 'insert-pages' ); ?>
1417 </label>
1418 <select name="insertpage-format-select" id="insertpage-format-select">
1419 <?php foreach ( $formats as $format => $label ) : ?>
1420 <option value='<?php echo esc_attr( $format ); ?>' <?php selected( $tinymce_state['format'], $format ); ?>><?php echo esc_html( $label ); ?></option>
1421 <?php endforeach; ?>
1422 </select>
1423 <select name="insertpage-template-select" id="insertpage-template-select" disabled="true">
1424 <?php foreach ( $templates as $template => $label ) : ?>
1425 <option value='<?php echo esc_attr( $template ); ?>' <?php selected( $tinymce_state['template'], $template ); ?>><?php echo esc_html( $label ); ?></option>
1426 <?php endforeach; ?>
1427 </select>
1428 <select name="insertpage-size-select" id="insertpage-size-select" disabled="true">
1429 <?php foreach ( $sizes as $size ) : ?>
1430 <option value='<?php echo esc_attr( $size ); ?>' <?php selected( $tinymce_state['size'], $size ); ?>><?php echo esc_html( $size ); ?></option>
1431 <?php endforeach; ?>
1432 </select>
1433 </div>
1434 <div class="insertpage-extra">
1435 <label for="insertpage-extra-classes">
1436 <?php esc_html_e( 'Extra Classes', 'insert-pages' ); ?>
1437 <input id="insertpage-extra-classes" type="text" autocomplete="off" value="<?php echo empty( $tinymce_state['class'] ) ? '' : esc_attr( $tinymce_state['class'] ); ?>" />
1438 </label>
1439 <label for="insertpage-extra-id">
1440 <?php esc_html_e( 'ID', 'insert-pages' ); ?>
1441 <input id="insertpage-extra-id" type="text" autocomplete="off" value="<?php echo empty( $tinymce_state['id'] ) ? '' : esc_attr( $tinymce_state['id'] ); ?>" />
1442 </label>
1443 <label for="insertpage-extra-inline">
1444 <?php esc_html_e( 'Inline?', 'insert-pages' ); ?>
1445 <input id="insertpage-extra-inline" type="checkbox" <?php checked( $tinymce_state['inline'] ); ?> />
1446 </label>
1447 <br class="<?php echo empty( $tinymce_state['hide_querystring'] ) ? '' : 'hidden'; ?>" />
1448 <label for="insertpage-extra-querystring" class="<?php echo empty( $tinymce_state['hide_querystring'] ) ? '' : 'hidden'; ?>">
1449 <?php esc_html_e( 'Querystring', 'insert-pages' ); ?>
1450 <input id="insertpage-extra-querystring" type="text" autocomplete="off" value="<?php echo empty( $tinymce_state['querystring'] ) ? '' : esc_attr( $tinymce_state['querystring'] ); ?>" />
1451 </label>
1452 <br class="<?php echo empty( $tinymce_state['hide_public'] ) ? '' : 'hidden'; ?>" />
1453 <label for="insertpage-extra-public" class="<?php echo empty( $tinymce_state['hide_public'] ) ? '' : 'hidden'; ?>">
1454 <input id="insertpage-extra-public" type="checkbox" <?php checked( $tinymce_state['public'] ); ?> />
1455 <?php esc_html_e( 'Anonymous users can see this inserted even if its status is private', 'insert-pages' ); ?>
1456 </label>
1457 </div>
1458 </div>
1459 </div>
1460 <div class="submitbox">
1461 <div id="wp-insertpage-cancel">
1462 <button type="button" class="button"><?php esc_html_e( 'Cancel', 'insert-pages' ); ?></button>
1463 </div>
1464 <div id="wp-insertpage-update">
1465 <input type="submit" value="<?php esc_attr_e( 'Insert Page', 'insert-pages' ); ?>" class="button button-primary" id="wp-insertpage-submit" name="wp-insertpage-submit">
1466 </div>
1467 </div>
1468 </form>
1469 </div>
1470 <?php
1471 }
1472
1473 /**
1474 * Modified from:
1475 * Internal linking functions.
1476 *
1477 * @package WordPress
1478 * @subpackage Administration
1479 * @since 3.1.0
1480 */
1481 public function insert_pages_insert_page_callback() {
1482 check_ajax_referer( 'internal-inserting', '_ajax_inserting_nonce' );
1483 $args = array();
1484
1485 // If a URL is provided, translate it to a post ID and search on that.
1486 if ( ! empty( $_POST['search'] ) && filter_var( wp_unslash( $_POST['search'] ), FILTER_VALIDATE_URL ) ) {
1487 $post_id = url_to_postid( sanitize_url( wp_unslash( $_POST['search'] ) ) );
1488 if ( ! empty( $post_id ) ) {
1489 $_POST['search'] = $post_id;
1490 $_POST['type'] = 'post_id';
1491 }
1492 }
1493
1494 if ( isset( $_POST['search'] ) ) {
1495 $args['s'] = wp_unslash( $_POST['search'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
1496 }
1497 $args['pagenum'] = ! empty( $_POST['page'] ) ? absint( $_POST['page'] ) : 1;
1498 $args['pageID'] = ! empty( $_POST['pageID'] ) ? absint( $_POST['pageID'] ) : 0;
1499
1500 // Change search to slug or post ID if we're not doing a plaintext
1501 // search (e.g., if we're editing an existing shortcode and the
1502 // search field is populated with the post's slug or ID).
1503 if ( isset( $_POST['type'] ) && 'slug' === $_POST['type'] ) {
1504 $args['name'] = $args['s'];
1505 unset( $args['s'] );
1506 } elseif ( array_key_exists( 'type', $_POST ) && 'post_id' === $_POST['type'] ) {
1507 $args['p'] = $args['s'];
1508 unset( $args['s'] );
1509 }
1510
1511 $results = $this->insert_pages_wp_query( $args );
1512
1513 // Fail if our query didn't work.
1514 if ( ! isset( $results ) ) {
1515 die( '0' );
1516 }
1517
1518 echo wp_json_encode( $results );
1519 echo "\n";
1520 die();
1521 }
1522
1523 /**
1524 * Save the user's last-selected display or template in the TinyMCE widget
1525 * whenever it changes.
1526 *
1527 * @hook wp_ajax_insertpage_save_presets
1528 */
1529 public function insert_pages_save_presets() {
1530 check_ajax_referer( 'internal-inserting', '_ajax_inserting_nonce' );
1531 $args = array();
1532 if ( isset( $_POST['format'] ) ) {
1533 $args['format'] = sanitize_key( wp_unslash( $_POST['format'] ) );
1534 }
1535 if ( isset( $_POST['template'] ) ) {
1536 $args['template'] = sanitize_file_name( wp_unslash( $_POST['template'] ) );
1537 }
1538
1539 if ( ! empty( $args ) ) {
1540 $tinymce_state = get_user_meta( get_current_user_id(), 'insert_pages_tinymce_state', true );
1541 if ( empty( $tinymce_state ) ) {
1542 $tinymce_state = array(
1543 'format' => 'title',
1544 'template' => 'all',
1545 );
1546 }
1547 $tinymce_state = array_merge( $tinymce_state, $args );
1548 update_user_meta( get_current_user_id(), 'insert_pages_tinymce_state', $tinymce_state );
1549 }
1550
1551 // Fail if our query didn't work.
1552 if ( ! isset( $results ) ) {
1553 die( '0' );
1554 }
1555
1556 echo wp_json_encode( 'Success' );
1557 echo "\n";
1558 die();
1559 }
1560
1561 /**
1562 * Modified from:
1563 * Performs post queries for internal linking.
1564 *
1565 * @since 3.1.0
1566 * @param array $args Optional. Accepts 'pagenum' and 's' (search) arguments.
1567 * @return array Results.
1568 */
1569 private function insert_pages_wp_query( $args = array() ) {
1570 $pts = get_post_types( array( 'public' => true ), 'objects' );
1571 $post_types = array_keys( $pts );
1572
1573 /**
1574 * Filter the post types that appear in the list of pages to insert.
1575 *
1576 * By default, all post types will apear.
1577 *
1578 * @since 2.0
1579 *
1580 * @param array $post_types Array of post type names to include.
1581 */
1582 $post_types = apply_filters( 'insert_pages_available_post_types', $post_types );
1583
1584 $query = array(
1585 'post_type' => $post_types,
1586 'update_post_term_cache' => false,
1587 'update_post_meta_cache' => false,
1588 'post_status' => array( 'publish', 'private' ),
1589 'order' => 'DESC',
1590 'orderby' => 'post_date',
1591 'posts_per_page' => 20,
1592 );
1593
1594 // Show non-admins only their own posts if the option is enabled.
1595 $options = get_option( 'wpip_settings' );
1596 if (
1597 ! empty( $options['wpip_classic_editor_hide_others_posts'] ) &&
1598 'enabled' === $options['wpip_classic_editor_hide_others_posts'] &&
1599 ! current_user_can( 'edit_others_posts' )
1600 ) {
1601 $query['author'] = get_current_user_id();
1602 }
1603
1604 $args['pagenum'] = isset( $args['pagenum'] ) ? absint( $args['pagenum'] ) : 1;
1605 $query['offset'] = $args['pagenum'] > 1 ? $query['posts_per_page'] * ( $args['pagenum'] - 1 ) : 0;
1606
1607 // Search post content and post title.
1608 if ( isset( $args['s'] ) ) {
1609 $query['s'] = $args['s'];
1610 }
1611
1612 // Search post_name (post slugs).
1613 if ( isset( $args['name'] ) ) {
1614 $query['name'] = $args['name'];
1615 }
1616
1617 // Search post ids.
1618 if ( isset( $args['p'] ) ) {
1619 $query['p'] = $args['p'];
1620 }
1621
1622 // Do main query.
1623 $get_posts = new WP_Query();
1624 $posts = $get_posts->query( $query );
1625 // Check if any posts were found.
1626 if ( ! $get_posts->post_count ) {
1627 return false;
1628 }
1629
1630 // Build results.
1631 $results = array();
1632 foreach ( $posts as $post ) {
1633 // Prevent unprivileged users (e.g., Contributors) from seeing and
1634 // inserting other user's private posts.
1635 $post_type = get_post_type_object( $post->post_type );
1636 if ( 'publish' !== $post->post_status && ! current_user_can( $post_type->cap->read_post, $post->ID ) ) {
1637 continue;
1638 }
1639
1640 if ( 'post' === $post->post_type ) {
1641 $info = mysql2date( 'Y/m/d', $post->post_date );
1642 } else {
1643 $info = $pts[ $post->post_type ]->labels->singular_name;
1644 }
1645 $results[] = array(
1646 'ID' => $post->ID,
1647 'title' => trim( esc_html( wp_strip_all_tags( get_the_title( $post ) ) ) ),
1648 'permalink' => get_permalink( $post->ID ),
1649 'slug' => $post->post_name,
1650 'path' => get_page_uri( $post ),
1651 'info' => $info,
1652 'status' => get_post_status( $post ),
1653 );
1654 }
1655 return $results;
1656 }
1657
1658 /**
1659 * Add Insert Page quicktag button to Text editor.
1660 *
1661 * @hook admin_print_footer_scripts
1662 *
1663 * @return void
1664 */
1665 public function insert_pages_add_quicktags() {
1666 if ( wp_script_is( 'quicktags' ) ) : ?>
1667 <script type="text/javascript">
1668 window.onload = function() {
1669 QTags.addButton( 'ed_insert_page', '[insert page]', "[insert page='your-page-slug' display='title|link|excerpt|excerpt-only|content|post-thumbnail|all']\n", '', '', 'Insert Page', 999 );
1670 }
1671 </script>
1672 <?php
1673 endif;
1674 }
1675
1676 /**
1677 * Indicates whether a particular post type is able to be inserted.
1678 *
1679 * @param boolean $type Post type.
1680 * @return boolean Whether post type is insertable.
1681 */
1682 private function is_post_type_insertable( $type ) {
1683 return ! in_array(
1684 $type,
1685 array(
1686 'nav_menu_item',
1687 'attachment',
1688 'revision',
1689 'customize_changeset',
1690 'oembed_cache',
1691 // Exclude Flamingo messages (created via Contact Form 7 submissions).
1692 // See: https://wordpress.org/support/topic/plugin-hacked-14/.
1693 'flamingo_inbound',
1694 ),
1695 true
1696 );
1697 }
1698
1699 /**
1700 * Fetch the default values for the TinyMCE modal fields.
1701 */
1702 private function get_tinymce_state() {
1703 // Get user's previously selected display and template to restore (if any).
1704 $tinymce_state = get_user_meta( get_current_user_id(), 'insert_pages_tinymce_state', true );
1705 if ( empty( $tinymce_state ) ) {
1706 $tinymce_state = array();
1707 }
1708
1709 // Merge user's format and template defaults with global defaults.
1710 $tinymce_state = wp_parse_args(
1711 $tinymce_state,
1712 array(
1713 'format' => 'title',
1714 'template' => 'all',
1715 'class' => '',
1716 'id' => '',
1717 'querystring' => '',
1718 'size' => '',
1719 'inline' => false,
1720 'public' => false,
1721 'hide_querystring' => false,
1722 'hide_public' => false,
1723 )
1724 );
1725
1726 /**
1727 * Filter the TinyMCE dialog field defaults.
1728 *
1729 * @param array $tinymce_state Array of field defaults for the TinyMCE modal.
1730 * 'format' (string) Display format. Default 'title'.
1731 * 'template' (string) Custom template. Default 'all'.
1732 * 'class' (string) HTML wrapper class. Default ''.
1733 * 'id' (string) HTML wrapper id. Default ''.
1734 * 'querystring' (string) Querystring params. Default ''.
1735 * 'size' (string) Image size when using format='thumbnail'.
1736 * 'inline' (bool) Use <span> element for wrapper. Default false.
1737 * 'public' (bool) Whether anonymous users can see this page if
1738 * its status is Private. Default false.
1739 * 'hide_querystring' (bool) Skip rendering querystring field. Default false.
1740 * 'hide_public' (bool) Skip rendering public field. Default false.
1741 */
1742 $tinymce_state = apply_filters( 'insert_pages_tinymce_state', $tinymce_state );
1743
1744 return $tinymce_state;
1745 }
1746
1747 /**
1748 * Registers the theme widget for inserting a page into an area.
1749 *
1750 * @return void
1751 */
1752 public function insert_pages_widgets_init() {
1753 register_widget( 'InsertPagesWidget' );
1754 }
1755
1756 /**
1757 * Render post meta as an unordered list.
1758 *
1759 * Note: This function sanitizes postmeta value via wp_kses_post(); the
1760 * core WordPress function the_meta() does not.
1761 *
1762 * @see https://developer.wordpress.org/reference/functions/the_meta/
1763 *
1764 * @param int $post_id Post ID.
1765 */
1766 public function the_meta( $post_id = 0 ) {
1767 if ( empty( $post_id ) ) {
1768 $post_id = get_the_ID();
1769 }
1770
1771 $keys = get_post_custom_keys( $post_id );
1772 if ( $keys ) {
1773 $li_html = '';
1774 foreach ( (array) $keys as $key ) {
1775 $keyt = trim( $key );
1776 if ( is_protected_meta( $keyt, 'post' ) ) {
1777 continue;
1778 }
1779
1780 $values = array_map( 'trim', get_post_custom_values( $key, $post_id ) );
1781 $value = implode( ', ', $values );
1782
1783 // Sanitize post meta values.
1784 $value = wp_kses_post( $value );
1785
1786 $html = sprintf(
1787 "<li><span class='post-meta-key'>%s</span> %s</li>\n",
1788 /* translators: %s: Post custom field name. */
1789 sprintf( _x( '%s:', 'Post custom field name', 'insert-pages' ), $key ),
1790 $value
1791 );
1792
1793 /**
1794 * Filters the HTML output of the li element in the post custom fields list.
1795 *
1796 * @since 2.2.0
1797 *
1798 * @param string $html The HTML output for the li element.
1799 * @param string $key Meta key.
1800 * @param string $value Meta value.
1801 */
1802 $li_html .= apply_filters( 'the_meta_key', $html, $key, $value );
1803 }
1804
1805 if ( $li_html ) {
1806 echo "<ul class='post-meta'>\n{$li_html}</ul>\n";
1807 }
1808 }
1809 }
1810 }
1811 }
1812
1813 // Initialize InsertPagesPlugin object.
1814 if ( class_exists( 'InsertPagesPlugin' ) ) {
1815 $insert_pages_plugin = InsertPagesPlugin::get_instance();
1816 }
1817
1818 // Actions and Filters handled by InsertPagesPlugin class.
1819 if ( isset( $insert_pages_plugin ) ) {
1820 // Include the code that generates the options page.
1821 require_once __DIR__ . '/options.php';
1822
1823 // Get options set in WordPress dashboard (Settings > Insert Pages).
1824 $options = get_option( 'wpip_settings' );
1825 if ( false === $options || ! is_array( $options ) || ! array_key_exists( 'wpip_format', $options ) || ! array_key_exists( 'wpip_wrapper', $options ) || ! array_key_exists( 'wpip_insert_method', $options ) || ! array_key_exists( 'wpip_tinymce_filter', $options ) ) {
1826 $options = wpip_set_defaults();
1827 }
1828
1829 // Register shortcode [insert ...].
1830 add_action( 'init', array( $insert_pages_plugin, 'insert_pages_init' ), 1 );
1831 // Register shortcode [insert ...] when TinyMCE is included in a frontend ACF form.
1832 add_action( 'acf_head-input', array( $insert_pages_plugin, 'insert_pages_init' ), 1 ); // ACF 3.
1833 add_action( 'acf/input/admin_head', array( $insert_pages_plugin, 'insert_pages_init' ), 1 ); // ACF 4.
1834
1835 // Add TinyMCE button for shortcode.
1836 add_action( 'admin_head', array( $insert_pages_plugin, 'insert_pages_admin_init' ), 1 );
1837
1838 // Add quicktags button for shortcode.
1839 add_action( 'admin_print_footer_scripts', array( $insert_pages_plugin, 'insert_pages_add_quicktags' ) );
1840
1841 // Preload TinyMCE popup.
1842 add_action( 'before_wp_tiny_mce', array( $insert_pages_plugin, 'insert_pages_wp_tinymce_dialog' ), 1 );
1843
1844 // Ajax: Populate page search in TinyMCE button popup.
1845 add_action( 'wp_ajax_insertpage', array( $insert_pages_plugin, 'insert_pages_insert_page_callback' ) );
1846
1847 // Ajax: save user's last selected display and template inputs.
1848 add_action( 'wp_ajax_insertpage_save_presets', array( $insert_pages_plugin, 'insert_pages_save_presets' ) );
1849
1850 // Use internal filter to wrap inserted content in a div or span.
1851 add_filter( 'insert_pages_wrap_content', array( $insert_pages_plugin, 'insert_pages_wrap_content' ), 10, 3 );
1852
1853 /**
1854 * Register TinyMCE plugin for the toolbar button if in compatibility mode.
1855 * (to work around a SiteOrigin PageBuilder bug).
1856 *
1857 * @see https://wordpress.org/support/topic/button-in-the-toolbar-of-tinymce-disappear-conflict-page-builder/
1858 */
1859 if ( 'compatibility' === $options['wpip_tinymce_filter'] ) {
1860 add_filter( 'mce_external_plugins', array( $insert_pages_plugin, 'insert_pages_handle_filter_mce_external_plugins' ) );
1861 add_filter( 'mce_buttons', array( $insert_pages_plugin, 'insert_pages_handle_filter_mce_buttons' ) );
1862 }
1863
1864 // Register Insert Pages shortcode widget.
1865 require_once __DIR__ . '/widget.php';
1866 add_action( 'widgets_init', array( $insert_pages_plugin, 'insert_pages_widgets_init' ) );
1867 }
1868