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

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