Insert Pages - Version 2.9.1

Version Description

  • API Change: modify insert_pages_wrap_content filter. Props @heiglandreas. Example 1: `/**
    • Enable nested shortcodes by hooking into insert_pages_wrap_content. *
    • @param string $content The post content of the inserted page.
    • @param array $posts The array of post objects (typically 1) returned from querying the inserted page.
    • @param array $attributes Extra parameters modifying the inserted page.
    • page: Page ID or slug of page to be inserted.
    • display: Content to display from inserted page.
    • class: Extra classes to add to inserted page wrapper element.
    • inline: Boolean indicating wrapper element should be a span.
    • should_apply_nesting_check: Whether to disable nested inserted pages.
    • should_apply_the_content_filter: Whether to apply the_content filter to post contents and excerpts.
    • wrapper_tag: Tag to use for the wrapper element (e.g., div, span). / function your_custom_wrapper_function( $content, $posts, $attributes ) { return do_shortcode( $content ); } add_filter( 'insert_pages_wrap_content', 'your_custom_wrapper_function', 9, 3 ); Example 2: /*
    • Completely modify markup generated by Insert Pages by hooking into insert_pages_wrap_content. *
    • @param string $content The post content of the inserted page.
    • @param array $posts The array of post objects (typically 1) returned from querying the inserted page.
    • @param array $attributes Extra parameters modifying the inserted page.
    • page: Page ID or slug of page to be inserted.
    • display: Content to display from inserted page.
    • class: Extra classes to add to inserted page wrapper element.
    • inline: Boolean indicating wrapper element should be a span.
    • should_apply_nesting_check: Whether to disable nested inserted pages.
    • should_apply_the_content_filter: Whether to apply the_content filter to post contents and excerpts.
    • wrapper_tag: Tag to use for the wrapper element (e.g., div, span). */ function your_custom_wrapper_function( $content, $posts, $attributes ) { return "
      $content
      "; } add_filter( 'insert_pages_wrap_content', 'your_custom_wrapper_function', 11, 3 );`
Download this release

Release Info

Developer figureone
Plugin Icon wp plugin Insert Pages
Version 2.9.1
Comparing to
See all releases

Code changes from version 2.9 to 2.9.1

Files changed (2) hide show
  1. insert-pages.php +66 -61
  2. readme.txt +28 -13
insert-pages.php CHANGED
@@ -5,7 +5,7 @@ Plugin Name: Insert Pages
5
  Plugin URI: https://github.com/uhm-coe/insert-pages
6
  Description: Insert Pages lets you embed any WordPress content (e.g., pages, posts, custom post types) into other WordPress content using the Shortcode API.
7
  Author: Paul Ryan
8
- Version: 2.9
9
  Author URI: http://www.linkedin.com/in/paulrryan
10
  License: GPL2
11
  */
@@ -107,13 +107,23 @@ if ( !class_exists( 'InsertPagesPlugin' ) ) {
107
  function insertPages_handleShortcode_insert( $atts, $content = null ) {
108
  global $wp_query, $post, $wp_current_filter;
109
 
110
- $shortcode_attributes = shortcode_atts( array(
 
111
  'page' => '0',
112
  'display' => 'all',
113
  'class' => '',
114
  'inline' => false,
115
  ), $atts );
116
- extract( $shortcode_attributes );
 
 
 
 
 
 
 
 
 
117
 
118
  // Get options set in WordPress dashboard (Settings > Insert Pages).
119
  $options = get_option( 'wpip_settings' );
@@ -121,17 +131,31 @@ if ( !class_exists( 'InsertPagesPlugin' ) ) {
121
  $options = wpip_set_defaults();
122
  }
123
 
124
- // Validation checks.
125
- if ( $page === '0' ) {
126
- return $content;
127
- }
 
 
 
 
128
 
129
- // Trying to embed same page in itself.
130
- if ( $page == $post->ID || $page == $post->post_name ) {
131
- return $content;
 
 
 
 
 
 
 
 
 
 
132
  }
133
 
134
- $should_apply_nesting_check = true;
135
  /**
136
  * Filter the flag indicating whether to apply deep nesting check
137
  * that can prevent circular loops. Note that some use cases rely
@@ -140,10 +164,10 @@ if ( !class_exists( 'InsertPagesPlugin' ) ) {
140
  *
141
  * @param bool $apply_the_content_filter Indicates whether to apply the_content filter.
142
  */
143
- $should_apply_nesting_check = apply_filters( 'insert_pages_apply_nesting_check', $should_apply_nesting_check );
144
 
145
  // Don't allow inserted pages to be added to the_content more than once (prevent infinite loops).
146
- if ( $should_apply_nesting_check ) {
147
  $done = false;
148
  foreach ( $wp_current_filter as $filter ) {
149
  if ( 'the_content' == $filter ) {
@@ -157,48 +181,25 @@ if ( !class_exists( 'InsertPagesPlugin' ) ) {
157
  }
158
 
159
  // Convert slugs to page IDs to standardize query_posts() lookup below.
160
- if ( ! is_numeric( $page ) ) {
161
- $page_object = get_page_by_path( $page, OBJECT, get_post_types() );
162
- $page = $page_object ? $page_object->ID : $page;
163
  }
164
 
165
- if ( is_numeric( $page ) ) {
166
  $args = array(
167
- 'p' => intval( $page ),
168
  'post_type' => get_post_types(),
169
  );
170
  } else {
171
  $args = array(
172
- 'name' => esc_attr( $page ),
173
  'post_type' => get_post_types(),
174
  );
175
  }
176
 
177
  $posts = query_posts( $args );
178
 
179
- $should_apply_the_content_filter = true;
180
- /**
181
- * Filter the flag indicating whether to apply the_content filter to post
182
- * contents and excerpts that are being inserted.
183
- *
184
- * @param bool $apply_the_content_filter Indicates whether to apply the_content filter.
185
- */
186
- $should_apply_the_content_filter = apply_filters( 'insert_pages_apply_the_content_filter', $should_apply_the_content_filter );
187
-
188
- $should_use_inline_wrapper = ( $inline !== false && $inline !== 'false' ) || array_search( 'inline', $atts ) === 0 || ( array_key_exists( 'wpip_wrapper', $options ) && $options['wpip_wrapper'] === 'inline' );
189
- /**
190
- * Filter the flag indicating whether to wrap the inserted content in inline tags (span).
191
- *
192
- * @param bool $use_inline_wrapper Indicates whether to wrap the content in span tags.
193
- */
194
- $should_use_inline_wrapper = apply_filters( 'insert_pages_use_inline_wrapper', $should_use_inline_wrapper );
195
-
196
- // Disable the_content filter if using inline tags, since wpautop
197
- // inserts p tags and we can't have any inside inline elements.
198
- if ( $should_use_inline_wrapper ) {
199
- $should_apply_the_content_filter = false;
200
- }
201
-
202
  // Start our new Loop (only iterate once).
203
  if ( have_posts() ) {
204
  ob_start(); // Start output buffering so we can save the output to string
@@ -208,8 +209,8 @@ if ( !class_exists( 'InsertPagesPlugin' ) ) {
208
  // since Beaver Builder relies on it to load the appropraite styles.
209
  if ( class_exists( 'FLBuilder' ) ) {
210
  $old_post_id = $post->ID;
211
- $post->ID = $page;
212
- FLBuilder::enqueue_layout_styles_scripts( $page );
213
  $post->ID = $old_post_id;
214
  }
215
 
@@ -218,10 +219,10 @@ if ( !class_exists( 'InsertPagesPlugin' ) ) {
218
  // This plugin conflicts with Sharing, because Sharing assumes the_content and the_excerpt filters
219
  // are only getting called once. The fix here is to disable processing of filters on the_content in
220
  // the inserted page. @see https://codex.wordpress.org/Function_Reference/the_content#Alternative_Usage
221
- switch ( $display ) {
222
  case "title":
223
  the_post();
224
- $title_tag = $should_use_inline_wrapper ? 'span' : 'h1';
225
  echo "<$title_tag class='insert-page-title'>";
226
  the_title();
227
  echo "</$title_tag>";
@@ -233,27 +234,27 @@ if ( !class_exists( 'InsertPagesPlugin' ) ) {
233
  case "excerpt":
234
  the_post();
235
  ?><h1><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></h1><?php
236
- if ( $should_apply_the_content_filter ) the_excerpt(); else echo get_the_excerpt();
237
  break;
238
  case "excerpt-only":
239
  the_post();
240
- if ( $should_apply_the_content_filter ) the_excerpt(); else echo get_the_excerpt();
241
  break;
242
  case "content":
243
  the_post();
244
- if ( $should_apply_the_content_filter ) the_content(); else echo get_the_content();
245
  break;
246
  case "all":
247
  the_post();
248
- $title_tag = $should_use_inline_wrapper ? 'span' : 'h1';
249
  echo "<$title_tag class='insert-page-title'>";
250
  the_title();
251
  echo "</$title_tag>";
252
- if ( $should_apply_the_content_filter ) the_content(); else echo get_the_content();
253
  the_meta();
254
  break;
255
  default: // display is either invalid, or contains a template file to use
256
- $template = locate_template( $display );
257
  if ( strlen( $template ) > 0 ) {
258
  include $template; // execute the template code
259
  } else { // Couldn't find template, so fall back to printing a link to the page.
@@ -276,24 +277,28 @@ if ( !class_exists( 'InsertPagesPlugin' ) ) {
276
 
277
  wp_reset_query();
278
 
279
- $wrapper_tag = $should_use_inline_wrapper ? 'span' : 'div';
280
  /**
281
  * Filter the markup generated for the inserted page.
282
  *
283
- * @param string $wrapper_tag The html element to wrap the content in (typically div or span).
284
- * @param string $extra_classes Space-delimited list of classes to add to the wrapper element.
285
- * @param int $post_id The ID of the inserted page.
286
  * @param string $content The post content of the inserted page.
 
 
 
 
 
 
 
 
 
287
  */
288
- $content = apply_filters( 'insert_pages_wrap_content', $wrapper_tag, $class, $page, $content );
289
 
290
  return $content;
291
- //return do_shortcode($content); // careful: watch for infinite loops with nested inserts
292
  }
293
 
294
  // Default filter for insert_pages_wrap_content.
295
- function insertPages_wrap_content( $wrapper_tag, $extra_classes, $post_id, $content ) {
296
- return "<{$wrapper_tag} data-post-id='{$post_id}' class='insert-page insert-page-{$post_id} {$extra_classes}'>{$content}</{$wrapper_tag}>";
297
  }
298
 
299
  // Filter hook: Add a button to the TinyMCE toolbar for our insert page tool
@@ -546,5 +551,5 @@ if ( isset( $insertPages_plugin ) ) {
546
  add_action( 'before_wp_tiny_mce', array( $insertPages_plugin, 'insertPages_wp_tinymce_dialog' ), 1 ); // Preload TinyMCE popup
547
  add_action( 'wp_ajax_insertpage', array( $insertPages_plugin, 'insertPages_insert_page_callback' ) ); // Populate page search in TinyMCE button popup in this ajax call
548
  add_action( 'admin_print_footer_scripts', array( $insertPages_plugin, 'insertPages_add_quicktags' ) );
549
- add_filter( 'insert_pages_wrap_content', array( $insertPages_plugin, 'insertPages_wrap_content' ), 10, 4 );
550
  }
5
  Plugin URI: https://github.com/uhm-coe/insert-pages
6
  Description: Insert Pages lets you embed any WordPress content (e.g., pages, posts, custom post types) into other WordPress content using the Shortcode API.
7
  Author: Paul Ryan
8
+ Version: 2.9.1
9
  Author URI: http://www.linkedin.com/in/paulrryan
10
  License: GPL2
11
  */
107
  function insertPages_handleShortcode_insert( $atts, $content = null ) {
108
  global $wp_query, $post, $wp_current_filter;
109
 
110
+ // Shortcode attributes.
111
+ $attributes = shortcode_atts( array(
112
  'page' => '0',
113
  'display' => 'all',
114
  'class' => '',
115
  'inline' => false,
116
  ), $atts );
117
+
118
+ // Validation checks.
119
+ if ( $attributes['page'] === '0' ) {
120
+ return $content;
121
+ }
122
+
123
+ // Trying to embed same page in itself.
124
+ if ( $attributes['page'] == $post->ID || $attributes['page'] == $post->post_name ) {
125
+ return $content;
126
+ }
127
 
128
  // Get options set in WordPress dashboard (Settings > Insert Pages).
129
  $options = get_option( 'wpip_settings' );
131
  $options = wpip_set_defaults();
132
  }
133
 
134
+ $attributes['inline'] = ( $attributes['inline'] !== false && $attributes['inline'] !== 'false' ) || array_search( 'inline', $atts ) === 0 || ( array_key_exists( 'wpip_wrapper', $options ) && $options['wpip_wrapper'] === 'inline' );
135
+ /**
136
+ * Filter the flag indicating whether to wrap the inserted content in inline tags (span).
137
+ *
138
+ * @param bool $use_inline_wrapper Indicates whether to wrap the content in span tags.
139
+ */
140
+ $attributes['inline'] = apply_filters( 'insert_pages_use_inline_wrapper', $attributes['inline'] );
141
+ $attributes['wrapper_tag'] = $attributes['inline'] ? 'span' : 'div';
142
 
143
+ $attributes['should_apply_the_content_filter'] = true;
144
+ /**
145
+ * Filter the flag indicating whether to apply the_content filter to post
146
+ * contents and excerpts that are being inserted.
147
+ *
148
+ * @param bool $apply_the_content_filter Indicates whether to apply the_content filter.
149
+ */
150
+ $attributes['should_apply_the_content_filter'] = apply_filters( 'insert_pages_apply_the_content_filter', $attributes['should_apply_the_content_filter'] );
151
+
152
+ // Disable the_content filter if using inline tags, since wpautop
153
+ // inserts p tags and we can't have any inside inline elements.
154
+ if ( $attributes['inline'] ) {
155
+ $attributes['should_apply_the_content_filter'] = false;
156
  }
157
 
158
+ $attributes['should_apply_nesting_check'] = true;
159
  /**
160
  * Filter the flag indicating whether to apply deep nesting check
161
  * that can prevent circular loops. Note that some use cases rely
164
  *
165
  * @param bool $apply_the_content_filter Indicates whether to apply the_content filter.
166
  */
167
+ $attributes['should_apply_nesting_check'] = apply_filters( 'insert_pages_apply_nesting_check', $attributes['should_apply_nesting_check'] );
168
 
169
  // Don't allow inserted pages to be added to the_content more than once (prevent infinite loops).
170
+ if ( $attributes['should_apply_nesting_check'] ) {
171
  $done = false;
172
  foreach ( $wp_current_filter as $filter ) {
173
  if ( 'the_content' == $filter ) {
181
  }
182
 
183
  // Convert slugs to page IDs to standardize query_posts() lookup below.
184
+ if ( ! is_numeric( $attributes['page'] ) ) {
185
+ $page_object = get_page_by_path( $attributes['page'], OBJECT, get_post_types() );
186
+ $attributes['page'] = $page_object ? $page_object->ID : $attributes['page'];
187
  }
188
 
189
+ if ( is_numeric( $attributes['page'] ) ) {
190
  $args = array(
191
+ 'p' => intval( $attributes['page'] ),
192
  'post_type' => get_post_types(),
193
  );
194
  } else {
195
  $args = array(
196
+ 'name' => esc_attr( $attributes['page'] ),
197
  'post_type' => get_post_types(),
198
  );
199
  }
200
 
201
  $posts = query_posts( $args );
202
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
203
  // Start our new Loop (only iterate once).
204
  if ( have_posts() ) {
205
  ob_start(); // Start output buffering so we can save the output to string
209
  // since Beaver Builder relies on it to load the appropraite styles.
210
  if ( class_exists( 'FLBuilder' ) ) {
211
  $old_post_id = $post->ID;
212
+ $post->ID = $attributes['page'];
213
+ FLBuilder::enqueue_layout_styles_scripts( $attributes['page'] );
214
  $post->ID = $old_post_id;
215
  }
216
 
219
  // This plugin conflicts with Sharing, because Sharing assumes the_content and the_excerpt filters
220
  // are only getting called once. The fix here is to disable processing of filters on the_content in
221
  // the inserted page. @see https://codex.wordpress.org/Function_Reference/the_content#Alternative_Usage
222
+ switch ( $attributes['display'] ) {
223
  case "title":
224
  the_post();
225
+ $title_tag = $attributes['inline'] ? 'span' : 'h1';
226
  echo "<$title_tag class='insert-page-title'>";
227
  the_title();
228
  echo "</$title_tag>";
234
  case "excerpt":
235
  the_post();
236
  ?><h1><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></h1><?php
237
+ if ( $attributes['should_apply_the_content_filter'] ) the_excerpt(); else echo get_the_excerpt();
238
  break;
239
  case "excerpt-only":
240
  the_post();
241
+ if ( $attributes['should_apply_the_content_filter'] ) the_excerpt(); else echo get_the_excerpt();
242
  break;
243
  case "content":
244
  the_post();
245
+ if ( $attributes['should_apply_the_content_filter'] ) the_content(); else echo get_the_content();
246
  break;
247
  case "all":
248
  the_post();
249
+ $title_tag = $attributes['inline'] ? 'span' : 'h1';
250
  echo "<$title_tag class='insert-page-title'>";
251
  the_title();
252
  echo "</$title_tag>";
253
+ if ( $attributes['should_apply_the_content_filter'] ) the_content(); else echo get_the_content();
254
  the_meta();
255
  break;
256
  default: // display is either invalid, or contains a template file to use
257
+ $template = locate_template( $attributes['display'] );
258
  if ( strlen( $template ) > 0 ) {
259
  include $template; // execute the template code
260
  } else { // Couldn't find template, so fall back to printing a link to the page.
277
 
278
  wp_reset_query();
279
 
 
280
  /**
281
  * Filter the markup generated for the inserted page.
282
  *
 
 
 
283
  * @param string $content The post content of the inserted page.
284
+ * @param array $posts The array of post objects (typically 1) returned from querying the inserted page.
285
+ * @param array $attributes Extra parameters modifying the inserted page.
286
+ * page: Page ID or slug of page to be inserted.
287
+ * display: Content to display from inserted page.
288
+ * class: Extra classes to add to inserted page wrapper element.
289
+ * inline: Boolean indicating wrapper element should be a span.
290
+ * should_apply_nesting_check: Whether to disable nested inserted pages.
291
+ * should_apply_the_content_filter: Whether to apply the_content filter to post contents and excerpts.
292
+ * wrapper_tag: Tag to use for the wrapper element (e.g., div, span).
293
  */
294
+ $content = apply_filters( 'insert_pages_wrap_content', $content, $posts, $attributes );
295
 
296
  return $content;
 
297
  }
298
 
299
  // Default filter for insert_pages_wrap_content.
300
+ function insertPages_wrap_content( $content, $posts, $attributes ) {
301
+ return "<{$attributes['wrapper_tag']} data-post-id='{$attributes['page']}' class='insert-page insert-page-{$attributes['page']} {$attributes['extra_classes']}'>{$content}</{$attributes['wrapper_tag']}>";
302
  }
303
 
304
  // Filter hook: Add a button to the TinyMCE toolbar for our insert page tool
551
  add_action( 'before_wp_tiny_mce', array( $insertPages_plugin, 'insertPages_wp_tinymce_dialog' ), 1 ); // Preload TinyMCE popup
552
  add_action( 'wp_ajax_insertpage', array( $insertPages_plugin, 'insertPages_insert_page_callback' ) ); // Populate page search in TinyMCE button popup in this ajax call
553
  add_action( 'admin_print_footer_scripts', array( $insertPages_plugin, 'insertPages_add_quicktags' ) );
554
+ add_filter( 'insert_pages_wrap_content', array( $insertPages_plugin, 'insertPages_wrap_content' ), 10, 3 );
555
  }
readme.txt CHANGED
@@ -86,34 +86,49 @@ Just one! The plugin prevents you from embedding a page in itself, but you can t
86
 
87
  == Changelog ==
88
 
89
- = 2.9 =
90
- * Add filter for altering the markup generated by Insert Pages. This filter is used internally at priority 10, so if you want to modify $content, do it earlier (priority 1-9); if you want to reconstruct the generated markup using the supplied parameters, do it after (priority 11+).
91
  Example 1:
92
  `/**
93
  * Enable nested shortcodes by hooking into insert_pages_wrap_content.
94
  *
95
- * @param string $wrapper_tag The html element to wrap the content in (typically div or span).
96
- * @param string $extra_classes Space-delimited list of classes to add to the wrapper element.
97
- * @param int $post_id The ID of the inserted page.
98
  * @param string $content The post content of the inserted page.
 
 
 
 
 
 
 
 
 
99
  */
100
- function your_custom_wrapper_function( $wrapper_tag, $extra_classes, $post_id, $content ) {
101
  return do_shortcode( $content );
102
  }
103
- add_filter( 'insert_pages_wrap_content', 'your_custom_wrapper_function', 9, 4 );`
104
  Example 2:
105
  `/**
106
  * Completely modify markup generated by Insert Pages by hooking into insert_pages_wrap_content.
107
  *
108
- * @param string $wrapper_tag The html element to wrap the content in (typically div or span).
109
- * @param string $extra_classes Space-delimited list of classes to add to the wrapper element.
110
- * @param int $post_id The ID of the inserted page.
111
  * @param string $content The post content of the inserted page.
 
 
 
 
 
 
 
 
 
112
  */
113
- function your_custom_wrapper_function( $wrapper_tag, $extra_classes, $post_id, $content ) {
114
- return "<section class='my-section $extra_classes'>$content</section>";
115
  }
116
- add_filter( 'insert_pages_wrap_content', 'your_custom_wrapper_function', 11, 4 );`
 
 
 
117
 
118
  = 2.8 =
119
  * Feature: Add options page with option to insert page IDs instead of page slugs (users of WPML will need this feature if translated pages all share the same page slug).
86
 
87
  == Changelog ==
88
 
89
+ = 2.9.1 =
90
+ * API Change: modify insert_pages_wrap_content filter. Props @heiglandreas.
91
  Example 1:
92
  `/**
93
  * Enable nested shortcodes by hooking into insert_pages_wrap_content.
94
  *
 
 
 
95
  * @param string $content The post content of the inserted page.
96
+ * @param array $posts The array of post objects (typically 1) returned from querying the inserted page.
97
+ * @param array $attributes Extra parameters modifying the inserted page.
98
+ * page: Page ID or slug of page to be inserted.
99
+ * display: Content to display from inserted page.
100
+ * class: Extra classes to add to inserted page wrapper element.
101
+ * inline: Boolean indicating wrapper element should be a span.
102
+ * should_apply_nesting_check: Whether to disable nested inserted pages.
103
+ * should_apply_the_content_filter: Whether to apply the_content filter to post contents and excerpts.
104
+ * wrapper_tag: Tag to use for the wrapper element (e.g., div, span).
105
  */
106
+ function your_custom_wrapper_function( $content, $posts, $attributes ) {
107
  return do_shortcode( $content );
108
  }
109
+ add_filter( 'insert_pages_wrap_content', 'your_custom_wrapper_function', 9, 3 );`
110
  Example 2:
111
  `/**
112
  * Completely modify markup generated by Insert Pages by hooking into insert_pages_wrap_content.
113
  *
 
 
 
114
  * @param string $content The post content of the inserted page.
115
+ * @param array $posts The array of post objects (typically 1) returned from querying the inserted page.
116
+ * @param array $attributes Extra parameters modifying the inserted page.
117
+ * page: Page ID or slug of page to be inserted.
118
+ * display: Content to display from inserted page.
119
+ * class: Extra classes to add to inserted page wrapper element.
120
+ * inline: Boolean indicating wrapper element should be a span.
121
+ * should_apply_nesting_check: Whether to disable nested inserted pages.
122
+ * should_apply_the_content_filter: Whether to apply the_content filter to post contents and excerpts.
123
+ * wrapper_tag: Tag to use for the wrapper element (e.g., div, span).
124
  */
125
+ function your_custom_wrapper_function( $content, $posts, $attributes ) {
126
+ return "<section class='my-section {$attributes['class']}'>$content</section>";
127
  }
128
+ add_filter( 'insert_pages_wrap_content', 'your_custom_wrapper_function', 11, 3 );`
129
+
130
+ = 2.9 =
131
+ * Add filter for altering the markup generated by Insert Pages. This filter is used internally at priority 10, so if you want to modify $content, do it earlier (priority 1-9); if you want to reconstruct the generated markup using the supplied parameters, do it after (priority 11+). Props @heiglandreas!
132
 
133
  = 2.8 =
134
  * Feature: Add options page with option to insert page IDs instead of page slugs (users of WPML will need this feature if translated pages all share the same page slug).