├── .gitignore ├── README.md ├── app ├── Activation │ ├── Activate.php │ ├── Dependencies.php │ └── Updates │ │ ├── CustomFieldsToHidden.php │ │ └── Updates.php ├── Bootstrap.php ├── Config │ ├── AdminMenuSettings.php │ ├── Settings.php │ └── SettingsRepository.php ├── Entities │ ├── AdminCustomization │ │ ├── AdminCustomizationBase.php │ │ ├── AdminCustomizationFactory.php │ │ ├── AdminMenuItems.php │ │ └── AdminMenuSanitization.php │ ├── AdminMenu │ │ ├── AdminMenu.php │ │ ├── AdminSubmenu.php │ │ ├── AdminSubmenuDefault.php │ │ ├── AdminSubmenuExpander.php │ │ ├── BlockEditorLink.php │ │ └── EnabledMenus.php │ ├── Confirmation │ │ ├── ConfirmationFactory.php │ │ ├── ConfirmationInterface.php │ │ ├── LinkDeletedConfirmation.php │ │ ├── TrashConfirmation.php │ │ └── TrashRestoredConfirmation.php │ ├── DefaultList │ │ ├── DefaultListFactory.php │ │ └── NestedViewLink.php │ ├── Listing │ │ ├── Listing.php │ │ ├── ListingActions.php │ │ ├── ListingQuery.php │ │ └── ListingRepository.php │ ├── NavMenu │ │ ├── NavMenuActions.php │ │ ├── NavMenuFrontEnd.php │ │ ├── NavMenuRemoveItem.php │ │ ├── NavMenuRepository.php │ │ ├── NavMenuSync.php │ │ ├── NavMenuSyncListing.php │ │ ├── NavMenuSyncMenu.php │ │ └── NavMenuTrashActions.php │ ├── PluginIntegration │ │ ├── AdvancedCustomFields.php │ │ ├── DarkMode.php │ │ ├── EditorialAccessManager.php │ │ ├── IntegrationFactory.php │ │ ├── WPML.php │ │ └── YoastSeo.php │ ├── Post │ │ ├── PostCloner.php │ │ ├── PostDataFactory.php │ │ ├── PostFactory.php │ │ ├── PostRepository.php │ │ ├── PostSaveActions.php │ │ ├── PostTrashActions.php │ │ ├── PostUpdateRepository.php │ │ └── PrivatePostParent.php │ ├── PostType │ │ ├── PostTypeCustomFields.php │ │ ├── PostTypeRepository.php │ │ └── RegisterPostTypes.php │ └── User │ │ ├── UserCapabilities.php │ │ └── UserRepository.php ├── Form │ ├── Events.php │ ├── Listeners │ │ ├── BaseHandler.php │ │ ├── BulkActions.php │ │ ├── BulkEdit.php │ │ ├── CategoryFilter.php │ │ ├── ClonePost.php │ │ ├── EmptyTrash.php │ │ ├── GetTaxonomies.php │ │ ├── ListingSort.php │ │ ├── ManualMenuSync.php │ │ ├── MenuSearch.php │ │ ├── NestToggle.php │ │ ├── NewBeforeAfter.php │ │ ├── NewChild.php │ │ ├── NewMenuItem.php │ │ ├── PostSearch.php │ │ ├── QuickEdit.php │ │ ├── QuickEditLink.php │ │ ├── ResetAdminMenuSettings.php │ │ ├── ResetSettings.php │ │ ├── ResetUserPreferences.php │ │ ├── Search.php │ │ ├── Sort.php │ │ ├── SyncMenu.php │ │ ├── ToggleStatusDisplay.php │ │ ├── TrashWithChildren.php │ │ └── WpmlTranslations.php │ └── Validation │ │ └── Validation.php ├── FrontEndBootstrap.php ├── Helpers.php ├── NestedPages.php ├── Redirects.php ├── RedirectsFrontEnd.php └── Views │ ├── forms │ ├── bulk-add.php │ ├── clone-form.php │ ├── delete-confirmation-modal.php │ ├── empty-trash-modal.php │ ├── link-form.php │ ├── new-child.php │ ├── quickedit-link.php │ └── quickedit-post.php │ ├── listing.php │ ├── partials │ ├── bulk-edit.php │ ├── list-header.php │ ├── row-link.php │ ├── row.php │ ├── tool-list.php │ └── wpml-translations.php │ └── settings │ ├── partials │ ├── nav-menu-settings.php │ └── nav-menu-settings │ │ └── header.php │ ├── settings-admincustom.php │ ├── settings-general.php │ ├── settings-posttypes.php │ └── settings.php ├── assets ├── banner-772x250.png ├── css │ └── nestedpages.css ├── icon-128x128.png ├── images │ ├── arrow-child.svg │ ├── arrow-down.svg │ ├── arrow-up.svg │ ├── border.png │ ├── checkbox-filled.svg │ ├── child-page.svg │ ├── clone.svg │ ├── comments.svg │ ├── datepicker-arrow-next.png │ ├── datepicker-arrow-prev.png │ ├── globe.svg │ ├── handle.svg │ ├── hidden.svg │ ├── insert-after.svg │ ├── insert-before.svg │ ├── link.svg │ ├── lock.svg │ ├── more.svg │ ├── spinner.svg │ ├── trash-black.svg │ ├── trash-children.svg │ └── trash.svg ├── js │ ├── lib │ │ ├── jquery.mjs.nestedSortable.js │ │ ├── jquery.ui.touch-punch.min.js │ │ ├── nestedpages-factory.js │ │ ├── nestedpages.bulk-actions.js │ │ ├── nestedpages.check-all.js │ │ ├── nestedpages.clone.js │ │ ├── nestedpages.confirm-delete.js │ │ ├── nestedpages.dropdowns.js │ │ ├── nestedpages.formatter.js │ │ ├── nestedpages.hidden-item-count.js │ │ ├── nestedpages.manual-sync.js │ │ ├── nestedpages.menu-links.js │ │ ├── nestedpages.menu-search.js │ │ ├── nestedpages.menu-toggle.js │ │ ├── nestedpages.modals.js │ │ ├── nestedpages.move-post.js │ │ ├── nestedpages.nesting.js │ │ ├── nestedpages.new-post.js │ │ ├── nestedpages.page-toggle.js │ │ ├── nestedpages.post-search.js │ │ ├── nestedpages.quickedit-link.js │ │ ├── nestedpages.quickedit-post.js │ │ ├── nestedpages.settings-admin-customization.js │ │ ├── nestedpages.settings-reset.js │ │ ├── nestedpages.settings.js │ │ ├── nestedpages.sync-menu-setting.js │ │ ├── nestedpages.tabs.js │ │ ├── nestedpages.trash-with-children.js │ │ ├── nestedpages.trash.js │ │ ├── nestedpages.userprefs-reset.js │ │ └── nestedpages.wpml.js │ ├── nestedpages.js │ ├── nestedpages.min.js │ └── nestedpages.settings.min.js ├── screenshot-1.png ├── screenshot-2.png ├── screenshot-3.gif ├── screenshot-4.gif ├── screenshot-5.png ├── screenshot-6.png ├── screenshot-7.png ├── screenshot-8.png ├── screenshot-9.png └── scss │ ├── _alerts.scss │ ├── _bulk-edit.scss │ ├── _buttons.scss │ ├── _columns.scss │ ├── _dark.scss │ ├── _datepicker.scss │ ├── _dropdowns.scss │ ├── _expand-handle.scss │ ├── _form-modal.scss │ ├── _inline-modals.scss │ ├── _link-form.scss │ ├── _loading.scss │ ├── _mixins.scss │ ├── _modals.scss │ ├── _page-listing.scss │ ├── _quick-edit.scss │ ├── _settings-admin-nav-menu.scss │ ├── _settings-tables.scss │ ├── _settings.scss │ ├── _tabs.scss │ ├── _toggle-all.scss │ ├── _top-tools.scss │ ├── _variables.scss │ ├── _wpml-translation-modal.scss │ └── nestedpages.scss ├── composer.json ├── gulpfile.js ├── languages ├── index.php ├── wp-nested-pages-cs_CZ.mo ├── wp-nested-pages-cs_CZ.po ├── wp-nested-pages-da_DK.mo ├── wp-nested-pages-da_DK.po ├── wp-nested-pages-de_CH.mo ├── wp-nested-pages-de_CH.po ├── wp-nested-pages-de_DE.mo ├── wp-nested-pages-de_DE.po ├── wp-nested-pages-es_ES.mo ├── wp-nested-pages-es_ES.po ├── wp-nested-pages-fi.mo ├── wp-nested-pages-fi.po ├── wp-nested-pages-fr_FR.mo ├── wp-nested-pages-fr_FR.po ├── wp-nested-pages-it_IT.mo ├── wp-nested-pages-it_IT.po ├── wp-nested-pages-nl_NL.mo ├── wp-nested-pages-nl_NL.po ├── wp-nested-pages-pt_BR.mo ├── wp-nested-pages-pt_BR.po ├── wp-nested-pages-pt_PT.mo ├── wp-nested-pages-pt_PT.po ├── wp-nested-pages-ru_RU.mo ├── wp-nested-pages-ru_RU.po ├── wp-nested-pages-sv_SE.mo ├── wp-nested-pages-sv_SE.po ├── wp-nested-pages-tr_TR.mo ├── wp-nested-pages-tr_TR.po └── wp-nested-pages.pot ├── nestedpages.php ├── package.json └── readme.txt /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/* 2 | /vendor/* 3 | .DS_Store 4 | Thumbs.db 5 | composer.lock 6 | package-lock.json -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Nested Pages for WordPress 2 | 3 | Nested Pages provides an intuitive drag and drop interface for managing pages & posts in the WordPress admin, while maintaining quick edit functionality. 4 | 5 | For installation and usage information visit [nestedpages.com](http://nestedpages.com). Available for download/installation in the [WordPress Plugin Directory](https://wordpress.org/plugins/wp-nested-pages). 6 | 7 | ## Compatibility 8 | **Nested Pages requires PHP v5.3.2+ and WordPress v3.8+.** 9 | 10 | **Why PHP 5.3.2+?:** Nested Pages takes advantage of [Composer](https://getcomposer.org), specifically its PSR-4 auto-loading component. Because of this, the minimum version of PHP supported is 5.3.2. 11 | 12 | **Why WordPress 3.8+?:** Nested Pages depends on admin theme changes introduced in WordPress 3.8. 13 | 14 |  15 | 16 | ## Description 17 | 18 | **Nested Pages offers** 19 | * A drag and drop interface - simple and intuitive 20 | * Quick edit functionality 21 | * An expandable, sortable tree view of your site's page structure 22 | * A native WordPress menu, automatically generated to match your Nested Pages screen 23 | * A way to quickly add child pages (ideal for development) 24 | * A touch-friendly interface 25 | 26 | ## Installation 27 | The latest release of this plugin is available through the [WordPress Plugin Directory](https://wordpress.org/plugins/wp-nested-pages). 28 | 29 | If installing from this repository, [composer](https://getcomposer.org) **must be run** before attempting to activate within WordPress: 30 | 1. `cd` into the primary plugin directory 31 | 2. Run `composer install` 32 | 33 | ## Attribution 34 | 35 | This plugin wouldn't be possible without the [Nested Sortable](https://github.com/ilikenwf/nestedSortable) jQuery Plugin. It also makes use of the [jQuery UI Touch Punch](https://github.com/furf/jquery-ui-touch-punch) plugin for touch support. 36 | -------------------------------------------------------------------------------- /app/Activation/Activate.php: -------------------------------------------------------------------------------- 1 | setVersion(); 25 | $this->updates = new Updates; 26 | $this->install(); 27 | } 28 | 29 | /** 30 | * Activation Hook 31 | */ 32 | public function install() 33 | { 34 | $this->updates->run($this->version); 35 | $this->saveVersion(); 36 | $this->setOptions(); 37 | new Dependencies; 38 | } 39 | 40 | /** 41 | * Set the Plugin Version 42 | */ 43 | private function setVersion() 44 | { 45 | global $np_version; 46 | $this->version = $np_version; 47 | } 48 | 49 | /** 50 | * Set the Plugin Version 51 | */ 52 | private function saveVersion() 53 | { 54 | update_option('nestedpages_version', $this->version); 55 | } 56 | 57 | /** 58 | * Set Default Options 59 | */ 60 | private function setOptions() 61 | { 62 | if ( !get_option('nestedpages_menusync') ){ 63 | update_option('nestedpages_menusync', 'nosync'); 64 | } 65 | } 66 | } -------------------------------------------------------------------------------- /app/Activation/Updates/CustomFieldsToHidden.php: -------------------------------------------------------------------------------- 1 | setKeys(); 16 | $this->convertFields(); 17 | } 18 | 19 | /** 20 | * Set the Keys to convert 21 | */ 22 | private function setKeys() 23 | { 24 | $this->meta_keys = [ 25 | 'np_nav_title', 26 | 'nested_pages_status', 27 | 'np_title_attribute', 28 | 'np_nav_css_classes', 29 | 'np_link_target', 30 | 'np_nav_status', 31 | 'np_nav_menu_item_type', 32 | 'np_nav_menu_item_object', 33 | 'np_nav_menu_item_object_id' 34 | ]; 35 | } 36 | 37 | /** 38 | * Convert the fields 39 | */ 40 | private function convertFields() 41 | { 42 | global $wpdb; 43 | $meta_table = $wpdb->prefix . 'postmeta'; 44 | foreach ( $this->meta_keys as $key ){ 45 | $newKey = '_' . $key; 46 | $sql = $wpdb->update( 47 | $meta_table, 48 | ['meta_key' => $newKey], 49 | ['meta_key' => $key] 50 | ); 51 | } 52 | $this->setOption(); 53 | } 54 | 55 | /** 56 | * Set the Updated to Hidden Fields Option so this process doesn't run again 57 | */ 58 | private function setOption() 59 | { 60 | update_option('nested_pages_custom_fields_hidden', 'true', true); 61 | } 62 | } -------------------------------------------------------------------------------- /app/Activation/Updates/Updates.php: -------------------------------------------------------------------------------- 1 | new_version = $new_version; 35 | $this->nav_menu_repo = new NavMenuRepository; 36 | $this->setCurrentVersion(); 37 | $this->clearMenu(); 38 | $this->addMenu(); 39 | $this->convertMenuToID(); 40 | $this->enablePagePostType(); 41 | $this->enabledDatepicker(); 42 | $this->convertCustomFieldsToHidden(); 43 | } 44 | 45 | /** 46 | * Set the plugin version 47 | */ 48 | private function setCurrentVersion() 49 | { 50 | $this->current_version = ( get_option('nestedpages_version') ) 51 | ? get_option('nestedpages_version') : $this->new_version; 52 | } 53 | 54 | /** 55 | * Add an empty Nested Pages menu if there isn't one 56 | * @since 1.1.5 57 | */ 58 | private function addMenu() 59 | { 60 | if ( get_option('nestedpages_menusync') !== 'sync' ) return; 61 | if ( get_option('nestedpages_disable_menu') == 'true' ) return; 62 | 63 | if ( !get_option('nestedpages_menu') ){ 64 | $menu_id = $this->nav_menu_repo->getMenuIDFromTitle('Nested Pages'); 65 | if ( !$menu_id ) $menu_id = wp_create_nav_menu('Nested Pages'); 66 | update_option('nestedpages_menu', $menu_id); 67 | } 68 | } 69 | 70 | /** 71 | * Convert existing nestedpages_menu option to menu ID rather than string/name 72 | * @since 1.1.5 73 | */ 74 | private function convertMenuToID() 75 | { 76 | if ( version_compare( $this->current_version, '1.1.5', '<' ) ){ 77 | $menu_option = get_option('nestedpages_menu'); 78 | $menu = get_term_by('name', $menu_option, 'nav_menu'); 79 | if ( $menu ){ 80 | delete_option('nestedpages_menu'); 81 | update_option('nestedpages_menu', $menu->term_id); 82 | } else { 83 | delete_option('nestedpages_menu'); 84 | $menu_id = wp_create_nav_menu('Nested Pages'); 85 | update_option('nestedpages_menu', $menu_id); 86 | } 87 | } 88 | } 89 | 90 | /** 91 | * Make Page Post Type Enabled by Default 92 | * Option can be blank, using get_option returns false if blank 93 | * @since 1.3.5 94 | */ 95 | private function enablePagePostType() 96 | { 97 | global $wpdb; 98 | $options_table = $wpdb->prefix . 'options'; 99 | $sql = "SELECT * FROM $options_table WHERE option_name = 'nestedpages_posttypes'"; 100 | $results = $wpdb->get_results($sql); 101 | if ( $results ) return; 102 | update_option('nestedpages_posttypes', array( 103 | 'page' => array( 104 | 'replace_menu' => true 105 | ) 106 | )); 107 | } 108 | 109 | /** 110 | * Enable the Datepicker 111 | */ 112 | private function enabledDatepicker() 113 | { 114 | if ( version_compare( $this->current_version, '1.3.1', '<' ) ){ 115 | $enabled = get_option('nestedpages_ui', false); 116 | $default = [ 117 | 'datepicker' => 'true' 118 | ]; 119 | if ( !$enabled ) update_option('nestedpages_ui', $default); 120 | } 121 | } 122 | 123 | /** 124 | * Regenerate the synced menu 125 | */ 126 | private function clearMenu() 127 | { 128 | if ( version_compare( $this->current_version, '1.5.2', '<' ) ){ 129 | $menu_id = $this->nav_menu_repo->getMenuID(); 130 | if ( $menu_id ) $this->nav_menu_repo->clearMenu($menu_id); 131 | if ( get_option('nestedpages_menusync') !== 'sync' ) return; 132 | $syncer = new NavMenuSyncListing; 133 | $syncer->sync(); 134 | } 135 | } 136 | 137 | /** 138 | * Convert the Nested Pages custom fields to hidden fields 139 | */ 140 | private function convertCustomFieldsToHidden() 141 | { 142 | if ( version_compare( $this->current_version, '1.7.0', '<=' ) ){ 143 | new CustomFieldsToHidden; 144 | } 145 | } 146 | } -------------------------------------------------------------------------------- /app/Bootstrap.php: -------------------------------------------------------------------------------- 1 | initializePlugin(); 12 | add_action( 'wp_loaded', [$this, 'wpLoaded']); 13 | add_action( 'init', [$this, 'initializeWordPress']); 14 | add_filter( 'plugin_action_links_' . 'wp-nested-pages/nestedpages.php', [$this, 'settingsLink']); 15 | } 16 | 17 | /** 18 | * WP Loaded 19 | */ 20 | public function wpLoaded() 21 | { 22 | new Activation\Activate; 23 | new Redirects; 24 | } 25 | 26 | /** 27 | * Initialize Plugin 28 | */ 29 | private function initializePlugin() 30 | { 31 | new Entities\PostType\RegisterPostTypes; 32 | new Entities\Post\PostTrashActions; 33 | new Entities\Post\PostSaveActions; 34 | new Entities\Post\PrivatePostParent; 35 | new Entities\Listing\ListingActions; 36 | new Entities\NavMenu\NavMenuActions; 37 | new Entities\NavMenu\NavMenuTrashActions; 38 | new Entities\User\UserCapabilities; 39 | new Form\Events; 40 | new Config\Settings; 41 | } 42 | 43 | /** 44 | * Wordpress Initialization Actions 45 | */ 46 | public function initializeWordPress() 47 | { 48 | new Entities\AdminMenu\AdminMenu; 49 | new Entities\DefaultList\DefaultListFactory; 50 | new Entities\AdminCustomization\AdminCustomizationFactory; 51 | $this->addLocalization(); 52 | } 53 | 54 | /** 55 | * Localization Domain 56 | */ 57 | public function addLocalization() 58 | { 59 | load_plugin_textdomain( 60 | 'wp-nested-pages', 61 | false, 62 | dirname( dirname( plugin_basename( __FILE__ ) ) ) . '/languages' ); 63 | } 64 | 65 | /** 66 | * Add a link to the settings on the plugin page 67 | */ 68 | public function settingsLink($links) 69 | { 70 | $settings_link = '' . __('Settings', 'wp-nested-pages') . ''; 71 | array_unshift($links, $settings_link); 72 | return $links; 73 | } 74 | } -------------------------------------------------------------------------------- /app/Config/Settings.php: -------------------------------------------------------------------------------- 1 | user_repo = new UserRepository; 68 | $this->settings = new SettingsRepository; 69 | $this->post_type_repo = new PostTypeRepository; 70 | $this->integrations = new IntegrationFactory; 71 | $this->listing_repo = new ListingRepository; 72 | $this->post_repo = new PostRepository; 73 | } 74 | 75 | /** 76 | * Register the settings page 77 | * @see admin_menu 78 | */ 79 | public function registerSettingsPage() 80 | { 81 | add_options_page( 82 | __('Nested Pages Settings', 'wp-nested-pages'), 83 | __('Nested Pages', 'wp-nested-pages'), 84 | 'manage_options', 85 | 'nested-pages-settings', 86 | [$this, 'settingsPage'] 87 | ); 88 | } 89 | 90 | /** 91 | * Register the settings 92 | * @see admin_init 93 | */ 94 | public function registerSettings() 95 | { 96 | register_setting( 'nestedpages-general', 'nestedpages_menu' ); 97 | register_setting( 'nestedpages-general', 'nestedpages_menusync' ); 98 | register_setting( 'nestedpages-general', 'nestedpages_disable_menu' ); 99 | register_setting( 'nestedpages-general', 'nestedpages_ui' ); 100 | register_setting( 'nestedpages-general', 'nestedpages_allowsorting' ); 101 | register_setting( 'nestedpages-general', 'nestedpages_allowsortview' ); 102 | register_setting( 'nestedpages-posttypes', 'nestedpages_posttypes' ); 103 | register_setting( 'nestedpages-admincustomization', 'nestedpages_admin' ); 104 | } 105 | 106 | /** 107 | * Set the Menu Object 108 | * @since 1.1.5 109 | */ 110 | private function setMenu() 111 | { 112 | $menu_id = get_option('nestedpages_menu'); 113 | $this->menu = get_term_by('id', $menu_id, 'nav_menu'); 114 | } 115 | 116 | /** 117 | * Get Post Types 118 | * @since 1.2.0 119 | */ 120 | private function getPostTypes() 121 | { 122 | return $this->post_type_repo->getPostTypesObject(); 123 | } 124 | 125 | /** 126 | * Display the Settings Page 127 | * Callback for registerSettingsPage method 128 | */ 129 | public function settingsPage() 130 | { 131 | $this->setMenu(); 132 | $tab = ( isset($_GET['tab']) ) ? sanitize_file_name($_GET['tab']) : 'general'; 133 | include( Helpers::view('settings/settings') ); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /app/Entities/AdminCustomization/AdminCustomizationBase.php: -------------------------------------------------------------------------------- 1 | post_type_repo = new PostTypeRepository; 53 | $this->user_repo = new UserRepository; 54 | $this->integrations = new IntegrationFactory; 55 | $this->settings = new SettingsRepository; 56 | $this->setPluginVersion(); 57 | $this->plugin_dir = Helpers::plugin_url(); 58 | } 59 | 60 | /** 61 | * Set the current user's roles 62 | */ 63 | protected function setCurrentUserRoles() 64 | { 65 | $current_user = wp_get_current_user(); 66 | if ( isset( $current_user->roles[0] ) ) { 67 | $this->current_user_role = $current_user->roles[0]; 68 | } 69 | } 70 | 71 | /** 72 | * Set the Plugin Version 73 | */ 74 | protected function setPluginVersion() 75 | { 76 | global $np_version; 77 | $this->plugin_version = $np_version; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /app/Entities/AdminCustomization/AdminCustomizationFactory.php: -------------------------------------------------------------------------------- 1 | $pages ) : 20 | foreach ( $pages as $page_id => $page_options ) : 21 | 22 | // Icon 23 | if ( isset($page_options['icon']) && $page_options['icon'] !== '' ) $value['nav_menu_options'][$role][$page_id]['icon'] = sanitize_text_field($page_options['icon']); 24 | 25 | // Label 26 | if ( isset($page_options['label']) && $page_options['label'] !== '' ) $value['nav_menu_options'][$role][$page_id]['label'] = sanitize_text_field($page_options['label']); 27 | 28 | endforeach; 29 | endforeach; 30 | return $value; 31 | } 32 | } -------------------------------------------------------------------------------- /app/Entities/AdminMenu/AdminMenu.php: -------------------------------------------------------------------------------- 1 | post_type = $post_type; 31 | $this->post_type_repo = new PostTypeRepository; 32 | $this->setSlug(); 33 | } 34 | 35 | /** 36 | * Add the submenu 37 | */ 38 | public function addSubmenu() 39 | { 40 | global $submenu; 41 | $c = 0; 42 | 43 | // Get the right submenu and remove all pages link 44 | foreach($submenu as $key => $sub){ 45 | if ($key == $this->post_type_repo->editSlug($this->post_type)){ 46 | $edit_key = $this->getSubMenuEditIndex($sub); 47 | if ( !$edit_key ) continue; 48 | $capability = ( isset($sub[$edit_key][1]) ) ? $sub[$edit_key][1] : 'edit_pages'; 49 | $submenu[$this->slug][50] = [$sub[$edit_key][0], $capability, esc_url('admin.php?page=' . $this->slug)]; 50 | if ( isset($sub[$edit_key]) ) unset($sub[$edit_key]); // Remove Top Level 51 | $menu_items = $sub; 52 | } 53 | } 54 | if ( isset($menu_items) ){ 55 | $c = 60; 56 | foreach($menu_items as $item){ 57 | // Make sure URLs for custom menu items are correct 58 | $url = ( isset($item[3]) ) ? 'edit.php?post_type=' . $this->post_type->name . '&page=' . $item[2] : $item[2]; 59 | $submenu[$this->slug][$c] = [$item[0], $item[1], esc_url($url)]; 60 | $c = $c + 10; 61 | } 62 | } 63 | $this->defaultLink($c); 64 | } 65 | 66 | /** 67 | * Get the edit submenu index within an individual submenu 68 | * @return int 69 | */ 70 | private function getSubMenuEditIndex($submenu) 71 | { 72 | foreach ( $submenu as $key => $items ){ 73 | foreach ( $items as $item ){ 74 | if ( $item == 'edit.php' || $item == 'edit.php?post_type=' . $this->post_type->name ) return $key; 75 | } 76 | } 77 | return false; 78 | } 79 | 80 | 81 | /** 82 | * Show the default link if set to show 83 | * @param int $c Menu Position Counter 84 | */ 85 | private function defaultLink($c) 86 | { 87 | global $submenu; 88 | if ( !$this->post_type_repo->postTypeSetting($this->post_type->name, 'hide_default') ){ 89 | $label = sprintf(__('Default %s', 'wp-nested-pages'), $this->post_type->labels->name); 90 | $label = apply_filters('nestedpages_default_submenu_text', $label, $this->post_type); 91 | $submenu[$this->slug][$c] = [ 92 | $label, 93 | 'edit_pages', 94 | $this->post_type_repo->editSlug($this->post_type) 95 | ]; 96 | } 97 | } 98 | 99 | /** 100 | * Set the Menu Slug 101 | */ 102 | private function setSlug() 103 | { 104 | $this->slug = $this->post_type_repo->getMenuSlug($this->post_type); 105 | } 106 | } -------------------------------------------------------------------------------- /app/Entities/AdminMenu/AdminSubmenuDefault.php: -------------------------------------------------------------------------------- 1 | post_type = $post_type; 31 | $this->post_type_repo = new PostTypeRepository; 32 | $this->findMenu(); 33 | } 34 | 35 | /** 36 | * Add the submenu 37 | */ 38 | public function findMenu() 39 | { 40 | global $submenu; 41 | foreach($submenu as $key => $sub){ 42 | if ($key == $this->post_type_repo->editSlug($this->post_type)){ 43 | $this->addSubMenu($key); 44 | } 45 | } 46 | } 47 | 48 | /** 49 | * Add the submenu item 50 | * @param string parent page slug 51 | */ 52 | private function addSubMenu($parent_slug) 53 | { 54 | $this->hook = add_submenu_page( 55 | $parent_slug, 56 | $this->post_type_repo->getSubmenuText($this->post_type), 57 | $this->post_type_repo->getSubmenuText($this->post_type), 58 | 'edit_posts', 59 | $this->post_type_repo->getMenuSlug($this->post_type), 60 | Listing::admin_menu($this->post_type->name) 61 | ); 62 | } 63 | 64 | /** 65 | * Get the Menu Hook 66 | */ 67 | public function getHook() 68 | { 69 | return $this->hook; 70 | } 71 | } -------------------------------------------------------------------------------- /app/Entities/AdminMenu/BlockEditorLink.php: -------------------------------------------------------------------------------- 1 | post_type_repo = new PostTypeRepository; 30 | $this->user = new UserRepository; 31 | $this->page = get_current_screen(); 32 | if ( !$this->page ) return; 33 | $this->loopPostTypes(); 34 | } 35 | 36 | /** 37 | * Loop through all the enabled post types 38 | * Skip if "Replace Menu" is not enabled 39 | */ 40 | private function loopPostTypes() 41 | { 42 | foreach($this->post_type_repo->getPostTypesObject() as $type){ 43 | if ( !$type->replace_menu ) continue; 44 | $this->fullPageEditorLink($type); 45 | } 46 | } 47 | 48 | /** 49 | * Full Page Editor Link back to parent listing 50 | */ 51 | private function fullPageEditorLink($type) 52 | { 53 | if ( $this->page->id !== $type->name ) return; 54 | $user_can_view = apply_filters("nestedpages_sort_view_$type->name", $this->user->canViewSorting($type->name), $this->user->getRoles()); 55 | if ( !$user_can_view ) return; 56 | $link = ( $type->name == 'page' ) ? 'admin.php?page=nestedpages' : 'admin.php?page=' . $this->post_type_repo->getMenuSlug($type); 57 | $url = admin_url($link); 58 | echo ''; 59 | } 60 | } -------------------------------------------------------------------------------- /app/Entities/AdminMenu/EnabledMenus.php: -------------------------------------------------------------------------------- 1 | post_type_repo = new PostTypeRepository; 39 | $this->user = new UserRepository; 40 | $this->setEnabled(); 41 | $this->loopEnabledTypes(); 42 | } 43 | 44 | /** 45 | * Set Enabled Post Types 46 | */ 47 | private function setEnabled() 48 | { 49 | $this->enabled_types = $this->post_type_repo->getPostTypesObject(); 50 | } 51 | 52 | /** 53 | * Set the Menus for each of the enabled post types 54 | */ 55 | private function loopEnabledTypes() 56 | { 57 | $c = 1; // Counter for position 58 | global $np_page_params; 59 | foreach($this->enabled_types as $key => $type){ 60 | $user_can_view = apply_filters("nestedpages_sort_view_$type->name", $this->user->canViewSorting($type->name), $this->user->getRoles()); 61 | if ( $type->np_enabled !== true ) continue; 62 | if ( !$user_can_view ) continue; 63 | if ( $type->replace_menu ) { 64 | $this->post_type = get_post_type_object($key); 65 | if ( (current_user_can($this->post_type->cap->edit_posts)) ){ 66 | $this->addMenu($c); 67 | $this->addSubmenu(); 68 | $this->removeExistingMenu(); 69 | } 70 | } else { 71 | $default = new AdminSubmenuDefault($type); 72 | $np_page_params[$default->getHook()] = ['post_type' => $type->name]; 73 | } 74 | $c++; 75 | } 76 | } 77 | 78 | /** 79 | * Add the primary top-level menu item 80 | * @param int counter 81 | */ 82 | private function addMenu($c) 83 | { 84 | global $np_page_params; 85 | $hook = add_menu_page( 86 | __($this->post_type->labels->name), 87 | __($this->post_type->labels->name), 88 | $this->post_type->cap->edit_posts, 89 | $this->getSlug(), 90 | Listing::admin_menu($this->post_type->name), 91 | $this->menuIcon(), 92 | $this->menuPosition($c) 93 | ); 94 | $np_page_params[$hook] = ['post_type' => $this->post_type->name]; 95 | } 96 | 97 | /** 98 | * Add Submenus 99 | */ 100 | private function addSubmenu() 101 | { 102 | $submenu = new AdminSubmenu($this->post_type); 103 | $submenu->addSubmenu(); 104 | } 105 | 106 | /** 107 | * Remove Default Menus 108 | */ 109 | private function removeExistingMenu() 110 | { 111 | remove_menu_page('edit.php?post_type=' . $this->post_type->name); 112 | if ( $this->post_type->name == 'post' ) remove_menu_page('edit.php'); 113 | } 114 | 115 | /** 116 | * Get the correct icon to use in menu 117 | * @return string 118 | */ 119 | private function menuIcon() 120 | { 121 | if ( $this->post_type->name == 'page' ) return 'dashicons-admin-page'; 122 | if ( $this->post_type->menu_icon ) return $this->post_type->menu_icon; 123 | return 'dashicons-admin-post'; 124 | } 125 | 126 | /** 127 | * Get the correct menu position for item 128 | * @param int counter 129 | */ 130 | private function menuPosition($c) 131 | { 132 | global $_wp_last_object_menu; 133 | if ( $this->post_type->name == 'post' ) return apply_filters('nestedpages_menu_order', 5, $this->post_type); 134 | if ( $this->post_type->name == 'page') return apply_filters('nestedpages_menu_order', 20, $this->post_type); 135 | if ( $this->post_type->menu_position ) return apply_filters('nestedpages_menu_order', $this->post_type->menu_position + 1, $this->post_type); 136 | return $_wp_last_object_menu + $c; 137 | } 138 | 139 | /** 140 | * Get the Edit Slug for post type 141 | */ 142 | private function getSlug() 143 | { 144 | return $this->post_type_repo->getMenuSlug($this->post_type); 145 | } 146 | } -------------------------------------------------------------------------------- /app/Entities/Confirmation/ConfirmationFactory.php: -------------------------------------------------------------------------------- 1 | build(); 25 | } 26 | 27 | /** 28 | * Set the Type of confirmation 29 | */ 30 | private function build() 31 | { 32 | if ( (isset($_GET['trashed'])) && (intval($_GET['trashed']) > 0) ) $this->type = 'TrashConfirmation'; 33 | if ( (isset($_GET['untrashed'])) && (intval($_GET['untrashed']) > 0) ) $this->type = 'TrashRestoredConfirmation'; 34 | if ( (isset($_GET['linkdeleted'])) && (intval($_GET['linkdeleted']) > 0 ) ) $this->type = 'LinkDeletedConfirmation'; 35 | if ( $this->type ) $this->createClass(); 36 | } 37 | 38 | /** 39 | * Set the confirmation message 40 | */ 41 | private function createClass() 42 | { 43 | $class = 'NestedPages\Entities\Confirmation\\' . $this->type; 44 | $confirm = new $class; 45 | $this->message = $confirm->setMessage(); 46 | } 47 | 48 | /** 49 | * Get the Message 50 | */ 51 | public function getMessage() 52 | { 53 | return $this->message; 54 | } 55 | } -------------------------------------------------------------------------------- /app/Entities/Confirmation/ConfirmationInterface.php: -------------------------------------------------------------------------------- 1 | 1 ) 26 | ? sprintf(__('%d %s moved to the trash.'), count($trashed), esc_html($post_type_object->labels->name)) 27 | : wp_kses(sprintf(__('%s moved to the trash.', 'wp-nested-pages'), get_the_title($trashed[0])), ['strong' => []] ); 28 | // Undo Link 29 | if ( current_user_can('delete_pages') ) { 30 | $ids = preg_replace( '/[^0-9,]/', '', sanitize_text_field($_GET['ids']) ); 31 | $out .= ' ' . __( 'Undo' ) . ""; 32 | } 33 | endif; 34 | 35 | return $out; 36 | } 37 | } -------------------------------------------------------------------------------- /app/Entities/Confirmation/TrashRestoredConfirmation.php: -------------------------------------------------------------------------------- 1 | post_type_repo = new PostTypeRepository; 25 | $this->addNestedViewLinks(); 26 | } 27 | 28 | /** 29 | * Loop through Post Types & add link to activated types 30 | */ 31 | private function addNestedViewLinks() 32 | { 33 | foreach($this->post_type_repo->getPostTypesObject() as $type){ 34 | if ( !$type->np_enabled ) continue; 35 | new NestedViewLink($type); 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /app/Entities/DefaultList/NestedViewLink.php: -------------------------------------------------------------------------------- 1 | post_type_repo = new PostTypeRepository; 25 | $this->post_type = $post_type; 26 | $this->addFilter(); 27 | } 28 | 29 | /** 30 | * Add the WP Filter 31 | */ 32 | private function addFilter() 33 | { 34 | add_filter( 'views_edit-' . $this->post_type->name, [$this, 'addLink']); 35 | } 36 | 37 | /** 38 | * Add a nested pages link to the subsub list (WP_List_Table class) 39 | */ 40 | public function addLink($views) 41 | { 42 | $screen = get_current_screen(); 43 | if ( $screen->parent_file == $this->post_type_repo->editSlug($this->post_type) ){ 44 | $link_text = $this->post_type_repo->getSubmenuText($this->post_type); 45 | $link_href = esc_url(admin_url('admin.php?page=' . $this->post_type_repo->getMenuSlug($this->post_type))); 46 | $link = [$link_text => '' . $link_text . '']; 47 | $views = array_merge($views, $link); 48 | } 49 | return $views; 50 | } 51 | } -------------------------------------------------------------------------------- /app/Entities/Listing/ListingActions.php: -------------------------------------------------------------------------------- 1 | get( 'post_title_like' ) ){ 18 | $like = $wpdb->esc_like( $post_title_like ); 19 | $where .= ' AND ' . $wpdb->posts . '.post_title LIKE \'%' . esc_sql( $like ) . '%\''; 20 | } 21 | return $where; 22 | } 23 | } -------------------------------------------------------------------------------- /app/Entities/NavMenu/NavMenuActions.php: -------------------------------------------------------------------------------- 1 | nav_menu_repo = new NavMenuRepository; 20 | $this->addUpdateHook(); 21 | } 22 | 23 | private function addUpdateHook() 24 | { 25 | add_action( 'wp_update_nav_menu', [$this, 'syncMenu'], 10, 2 ); 26 | } 27 | 28 | private function removeUpdateHook() 29 | { 30 | remove_action( 'wp_update_nav_menu', [$this, 'syncMenu'], 10); 31 | } 32 | 33 | /** 34 | * Sync Pages when updating nav menu 35 | */ 36 | public function syncMenu($menu_id, $menu_data = null) 37 | { 38 | if ( get_option('nestedpages_menusync') !== 'sync' ) return; 39 | if ( get_option('nestedpages_disable_menu') == 'true' ) return; 40 | if ( $menu_id !== $this->nav_menu_repo->getMenuID() ) return; // Don't try to sync menus not managed by NP 41 | $this->removeUpdateHook(); 42 | if ( $menu_data == null ) $sync = new NavMenuSyncMenu($menu_id); 43 | $this->addUpdateHook(); 44 | } 45 | } -------------------------------------------------------------------------------- /app/Entities/NavMenu/NavMenuFrontEnd.php: -------------------------------------------------------------------------------- 1 | nav_menu_repo = new NavMenuRepository; 19 | add_filter('nav_menu_link_attributes', [$this, 'linkAttribute'], 10, 3); 20 | add_filter('nav_menu_link_attributes', [$this, 'hrefAttribute'], 10, 3); 21 | } 22 | 23 | /** 24 | * Filter the link attributes on the generated menu 25 | */ 26 | public function linkAttribute($atts, $item, $args) 27 | { 28 | if ( get_option('nestedpages_menusync') !== 'sync' ) return $atts; 29 | if ( get_option('nestedpages_disable_menu') == 'true' ) return $atts; 30 | if ( $this->nav_menu_repo->getMenuID() == null ) return $atts; 31 | 32 | if ( !isset($args->menu->term_id) ) return $atts; 33 | if ( $args->menu->term_id !== $this->nav_menu_repo->getMenuID() ) return $atts; 34 | 35 | // Remove the rel= attribute created from saving the menu object for syncing 36 | foreach($atts as $attribute => $value){ 37 | if ( strtolower($attribute) != 'rel' ) continue; 38 | if ( $value == $item->object ) unset($atts[$attribute]); 39 | if ( is_numeric($value) ) unset($atts[$attribute]); 40 | } 41 | 42 | return $atts; 43 | } 44 | 45 | /** 46 | * Add a custom link if one is set 47 | */ 48 | public function hrefAttribute($atts, $item, $args) 49 | { 50 | if ( get_option('nestedpages_menusync') !== 'sync' ) return $atts; 51 | if ( get_option('nestedpages_disable_menu') == 'true' ) return $atts; 52 | if ( $this->nav_menu_repo->getMenuID() == null ) return $atts; 53 | if ( !isset($item->object_id) ) return $atts; 54 | $custom_url = get_post_meta($item->object_id, '_np_nav_custom_url', true); 55 | if ( !$custom_url ) return $atts; 56 | $atts['href'] = $custom_url; 57 | return $atts; 58 | } 59 | } -------------------------------------------------------------------------------- /app/Entities/NavMenu/NavMenuRemoveItem.php: -------------------------------------------------------------------------------- 1 | item_id = $item_id; 18 | $this->removeItem(); 19 | } 20 | 21 | /** 22 | * Delete the Item 23 | */ 24 | private function removeItem() 25 | { 26 | wp_delete_post($this->item_id, true); 27 | } 28 | } -------------------------------------------------------------------------------- /app/Entities/NavMenu/NavMenuSync.php: -------------------------------------------------------------------------------- 1 | nav_menu_repo = new NavMenuRepository; 40 | $this->integrations = new IntegrationFactory; 41 | $this->settings = new SettingsRepository; 42 | $this->setMenuID(); 43 | } 44 | 45 | /** 46 | * Menu ID Setter 47 | */ 48 | protected function setMenuID() 49 | { 50 | $this->id = $this->nav_menu_repo->getMenuID(); 51 | } 52 | 53 | /** 54 | * Remove a Menu Item 55 | * @since 1.3.4 56 | * 57 | * @param int $id - ID of nav menu item 58 | */ 59 | protected function removeItem($id) { 60 | 61 | $recurse = apply_filters('nestedpages_recursive_remove_menu_item', false, $id); 62 | if ( ! empty( $id ) && $recurse ) { 63 | $args = [ 64 | 'post_type' => 'nav_menu_item', 65 | 'meta_query' => [ 66 | [ 67 | 'key' => '_menu_item_menu_item_parent', 68 | 'value' => $id 69 | ] 70 | ], 71 | 'fields' => 'ids' 72 | ]; 73 | $children = get_posts( $args ); 74 | if ( $children ) { 75 | foreach ( $children as $child ) { 76 | $this->removeItem( $child, true ); 77 | } 78 | } 79 | } 80 | 81 | wp_delete_post( $id, true ); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /app/Entities/NavMenu/NavMenuTrashActions.php: -------------------------------------------------------------------------------- 1 | nav_menu_repo = new NavMenuRepository; 19 | add_action( 'before_delete_post', [$this, 'removeLinkItem'], 10 ); 20 | add_action( 'before_delete_post', [$this, 'hidePagefromNav'], 10 ); 21 | } 22 | 23 | /** 24 | * Remove Link Post (np-redirect) when a link in the menu is removed 25 | */ 26 | public function removeLinkItem($post_id) 27 | { 28 | remove_action( 'before_delete_post', [$this, 'removeLinkItem'], 10 ); 29 | $redirect_id = get_post_meta($post_id, '_menu_item_xfn', true); 30 | $hidden = get_post_meta($redirect_id, '_np_nav_status', true); 31 | if ( $redirect_id !== "" && $hidden !== 'hide' ) wp_delete_post($redirect_id, true); 32 | return true; 33 | } 34 | 35 | /** 36 | * Set Page items to hide from nav if page nav item is deleted 37 | */ 38 | public function hidePagefromNav($post_id) 39 | { 40 | if ( get_post_type($post_id) == 'nav_menu_item' ){ 41 | 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /app/Entities/PluginIntegration/AdvancedCustomFields.php: -------------------------------------------------------------------------------- 1 | installed = true; 28 | $this->setUnsupportedFieldTypes(); 29 | return; 30 | } 31 | } 32 | 33 | /** 34 | * Set the unsupported field types 35 | */ 36 | private function setUnsupportedFieldTypes() 37 | { 38 | $this->unsupported = [ 39 | 'relationship' 40 | ]; 41 | } 42 | 43 | /** 44 | * Get the ACF fields associated with a post type 45 | * @param string post_type 46 | * @return array 47 | */ 48 | public function getFieldsForPostType($post_type) 49 | { 50 | if ( !function_exists('get_field') ) return false; 51 | $field_groups = acf_get_field_groups([ 52 | 'post_type' => $post_type 53 | ]); 54 | if ( !$field_groups ) return false; 55 | $all_fields = []; 56 | $c = 0; 57 | foreach ( $field_groups as $key => $group ){ 58 | $fields = acf_get_fields($group); 59 | foreach($fields as $field){ 60 | if ( in_array($field['type'], $this->unsupported) ) continue; 61 | $all_fields[$c]['key'] = $field['key']; 62 | $all_fields[$c]['label'] = $field['label']; 63 | $all_fields[$c]['type'] = $field['type']; 64 | $c++; 65 | } 66 | $c++; 67 | } 68 | return $all_fields; 69 | } 70 | } -------------------------------------------------------------------------------- /app/Entities/PluginIntegration/DarkMode.php: -------------------------------------------------------------------------------- 1 | installed = true; 16 | } 17 | } -------------------------------------------------------------------------------- /app/Entities/PluginIntegration/EditorialAccessManager.php: -------------------------------------------------------------------------------- 1 | installed = true; 32 | $this->user = wp_get_current_user(); 33 | } 34 | } 35 | 36 | /** 37 | * Does the current user have access to the specified post id? 38 | * @return boolean 39 | */ 40 | public function hasAccess($post_id) 41 | { 42 | if ( $this->abortCheck() ) return true; 43 | 44 | $access_meta = get_post_meta($post_id, 'eam_enable_custom_access', true); 45 | 46 | if ( $access_meta == 'users' ){ 47 | $allowed_users = (array) get_post_meta($post_id, 'eam_allowed_users', true); 48 | if ( isset($allowed_users[0]) && $allowed_users[0] == "" ) return true; 49 | if ( !in_array($this->user->ID, $allowed_users) ) return false; 50 | } 51 | 52 | if ( $access_meta == 'roles' ){ 53 | $allowed_roles = (array) get_post_meta($post_id, 'eam_allowed_roles', true); 54 | if ( count( array_diff( $this->user->roles, $allowed_roles ) ) >= 1 ) return false; 55 | } 56 | 57 | return true; 58 | } 59 | 60 | /** 61 | * Abort Role Check? 62 | * @return boolean 63 | */ 64 | private function abortCheck() 65 | { 66 | if ( !$this->installed ) return true; 67 | if ( in_array('administrator', $this->user->roles) ) return true; 68 | return false; 69 | } 70 | } -------------------------------------------------------------------------------- /app/Entities/PluginIntegration/IntegrationFactory.php: -------------------------------------------------------------------------------- 1 | build(); 20 | return $this->plugins; 21 | } 22 | 23 | public function build() 24 | { 25 | $this->plugins = new \StdClass(); 26 | $this->plugins->editorial_access_manager = new EditorialAccessManager; 27 | $this->plugins->acf = new AdvancedCustomFields; 28 | $this->plugins->yoast = new YoastSeo; 29 | $this->plugins->wpml = new WPML; 30 | $this->plugins->dark_mode = new DarkMode; 31 | } 32 | } -------------------------------------------------------------------------------- /app/Entities/PluginIntegration/YoastSeo.php: -------------------------------------------------------------------------------- 1 | installed = true; 20 | return; 21 | } 22 | } 23 | 24 | /** 25 | * Get the Score HTML 26 | * @return str|html - Yoast indicator 27 | */ 28 | public function getScore($post_id) 29 | { 30 | $yoast_score = get_post_meta($post_id, '_yoast_wpseo_meta-robots-noindex', true); 31 | if ( ! $yoast_score ) { 32 | $yoast_score = get_post_meta($post_id, '_yoast_wpseo_linkdex', true); 33 | if ( version_compare(WPSEO_VERSION, '19.5.1', '<') ){ 34 | $score = \WPSEO_Utils::translate_score($yoast_score); 35 | return ''; 36 | } 37 | $score_icon_helper = YoastSEO()->helpers->score_icon; 38 | $score_icon_presenter = $score_icon_helper->for_readability($yoast_score); 39 | return $score_icon_presenter->present(); 40 | } else { 41 | return ''; 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /app/Entities/Post/PostSaveActions.php: -------------------------------------------------------------------------------- 1 | settings = new SettingsRepository; 27 | $this->post_type_repo = new PostTypeRepository; 28 | add_action( 'save_post', [$this, 'defaultNavStatus'], 10, 3); 29 | } 30 | 31 | /** 32 | * If this is the initial save and the "hide in nav menu by default" option is selected, update the post meta 33 | */ 34 | public function defaultNavStatus($post_id, $post_object, $update) 35 | { 36 | if ( $update ) return; 37 | if ( !$this->settings->defaultHideInNav() ) return; 38 | if ( $post_object->post_type !== 'page' ) return; 39 | $types = $this->post_type_repo->getPostTypesObject(); 40 | if ( isset($types['page']) && isset($types['page']->np_enabled) && $types['page']->np_enabled ){ 41 | update_post_meta($post_id, '_np_nav_status', 'hide'); 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /app/Entities/Post/PostTrashActions.php: -------------------------------------------------------------------------------- 1 | user_repo = new UserRepository; 28 | $this->nav_menu_repo = new NavMenuRepository; 29 | add_action( 'trashed_post', [$this, 'trashHook']); 30 | add_action( 'delete_post', [$this, 'removeLinkNavItem'], 10 ); 31 | } 32 | 33 | /** 34 | * Trash hook - make sure child pages of trashed page are visible 35 | * @since 1.3.4 36 | */ 37 | public function trashHook($post_id) 38 | { 39 | $post_type = get_post_type($post_id); 40 | $this->resetToggles($post_id, $post_type); 41 | if ( get_option('nestedpages_menusync') !== 'sync' ) return; 42 | $this->removeNavMenuItem($post_id); 43 | if ( $post_type == 'page' ){ 44 | $sync = new NavMenuSyncListing; 45 | $sync->sync(); 46 | } 47 | } 48 | 49 | /** 50 | * Link Post Types are immediately deleted, so trash hook isn't called 51 | * Must remove nav menu items when they're deleted 52 | */ 53 | public function removeLinkNavItem($post_id) 54 | { 55 | $post_type = get_post_type($post_id); 56 | if ( $post_type == 'nav_menu_item' ) return; 57 | if ( $post_type == 'np-redirect' ) $this->removeNavMenuItem($post_id); 58 | } 59 | 60 | /** 61 | * Remove the nav menu item 62 | */ 63 | private function removeNavMenuItem($post_id) 64 | { 65 | $query_type = ( get_post_type($post_id) == 'np-redirect' ) ? 'xfn' : 'object_id'; 66 | $nav_item_id = $this->nav_menu_repo->getMenuItem($post_id, $query_type); 67 | if ( $nav_item_id ) new NavMenuRemoveItem($nav_item_id); 68 | } 69 | 70 | /** 71 | * Make sure children of trashed pages are viewable in Nested Pages 72 | */ 73 | private function resetToggles($post_id, $post_type) 74 | { 75 | $visible_pages = $this->user_repo->getVisiblePages(); 76 | if ( !isset($visible_pages[$post_type]) ) return; 77 | $visible_pages = $visible_pages[$post_type]; 78 | 79 | if ( !isset($visible_pages[$post_type]) ) return; 80 | 81 | $child_pages = []; 82 | 83 | $children = new \WP_Query(['post_type'=>$post_type, 'posts_per_page'=>-1, 'post_parent'=>$post_id]); 84 | if ( $children->have_posts() ) : while ( $children->have_posts() ) : $children->the_post(); 85 | array_push($child_pages, get_the_id()); 86 | endwhile; endif; wp_reset_postdata(); 87 | 88 | foreach($child_pages as $child_page){ 89 | if ( !in_array($child_page, $visible_pages) ) array_push($visible_pages, $child_page); 90 | } 91 | 92 | $this->user_repo->updateVisiblePages($post_type, $visible_pages); 93 | } 94 | } -------------------------------------------------------------------------------- /app/Entities/Post/PrivatePostParent.php: -------------------------------------------------------------------------------- 1 | post_type ) return $output; 23 | if ( $arguments['name'] !== 'parent_id' ) return $output; 24 | 25 | $args = [ 26 | 'post_type' => $post->post_type, 27 | 'exclude_tree' => $post->ID, 28 | 'selected' => $post->post_parent, 29 | 'name' => 'parent_id', 30 | 'show_option_none' => __('(no parent)'), 31 | 'sort_column' => 'menu_order, post_title', 32 | 'echo' => 0, 33 | 'post_status' => ['publish', 'private', 'draft'], 34 | ]; 35 | $args = apply_filters( 'page_attributes_dropdown_pages_args', $args, $post ); 36 | 37 | $defaults = [ 38 | 'depth' => 0, 39 | 'child_of' => 0, 40 | 'selected' => 0, 41 | 'echo' => 1, 42 | 'name' => 'page_id', 43 | 'id' => '', 44 | 'show_option_none' => '', 45 | 'show_option_no_change' => '', 46 | 'option_none_value' => '', 47 | ]; 48 | 49 | $r = wp_parse_args($args, $defaults); 50 | extract($r, EXTR_SKIP); 51 | 52 | $pages = get_pages($r); 53 | $name = esc_attr($name); 54 | 55 | // Back-compat with old system where both id and name were based on $name argument 56 | if (empty($id)) $id = $name; 57 | 58 | if (empty($pages)) return; 59 | 60 | $output = "\n"; 66 | 67 | return $output; 68 | } 69 | } -------------------------------------------------------------------------------- /app/Entities/PostType/RegisterPostTypes.php: -------------------------------------------------------------------------------- 1 | integrations = new IntegrationFactory; 19 | if ( $this->integrations->plugins->wpml->installed ) return; 20 | add_action( 'init', [ $this, 'registerRedirects'] ); 21 | } 22 | 23 | /** 24 | * Redirects Post Type 25 | */ 26 | public function registerRedirects() 27 | { 28 | $labels = [ 29 | 'name' => __('Redirects', 'wp-nested-pages'), 30 | 'singular_name' => __('Redirect', 'wp-nested-pages'), 31 | 'add_new_item'=> 'Add Redirect', 32 | 'edit_item' => 'Edit Redirect', 33 | 'view_item' => 'View Redirect' 34 | ]; 35 | $args = [ 36 | 'labels' => $labels, 37 | 'public' => false, 38 | 'show_ui' => false, 39 | 'exclude_from_search' => true, 40 | 'capability_type' => 'post', 41 | 'hierarchical' => true, 42 | 'has_archive' => false, 43 | 'supports' => ['title','editor'], 44 | '_edit_link' => 'post.php?post=%d', 45 | 'rewrite' => ['slug' => 'np-redirect', 'with_front' => false] 46 | ]; 47 | register_post_type( 'np-redirect' , $args ); 48 | } 49 | } -------------------------------------------------------------------------------- /app/Entities/User/UserCapabilities.php: -------------------------------------------------------------------------------- 1 | settings = new SettingsRepository; 19 | add_action('init', [$this, 'addSortingCapabilities']); 20 | } 21 | 22 | /** 23 | * Adds custom capability of nestedpages_sort_$type 24 | */ 25 | public function addSortingCapabilities() 26 | { 27 | $post_types = get_post_types(['public' => true]); 28 | $invalid = ['attachment']; 29 | $granted_roles = ['administrator']; 30 | $roles = wp_roles(); 31 | foreach ( $post_types as $type ) : 32 | if ( in_array($type, $invalid) ) continue; 33 | foreach ( $roles->roles as $name => $role_obj ) : 34 | $role = get_role($name); 35 | $grant_capability = ( in_array($name, $granted_roles) ) ? true : false; 36 | if ( $role->has_cap("nestedpages_sorting_$type") ) $grant_capability = true; 37 | 38 | /** 39 | * Filters the sorting capability for a given role and post type. 40 | * 41 | * @since 3.1.9 42 | * 43 | * @param bool $grant_role Whether role may sort post type. 44 | * @param string $type The post type name. 45 | * @param string $role_name The Role Name. 46 | */ 47 | $grant_capability = apply_filters("nestedpages_sorting_capability", $grant_capability, $type, $role); 48 | if ( $grant_capability ) $role->add_cap("nestedpages_sorting_$type", true); 49 | endforeach; 50 | endforeach; 51 | } 52 | } -------------------------------------------------------------------------------- /app/Form/Events.php: -------------------------------------------------------------------------------- 1 | registerEvents(); 24 | } 25 | 26 | /** 27 | * Set the Form Actions 28 | */ 29 | public function registerEvents() 30 | { 31 | $this->actions = [ 32 | 'wp_ajax_npsort', 33 | 'wp_ajax_npquickEdit', 34 | 'wp_ajax_npsyncMenu', 35 | 'wp_ajax_npnestToggle', 36 | 'wp_ajax_npquickEditLink', 37 | 'wp_ajax_npgetTaxonomies', 38 | 'wp_ajax_npnewChild', 39 | 'wp_ajax_npnewBeforeAfter', 40 | 'admin_post_npListingSort', 41 | 'wp_ajax_npEmptyTrash', 42 | 'admin_post_npSearch', 43 | 'wp_ajax_npclonePost', 44 | 'wp_ajax_npmenuSearch', 45 | 'wp_ajax_npnewMenuItem', 46 | 'admin_post_npCategoryFilter', 47 | 'admin_post_npBulkActions', 48 | 'wp_ajax_npmanualMenuSync', 49 | 'admin_post_npBulkEdit', 50 | 'wp_ajax_nptrashWithChildren', 51 | 'wp_ajax_nppostSearch', 52 | 'wp_ajax_npWpmlTranslations', 53 | 'wp_ajax_npresetSettings', 54 | 'wp_ajax_npresetUserPreferences', 55 | 'wp_ajax_npresetAdminMenuSettings', 56 | 'wp_ajax_nptoggleStatusDisplay' 57 | ]; 58 | $this->setHandlers(); 59 | } 60 | 61 | /** 62 | * Set the Handlers Object 63 | */ 64 | public function setHandlers() 65 | { 66 | foreach($this->actions as $key => $action){ 67 | $class = str_replace('admin_post_np', '', $action); // Non-AJAX forms 68 | $class = ucfirst(str_replace('wp_ajax_np', '', $class)); // AJAX forms 69 | $this->handlers[$key] = new \stdClass(); 70 | $this->handlers[$key]->action = $action; 71 | $this->handlers[$key]->class = 'NestedPages\Form\Listeners\\' . $class; 72 | } 73 | $this->build(); 74 | } 75 | 76 | /** 77 | * Register the WP Actions 78 | */ 79 | public function build() 80 | { 81 | foreach($this->handlers as $handler){ 82 | add_action($handler->action, function() use ($handler) { 83 | new $handler->class; 84 | }); 85 | } 86 | } 87 | } -------------------------------------------------------------------------------- /app/Form/Listeners/BaseHandler.php: -------------------------------------------------------------------------------- 1 | post_repo = new PostRepository; 73 | $this->post_update_repo = new PostUpdateRepository; 74 | $this->user = new UserRepository; 75 | $this->integrations = new IntegrationFactory; 76 | $this->settings = new SettingsRepository; 77 | $this->setData(); 78 | $this->validateNonce(); 79 | } 80 | 81 | /** 82 | * Set the Form Data 83 | */ 84 | protected function setData() 85 | { 86 | $this->nonce = sanitize_text_field($_POST['nonce']); 87 | $data = []; 88 | foreach( $_POST as $key => $value ){ 89 | $data[$key] = $value; 90 | } 91 | $this->data = $data; 92 | } 93 | 94 | /** 95 | * Validate the Nonce 96 | */ 97 | protected function validateNonce() 98 | { 99 | if ( ! wp_verify_nonce( $this->nonce, 'nestedpages-nonce' ) ){ 100 | $this->response = [ 'status' => 'error', 'message' => __('Invalid Nonce', 'wp-nested-pages') ]; 101 | $this->sendResponse(); 102 | die(); 103 | } 104 | } 105 | 106 | /** 107 | * Sync the Nav Menu 108 | */ 109 | protected function syncMenu() 110 | { 111 | if ( get_option('nestedpages_menusync') !== 'sync' ) return; 112 | if ( get_option('nestedpages_disable_menu') == 'true' ) return; 113 | 114 | if ( $_POST['post_type'] == 'page' ) { 115 | if ( $_POST['syncmenu'] !== 'sync' ){ 116 | return update_option('nestedpages_menusync', 'nosync'); 117 | } 118 | update_option('nestedpages_menusync', 'sync'); 119 | try { 120 | $menu = new NavMenuSyncListing; 121 | $menu->sync(); 122 | } catch ( \Exception $e ){ 123 | return $this->exception($e->getMessage()); 124 | } 125 | return; 126 | } 127 | } 128 | 129 | /** 130 | * Send a Generic Success Message 131 | */ 132 | protected function sendErrorResponse($message = null) 133 | { 134 | $message = ( $message ) ? $message : __('There was an error updating the page.', 'wp-nested-pages'); 135 | $this->response = [ 136 | 'status' => 'error', 137 | 'message' => $message 138 | ]; 139 | $this->sendResponse(); 140 | } 141 | 142 | /** 143 | * Send Error from Exception 144 | */ 145 | protected function exception($message) 146 | { 147 | return wp_send_json([ 148 | 'status' => 'error', 149 | 'message' => $message 150 | ]); 151 | } 152 | 153 | /** 154 | * Return Response 155 | */ 156 | protected function sendResponse() 157 | { 158 | return wp_send_json($this->response); 159 | } 160 | 161 | /** 162 | * Redirect to new URL 163 | */ 164 | protected function redirect() 165 | { 166 | wp_safe_redirect($this->url); 167 | } 168 | } -------------------------------------------------------------------------------- /app/Form/Listeners/BulkActions.php: -------------------------------------------------------------------------------- 1 | setURL(); 37 | $this->setPostIds(); 38 | $this->performAction(); 39 | $this->redirect(); 40 | } 41 | 42 | /** 43 | * Build the URL to Redirect to 44 | */ 45 | private function setURL() 46 | { 47 | $this->url = sanitize_text_field($_POST['page']); 48 | } 49 | 50 | /** 51 | * Set the Post IDs 52 | */ 53 | private function setPostIds() 54 | { 55 | $ids = sanitize_text_field($_POST['post_ids']); 56 | $this->post_ids = rtrim($ids, ","); 57 | $ids = explode(',', $this->post_ids); 58 | $this->post_ids_array = $ids; 59 | 60 | $r_ids = sanitize_text_field($_POST['redirect_post_ids']); 61 | $this->redirect_post_ids = rtrim($r_ids, ","); 62 | $r_ids = explode(',', $this->redirect_post_ids); 63 | $this->redirect_post_ids_array = $r_ids; 64 | } 65 | 66 | /** 67 | * Perform the Bulk Actions 68 | */ 69 | private function performAction() 70 | { 71 | $action = sanitize_text_field($_POST['np_bulk_action']); 72 | 73 | if ( $action == 'no-action' || !$action ){ 74 | $this->redirect(); 75 | return; 76 | } 77 | 78 | if ( $action == 'trash' ){ 79 | $this->trashPosts(); 80 | return; 81 | } 82 | } 83 | 84 | /** 85 | * Move Posts to Trash 86 | */ 87 | private function trashPosts() 88 | { 89 | if ( !current_user_can('delete_pages') ){ 90 | $this->redirect(); 91 | return; 92 | } 93 | 94 | foreach ( $this->post_ids_array as $id ){ 95 | wp_trash_post($id); 96 | } 97 | 98 | foreach ( $this->redirect_post_ids_array as $id ){ 99 | wp_delete_post($id, true); 100 | } 101 | 102 | $this->url = $this->url . '&bulk=true&trashed=1'; 103 | 104 | if ( $this->post_ids != '' ) $this->url = $this->url . '&ids=' . $this->post_ids; 105 | if ( $this->redirect_post_ids != '' ) $this->url = $this->url . '&link_ids=' . $this->redirect_post_ids; 106 | } 107 | } -------------------------------------------------------------------------------- /app/Form/Listeners/BulkEdit.php: -------------------------------------------------------------------------------- 1 | setURL(); 21 | $this->setFieldData(); 22 | $this->performEdits(); 23 | $this->redirect(); 24 | } 25 | 26 | /** 27 | * Build the URL to Redirect to 28 | */ 29 | private function setURL() 30 | { 31 | $this->url = sanitize_text_field($_POST['page']); 32 | } 33 | 34 | /** 35 | * Set the field data (Removed unchanged fields so they're not overwritten) 36 | */ 37 | private function setFieldData() 38 | { 39 | foreach ( $_POST as $field => $value ){ 40 | if ( $value == '' || $value == '-1' ) unset($_POST[$field]); 41 | } 42 | $this->data = $_POST; 43 | } 44 | 45 | /** 46 | * Perform the Bulk Edits 47 | */ 48 | private function performEdits() 49 | { 50 | foreach ( $this->data['post_ids'] as $post_id ){ 51 | $data = $this->data; 52 | $data['post_id'] = $post_id; 53 | $this->post_update_repo->updatePost($data, $append_taxonomies = true); 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /app/Form/Listeners/CategoryFilter.php: -------------------------------------------------------------------------------- 1 | setURL(); 13 | $this->redirect(); 14 | } 15 | 16 | /** 17 | * Build the URL to Redirect to 18 | */ 19 | private function setURL() 20 | { 21 | $this->url = sanitize_text_field($_POST['page']); 22 | $this->setCategories(); 23 | } 24 | 25 | /** 26 | * Set Order parameters 27 | */ 28 | private function setCategories() 29 | { 30 | $this->url .= '&category=' . sanitize_text_field($_POST['np_category']); 31 | } 32 | } -------------------------------------------------------------------------------- /app/Form/Listeners/ClonePost.php: -------------------------------------------------------------------------------- 1 | cloner = new PostCloner; 25 | $this->setPostID(); 26 | if ( !current_user_can('edit_post', $this->data['post_id']) ) return; 27 | $this->clonePost(); 28 | } 29 | 30 | /** 31 | * Set the Post ID to Clone 32 | */ 33 | private function setPostID() 34 | { 35 | if ( !isset($_POST['parent_id']) ){ 36 | return $this->sendResponse(['status' => 'error', 'message' => __('Post Not Found', 'wp-nested-pages')]); 37 | } 38 | $this->data['post_id'] = intval(sanitize_text_field($_POST['parent_id'])); 39 | $this->data['status'] = sanitize_text_field($_POST['status']); 40 | $this->data['author'] = intval(sanitize_text_field($_POST['author'])); 41 | $this->data['quantity'] = intval(sanitize_text_field($_POST['quantity'])); 42 | $this->data['post_type'] = sanitize_text_field($_POST['posttype']); 43 | $this->data['clone_children'] = ( $_POST['clone_children'] == 'true' ) ? true : false; 44 | } 45 | 46 | /** 47 | * Clone the post 48 | */ 49 | private function clonePost() 50 | { 51 | $this->cloner->clonePost( 52 | $this->data['post_id'], 53 | $this->data['quantity'], 54 | $this->data['status'], 55 | $this->data['author'], 56 | $this->data['clone_children'] 57 | ); 58 | return wp_send_json(['status' => 'success']); 59 | } 60 | } -------------------------------------------------------------------------------- /app/Form/Listeners/EmptyTrash.php: -------------------------------------------------------------------------------- 1 | emptyTrash(); 10 | } 11 | 12 | private function emptyTrash() 13 | { 14 | if ( $this->post_repo->emptyTrash($_POST['posttype']) ){ 15 | return wp_send_json([ 16 | 'status'=>'success', 17 | 'message'=> __('Trash successfully emptied.', 'wp-nested-pages') 18 | ]); 19 | } 20 | $this->sendErrorResponse(); 21 | } 22 | } -------------------------------------------------------------------------------- /app/Form/Listeners/GetTaxonomies.php: -------------------------------------------------------------------------------- 1 | loopTaxonomies(); 20 | $this->setResponse(); 21 | $this->sendResponse(); 22 | } 23 | 24 | /** 25 | * Loop through the taxonomies 26 | */ 27 | private function loopTaxonomies() 28 | { 29 | if ( !isset($this->data['terms']) ) return; 30 | $terms = $this->data['terms']; 31 | foreach ($terms as $taxonomy => $tax_terms){ 32 | $this->setTermNames($taxonomy, $tax_terms); 33 | } 34 | } 35 | 36 | /** 37 | * Get the Term names for each taxonomy 38 | */ 39 | private function setTermNames($taxonomy, $terms) 40 | { 41 | foreach ( $terms as $key => $term ) 42 | { 43 | $single_term = get_term_by('id', $term, $taxonomy); 44 | $term_name = $single_term->name; 45 | $this->terms[$taxonomy][$key] = $term_name; 46 | } 47 | } 48 | 49 | /** 50 | * Prepare Response 51 | */ 52 | private function setResponse() 53 | { 54 | $this->response = ['status'=>'success', 'terms'=>$this->terms]; 55 | } 56 | } -------------------------------------------------------------------------------- /app/Form/Listeners/ListingSort.php: -------------------------------------------------------------------------------- 1 | post_type_repo = new PostTypeRepository; 27 | $this->setURL(); 28 | $this->redirect(); 29 | } 30 | 31 | /** 32 | * Build the URL to Redirect to 33 | */ 34 | private function setURL() 35 | { 36 | $this->url = sanitize_text_field($_POST['page']); 37 | $this->post_type = sanitize_text_field($_POST['post_type']); 38 | $this->setOrderBy(); 39 | $this->setOrder(); 40 | $this->setAuthor(); 41 | $this->setTaxonomies(); 42 | } 43 | 44 | /** 45 | * Set Order by parameters 46 | */ 47 | private function setOrderBy() 48 | { 49 | $allowed = ['menu_order', 'date', 'title']; // prevent tomfoolery 50 | if ( isset($_POST['np_orderby']) && $_POST['np_orderby'] !== "" && in_array($_POST['np_orderby'], $allowed) ) $this->url .= '&orderby=' . sanitize_text_field($_POST['np_orderby']); 51 | } 52 | 53 | /** 54 | * Set Order parameters 55 | */ 56 | private function setOrder() 57 | { 58 | $allowed = ['ASC', 'DESC']; // prevent tomfoolery 59 | if ( isset($_POST['np_order']) && in_array($_POST['np_order'], $allowed) ) $this->url .= '&order=' . sanitize_text_field($_POST['np_order']); 60 | } 61 | 62 | /** 63 | * Set Author parameters 64 | */ 65 | private function setAuthor() 66 | { 67 | if ( (isset($_POST['np_author'])) && ($_POST['np_author'] !== "") ) $this->url .= '&author=' . sanitize_text_field($_POST['np_author']); 68 | } 69 | 70 | /** 71 | * Set Taxonomy Parameters 72 | */ 73 | private function setTaxonomies() 74 | { 75 | $h_taxonomies = $this->post_type_repo->getTaxonomies($this->post_type, true); 76 | $f_taxonomies = $this->post_type_repo->getTaxonomies($this->post_type, false); 77 | $taxonomies = array_merge($h_taxonomies, $f_taxonomies); 78 | foreach ( $taxonomies as $tax ) : 79 | if ( $this->post_type_repo->sortOptionEnabled($this->post_type, $tax->name, true) ) : 80 | if ( isset($_POST[$tax->name]) && $_POST[$tax->name] !== 'all' ) $this->url .= '&' . $tax->name . '=' . sanitize_text_field($_POST[$tax->name]); 81 | endif; 82 | endforeach; 83 | } 84 | } -------------------------------------------------------------------------------- /app/Form/Listeners/ManualMenuSync.php: -------------------------------------------------------------------------------- 1 | syncMenu(); 14 | $this->response = ['status' => 'success']; 15 | $this->sendResponse(); 16 | } 17 | } -------------------------------------------------------------------------------- /app/Form/Listeners/MenuSearch.php: -------------------------------------------------------------------------------- 1 | setFormData(); 20 | $this->search(); 21 | return wp_send_json(['status' => 'success', 'results' => $this->results]); 22 | } 23 | 24 | /** 25 | * Set the search-specific form data 26 | */ 27 | private function setFormData() 28 | { 29 | $this->data['term'] = sanitize_text_field($_POST['term']); 30 | $this->data['searchType'] = sanitize_text_field($_POST['searchType']); 31 | $this->data['searchObject'] = sanitize_text_field($_POST['searchObject']); 32 | } 33 | 34 | /** 35 | * Perform the search 36 | */ 37 | private function search() 38 | { 39 | if ( $this->data['searchType'] == 'post_type' ) $this->searchPosts(); 40 | if ( $this->data['searchType'] == 'taxonomy' ) $this->searchTaxonomies(); 41 | } 42 | 43 | /** 44 | * Perform a search on posts 45 | */ 46 | private function searchPosts() 47 | { 48 | $sq = new \WP_Query([ 49 | 'post_type' => $this->data['searchObject'], 50 | 's' => $this->data['term'] 51 | ]); 52 | if ( $sq->have_posts() ) : 53 | $this->results = $sq->posts; 54 | $pt = get_post_type_object($this->data['searchObject']); 55 | 56 | foreach($sq->posts as $key => $post){ 57 | $this->results[$key]->permalink = get_the_permalink($post->ID); 58 | $this->results[$key]->singular_name = $pt->labels->singular_name; 59 | } 60 | else : 61 | $this->results = null; 62 | endif; wp_reset_postdata(); 63 | } 64 | 65 | /** 66 | * Perform a taxonomy search 67 | */ 68 | private function searchTaxonomies() 69 | { 70 | $terms = get_terms($this->data['searchObject'], [ 71 | 'name__like' => $this->data['term'] 72 | ]); 73 | if ( $terms ){ 74 | $this->results = $terms; 75 | foreach($terms as $key => $term){ 76 | $this->results[$key]->permalink = get_term_link($term); 77 | } 78 | return; 79 | } 80 | $this->results = null; 81 | } 82 | } -------------------------------------------------------------------------------- /app/Form/Listeners/NestToggle.php: -------------------------------------------------------------------------------- 1 | updateUserMeta(); 15 | } 16 | 17 | /** 18 | * Make sure this is an array of integers 19 | */ 20 | private function validateIDs() 21 | { 22 | if ( !is_array($this->data['ids']) ) $this->sendErrorResponse(); 23 | foreach ($this->data['ids'] as $id){ 24 | if ( !is_numeric($id) ) $this->sendErrorResponse(); 25 | } 26 | } 27 | 28 | /** 29 | * Update the user meta with the array of IDs 30 | */ 31 | private function updateUserMeta() 32 | { 33 | $this->user->updateVisiblePages($this->data['posttype'], $this->data['ids']); 34 | $this->response = ['status'=>'success', 'data'=>$this->data]; 35 | $this->sendResponse(); 36 | } 37 | } -------------------------------------------------------------------------------- /app/Form/Listeners/NewBeforeAfter.php: -------------------------------------------------------------------------------- 1 | factory = new PostFactory; 27 | $this->validation = new Validation; 28 | $this->savePages(); 29 | $this->syncMenu(); 30 | $this->sendResponse(); 31 | } 32 | 33 | /** 34 | * Run Validation 35 | */ 36 | private function validates() 37 | { 38 | return $this->validation->validateNewPages($this->data); 39 | } 40 | 41 | /** 42 | * Save the new page(s) 43 | */ 44 | private function savePages() 45 | { 46 | if ( $this->validates() ){ 47 | $this->data['new_pages'] = $this->factory->createBeforeAfterPosts($this->data); 48 | $this->setResponse(); 49 | return; 50 | } 51 | $this->sendErrorResponse(); 52 | } 53 | 54 | /** 55 | * Set the Response 56 | */ 57 | private function setResponse() 58 | { 59 | $this->response = [ 60 | 'status'=>'success', 61 | 'new_pages' => $this->data['new_pages'] 62 | ]; 63 | } 64 | } -------------------------------------------------------------------------------- /app/Form/Listeners/NewChild.php: -------------------------------------------------------------------------------- 1 | factory = new PostFactory; 27 | $this->validation = new Validation; 28 | $this->savePages(); 29 | $this->syncMenu(); 30 | $this->sendResponse(); 31 | } 32 | 33 | /** 34 | * Run Validation 35 | */ 36 | private function validates() 37 | { 38 | return $this->validation->validateNewPages($this->data); 39 | } 40 | 41 | /** 42 | * Save the new page(s) 43 | */ 44 | private function savePages() 45 | { 46 | if ( $this->validates() ){ 47 | $this->data['new_pages'] = $this->factory->createChildPosts($this->data); 48 | $this->setResponse(); 49 | return; 50 | } 51 | $this->sendErrorResponse(); 52 | } 53 | 54 | /** 55 | * Set the Response 56 | */ 57 | private function setResponse() 58 | { 59 | $this->response = [ 60 | 'status'=>'success', 61 | 'new_pages' => $this->data['new_pages'] 62 | ]; 63 | } 64 | } -------------------------------------------------------------------------------- /app/Form/Listeners/NewMenuItem.php: -------------------------------------------------------------------------------- 1 | validateFields(); 17 | $this->saveRedirect(); 18 | $this->syncMenu(); 19 | $this->sendResponse(); 20 | } 21 | 22 | /** 23 | * Validate 24 | */ 25 | private function validateFields() 26 | { 27 | if ( $_POST['menuType'] == 'custom' && $_POST['navigationLabel'] == "" ) return wp_send_json(array('status' => 'error', 'message' => __('Custom Links must have a label.', 'wp-nested-pages'))); 28 | if ( $_POST['menuType'] == 'custom' && $_POST['url'] == "" ) return wp_send_json(array('status' => 'error', 'message' => __('Please provide a valid URL.', 'wp-nested-pages'))); 29 | } 30 | 31 | /** 32 | * Save the item as a redirect post type 33 | */ 34 | private function saveRedirect() 35 | { 36 | $new_link = $this->post_update_repo->saveRedirect($this->data); 37 | if ( !$new_link ) $this->sendErrorResponse(); 38 | 39 | $this->data['post'] = $_POST; 40 | $this->data['post']['id'] = $new_link; 41 | $this->data['post']['content'] = esc_url($_POST['url']); 42 | $this->data['post']['delete_link'] = get_delete_post_link($new_link, '', true); 43 | $this->addExtras($new_link); 44 | 45 | $this->response = [ 46 | 'status' => 'success', 47 | 'message' => __('Link successfully updated.', 'wp-nested-pages'), 48 | 'post_data' => $this->data['post'] 49 | ]; 50 | } 51 | 52 | /** 53 | * Add extra post data 54 | */ 55 | private function addExtras($post) 56 | { 57 | $this->data['post']['link_target'] = ( isset($_POST['linkTarget']) && $_POST['linkTarget'] == "_blank" ) ? "_blank" : ""; 58 | 59 | // Add custom menu data 60 | $type = $this->data['post']['menuType']; 61 | if ( $type == 'custom' ){ 62 | $this->data['post']['original_link'] = null; 63 | $this->data['post']['original_title'] = null; 64 | return; 65 | } 66 | if ( $type == 'taxonomy' ){ 67 | $term = get_term_by('id', $this->data['post']['objectId'], $this->data['post']['objectType']); 68 | $this->data['post']['original_link'] = get_term_link($term); 69 | $this->data['post']['original_title'] = $term->name; 70 | return; 71 | } 72 | if ( $type == 'post_type_archive' ){ 73 | $this->data['post']['original_link'] = get_post_type_archive_link($this->data['post']['objectType']); 74 | $this->data['post']['original_title'] = sanitize_text_field($this->data['post']['menuTitle']); 75 | return; 76 | } 77 | $id = $this->data['post']['objectId']; 78 | $this->data['post']['original_link'] = get_the_permalink($id); 79 | $this->data['post']['original_title'] = get_the_title($id); 80 | } 81 | } -------------------------------------------------------------------------------- /app/Form/Listeners/PostSearch.php: -------------------------------------------------------------------------------- 1 | setFormData(); 20 | $this->searchPosts(); 21 | return wp_send_json(['status' => 'success', 'results' => $this->results]); 22 | } 23 | 24 | /** 25 | * Set the search-specific form data 26 | */ 27 | private function setFormData() 28 | { 29 | $this->data['term'] = sanitize_text_field($_POST['term']); 30 | $this->data['postType'] = sanitize_text_field($_POST['postType']); 31 | } 32 | 33 | /** 34 | * Perform a search on posts 35 | */ 36 | private function searchPosts() 37 | { 38 | $sq = new \WP_Query([ 39 | 'post_type' => $this->data['postType'], 40 | 's' => $this->data['term'], 41 | 'posts_per_page' => -1 42 | ]); 43 | if ( $sq->have_posts() ) : 44 | $this->results = $sq->posts; 45 | else : 46 | $this->results = null; 47 | endif; wp_reset_postdata(); 48 | } 49 | } -------------------------------------------------------------------------------- /app/Form/Listeners/QuickEdit.php: -------------------------------------------------------------------------------- 1 | updatePost(); 14 | $this->syncMenu(); 15 | $this->sendResponse(); 16 | } 17 | 18 | /** 19 | * Update the Post 20 | * @todo update taxonomies 21 | */ 22 | private function updatePost() 23 | { 24 | if ( !current_user_can('edit_others_posts') ) { 25 | $author = get_post_field('post_author', $this->data['post_id']); 26 | if ( intval($author) !== get_current_user_id() ) return $this->sendErrorResponse(__('You do not have sufficient privileges to edit this post.', 'wp-nested-pages')); 27 | } 28 | $updated = $this->post_update_repo->updatePost($this->data); 29 | if ( !$updated ) $this->sendErrorResponse(); 30 | if ( isset($this->data['tax_input']) ) $this->addFlatTaxonomies(); 31 | if ( $this->integrations->plugins->wpml->installed ) $this->integrations->plugins->wpml->syncPosts($this->data['post_id']); 32 | $this->addData(); 33 | $this->response = [ 34 | 'status' => 'success', 35 | 'message' => __('Post successfully updated.', 'wp-nested-pages'), 36 | 'post_data' => $this->data 37 | ]; 38 | } 39 | 40 | /** 41 | * Add Flat Taxonomy IDs 42 | */ 43 | private function addFlatTaxonomies() 44 | { 45 | $taxonomies = $this->data['tax_input']; 46 | foreach($taxonomies as $key => $tax_terms){ 47 | $tax = get_taxonomy($key); 48 | if ( (!is_taxonomy_hierarchical($tax->name)) && !empty($tax_terms) ){ 49 | unset($this->data['tax_input'][$key]); // remove taxonomy from returned tax input 50 | $terms = explode(',', $tax_terms); 51 | foreach ( $terms as $i => $term ){ 52 | if ( $term !== "" ){ 53 | $term_obj = get_term_by('name', $term, $tax->name); 54 | $this->data['flat_tax'][$key][$i] = $term_obj->term_id; // add the new flat_tax returned object 55 | } 56 | } 57 | } 58 | } 59 | } 60 | 61 | /** 62 | * Add additional data to the response object 63 | */ 64 | private function addData() 65 | { 66 | if ( isset($this->data['post_author']) ) $this->data['author_name'] = get_the_author_meta('display_name', $this->data['post_author']); 67 | if ( !isset($this->data['sticky']) ) $this->data['sticky'] = false; 68 | $this->data['nav_status'] = ( isset($this->data['nav_status']) ) ? 'hide' : 'show'; 69 | $this->data['np_status'] = ( isset($this->data['nested_pages_status']) ) ? 'hide' : 'show'; 70 | $this->data['link_target'] = ( isset($this->data['link_target']) ) ? '_blank' : 'none'; 71 | $this->data['keep_private'] = ( isset($this->data['keep_private']) ) ? 'private' : 'public'; 72 | $this->data['_status'] = ( isset($this->data['_status']) ) ? $this->data['_status'] : 'publish'; 73 | $this->data['permalink'] = get_the_permalink($this->data['post_id']); 74 | if ( isset($this->data['post_title']) ) $this->data['post_title'] = stripslashes($this->data['post_title']); 75 | if ( !isset($_POST['comment_status']) ) $this->data['comment_status'] = 'closed'; 76 | } 77 | } -------------------------------------------------------------------------------- /app/Form/Listeners/QuickEditLink.php: -------------------------------------------------------------------------------- 1 | updatePost(); 14 | $this->syncMenu(); 15 | $this->sendResponse(); 16 | } 17 | 18 | /** 19 | * Update the Post 20 | */ 21 | private function updatePost() 22 | { 23 | $updated = $this->post_update_repo->updateRedirect($this->data); 24 | if ( !$updated ) $this->sendErrorResponse(); 25 | $this->addData(); 26 | $this->response = [ 27 | 'status' => 'success', 28 | 'message' => __('Link successfully updated.', 'wp-nested-pages'), 29 | 'post_data' => $this->data 30 | ]; 31 | } 32 | 33 | /** 34 | * Add additional data to the response object 35 | */ 36 | private function addData() 37 | { 38 | $this->data['nav_status'] = ( isset($this->data['nav_status']) && $this->data['nav_status'] == 'hide' ) ? 'hide' : 'show'; 39 | $this->data['np_status'] = ( isset($this->data['nested_pages_status']) ) ? 'hide' : 'show'; 40 | $this->data['linkTarget'] = ( isset($this->data['linkTarget']) ) ? '_blank' : 'none'; 41 | } 42 | } -------------------------------------------------------------------------------- /app/Form/Listeners/ResetAdminMenuSettings.php: -------------------------------------------------------------------------------- 1 | reset(); 10 | } 11 | 12 | private function reset() 13 | { 14 | $this->settings->resetAdminMenuSettings(); 15 | return wp_send_json(['status' => 'success']); 16 | } 17 | } -------------------------------------------------------------------------------- /app/Form/Listeners/ResetSettings.php: -------------------------------------------------------------------------------- 1 | reset(); 10 | } 11 | 12 | private function reset() 13 | { 14 | if ( !current_user_can('manage_options') ) return; 15 | $this->settings->resetSettings(); 16 | return wp_send_json(['status' => 'success']); 17 | } 18 | } -------------------------------------------------------------------------------- /app/Form/Listeners/ResetUserPreferences.php: -------------------------------------------------------------------------------- 1 | reset(); 10 | } 11 | 12 | private function reset() 13 | { 14 | if ( !current_user_can('manage_options') ) return; 15 | global $wpdb; 16 | $wpdb->delete($wpdb->usermeta, ['meta_key' => 'np_visible_posts']); 17 | return wp_send_json(['status' => 'success']); 18 | } 19 | } -------------------------------------------------------------------------------- /app/Form/Listeners/Search.php: -------------------------------------------------------------------------------- 1 | setURL(); 10 | $this->redirect(); 11 | } 12 | 13 | /** 14 | * Set the URL 15 | */ 16 | private function setURL() 17 | { 18 | $this->url = sanitize_text_field($_POST['page']) . '&search=' . sanitize_text_field($_POST['search_term']); 19 | } 20 | } -------------------------------------------------------------------------------- /app/Form/Listeners/Sort.php: -------------------------------------------------------------------------------- 1 | updateOrder(); 17 | $this->syncMenu(); 18 | $this->sendResponse(); 19 | } 20 | 21 | /** 22 | * Update Post Order 23 | */ 24 | private function updateOrder() 25 | { 26 | $posts = $this->data['list']; 27 | $filtered = ( isset($this->data['filtered']) && $this->data['filtered'] == 'true' ) ? true : false; 28 | $order = $this->post_update_repo->updateOrder($posts, 0, $filtered); 29 | if ( $order ){ 30 | if ( $this->integrations->plugins->wpml->installed ) $this->integrations->plugins->wpml->syncPostOrder($posts); 31 | $this->response = ['status' => 'success', 'message' => __('Page order successfully updated.','wp-nested-pages') ]; 32 | } else { 33 | $this->response = ['status'=>'error', 'message'=> __('There was an error updating the page order.','wp-nested-pages') ]; 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /app/Form/Listeners/SyncMenu.php: -------------------------------------------------------------------------------- 1 | updateSync(); 14 | $this->sendResponse(); 15 | } 16 | 17 | /** 18 | * Update the sync setting 19 | */ 20 | private function updateSync() 21 | { 22 | if ( $this->data['syncmenu'] == 'sync' ) return $this->sync(); 23 | update_option('nestedpages_menusync', 'nosync'); 24 | $this->response = ['status'=>'success', 'message'=> __('Menu sync disabled.', 'wp-nested-pages')]; 25 | } 26 | 27 | /** 28 | * Sync the Menu 29 | */ 30 | private function sync() 31 | { 32 | update_option('nestedpages_menusync', 'sync'); 33 | $this->syncMenu(); 34 | $this->response = ['status'=>'success', 'message'=> __('Menu sync enabled.', 'wp-nested-pages')]; 35 | } 36 | } -------------------------------------------------------------------------------- /app/Form/Listeners/ToggleStatusDisplay.php: -------------------------------------------------------------------------------- 1 | updateStatus(); 27 | $this->sendResponse(); 28 | } 29 | 30 | private function updateStatus() 31 | { 32 | $status = sanitize_text_field($this->data['status']); 33 | $post_type = sanitize_text_field($this->data['post_type']); 34 | $this->user->updateStatusPreference($post_type, $status); 35 | $this->response = ['status'=>'success', 'message'=> __('Status preference updated.', 'wp-nested-pages')]; 36 | } 37 | } -------------------------------------------------------------------------------- /app/Form/Listeners/TrashWithChildren.php: -------------------------------------------------------------------------------- 1 | setParentId(); 34 | $this->getAllPosts($this->parent_id); 35 | $this->trashPosts(); 36 | $this->setRedirect(); 37 | $this->response = [ 38 | 'status' => 'success', 39 | 'redirect' => $this->redirect 40 | ]; 41 | $this->sendResponse(); 42 | } 43 | 44 | /** 45 | * Set the Post ID to Clone 46 | */ 47 | private function setParentId() 48 | { 49 | if ( !isset($_POST['post_id']) ){ 50 | return $this->sendResponse(['status' => 'error', 'message' => __('Post Not Found', 'wp-nested-pages')]); 51 | } 52 | if ( !isset($_POST['screen']) ){ 53 | return $this->sendResponse(['status' => 'error', 'message' => __('Current page could not be determined', 'wp-nested-pages')]); 54 | } 55 | $this->parent_id = intval(sanitize_text_field($_POST['post_id'])); 56 | $this->post_type = get_post_type($this->parent_id); 57 | $this->trash_ids[] = $this->parent_id; 58 | } 59 | 60 | /** 61 | * Trash the post and its children 62 | */ 63 | private function getAllPosts($parent) 64 | { 65 | $q = new \WP_Query([ 66 | 'post_type' => $this->post_type, 67 | 'posts_per_page' => -1, 68 | 'post_parent' => $parent, 69 | 'fields' => 'ids' 70 | ]); 71 | $posts = []; 72 | if ( $q->have_posts() ) : 73 | $this->trash_ids = array_merge($this->trash_ids, $q->posts); 74 | while ( $q->have_posts() ) : $q->the_post(); 75 | $this->getAllPosts(get_the_id()); 76 | endwhile; 77 | endif; wp_reset_postdata(); 78 | } 79 | 80 | /** 81 | * Trash the Posts 82 | */ 83 | private function trashPosts() 84 | { 85 | foreach ( $this->trash_ids as $post_id ){ 86 | $post_type = get_post_type($post_id); 87 | $capability = ( $post_type == 'page' ) ? 'delete_page' : 'delete_posts'; 88 | if ( current_user_can( $capability, get_the_id() ) ) wp_trash_post($post_id); 89 | } 90 | } 91 | 92 | /** 93 | * Set the URL to redirect to 94 | */ 95 | private function setRedirect() 96 | { 97 | $current_screen = sanitize_text_field($_POST['screen']); 98 | $url = ( $current_screen == 'nestedpages' ) ? 'admin.php' : 'edit.php'; 99 | $url .= '?page=' . $current_screen . '&trashed=1&ids='; 100 | foreach ( $this->trash_ids as $key => $id ){ 101 | $url .= $id; 102 | if ( ($key +1) < count($this->trash_ids) ) $url .= ','; 103 | } 104 | $this->redirect = admin_url($url); 105 | } 106 | } -------------------------------------------------------------------------------- /app/Form/Listeners/WpmlTranslations.php: -------------------------------------------------------------------------------- 1 | wpml = new WPML; 27 | if ( !$this->wpml->installed ){ 28 | $this->exception(__('WPML is not currently installed.', 'wp-nested-pages')); 29 | return; 30 | } 31 | $this->setPostId(); 32 | $this->getTranslations(); 33 | $this->organizeLanguages(); 34 | if ( !is_array($this->translations) || sizeof($this->translations) == 0 ){ 35 | $this->exception(__('There are currently no translations for the selected post.', 'wp-nested-pages')); 36 | return; 37 | } 38 | return wp_send_json(['status' => 'success', 'translations' => $this->translations]); 39 | } 40 | 41 | /** 42 | * Set the Post Id 43 | */ 44 | private function setPostId() 45 | { 46 | if ( !isset($_POST['post_id']) ){ 47 | return $this->sendResponse(['status' => 'error', 'message' => __('Post Not Found', 'nestedapges')]); 48 | } 49 | $this->data['post_id'] = intval(sanitize_text_field($_POST['post_id'])); 50 | } 51 | 52 | /** 53 | * Get all the translated versions of this post 54 | */ 55 | private function getTranslations() 56 | { 57 | $this->translations = $this->wpml->getAllTranslations($this->data['post_id']); 58 | } 59 | 60 | /** 61 | * Add Translation links 62 | */ 63 | private function organizeLanguages() 64 | { 65 | global $sitepress; 66 | $all_languages = $this->wpml->getLanguages(); 67 | $current_language = $this->wpml->getCurrentLanguage(); 68 | foreach ( $all_languages as $lang_code => $lang ) 69 | { 70 | $add_link = 'post-new.php?' . http_build_query ( 71 | [ 72 | 'lang' => $lang_code, 73 | 'post_type' => get_post_type ( $this->data['post_id'] ), 74 | 'trid' => $sitepress->get_element_trid($this->data['post_id']), 75 | 'source_lang' => $current_language 76 | ] 77 | ); 78 | $all_languages[$lang_code]['add_link'] = $add_link; 79 | if ( array_key_exists($lang_code, $this->translations) ){ 80 | $all_languages[$lang_code]['has_translation'] = true; 81 | $all_languages[$lang_code]['translation'] = $this->translations[$lang_code]; 82 | $all_languages[$lang_code]['edit_link'] = get_edit_post_link($this->translations[$lang_code]->element_id); 83 | } else { 84 | $all_languages[$lang_code]['has_translation'] = false; 85 | } 86 | } 87 | $this->translations = $all_languages; 88 | } 89 | } -------------------------------------------------------------------------------- /app/FrontEndBootstrap.php: -------------------------------------------------------------------------------- 1 | init(); 9 | } 10 | 11 | public function init() 12 | { 13 | new Entities\NavMenu\NavMenuFrontEnd; 14 | } 15 | } -------------------------------------------------------------------------------- /app/Helpers.php: -------------------------------------------------------------------------------- 1 | id == 'edit-page') && 24 | (isset($_GET['trashed'])) && 25 | (intval($_GET['trashed']) > 0) && 26 | (!isset($_GET['bulk'])) && 27 | $this->arePagesNested() 28 | ){ 29 | $query_args = array( 30 | 'page' => 'nestedpages', 31 | 'trashed' => true 32 | ); 33 | if ( isset($_GET['ids']) ) $query_args['ids'] = urlencode(sanitize_text_field($_GET['ids'])); 34 | $redirect = add_query_arg(['page'=>'nestedpages', 'trashed' => true ]); 35 | wp_redirect($redirect); 36 | exit(); 37 | } 38 | } 39 | 40 | /** 41 | * Redirect to nested pages after page moved out of trash 42 | */ 43 | public function pageRestored() 44 | { 45 | $screen = get_current_screen(); 46 | if ( 47 | ($screen->id == 'edit-page') && 48 | (isset($_GET['untrashed'])) && 49 | (intval($_GET['untrashed']) > 0) && 50 | (!isset($_GET['bulk'])) && 51 | $this->arePagesNested() 52 | ){ 53 | $redirect = add_query_arg(['page'=>'nestedpages', 'untrashed' => true, 'untrashed' => urlencode(sanitize_text_field($_GET['untrashed'])) ]); 54 | wp_redirect($redirect); 55 | exit(); 56 | } 57 | } 58 | 59 | /** 60 | * Add link trashed param to URL after delete (for notification) 61 | */ 62 | public function linkDeleted($post_id) 63 | { 64 | $screen = get_current_screen(); 65 | if ( !$screen ) return; 66 | if ( 67 | (get_post_type($post_id) == 'np-redirect') && 68 | ($screen->id == 'np-redirect') && 69 | $this->arePagesNested() 70 | ){ 71 | $post_type = ( isset($_GET['parent_post_type']) && $_GET['parent_post_type'] !== '' ) 72 | ? sanitize_text_field($_GET['parent_post_type']) : null; 73 | if ( $post_type ) $page = 'nestedpages-' . $post_type; 74 | 75 | $redirect = ( $post_type ) 76 | ? add_query_arg(['page' => $page, 'linkdeleted' => true, '_wpnonce' => false, 'post' => false, 'action' => false]) 77 | : add_query_arg(['page' => 'nestedpages', 'linkdeleted' => true, '_wpnonce' => false, 'post' => false, 'action' => false]); 78 | 79 | wp_redirect($redirect); 80 | exit(); 81 | } 82 | } 83 | 84 | /** 85 | * Return true/false if nested pages are enabled for Page post types 86 | */ 87 | private function arePagesNested() { 88 | $postTypeRepository = new \NestedPages\Entities\PostType\PostTypeRepository(); 89 | $enabledPostTypes = $postTypeRepository->enabledPostTypes(); 90 | return array_key_exists( 'page', $enabledPostTypes ); 91 | } 92 | } -------------------------------------------------------------------------------- /app/RedirectsFrontEnd.php: -------------------------------------------------------------------------------- 1 | renameLinkSlugs($post, $post_link); 22 | return $post_link; 23 | } 24 | 25 | public function parseRequest($wp) 26 | { 27 | if ( isset($wp->query_vars['error']) ) $slug = basename($wp->request); 28 | if ( isset($wp->query_vars['pagename']) && ! empty($wp->query_vars['pagename']) ) $slug = $wp->query_vars['pagename']; 29 | if ( isset($wp->query_vars['name']) && ! empty($wp->query_vars['name']) ) $slug = $wp->query_vars['name']; 30 | if ( isset($wp->query_vars['attachment']) && ! empty($wp->query_vars['attachment']) ) $slug = $wp->query_vars['attachment']; 31 | if ( !isset($slug) ) return; 32 | 33 | $segments = explode('/', $wp->request); 34 | $slug = basename($slug); 35 | 36 | // If this is a redirect link, strip out the np-r and go to the original 37 | if ( substr($slug, -4) == 'np-r' ){ 38 | $slug = substr($slug, 0, -5); 39 | $wp->request = $slug; 40 | $wp->query_vars['pagename'] = $slug; 41 | $wp->query_vars['name'] = $slug; 42 | return; 43 | }; 44 | 45 | $redirect = false; 46 | if ( count($segments) == 1 ) return; 47 | 48 | $parent_slug = $segments[count($segments) - 2]; 49 | if ( substr($parent_slug, -4) == 'np-r' ){ 50 | $redirect = true; 51 | $parent_slug = substr($parent_slug, 0, -5); 52 | } 53 | $parent_args = [ 54 | 'name' => sanitize_text_field($parent_slug), 55 | 'posts_per_page' => 1 56 | ]; 57 | $parent_args['post_type'] = ( $redirect ) ? 'np-redirect' : 'any'; 58 | $parent_post = get_posts($parent_args); 59 | 60 | $page_args = [ 61 | 'name' => sanitize_text_field($slug), 62 | 'post_type' => 'any', 63 | 'posts_per_page' => 1 64 | ]; 65 | $page_args['post_parent'] = ( isset($parent_post) && $redirect ) ? $parent_post[0]->ID : null; 66 | $page = get_posts($page_args); 67 | if ( !$page ) return; 68 | 69 | unset($wp->query_vars['attachment']); 70 | unset($wp->query_vars['error']); 71 | 72 | $wp->query_vars['page_id'] = $page[0]->ID; 73 | } 74 | 75 | /** 76 | * Recursive function removes non-page parent slugs 77 | */ 78 | private function renameLinkSlugs($post, $slug) 79 | { 80 | if ( is_admin() ) return $slug; 81 | if ( $post->post_parent > 0 ) { 82 | $parent_post = get_post($post->post_parent); 83 | if ( $parent_post->post_type == 'np-redirect' ){ 84 | $slug = str_replace($parent_post->post_name . '/', $parent_post->post_name . '-np-r/', $slug); 85 | } 86 | return $this->renameLinkSlugs($parent_post, $slug); 87 | } 88 | return $slug; 89 | } 90 | } -------------------------------------------------------------------------------- /app/Views/forms/bulk-add.php: -------------------------------------------------------------------------------- 1 | 7 |
8 | -------------------------------------------------------------------------------- /app/Views/forms/clone-form.php: -------------------------------------------------------------------------------- 1 | 6 | 7 | -------------------------------------------------------------------------------- /app/Views/forms/delete-confirmation-modal.php: -------------------------------------------------------------------------------- 1 | 6 | 7 | -------------------------------------------------------------------------------- /app/Views/forms/empty-trash-modal.php: -------------------------------------------------------------------------------- 1 | 6 | 7 | -------------------------------------------------------------------------------- /app/Views/forms/quickedit-link.php: -------------------------------------------------------------------------------- 1 | cap->publish_posts ); 4 | ?> 5 | 6 | -------------------------------------------------------------------------------- /app/Views/partials/list-header.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/Views/partials/wpml-translations.php: -------------------------------------------------------------------------------- 1 | integrations->plugins->wpml->getLanguages(); ?> 2 | -------------------------------------------------------------------------------- /app/Views/settings/partials/nav-menu-settings/header.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/Views/settings/settings-admincustom.php: -------------------------------------------------------------------------------- 1 | admin_menu_settings = new NestedPages\Config\AdminMenuSettings; 3 | settings_fields( 'nestedpages-admincustomization' ); ?> 4 | 5 |