PluginProbe
Insert Pages / 3.11.5
Insert Pages v3.11.5
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.11.5, at insert-pages.php

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