diff --git a/docs/screenshots/legacy-pages-transition/desktop.jpg b/docs/screenshots/legacy-pages-transition/desktop.jpg
new file mode 100644
index 0000000..98f62dd
Binary files /dev/null and b/docs/screenshots/legacy-pages-transition/desktop.jpg differ
diff --git a/docs/screenshots/legacy-pages-transition/mobile.jpg b/docs/screenshots/legacy-pages-transition/mobile.jpg
new file mode 100644
index 0000000..97ea51c
Binary files /dev/null and b/docs/screenshots/legacy-pages-transition/mobile.jpg differ
diff --git a/src/assets/js/simply-rets-admin.js b/src/assets/js/simply-rets-admin.js
index 496e986..f9cf1bc 100644
--- a/src/assets/js/simply-rets-admin.js
+++ b/src/assets/js/simply-rets-admin.js
@@ -97,9 +97,20 @@ jQuery(function($) {
}
try { active = window.sessionStorage.getItem(storageKey) || active; } catch (error) { /* Use the account section. */ }
+ function linkedSection() {
+ var prefix = '#sr-section-';
+ if (window.location.hash.indexOf(prefix) !== 0) return null;
+ var section = window.location.hash.slice(prefix.length);
+ return panels.filter(function() { return $(this).attr('data-sr-panel') === section; }).length ? section : null;
+ }
+ active = linkedSection() || active;
root.addClass('sr-settings-enhanced');
root.find('.sr-settings-toolbar, .sr-settings-nav, .sr-settings-savebar-info, .sr-reveal-credentials').prop('hidden', false);
showSection(active, false);
+ $(window).on('hashchange', function() {
+ var section = linkedSection();
+ if (section) showSection(section, true);
+ });
nav.on('click', 'a', function(event) {
event.preventDefault();
diff --git a/src/simply-rets-admin.php b/src/simply-rets-admin.php
index 0195b01..950b3e4 100644
--- a/src/simply-rets-admin.php
+++ b/src/simply-rets-admin.php
@@ -14,9 +14,63 @@
add_action("wp_loaded", array("SrAdminSettings", "createDemoPage"));
add_action("admin_notices", array("SrAdminSettings", "adminMessages"));
+add_action('admin_notices', array('SrAdminSettings', 'legacyPagesNotice'));
+add_action('admin_post_sr_dismiss_legacy_pages_notice', array('SrAdminSettings', 'dismissLegacyPagesNotice'));
class SrAdminSettings {
+ public static function canManageLegacyPages() {
+ $post_type = get_post_type_object('sr-listings');
+ return $post_type && current_user_can('manage_options') && current_user_can($post_type->cap->edit_posts);
+ }
+
+ public static function hasLegacyPages() {
+ global $wpdb;
+ // Look for persisted posts only, including drafts, private pages and trash.
+ // Automatic drafts are editor placeholders; dynamic routes are not stored.
+ return $wpdb->get_var($wpdb->prepare(
+ "SELECT ID FROM {$wpdb->posts} WHERE post_type = %s AND post_status NOT IN (%s, %s) LIMIT 1",
+ 'sr-listings', 'auto-draft', 'inherit'
+ )) !== null;
+ }
+
+ public static function legacyPagesSettingsUrl() {
+ return admin_url('options-general.php?page=simplyrets-admin.php') . '#sr-section-legacy-pages';
+ }
+
+ public static function legacyPagesNotice() {
+ if ((defined('DOING_AJAX') && DOING_AJAX) ||
+ (defined('IFRAME_REQUEST') && IFRAME_REQUEST) ||
+ (function_exists('is_network_admin') && is_network_admin()) ||
+ !self::canManageLegacyPages() || !self::hasLegacyPages() ||
+ get_user_option('sr_legacy_pages_transition_dismissed')) {
+ return;
+ }
+ ?>
+
+
SimplyRETS Pages are now legacy pages. Your existing pages continue to work. For new pages, use SimplyRETS shortcodes on ordinary WordPress pages.
+
Find your existing pages in Settings → SimplyRETS → Legacy Pages.
+
+
+ array('Analytics', 'ListHub tracking', 'chart-bar'),
'messages' => array('Custom messages', 'Disclaimers & empty results', 'editor-alignleft')
);
+ $show_legacy_pages = self::canManageLegacyPages() && self::hasLegacyPages();
+ if ($show_legacy_pages) {
+ $sections['legacy-pages'] = array('Legacy Pages', 'Manage existing pages', 'admin-page');
+ }
// If meta data refresh action was posted, verify nonce and perform action
$update_meta_nonce_field = 'sr_update_meta_data_nonce_field';
@@ -859,6 +917,15 @@ public static function sr_admin_page() {
+
+
+
Legacy Pages
+
The custom SimplyRETS page editor is deprecated. Your existing pages are still supported and continue to work, including their filters, templates, and URLs.
+
For new listing pages, add SimplyRETS shortcodes to ordinary WordPress pages. For example, use [sr_map_search search_form="true" list_view="true"] for a map and listing search.
+
Explore shortcode examples (opens in a new tab)
+
Manage existing pages
+
+
No matching settings
Try a different keyword, or clear the search to browse all sections.
No unsaved changesSave applies to all sections.
@@ -869,4 +936,4 @@ public static function sr_admin_page() {
\ No newline at end of file
+} ?>
diff --git a/src/simply-rets-setup.php b/src/simply-rets-setup.php
index 2ca7160..f0a899d 100644
--- a/src/simply-rets-setup.php
+++ b/src/simply-rets-setup.php
@@ -94,6 +94,8 @@ public static function srRegisterPostType() {
);
$args = array(
'public' => true,
+ // Keep saved pages and their editor/routes available through settings.
+ 'show_in_menu' => false,
'has_archive' => false,
'labels' => $labels,
'description' => 'SimplyRETS property listings pages',
diff --git a/src/tests/test-legacy-pages.php b/src/tests/test-legacy-pages.php
new file mode 100644
index 0000000..9b0d202
--- /dev/null
+++ b/src/tests/test-legacy-pages.php
@@ -0,0 +1,168 @@
+factory->user->create(array('role' => $role));
+ wp_set_current_user($user_id);
+ return $user_id;
+ }
+
+ private function noticeMarkup() {
+ ob_start();
+ SrAdminSettings::legacyPagesNotice();
+ return ob_get_clean();
+ }
+
+ private function dismissalIsRejected() {
+ $handler = function() {
+ return function() { throw new RuntimeException('Dismissal rejected'); };
+ };
+ add_filter('wp_die_handler', $handler);
+ try {
+ SrAdminSettings::dismissLegacyPagesNotice();
+ } catch (RuntimeException $error) {
+ return $error->getMessage() === 'Dismissal rejected';
+ } finally {
+ remove_filter('wp_die_handler', $handler);
+ }
+ return false;
+ }
+
+ public function testUnauthorizedUsersCannotDismissNotice() {
+ $user_id = $this->signIn('editor');
+ $original_request = $_REQUEST;
+ try {
+ $_REQUEST['_wpnonce'] = wp_create_nonce('sr_dismiss_legacy_pages_notice');
+ $this->assertTrue($this->dismissalIsRejected());
+ $this->assertFalse(get_user_option('sr_legacy_pages_transition_dismissed', $user_id));
+ } finally {
+ $_REQUEST = $original_request;
+ }
+ }
+
+ public function testMissingAndInvalidNoncesCannotDismissNotice() {
+ $user_id = $this->signIn('administrator');
+ $original_request = $_REQUEST;
+ try {
+ unset($_REQUEST['_wpnonce']);
+ $this->assertTrue($this->dismissalIsRejected());
+ $_REQUEST['_wpnonce'] = 'invalid';
+ $this->assertTrue($this->dismissalIsRejected());
+ $this->assertFalse(get_user_option('sr_legacy_pages_transition_dismissed', $user_id));
+ } finally {
+ $_REQUEST = $original_request;
+ }
+ }
+
+ public function testOrdinaryPagesAndAutomaticDraftsDoNotTriggerTransition() {
+ $this->signIn('administrator');
+ $this->factory->post->create(array('post_type' => 'page'));
+ $this->factory->post->create(array('post_type' => 'sr-listings', 'post_status' => 'auto-draft'));
+ $this->assertFalse(SrAdminSettings::hasLegacyPages());
+ $this->assertSame('', $this->noticeMarkup());
+ ob_start();
+ SrAdminSettings::sr_admin_page();
+ $this->assertFalse(strpos(ob_get_clean(), 'data-sr-panel="legacy-pages"'));
+ }
+
+ public function testSavedStatusesIncludingPrivateDraftAndTrashTriggerTransition() {
+ foreach (array('publish', 'draft', 'private', 'pending', 'future', 'trash') as $status) {
+ $post_id = $this->factory->post->create(array(
+ 'post_type' => 'sr-listings',
+ 'post_status' => $status,
+ 'post_date' => $status === 'future' ? '2099-01-01 00:00:00' : '2020-01-01 00:00:00'
+ ));
+ $this->assertTrue(SrAdminSettings::hasLegacyPages(), $status);
+ wp_delete_post($post_id, true);
+ $this->assertFalse(SrAdminSettings::hasLegacyPages(), $status . ' removed');
+ }
+ }
+
+ public function testSynthesizedListingRoutesDoNotTriggerTransition() {
+ global $wp_query;
+ $original_query = $wp_query;
+ try {
+ foreach (array('sr-single', 'sr-search', 'sr-openhouses') as $route) {
+ $wp_query = new WP_Query();
+ $wp_query->query = array('sr-listings' => $route);
+ $posts = SimplyRetsCustomPostPages::srCreateDynamicPost(array());
+ $this->assertCount(1, $posts);
+ $this->assertSame('sr-listings', $posts[0]->post_type);
+ $this->assertFalse(SrAdminSettings::hasLegacyPages());
+ }
+ } finally {
+ $wp_query = $original_query;
+ }
+ }
+
+ public function testNoticeAndTabRequireSettingsAndPageEditingPermissions() {
+ $this->factory->post->create(array('post_type' => 'sr-listings'));
+ foreach (array('subscriber', 'editor') as $role) {
+ $this->signIn($role);
+ $this->assertFalse(SrAdminSettings::canManageLegacyPages());
+ $this->assertSame('', $this->noticeMarkup());
+ }
+ $this->signIn('subscriber');
+ $user = wp_get_current_user();
+ $user->add_cap('manage_options');
+ $this->assertFalse(SrAdminSettings::canManageLegacyPages());
+ ob_start();
+ SrAdminSettings::sr_admin_page();
+ $this->assertFalse(strpos(ob_get_clean(), 'data-sr-panel="legacy-pages"'));
+ $user->add_cap('edit_pages');
+ $this->assertTrue(SrAdminSettings::canManageLegacyPages());
+ }
+
+ public function testNoticeDismissalIsPerUserAndSurvivesNewPageVisits() {
+ $this->factory->post->create(array('post_type' => 'sr-listings'));
+ $user_id = $this->signIn('administrator');
+ $markup = $this->noticeMarkup();
+ $this->assertNotFalse(strpos($markup, '#sr-section-legacy-pages'));
+ $this->assertNotFalse(strpos($markup, 'name="_wpnonce"'));
+ $this->assertNotFalse(strpos($markup, 'sr_dismiss_legacy_pages_notice'));
+ update_user_option($user_id, 'sr_legacy_pages_transition_dismissed', 1, false);
+ global $wpdb;
+ $this->assertSame('1', (string) get_user_meta($user_id, $wpdb->prefix . 'sr_legacy_pages_transition_dismissed', true));
+ $this->assertSame('', get_user_meta($user_id, 'sr_legacy_pages_transition_dismissed', true));
+ $this->assertSame('', $this->noticeMarkup());
+ $this->signIn('administrator');
+ $this->assertNotSame('', $this->noticeMarkup());
+ wp_set_current_user($user_id);
+ $this->assertSame('', $this->noticeMarkup());
+ }
+
+ public function testLegacyPanelKeepsManagementWithinSettings() {
+ $this->signIn('administrator');
+ $this->factory->post->create(array('post_type' => 'sr-listings', 'post_status' => 'trash'));
+ ob_start();
+ SrAdminSettings::sr_admin_page();
+ $markup = ob_get_clean();
+ $this->assertNotFalse(strpos($markup, 'data-sr-section="legacy-pages"'));
+ $this->assertNotFalse(strpos($markup, 'id="sr-section-legacy-pages"'));
+ $this->assertFalse(strpos($markup, 'data-sr-panel="legacy-pages" hidden'));
+ $this->assertNotFalse(strpos($markup, esc_url(admin_url('edit.php?post_type=sr-listings'))));
+ $this->assertTrue(strpos($markup, 'data-sr-section="messages"') < strpos($markup, 'data-sr-section="legacy-pages"'));
+ $this->assertTrue(strpos($markup, 'id="sr-section-legacy-pages"') < strpos($markup, 'Manage existing pages'));
+ }
+
+ public function testPostTypeKeepsEditorAndRoutingWithoutSidebarEntry() {
+ $post_type = get_post_type_object('sr-listings');
+ $this->assertFalse($post_type->show_in_menu);
+ $this->assertTrue($post_type->show_ui);
+ $this->assertTrue($post_type->public);
+ $this->assertSame('sr-listings', $post_type->query_var);
+ $this->assertSame('edit_pages', $post_type->cap->edit_posts);
+ $this->assertTrue(post_type_supports('sr-listings', 'editor'));
+ $post_id = $this->factory->post->create(array('post_type' => 'sr-listings', 'post_status' => 'publish'));
+ $this->assertNotEmpty(get_permalink($post_id));
+ $this->assertNotFalse(has_filter('the_posts', array('SimplyRetsCustomPostPages', 'srCreateDynamicPost')));
+ $this->assertNotFalse(has_action('add_meta_boxes', array('SimplyRetsCustomPostPages', 'postFilterMetaBox')));
+ $this->assertNotFalse(has_filter('single_template', array('SimplyRetsCustomPostPages', 'srLoadPostTemplate')));
+ }
+}