├── .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 | ![Screenshot](https://raw.githubusercontent.com/kylephillips/wp-nested-pages/master/assets/screenshot-1.png) 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 |
9 | 10 |
-------------------------------------------------------------------------------- /app/Views/forms/clone-form.php: -------------------------------------------------------------------------------- 1 | 6 |
7 |
8 | 9 | 82 | 83 |
-------------------------------------------------------------------------------- /app/Views/forms/delete-confirmation-modal.php: -------------------------------------------------------------------------------- 1 | 6 |
7 |
8 | 13 |
-------------------------------------------------------------------------------- /app/Views/forms/empty-trash-modal.php: -------------------------------------------------------------------------------- 1 | 6 |
7 |
8 | 14 |
-------------------------------------------------------------------------------- /app/Views/forms/quickedit-link.php: -------------------------------------------------------------------------------- 1 | cap->publish_posts ); 4 | ?> 5 | 6 |
7 |
8 |

9 | 10 | 11 | 12 |
13 | 14 |
15 | 16 |
17 | 18 | 19 |
20 | 21 |
22 | 23 | 24 |
25 | 26 |
27 | 28 | 36 |
37 | 38 |
39 | 40 |
41 | 42 | user->canSortPosts($this->post_type->name) && !$this->listing_repo->isSearch() ) : // Menu Options Button ?> 43 |
44 | 45 | 46 |
47 |
48 | 49 | 50 |
51 |
52 | 56 |
57 |
58 | 62 |
63 |
64 | 68 |
69 | 70 | 71 |
72 | 73 |
74 | 75 |
76 | 77 |
78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 |
90 | 91 |
92 |
93 |
-------------------------------------------------------------------------------- /app/Views/partials/list-header.php: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 | 20 |

.

21 |
-------------------------------------------------------------------------------- /app/Views/partials/wpml-translations.php: -------------------------------------------------------------------------------- 1 | integrations->plugins->wpml->getLanguages(); ?> 2 | -------------------------------------------------------------------------------- /app/Views/settings/partials/nav-menu-settings/header.php: -------------------------------------------------------------------------------- 1 |
2 |
3 | 16 |
17 |
18 |
19 | 20 |
21 |
    22 |
  • 23 |
24 |
25 |
26 |
27 |
28 |
-------------------------------------------------------------------------------- /app/Views/settings/settings-admincustom.php: -------------------------------------------------------------------------------- 1 | admin_menu_settings = new NestedPages\Config\AdminMenuSettings; 3 | settings_fields( 'nestedpages-admincustomization' ); ?> 4 |

5 |
6 | 7 | 8 |
9 |
10 |
11 | settings->adminCustomEnabled('enabled_menu') ) echo 'checked'; ?> id="side_menu" /> 12 |
13 | 14 | 15 |
16 |
17 |
    18 |
  • 19 |
    20 |
    21 |


    22 |


    23 |

    24 | 25 |

    26 |
    27 |
    28 | 29 |
    30 |
    31 |
  • 32 |
33 |
34 |
35 | 36 |
-------------------------------------------------------------------------------- /app/Views/settings/settings.php: -------------------------------------------------------------------------------- 1 |
2 |

3 | 4 | 17 | 18 | '; 20 | include(NestedPages\Helpers::view('settings/settings-' . $tab)); 21 | if ( $tab !== 'general' ) { 22 | submit_button(); 23 | echo ''; 24 | } 25 | ?> 26 |
-------------------------------------------------------------------------------- /assets/banner-772x250.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kylephillips/wp-nested-pages/5b5bb4acd518f9db55e4676b8a103605cc92499f/assets/banner-772x250.png -------------------------------------------------------------------------------- /assets/icon-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kylephillips/wp-nested-pages/5b5bb4acd518f9db55e4676b8a103605cc92499f/assets/icon-128x128.png -------------------------------------------------------------------------------- /assets/images/arrow-child.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /assets/images/arrow-down.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assets/images/arrow-up.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assets/images/border.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kylephillips/wp-nested-pages/5b5bb4acd518f9db55e4676b8a103605cc92499f/assets/images/border.png -------------------------------------------------------------------------------- /assets/images/checkbox-filled.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/child-page.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /assets/images/clone.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /assets/images/comments.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /assets/images/datepicker-arrow-next.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kylephillips/wp-nested-pages/5b5bb4acd518f9db55e4676b8a103605cc92499f/assets/images/datepicker-arrow-next.png -------------------------------------------------------------------------------- /assets/images/datepicker-arrow-prev.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kylephillips/wp-nested-pages/5b5bb4acd518f9db55e4676b8a103605cc92499f/assets/images/datepicker-arrow-prev.png -------------------------------------------------------------------------------- /assets/images/globe.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /assets/images/handle.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /assets/images/hidden.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /assets/images/insert-after.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /assets/images/insert-before.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /assets/images/link.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /assets/images/lock.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /assets/images/more.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assets/images/spinner.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | -------------------------------------------------------------------------------- /assets/images/trash-black.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /assets/images/trash-children.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /assets/images/trash.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /assets/js/lib/jquery.ui.touch-punch.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery UI Touch Punch 0.2.3 3 | * 4 | * Copyright 2011–2014, Dave Furfero 5 | * Dual licensed under the MIT or GPL Version 2 licenses. 6 | * 7 | * Depends: 8 | * jquery.ui.widget.js 9 | * jquery.ui.mouse.js 10 | */ 11 | !function(a){function f(a,b){if(!(a.originalEvent.touches.length>1)){a.preventDefault();var c=a.originalEvent.changedTouches[0],d=document.createEvent("MouseEvents");d.initMouseEvent(b,!0,!0,window,1,c.screenX,c.screenY,c.clientX,c.clientY,!1,!1,!1,!1,0,null),a.target.dispatchEvent(d)}}if(a.support.touch="ontouchend"in document,a.support.touch){var e,b=a.ui.mouse.prototype,c=b._mouseInit,d=b._mouseDestroy;b._touchStart=function(a){var b=this;!e&&b._mouseCapture(a.originalEvent.changedTouches[0])&&(e=!0,b._touchMoved=!1,f(a,"mouseover"),f(a,"mousemove"),f(a,"mousedown"))},b._touchMove=function(a){e&&(this._touchMoved=!0,f(a,"mousemove"))},b._touchEnd=function(a){e&&(f(a,"mouseup"),f(a,"mouseout"),this._touchMoved||f(a,"click"),e=!1)},b._mouseInit=function(){var b=this;b.element.bind({touchstart:a.proxy(b,"_touchStart"),touchmove:a.proxy(b,"_touchMove"),touchend:a.proxy(b,"_touchEnd")}),c.call(b)},b._mouseDestroy=function(){var b=this;b.element.unbind({touchstart:a.proxy(b,"_touchStart"),touchmove:a.proxy(b,"_touchMove"),touchend:a.proxy(b,"_touchEnd")}),d.call(b)}}}(jQuery); -------------------------------------------------------------------------------- /assets/js/lib/nestedpages.check-all.js: -------------------------------------------------------------------------------- 1 | var NestedPages = NestedPages || {}; 2 | 3 | /** 4 | * Check All functionality for Nested Pages 5 | * @package Nested Pages 6 | * @author Kyle Phillips - https://github.com/kylephillips/wp-nested-pages 7 | */ 8 | NestedPages.CheckAll = function() 9 | { 10 | var plugin = this; 11 | var $ = jQuery; 12 | 13 | plugin.activeCheckbox = ""; 14 | 15 | plugin.selectors = { 16 | checkbox : '[data-np-check-all]', 17 | } 18 | 19 | plugin.bindEvents = function() 20 | { 21 | $(document).on('change', plugin.selectors.checkbox, function(){ 22 | plugin.activeCheckbox = $(this); 23 | plugin.toggleCheckboxes(); 24 | }); 25 | $(document).on('change', 'input[type=checkbox]', function(){ 26 | plugin.checkAllStatus($(this)); 27 | }); 28 | } 29 | 30 | plugin.init = function() 31 | { 32 | plugin.bindEvents(); 33 | } 34 | 35 | plugin.toggleCheckboxes = function() 36 | { 37 | var checked = ( $(plugin.activeCheckbox).is(':checked') ) ? true : false; 38 | var name = $(plugin.activeCheckbox).attr('data-np-check-all'); 39 | 40 | var checkboxes = $('*[name="' + name + '"]'); 41 | $.each(checkboxes, function(){ 42 | var row = $(this).parents(NestedPages.selectors.rows); 43 | $(this).prop('checked', checked); 44 | // Uncheck any hidden checkboxes 45 | if ( $(row).hasClass('np-hide') && !$(row).is(':visible') ) { 46 | $(row).find(NestedPages.selectors.bulkActionsCheckbox).prop('checked', false) 47 | }; 48 | }); 49 | 50 | plugin.toggleCheckAll(); 51 | } 52 | 53 | /** 54 | * Toggle the "Partial" class for the checkall checkbox 55 | */ 56 | plugin.toggleCheckAll = function() 57 | { 58 | var name = $(plugin.activeCheckbox).attr('data-np-check-all'); 59 | 60 | var checkboxes_total = $('*[name="' + name + '"]').length; 61 | var hidden_checkboxes = $('.np-hide').find(NestedPages.selectors.bulkActionsCheckbox).length; 62 | var hidden_checkboxes_visible = $('.np-hide:visible').find(NestedPages.selectors.bulkActionsCheckbox).length; 63 | 64 | checkboxes_total = ( checkboxes_total - hidden_checkboxes ) + hidden_checkboxes_visible; 65 | 66 | var checkboxes_checked = $('*[name="' + name + '"]:checked').length; 67 | 68 | if ( checkboxes_total == checkboxes_checked ){ 69 | $(plugin.activeCheckbox).prop('checked', true); 70 | $(plugin.activeCheckbox).removeClass('check-all-partial'); 71 | return; 72 | } 73 | if ( checkboxes_checked > 0 ){ 74 | $(plugin.activeCheckbox).addClass('check-all-partial'); 75 | return; 76 | } 77 | $(plugin.activeCheckbox).attr('checked', false); 78 | $(plugin.activeCheckbox).removeClass('check-all-partial'); 79 | } 80 | 81 | plugin.checkAllStatus = function(checkbox) 82 | { 83 | var name = $(checkbox).attr('name'); 84 | var toggleAllCheckbox = $('*[data-np-check-all="' + name + '"]'); 85 | if ( toggleAllCheckbox.length === 0 ) return; 86 | plugin.activeCheckbox = $(toggleAllCheckbox)[0]; 87 | plugin.toggleCheckAll(); 88 | } 89 | 90 | return plugin.init(); 91 | } -------------------------------------------------------------------------------- /assets/js/lib/nestedpages.clone.js: -------------------------------------------------------------------------------- 1 | var NestedPages = NestedPages || {}; 2 | 3 | /** 4 | * Post clone functionality 5 | * @package Nested Pages 6 | * @author Kyle Phillips - https://github.com/kylephillips/wp-nested-pages 7 | */ 8 | NestedPages.Clone = function() 9 | { 10 | var plugin = this; 11 | var $ = jQuery; 12 | 13 | plugin.parent_id = ''; // The parent/source post ID 14 | plugin.parent_title = ''; // The parent title 15 | plugin.parentLi = null; 16 | 17 | plugin.formatter = new NestedPages.Formatter; 18 | 19 | plugin.bindEvents = function() 20 | { 21 | $(document).on('click', NestedPages.selectors.cloneButton, function(e){ 22 | e.preventDefault(); 23 | plugin.parent_id = $(this).attr('data-id'); 24 | plugin.parent_title = $(this).attr('data-parentname'); 25 | plugin.parentLi = $(this).parent('.row').parent('.page-row').parent('.npList'); 26 | plugin.openModal(); 27 | }); 28 | $(document).on('click', NestedPages.selectors.confirmClone, function(e){ 29 | e.preventDefault(); 30 | plugin.clone(); 31 | }); 32 | } 33 | 34 | // Open the modal with clone options 35 | plugin.openModal = function() 36 | { 37 | $('#' + NestedPages.selectors.cloneModal).find('[data-clone-parent]').text(plugin.parent_title); 38 | $(document).trigger('open-modal-manual', NestedPages.selectors.cloneModal); 39 | } 40 | 41 | // Clone the post 42 | plugin.clone = function() 43 | { 44 | var clone_children = ( $(NestedPages.selectors.cloneChildren).is(':checked') ) ? true : false; 45 | plugin.toggleLoading(true); 46 | $.ajax({ 47 | url : NestedPages.jsData.ajaxurl, 48 | type : 'post', 49 | data : { 50 | action : NestedPages.formActions.clonePost, 51 | parent_id : plugin.parent_id, 52 | quantity : $(NestedPages.selectors.cloneQuantity).val(), 53 | status : $(NestedPages.selectors.cloneStatus).val(), 54 | author : $(NestedPages.selectors.cloneAuthor).find('select').val(), 55 | clone_children : clone_children, 56 | nonce : NestedPages.jsData.nonce, 57 | posttype : NestedPages.jsData.posttype 58 | }, 59 | success : function(data){ 60 | plugin.toggleLoading(false); 61 | $(document).trigger('close-modal-manual'); 62 | location.reload(); 63 | } 64 | }); 65 | } 66 | 67 | // Toggle Loading 68 | plugin.toggleLoading = function(loading) 69 | { 70 | if ( loading ){ 71 | $('#' + NestedPages.selectors.cloneModal).find('[data-clone-loading]').show(); 72 | $(NestedPages.selectors.confirmClone).attr('disabled', 'disabled'); 73 | return; 74 | } 75 | $('#' + NestedPages.selectors.cloneModal).find('[data-clone-loading]').hide(); 76 | $(NestedPages.selectors.confirmClone).attr('disabled', false); 77 | } 78 | 79 | return plugin.bindEvents(); 80 | } -------------------------------------------------------------------------------- /assets/js/lib/nestedpages.confirm-delete.js: -------------------------------------------------------------------------------- 1 | var NestedPages = NestedPages || {}; 2 | 3 | /** 4 | * Confirm deletion of links 5 | * @package Nested Pages 6 | * @author Kyle Phillips - https://github.com/kylephillips/wp-nested-pages 7 | */ 8 | NestedPages.ConfirmDelete = function() 9 | { 10 | var plugin = this; 11 | var $ = jQuery; 12 | 13 | // JS Data 14 | plugin.deleteLink = ''; // Link for deleting the item 15 | 16 | plugin.bindEvents = function() 17 | { 18 | $(document).on('click', NestedPages.selectors.linkDeleteButton, function(e){ 19 | e.preventDefault(); 20 | plugin.confirmSingle($(this)); 21 | }); 22 | $(document).on('click', NestedPages.selectors.linkDeleteConfirmationButton, function(e){ 23 | e.preventDefault(); 24 | if ( !$(this).hasClass('bulk') ){ 25 | plugin.deleteSingle(); 26 | return; 27 | } 28 | plugin.deleteMultiple(); 29 | }); 30 | $(document).on('submit', NestedPages.selectors.bulkActionsForm, function(e){ 31 | plugin.confirmMultiple(e); 32 | }); 33 | } 34 | 35 | // Confirm a single link deletion 36 | plugin.confirmSingle = function(button) 37 | { 38 | plugin.deleteLink = $(button).attr('href'); 39 | $(NestedPages.selectors.linkDeleteConfirmationModalText).text(nestedpages.link_delete_confirmation_singular); 40 | $(NestedPages.selectors.linkDeleteConfirmationButton).text(nestedpages.delete).removeClass('bulk'); 41 | $(document).trigger('open-modal-manual', NestedPages.selectors.linkDeleteConfirmationModal); 42 | } 43 | 44 | // Confirm Multiple link deletion 45 | plugin.confirmMultiple = function(event) 46 | { 47 | if ( $('select[name="np_bulk_action"]').val() !== 'trash' ) return; 48 | var linkCount = $(NestedPages.selectors.bulkActionRedirectIds).val(); 49 | if ( linkCount === '' ) return; 50 | event.preventDefault(); 51 | $(NestedPages.selectors.linkDeleteConfirmationModalText).text(nestedpages.link_delete_confirmation); 52 | $(NestedPages.selectors.linkDeleteConfirmationButton).text(nestedpages.trash_delete_links).addClass('bulk'); 53 | $(document).trigger('open-modal-manual', [NestedPages.selectors.linkDeleteConfirmationModal]); 54 | } 55 | 56 | // Submit the form to delete multiple 57 | plugin.deleteMultiple = function() 58 | { 59 | $(NestedPages.selectors.bulkActionsForm)[0].submit(); 60 | } 61 | 62 | // Delete the single 63 | plugin.deleteSingle = function() 64 | { 65 | window.location.replace(plugin.deleteLink); 66 | } 67 | 68 | return plugin.bindEvents(); 69 | } 70 | -------------------------------------------------------------------------------- /assets/js/lib/nestedpages.dropdowns.js: -------------------------------------------------------------------------------- 1 | var NestedPages = NestedPages || {}; 2 | /** 3 | * Dropdowns 4 | * 5 | * @author Kyle Phillips 6 | * 7 | * To use, wrap dropdown content and toggle link/button in an element with data attribute of data-dropdown 8 | * Give content data attribute of data-dropdown-content and toggle element data attribute of data-dropdown-toggle 9 | * For CSS, wrapping/parent element gets class of "dropdown", content gets class of "dropdown-content" 10 | */ 11 | NestedPages.Dropdowns = function() 12 | { 13 | var self = this; 14 | var $ = jQuery; 15 | 16 | self.dropdown = ''; // The Active Dropdown 17 | self.activeBtn = ''; // The Active Button 18 | self.activeContent = ''; // The Active Dropdown Content 19 | self.toggleBtn = '[data-dropdown-toggle]'; 20 | self.dropdownContainer = '[data-dropdown]'; 21 | self.dropdownContent = '[data-dropdown-content]' 22 | 23 | self.selectors = { 24 | caret_up : 'icon-arrow_drop_up', 25 | caret_down : 'icon-arrow_drop_down' 26 | } 27 | 28 | self.bindEvents = function() 29 | { 30 | $(document).on('click', self.toggleBtn, function(e){ 31 | e.preventDefault(); 32 | self.activeBtn = $(this); 33 | self.dropdown = $(this).parents(self.dropdownContainer); 34 | self.toggleDropdown(); 35 | }); 36 | $(document).on('click', function(e){ 37 | self.closeDropdowns(e.target); 38 | }); 39 | $(document).on('dropdown-opened', function(e, content){ 40 | if ( $(content).parents(NestedPages.selectors.row).length > 0 ){ 41 | $(content).parents(NestedPages.selectors.row).addClass('active'); 42 | } 43 | }); 44 | $(document).on('dropdown-closed', function(){ 45 | $(NestedPages.selectors.row).removeClass('active'); 46 | }); 47 | } 48 | 49 | self.toggleDropdown = function() 50 | { 51 | $('.' + self.selectors.caret_up).attr('class', self.selectors.caret_down); 52 | var content = $(self.dropdown).find(self.dropdownContent); 53 | self.activeContent = content; 54 | if ( $(content).hasClass('active') ){ 55 | $(content).removeClass('active'); 56 | $(self.activeBtn).removeClass('active'); 57 | $(self.activeBtn).find('.' + self.selectors.caret_up).attr('class', self.selectors.caret_down); 58 | $(document).trigger('dropdown-closed', content); 59 | return; 60 | } 61 | self.setPositioning(); 62 | $(self.toggleBtn).removeClass('active'); 63 | $(self.dropdownContent).removeClass('active'); 64 | $(content).addClass('active'); 65 | $(self.activeBtn).find('.' + self.selectors.caret_down).attr('class', self.selectors.caret_up); 66 | $(self.activeBtn).addClass('active'); 67 | $(document).trigger('dropdown-opened', content); 68 | } 69 | 70 | 71 | self.setPositioning = function() 72 | { 73 | var buttonHeight = $(self.activeBtn).outerHeight(); 74 | $(self.activeContent).css('top', buttonHeight + 'px'); 75 | } 76 | 77 | self.closeDropdowns = function(target) 78 | { 79 | if ( $(target).parents(self.dropdownContainer).length === 0 ){ 80 | $(self.dropdownContent).removeClass('active'); 81 | $(self.toggleBtn).removeClass('active'); 82 | $(self.activeBtn).find('.' + self.selectors.caret_up).attr('class', self.selectors.caret_down); 83 | var content; 84 | $(document).trigger('dropdown-closed', content); 85 | } 86 | } 87 | 88 | return self.bindEvents(); 89 | } -------------------------------------------------------------------------------- /assets/js/lib/nestedpages.hidden-item-count.js: -------------------------------------------------------------------------------- 1 | var NestedPages = NestedPages || {}; 2 | 3 | /** 4 | * The Hidden Item Count for selected items in the Nested View 5 | * @package Nested Pages 6 | * @author Kyle Phillips - https://github.com/kylephillips/wp-nested-pages 7 | */ 8 | NestedPages.HiddenItemCount = function() 9 | { 10 | var plugin = this; 11 | var $ = jQuery; 12 | 13 | plugin.init = function() 14 | { 15 | plugin.bindEvents(); 16 | } 17 | 18 | plugin.bindEvents = function() 19 | { 20 | $(document).on('change', NestedPages.selectors.bulkActionsCheckbox, function(){ 21 | plugin.toggleHiddenCount(); 22 | }); 23 | $(document).on('click', NestedPages.selectors.toggleAll, function(){ 24 | plugin.toggleHiddenCount(); 25 | }); 26 | $(document).on('click', NestedPages.selectors.pageToggle, function(){ 27 | plugin.toggleHiddenCount(); 28 | }); 29 | } 30 | 31 | /** 32 | * Toggle the Hidden Count 33 | */ 34 | plugin.toggleHiddenCount = function() 35 | { 36 | var hiddenCount = 0; 37 | $.each($(NestedPages.selectors.bulkActionsCheckbox), function(){ 38 | if ( $(this).is(':checked') ) { 39 | if ( $(this).parent('div').hasClass('np-check-all') ) return; 40 | var row = $(this).closest('.page-row'); 41 | if ( !$(row).is(':visible') ) hiddenCount++; 42 | } 43 | }); 44 | if ( hiddenCount < 1 ){ 45 | $(NestedPages.selectors.hiddenItemCountParent).hide(); 46 | return; 47 | } 48 | $(NestedPages.selectors.hiddenItemCount).text(hiddenCount); 49 | $(NestedPages.selectors.hiddenItemCountParent).show(); 50 | } 51 | 52 | return plugin.init(); 53 | } -------------------------------------------------------------------------------- /assets/js/lib/nestedpages.manual-sync.js: -------------------------------------------------------------------------------- 1 | var NestedPages = NestedPages || {}; 2 | 3 | /** 4 | * Manual Sync functionality for nested view 5 | * @package Nested Pages 6 | * @author Kyle Phillips - https://github.com/kylephillips/wp-nested-pages 7 | */ 8 | NestedPages.ManualSync = function() 9 | { 10 | var plugin = this; 11 | var $ = jQuery; 12 | 13 | plugin.activeBtn = ''; 14 | 15 | plugin.bindEvents = function() 16 | { 17 | $(document).on('click', NestedPages.selectors.manualMenuSync, function(e){ 18 | e.preventDefault(); 19 | plugin.activeBtn = $(this); 20 | plugin.syncMenu(); 21 | }); 22 | $(document).on('click', NestedPages.selectors.manualOrderSync, function(e){ 23 | e.preventDefault(); 24 | plugin.activeBtn = $(this); 25 | plugin.syncOrder(); 26 | }); 27 | } 28 | 29 | plugin.syncMenu = function() 30 | { 31 | plugin.loading(true); 32 | 33 | $.ajax({ 34 | url: ajaxurl, 35 | type: 'post', 36 | datatype: 'json', 37 | data: { 38 | action : NestedPages.formActions.manualMenuSync, 39 | nonce : NestedPages.jsData.nonce, 40 | post_type : NestedPages.jsData.posttype, 41 | syncmenu : 'sync' 42 | }, 43 | success: function(data){ 44 | if (data.status === 'error'){ 45 | $(NestedPages.selectors.errorDiv).text(data.message).show(); 46 | $(NestedPages.selectors.loadingIndicator).hide(); 47 | } else { 48 | plugin.loading(false); 49 | } 50 | } 51 | }); 52 | } 53 | 54 | plugin.syncOrder = function() 55 | { 56 | plugin.loading(true); 57 | var nestingClass = new NestedPages.Nesting; 58 | nestingClass.syncNesting(true, plugin.loading(false)); 59 | } 60 | 61 | plugin.loading = function(loading) 62 | { 63 | if ( loading ){ 64 | $(plugin.activeBtn).addClass('disabled'); 65 | $(NestedPages.selectors.loadingIndicator).show(); 66 | return; 67 | } 68 | $(plugin.activeBtn).removeClass('disabled'); 69 | $(NestedPages.selectors.loadingIndicator).hide(); 70 | } 71 | 72 | return plugin.bindEvents(); 73 | } -------------------------------------------------------------------------------- /assets/js/lib/nestedpages.menu-toggle.js: -------------------------------------------------------------------------------- 1 | var NestedPages = NestedPages || {}; 2 | 3 | /** 4 | * Toggles Menu Elements 5 | * @package Nested Pages 6 | * @author Kyle Phillips - https://github.com/kylephillips/wp-nested-pages 7 | */ 8 | NestedPages.MenuToggle = function() 9 | { 10 | var plugin = this; 11 | var $ = jQuery; 12 | 13 | plugin.formatter = new NestedPages.Formatter; 14 | 15 | plugin.bindEvents = function() 16 | { 17 | $(document).on('click', NestedPages.selectors.childToggleLink, function(e){ 18 | e.preventDefault(); 19 | plugin.toggleSingleMenu($(this)); 20 | }); 21 | $(document).on('click', NestedPages.selectors.toggleAll, function(e){ 22 | e.preventDefault(); 23 | plugin.toggleAllMenus(); 24 | }); 25 | } 26 | 27 | 28 | // Toggle individual submenus 29 | plugin.toggleSingleMenu = function(button) 30 | { 31 | var submenu = $(button).parent(NestedPages.selectors.childToggle).parent(NestedPages.selectors.row).siblings('ol'); 32 | $(button).toggleClass('open'); 33 | $(submenu).toggle(); 34 | plugin.formatter.setBorders(); 35 | plugin.formatter.setNestedMargins(); 36 | plugin.syncUserToggles(); 37 | } 38 | 39 | 40 | // Toggle All Submenus 41 | plugin.toggleAllMenus = function() 42 | { 43 | var button = NestedPages.selectors.toggleAll; 44 | if ( $(button).attr('data-toggle') === 'closed' ){ 45 | $(NestedPages.selectors.lists).show(); 46 | $(button).attr('data-toggle', 'opened').text(NestedPages.jsData.collapseText); 47 | $(NestedPages.selectors.childToggle + ' a').addClass('open'); 48 | plugin.formatter.setBorders(); 49 | plugin.syncUserToggles(); 50 | return; 51 | } 52 | 53 | $(NestedPages.selectors.lists).not($(NestedPages.selectors.lists)[0]).hide(); 54 | $(button).attr('data-toggle', 'closed').text(NestedPages.jsData.expandText); 55 | $(NestedPages.selectors.childToggle + ' a').removeClass('open'); 56 | plugin.formatter.setBorders(); 57 | plugin.syncUserToggles(); 58 | } 59 | 60 | 61 | // Get an array of visible rows 62 | plugin.visibleRowIDs = function() 63 | { 64 | var visible_ids = []; 65 | var visible = $(NestedPages.selectors.rows + ':visible'); 66 | $.each(visible, function(i, v){ 67 | var id = $(this).attr('id'); 68 | visible_ids.push(id.replace("menuItem_", "")); 69 | }); 70 | return visible_ids; 71 | } 72 | 73 | 74 | // Save the user's toggled menus 75 | plugin.syncUserToggles = function() 76 | { 77 | $.ajax({ 78 | url: NestedPages.jsData.ajaxurl, 79 | type: 'post', 80 | datatype: 'json', 81 | data: { 82 | action : NestedPages.formActions.syncToggles, 83 | nonce : NestedPages.jsData.nonce, 84 | ids : plugin.visibleRowIDs(), 85 | posttype : NestedPages.jsData.posttype 86 | }, 87 | success: function(data){ 88 | if ( data.status !== 'success' ){ 89 | console.log('There was an error saving toggled pages.'); 90 | } 91 | } 92 | }); 93 | } 94 | 95 | 96 | return plugin.bindEvents(); 97 | } -------------------------------------------------------------------------------- /assets/js/lib/nestedpages.modals.js: -------------------------------------------------------------------------------- 1 | var NestedPages = NestedPages || {}; 2 | /** 3 | * Modal Windows 4 | * 5 | * @author Kyle Phillips 6 | * 7 | * To use, include a modal backdrop and modal content window with the appropriate data-attributes 8 | * The data attributes should match the value of the toggle buttons data-modal-toggle attribute 9 | */ 10 | NestedPages.Modals = function() 11 | { 12 | var self = this; 13 | var $ = jQuery; 14 | 15 | self.activeBtn = ''; 16 | self.activeModal = ''; 17 | self.modalOpen = false; 18 | 19 | self.selectors = { 20 | toggleBtn : '[data-nestedpages-modal-toggle]', 21 | backdrop : '[data-nestedpages-modal-backdrop]', 22 | closeBtn : '[data-nestedpages-modal-close]', 23 | title : 'data-nestedpages-modal-title', 24 | content : 'data-nestedpages-modal-content' 25 | } 26 | 27 | self.bindEvents = function() 28 | { 29 | $(document).on('click', self.selectors.toggleBtn, function(e){ 30 | e.preventDefault(); 31 | self.activeBtn = $(this); 32 | self.openModal(); 33 | }); 34 | $(document).on('click', self.selectors.closeBtn, function(e){ 35 | e.preventDefault(); 36 | self.closeModals(); 37 | }); 38 | $(document).on('open-modal-manual', function(e, modal){ 39 | self.activeModal = $('[data-nestedpages-modal="' + modal + '"]'); 40 | self.openModal(); 41 | }); 42 | $(document).on('close-modal-manual', function(e){ 43 | self.closeModals(); 44 | }); 45 | $(document).on('click', self.selectors.backdrop, function(e){ 46 | self.closeModals(); 47 | }); 48 | $(document).ready(function(){ 49 | self.checkHash(); 50 | }); 51 | } 52 | 53 | /** 54 | * Open the Modal Window 55 | */ 56 | self.openModal = function() 57 | { 58 | if ( self.modalOpen ){ 59 | self.closeModals(); 60 | return; 61 | } 62 | if ( $(self.activeBtn).length > 0 ){ 63 | var modal = $(self.activeBtn).attr('data-nestedpages-modal-toggle'); 64 | self.activeModal = $('*[data-nestedpages-modal="' + modal + '"]'); 65 | } 66 | $(self.activeModal).addClass('active'); 67 | self.modalOpen = true; 68 | self.populateModal(); 69 | $(document).trigger('open-modal', [self.activeBtn, self.activeModal]); 70 | } 71 | 72 | /** 73 | * Close the Modal Window 74 | */ 75 | self.closeModals = function() 76 | { 77 | self.modalOpen = false; 78 | $('[data-nestedpages-modal]').removeClass('active'); 79 | self.activeModal = ''; 80 | self.activeBtn = ''; 81 | } 82 | 83 | /** 84 | * Populate the Modal if needed 85 | */ 86 | self.populateModal = function() 87 | { 88 | var title = $(self.activeBtn).attr(self.selectors.title); 89 | if ( typeof title !== 'undefined' && title !== '' ){ 90 | $(self.activeModal).find('[data-nestedpages-modal-title]').text(title); 91 | } 92 | var content = $(self.activeBtn).attr(self.selectors.content); 93 | if ( typeof content !== 'undefined' && content !== '' ){ 94 | $(self.activeModal).find('[data-nestedpages-modal-content]').html(content); 95 | } 96 | } 97 | 98 | /** 99 | * Check for Hash 100 | */ 101 | self.checkHash = function() 102 | { 103 | if ( !window.location.hash ) return; 104 | var hashType = window.location.hash.substring(0, 6); 105 | if ( hashType !== '#modal' ) return; 106 | 107 | var modalId = window.location.hash.substring(7); 108 | self.activeModal = $('*[data-nestedpages-modal="' + modalId + '"]'); 109 | self.openModal(); 110 | } 111 | 112 | return self.bindEvents(); 113 | } -------------------------------------------------------------------------------- /assets/js/lib/nestedpages.move-post.js: -------------------------------------------------------------------------------- 1 | var NestedPages = NestedPages || {}; 2 | 3 | /** 4 | * Move a Post Up or Down in the list 5 | * @package Nested Pages 6 | * @author Kyle Phillips - https://github.com/kylephillips/wp-nested-pages 7 | */ 8 | NestedPages.MovePost = function() 9 | { 10 | var plugin = this; 11 | var $ = jQuery; 12 | 13 | plugin.formatter = new NestedPages.Formatter; 14 | plugin.nesting = new NestedPages.Nesting; 15 | plugin.activeRow; 16 | 17 | plugin.selectors = { 18 | moveToTop : 'data-push-to-top', 19 | moveToBottom : 'data-push-to-bottom' 20 | } 21 | 22 | plugin.bindEvents = function() 23 | { 24 | $(document).ready(function(){ 25 | plugin.disableTopOnFirst(); 26 | plugin.disableBottomOnLast(); 27 | }); 28 | $(document).on('click', '[' + plugin.selectors.moveToTop + ']', function(e){ 29 | e.preventDefault(); 30 | if ( $(this).hasClass('disabled') ) return; 31 | plugin.activeRow = $(this).closest(NestedPages.selectors.rows); 32 | plugin.moveToTop(); 33 | }); 34 | $(document).on('click', '[' + plugin.selectors.moveToBottom + ']', function(e){ 35 | e.preventDefault(); 36 | if ( $(this).hasClass('disabled') ) return; 37 | plugin.activeRow = $(this).closest(NestedPages.selectors.rows); 38 | plugin.moveToBottom(); 39 | }); 40 | } 41 | 42 | /** 43 | * Move a post to the top of its list 44 | */ 45 | plugin.moveToTop = function() 46 | { 47 | var parent = $(plugin.activeRow).parent(NestedPages.selectors.lists); 48 | var first = $(parent).find(NestedPages.selectors.rows).first(); 49 | $(plugin.activeRow).insertBefore(first); 50 | plugin.formatter.setBorders(); 51 | $(document).click(); // Close Dropdowns 52 | plugin.nesting.syncNesting(); 53 | plugin.disableTopOnFirst(); 54 | plugin.disableBottomOnLast(); 55 | } 56 | 57 | /** 58 | * Move a post to the bottom of its list 59 | */ 60 | plugin.moveToBottom = function() 61 | { 62 | var parent = $(plugin.activeRow).parent(NestedPages.selectors.lists); 63 | var last = $(parent).children(NestedPages.selectors.rows).last(); 64 | $(plugin.activeRow).insertAfter(last); 65 | plugin.formatter.setBorders(); 66 | $(document).click(); // Close Dropdowns 67 | plugin.nesting.syncNesting(); 68 | plugin.disableTopOnFirst(); 69 | plugin.disableBottomOnLast(); 70 | } 71 | 72 | plugin.disableTopOnFirst = function() 73 | { 74 | var lists = $(NestedPages.selectors.lists); 75 | $.each(lists, function(){ 76 | $(this).find('[' + plugin.selectors.moveToTop + ']').removeClass('disabled'); 77 | var first = $(this).find(NestedPages.selectors.rows).first(); 78 | $(first).find('[' + plugin.selectors.moveToTop + ']').addClass('disabled'); 79 | }); 80 | } 81 | 82 | plugin.disableBottomOnLast = function() 83 | { 84 | var lists = $(NestedPages.selectors.lists); 85 | $.each(lists, function(){ 86 | $(this).find('[' + plugin.selectors.moveToBottom + ']').removeClass('disabled'); 87 | var last = $(this).find(NestedPages.selectors.rows).last(); 88 | $(last).find('[' + plugin.selectors.moveToBottom + ']').addClass('disabled'); 89 | }); 90 | } 91 | 92 | return plugin.bindEvents(); 93 | } -------------------------------------------------------------------------------- /assets/js/lib/nestedpages.page-toggle.js: -------------------------------------------------------------------------------- 1 | var NestedPages = NestedPages || {}; 2 | 3 | /** 4 | * Toggle Page Rows 5 | * @package Nested Pages 6 | * @author Kyle Phillips - https://github.com/kylephillips/wp-nested-pages 7 | */ 8 | NestedPages.PageToggle = function() 9 | { 10 | var plugin = this; 11 | var $ = jQuery; 12 | 13 | plugin.activeButton = ''; 14 | 15 | plugin.formatter = new NestedPages.Formatter; 16 | 17 | plugin.bindEvents = function() 18 | { 19 | $(document).on('click', NestedPages.selectors.toggleHidden, function(e){ 20 | e.preventDefault(); 21 | plugin.activeButton = $(this); 22 | plugin.toggleHidden(); 23 | }); 24 | $(document).on('click', NestedPages.selectors.toggleStatus, function(e){ 25 | e.preventDefault(); 26 | plugin.toggleStatus($(this)); 27 | }); 28 | } 29 | 30 | 31 | // Toggle Hidden Pages 32 | plugin.toggleHidden = function() 33 | { 34 | var button = $(plugin.activeButton); 35 | var action = $(button).attr('href'); 36 | 37 | if ( action == '#show' ){ 38 | $(button).attr('href', '#hide').text(NestedPages.jsData.showHiddenText); 39 | $(NestedPages.selectors.hiddenRows).removeClass('shown').hide(); 40 | plugin.formatter.updateSubMenuToggle(); 41 | plugin.formatter.setBorders(); 42 | return; 43 | } 44 | 45 | if ( action == '#hide' ){ 46 | $(button).attr('href', '#show').text(NestedPages.jsData.hideHiddenText); 47 | $(NestedPages.selectors.hiddenRows).addClass('shown').show(); 48 | plugin.formatter.updateSubMenuToggle(); 49 | plugin.formatter.setBorders(); 50 | } 51 | } 52 | 53 | // Toggle Pages based on status 54 | plugin.toggleStatus = function(button) 55 | { 56 | var target = $(button).attr('href'); 57 | target = target.substring(1); 58 | $(NestedPages.selectors.syncCheckbox).attr('disabled', false); 59 | $(NestedPages.selectors.toggleStatus).removeClass('active'); 60 | $(button).addClass('active'); 61 | plugin.saveStatusPreference(target); 62 | if ( target == 'draft' ){ 63 | $(NestedPages.selectors.syncCheckbox).attr('disabled', true); 64 | $('.' + target).addClass('nested-visible'); 65 | } 66 | if ( target == 'all' ){ 67 | $(NestedPages.selectors.rows).show(); 68 | return; 69 | } 70 | $(NestedPages.selectors.rows).hide(); 71 | $('.' + target).show(); 72 | return; 73 | } 74 | 75 | // Save the user's status preference 76 | plugin.saveStatusPreference = function(status) 77 | { 78 | $.ajax({ 79 | url: ajaxurl, 80 | type: 'post', 81 | datatype: 'json', 82 | data: { 83 | action : NestedPages.formActions.toggleStatusDisplay, 84 | nonce : NestedPages.jsData.nonce, 85 | status : status, 86 | post_type : NestedPages.jsData.posttype 87 | } 88 | }); 89 | } 90 | 91 | return plugin.bindEvents(); 92 | 93 | } -------------------------------------------------------------------------------- /assets/js/lib/nestedpages.settings-reset.js: -------------------------------------------------------------------------------- 1 | var NestedPages = NestedPages || {}; 2 | 3 | /** 4 | * Reset Settings to Default 5 | * @package Nested Pages 6 | * @author Kyle Phillips - https://github.com/kylephillips/wp-nested-pages 7 | */ 8 | NestedPages.SettingsReset = function() 9 | { 10 | var plugin = this; 11 | var $ = jQuery; 12 | 13 | plugin.selectors = { 14 | resetButton : 'data-nestedpages-reset-settings', 15 | resetForm : '.nestedpages-reset-settings', 16 | formComplete : '.nestedpages-reset-settings-complete', 17 | resetAdminMenu : 'data-nestedpages-reset-admin-menu' 18 | } 19 | 20 | plugin.bindEvents = function() 21 | { 22 | $(document).on('click', '[' + plugin.selectors.resetButton + ']', function(e){ 23 | e.preventDefault(); 24 | plugin.resetSettings(); 25 | }); 26 | $(document).on('click', '[' + plugin.selectors.resetAdminMenu + ']', function(e){ 27 | e.preventDefault(); 28 | plugin.resetAdminMenu(); 29 | }); 30 | } 31 | 32 | plugin.resetSettings = function() 33 | { 34 | plugin.loading(true); 35 | $.ajax({ 36 | url: NestedPages.jsData.ajaxurl, 37 | type: 'post', 38 | datatype: 'json', 39 | data: { 40 | action : NestedPages.formActions.resetSettings, 41 | nonce : NestedPages.jsData.nonce 42 | }, 43 | success: function(data){ 44 | plugin.loading(false); 45 | $(plugin.selectors.resetForm).hide(); 46 | $(plugin.selectors.formComplete).show(); 47 | if ( data.status !== 'success' ){ 48 | console.log('There was an error saving toggled pages.'); 49 | } 50 | } 51 | }); 52 | } 53 | 54 | plugin.resetAdminMenu = function() 55 | { 56 | plugin.loading(true); 57 | $.ajax({ 58 | url: NestedPages.jsData.ajaxurl, 59 | type: 'post', 60 | datatype: 'json', 61 | data: { 62 | action : NestedPages.formActions.resetAdminMenuSettings, 63 | nonce : NestedPages.jsData.nonce 64 | }, 65 | success: function(data){ 66 | plugin.loading(false); 67 | if ( data.status == 'success' ){ 68 | location.reload(); 69 | } 70 | if ( data.status !== 'success' ){ 71 | console.log('There was an error saving toggled pages.'); 72 | } 73 | } 74 | }); 75 | } 76 | 77 | plugin.loading = function(loading) 78 | { 79 | if ( loading ){ 80 | $('[' + plugin.selectors.resetButton + ']').attr('disabled', true); 81 | $('[' + plugin.selectors.resetAdminMenu + ']').attr('disabled', true); 82 | return; 83 | } 84 | $('[' + plugin.selectors.resetButton + ']').removeAttr('disabled'); 85 | $('[' + plugin.selectors.resetAdminMenu + ']').removeAttr('disabled'); 86 | } 87 | 88 | return plugin.bindEvents(); 89 | } 90 | 91 | new NestedPages.SettingsReset; -------------------------------------------------------------------------------- /assets/js/lib/nestedpages.sync-menu-setting.js: -------------------------------------------------------------------------------- 1 | var NestedPages = NestedPages || {}; 2 | 3 | /** 4 | * Sync the "sync menu" setting 5 | * @package Nested Pages 6 | * @author Kyle Phillips - https://github.com/kylephillips/wp-nested-pages 7 | */ 8 | NestedPages.SyncMenuSetting = function() 9 | { 10 | var plugin = this; 11 | var $ = jQuery; 12 | 13 | plugin.formatter = new NestedPages.Formatter; 14 | 15 | plugin.init = function() 16 | { 17 | plugin.bindEvents(); 18 | } 19 | 20 | plugin.bindEvents = function() 21 | { 22 | $(document).ready(function(){ // catches trash updates 23 | if ( nestedpages.manual_menu_sync === '1' ) return; 24 | if ( nestedpages.syncmenu === '1' ) plugin.syncSetting(); 25 | }); 26 | $(document).on('change', NestedPages.selectors.syncCheckbox, function(){ 27 | plugin.syncSetting(); 28 | }); 29 | } 30 | 31 | // Sync the "Sync menu" preference / setting 32 | plugin.syncSetting = function() 33 | { 34 | 35 | if ( NestedPages.jsData.posttype !== 'page' ) return; 36 | if ($(NestedPages.selectors.syncCheckbox).length === 0) return; 37 | 38 | NestedPages.jsData.syncmenu = ( $(NestedPages.selectors.syncCheckbox).is(':checked') ) ? 'sync' : 'nosync'; 39 | 40 | $.ajax({ 41 | url: NestedPages.jsData.ajaxurl, 42 | type: 'post', 43 | datatype: 'json', 44 | data: { 45 | action : NestedPages.formActions.syncMenu, 46 | nonce : NestedPages.jsData.nonce, 47 | post_type : NestedPages.jsData.posttype, 48 | syncmenu : NestedPages.jsData.syncmenu 49 | }, 50 | success: function(data){ 51 | if (data.status === 'error'){ 52 | plugin.formatter.showAjaxError(data.message); 53 | } 54 | }, 55 | error: function(data){ 56 | console.log(data); 57 | } 58 | }); 59 | } 60 | 61 | return plugin.bindEvents(); 62 | } -------------------------------------------------------------------------------- /assets/js/lib/nestedpages.tabs.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Tabs 3 | * 4 | * @author Kyle Phillips 5 | * 6 | * To use, include links with a data-tab-toggle attribute which matches the tab pane's data-tab-pane attribute 7 | * The tabs and panes should all the same data-tab-group attribute value 8 | * Add a CSS selector of .tab-pane to panes to hide them 9 | */ 10 | var NestedPages = NestedPages || {}; 11 | NestedPages.Tabs = function() 12 | { 13 | var self = this; 14 | var $ = jQuery; 15 | 16 | self.selectors = { 17 | tabToggle : 'data-np-tab-toggle', 18 | tabPane : 'data-np-tab-pane', 19 | tabGroup : 'data-np-tab-group' 20 | } 21 | 22 | self.bindEvents = function() 23 | { 24 | $(document).on('click', '[' + self.selectors.tabToggle + ']', function(e){ 25 | e.preventDefault(); 26 | self.toggleTabs($(this)); 27 | }); 28 | } 29 | 30 | /** 31 | * Toggle the Tabs 32 | */ 33 | self.toggleTabs = function(tab) 34 | { 35 | var tabGroup = $(tab).attr(self.selectors.tabGroup); 36 | var tabPanes = $('*[' + self.selectors.tabGroup + '=' + tabGroup + '][' + self.selectors.tabPane + ']'); 37 | var activeTab = $(tab).attr(self.selectors.tabToggle); 38 | var buttons = $('*[' + self.selectors.tabGroup + '=' + tabGroup + '][' + self.selectors.tabToggle + ']'); 39 | var listItems = []; 40 | 41 | for ( var i = 0; i < buttons.length; i++ ){ 42 | listItems[i] = $(buttons[i]).parent('li')[0]; 43 | } 44 | 45 | $(tabPanes).hide(); 46 | $(buttons).removeClass('active'); 47 | $(listItems).removeClass('active'); 48 | 49 | $.each(tabPanes, function(){ 50 | if ( $(this).attr(self.selectors.tabPane) == activeTab ) $(this).show(); 51 | }); 52 | 53 | $.each(buttons, function(){ 54 | if ( $(this).attr(self.selectors.tabToggle) == activeTab ) { 55 | $(this).addClass('active'); 56 | $(this).parent('li').addClass('active'); 57 | } 58 | }); 59 | 60 | $(document).trigger('tabs-changed', [activeTab, tabGroup, tab]); 61 | } 62 | 63 | return self.bindEvents(); 64 | } -------------------------------------------------------------------------------- /assets/js/lib/nestedpages.trash-with-children.js: -------------------------------------------------------------------------------- 1 | var NestedPages = NestedPages || {}; 2 | 3 | /** 4 | * Trash post with all children 5 | * @package Nested Pages 6 | * @author Kyle Phillips - https://github.com/kylephillips/wp-nested-pages 7 | */ 8 | NestedPages.TrashWithChildren = function() 9 | { 10 | var plugin = this; 11 | var $ = jQuery; 12 | 13 | plugin.post_id = ''; // The parent/source post ID 14 | 15 | plugin.bindEvents = function() 16 | { 17 | $(document).on('click', NestedPages.selectors.trashWithChildrenButton, function(e){ 18 | e.preventDefault(); 19 | plugin.post_id = $(this).attr('data-post-id'); 20 | plugin.trash(); 21 | }); 22 | } 23 | 24 | // Trash the posts 25 | plugin.trash = function() 26 | { 27 | $.ajax({ 28 | url : NestedPages.jsData.ajaxurl, 29 | type : 'post', 30 | data : { 31 | action : NestedPages.formActions.trashWithChildren, 32 | nonce : NestedPages.jsData.nonce, 33 | post_id : plugin.post_id, 34 | screen : nestedpages.current_page 35 | }, 36 | success : function(data){ 37 | window.location.replace(data.redirect); 38 | }, error : function(data){ 39 | console.log(data); 40 | } 41 | }); 42 | } 43 | 44 | return plugin.bindEvents(); 45 | } -------------------------------------------------------------------------------- /assets/js/lib/nestedpages.trash.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Empty Trash Functionality 3 | * @package Nested Pages 4 | * @author Kyle Phillips - https://github.com/kylephillips/wp-nested-pages 5 | */ 6 | jQuery(document).ready(function(){ 7 | new NestedPagesTrash; 8 | }); 9 | 10 | var NestedPagesTrash = function() 11 | { 12 | var plugin = this; 13 | var $ = jQuery; 14 | 15 | // DOM Selectors 16 | plugin.trashButton = '.np-empty-trash'; // Trash Link 17 | plugin.confirmButton = '.np-trash-confirm'; // Confirm button in modal 18 | plugin.warningModal = '#np-trash-modal'; // Modal with empty confirmation 19 | plugin.errorAlert = '#np-error'; // Alert Error Notification 20 | plugin.loadingIndicator = '#nested-loading'; // Loading Indication 21 | plugin.trashLinks = '.np-trash-links'; 22 | plugin.postType = $('#np-trash-posttype').val(); 23 | 24 | // JS Data 25 | plugin.nonce = nestedpages.np_nonce; 26 | plugin.formAction = 'npEmptyTrash'; 27 | 28 | plugin.bindEvents = function(){ 29 | $(document).on('click', plugin.confirmButton, function(e){ 30 | e.preventDefault(); 31 | plugin.confirmEmpty(); 32 | }); 33 | } 34 | 35 | // Confirm Trash Empty 36 | plugin.confirmEmpty = function(){ 37 | plugin.loading(true); 38 | $(document).trigger('close-modal-manual'); 39 | $(plugin.errorAlert).hide(); 40 | plugin.emptyTrash(); 41 | } 42 | 43 | // Empty the Trash 44 | plugin.emptyTrash = function(){ 45 | $.ajax({ 46 | url: ajaxurl, 47 | type: 'post', 48 | datatype: 'json', 49 | data: { 50 | action : plugin.formAction, 51 | nonce : plugin.nonce, 52 | posttype : plugin.postType 53 | }, 54 | success: function(data){ 55 | plugin.loading(false); 56 | if (data.status === 'error'){ 57 | $(plugin.errorAlert).text(data.message).show(); 58 | } else { 59 | $(plugin.trashLinks).hide(); 60 | $('.notice-dismiss').click(); 61 | } 62 | } 63 | }); 64 | } 65 | 66 | // Loading Indication 67 | plugin.loading = function(loading){ 68 | if ( loading ){ 69 | $(plugin.loadingIndicator).show(); 70 | return; 71 | } 72 | $(plugin.loadingIndicator).hide(); 73 | } 74 | 75 | return plugin.bindEvents(); 76 | } -------------------------------------------------------------------------------- /assets/js/lib/nestedpages.userprefs-reset.js: -------------------------------------------------------------------------------- 1 | var NestedPages = NestedPages || {}; 2 | 3 | /** 4 | * Reset User Preferences 5 | * @package Nested Pages 6 | * @author Kyle Phillips - https://github.com/kylephillips/wp-nested-pages 7 | */ 8 | NestedPages.UserPreferencesReset = function() 9 | { 10 | var plugin = this; 11 | var $ = jQuery; 12 | 13 | plugin.selectors = { 14 | resetButton : 'data-nestedpages-reset-user-prefs', 15 | resetForm : '.nestedpages-reset-user-prefs', 16 | formComplete : '.nestedpages-reset-user-prefs-complete' 17 | } 18 | 19 | plugin.bindEvents = function() 20 | { 21 | $(document).on('click', '[' + plugin.selectors.resetButton + ']', function(e){ 22 | e.preventDefault(); 23 | plugin.resetPreferences(); 24 | }); 25 | } 26 | 27 | plugin.resetPreferences = function() 28 | { 29 | plugin.loading(true); 30 | $.ajax({ 31 | url: NestedPages.jsData.ajaxurl, 32 | type: 'post', 33 | datatype: 'json', 34 | data: { 35 | action : NestedPages.formActions.resetUserPrefs, 36 | nonce : NestedPages.jsData.nonce 37 | }, 38 | success: function(data){ 39 | plugin.loading(false); 40 | $(plugin.selectors.resetForm).hide(); 41 | $(plugin.selectors.formComplete).show(); 42 | if ( data.status !== 'success' ){ 43 | console.log('There was an error clearing user preferences.'); 44 | } 45 | } 46 | }); 47 | } 48 | 49 | plugin.loading = function(loading) 50 | { 51 | if ( loading ){ 52 | $('[' + plugin.selectors.resetButton + ']').attr('disabled', true); 53 | return; 54 | } 55 | $('[' + plugin.selectors.resetButton + ']').removeAttr('disabled'); 56 | } 57 | 58 | return plugin.bindEvents(); 59 | } 60 | 61 | new NestedPages.UserPreferencesReset; -------------------------------------------------------------------------------- /assets/js/lib/nestedpages.wpml.js: -------------------------------------------------------------------------------- 1 | var NestedPages = NestedPages || {}; 2 | 3 | /** 4 | * WPML functionality 5 | * @package Nested Pages 6 | * @author Kyle Phillips - https://github.com/kylephillips/wp-nested-pages 7 | */ 8 | NestedPages.Wpml = function() 9 | { 10 | var plugin = this; 11 | var $ = jQuery; 12 | 13 | plugin.button = null; // The quick edit button with all the data-attributes for the post 14 | plugin.postData = null; // Object containing post data 15 | plugin.modal = null; // The modal element 16 | plugin.parent_li = null; // The post's nested pages list element 17 | plugin.formatter = new NestedPages.Formatter; 18 | 19 | plugin.selectors = { 20 | translationsBtn : 'data-nestedpages-translations', 21 | modal : 'data-np-wpml-translations-modal', 22 | title : 'data-wmpl-translation-title', 23 | table : 'data-np-wpml-translations-modal-table' 24 | } 25 | 26 | plugin.bindEvents = function() 27 | { 28 | if ( !nestedpages.wpml ) return; 29 | $(document).on('click', '[' + plugin.selectors.translationsBtn + ']', function(e){ 30 | e.preventDefault(); 31 | plugin.createTranslationsModal($(this)); 32 | }); 33 | } 34 | 35 | /** 36 | * Create the translations modal 37 | */ 38 | plugin.createTranslationsModal = function(button) 39 | { 40 | plugin.parent_li = $(button).parents('.action-buttons').closest(NestedPages.selectors.row).parent('li'); 41 | plugin.button = $(plugin.parent_li).find(NestedPages.selectors.quickEditOpen); 42 | plugin.postData = { 43 | id : $(plugin.button).attr('data-id'), 44 | title : $(plugin.button).attr('data-title'), 45 | slug : $(plugin.button).attr('data-slug') 46 | } 47 | plugin.modal = $('[' + plugin.selectors.modal + ']').clone(); 48 | 49 | if ( $(plugin.parent_li).children('ol').length > 0 ){ 50 | var child_ol = $(plugin.parent_li).children('ol'); 51 | $(plugin.modal).insertBefore(child_ol); 52 | } else { 53 | $(plugin.modal).appendTo(plugin.parent_li); 54 | } 55 | $(plugin.modal).find('[' + plugin.selectors.title + ']').text(plugin.postData.title); 56 | plugin.formatter.showQuickEdit(); 57 | $(plugin.modal).show(); 58 | plugin.getTranslationData(); 59 | } 60 | 61 | /** 62 | * Get the Translation Data for the Post 63 | */ 64 | plugin.getTranslationData = function() 65 | { 66 | $.ajax({ 67 | url: NestedPages.jsData.ajaxurl, 68 | type: 'post', 69 | datatype: 'json', 70 | data : { 71 | action : NestedPages.formActions.wpmlTranslations, 72 | post_id : plugin.postData.id, 73 | nonce : NestedPages.jsData.nonce 74 | }, 75 | success: function(data){ 76 | console.log(data); 77 | console.log(plugin.postData.id); 78 | if ( data.status === 'success' ){ 79 | plugin.populateModal(data.translations); 80 | } else { 81 | $(plugin.modal).find(NestedPages.selectors.quickEditErrorDiv).text(data.message).show(); 82 | plugin.toggleLoading(false); 83 | } 84 | } 85 | }); 86 | } 87 | 88 | /** 89 | * Open the Modal 90 | */ 91 | plugin.populateModal = function(translations) 92 | { 93 | var html = ''; 94 | $.each(translations, function(i, v){ 95 | var translation = translations[i]; 96 | html += ''; 97 | html += '' + translation.translated_name + ' ' + translation.translated_name + ''; 98 | html += ''; 99 | if ( translation.has_translation && translation.edit_link ){ 100 | html += '' + translation.translation.post_title + ' (' + nestedpages.edit + ')'; 101 | } else { 102 | html += '+ ' + nestedpages.add_translation + ''; 103 | } 104 | html += ''; 105 | html += ''; 106 | }); 107 | html += ''; 108 | $(plugin.modal).find('[' + plugin.selectors.table + ']').html(html); 109 | plugin.toggleLoading(false); 110 | } 111 | 112 | /** 113 | * Toggle the Loading State 114 | */ 115 | plugin.toggleLoading = function(loading) 116 | { 117 | if ( loading ){ 118 | $(plugin.modal).addClass('loading'); 119 | return; 120 | } 121 | $(plugin.modal).removeClass('loading'); 122 | } 123 | 124 | 125 | return plugin.bindEvents(); 126 | } -------------------------------------------------------------------------------- /assets/screenshot-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kylephillips/wp-nested-pages/5b5bb4acd518f9db55e4676b8a103605cc92499f/assets/screenshot-1.png -------------------------------------------------------------------------------- /assets/screenshot-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kylephillips/wp-nested-pages/5b5bb4acd518f9db55e4676b8a103605cc92499f/assets/screenshot-2.png -------------------------------------------------------------------------------- /assets/screenshot-3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kylephillips/wp-nested-pages/5b5bb4acd518f9db55e4676b8a103605cc92499f/assets/screenshot-3.gif -------------------------------------------------------------------------------- /assets/screenshot-4.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kylephillips/wp-nested-pages/5b5bb4acd518f9db55e4676b8a103605cc92499f/assets/screenshot-4.gif -------------------------------------------------------------------------------- /assets/screenshot-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kylephillips/wp-nested-pages/5b5bb4acd518f9db55e4676b8a103605cc92499f/assets/screenshot-5.png -------------------------------------------------------------------------------- /assets/screenshot-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kylephillips/wp-nested-pages/5b5bb4acd518f9db55e4676b8a103605cc92499f/assets/screenshot-6.png -------------------------------------------------------------------------------- /assets/screenshot-7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kylephillips/wp-nested-pages/5b5bb4acd518f9db55e4676b8a103605cc92499f/assets/screenshot-7.png -------------------------------------------------------------------------------- /assets/screenshot-8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kylephillips/wp-nested-pages/5b5bb4acd518f9db55e4676b8a103605cc92499f/assets/screenshot-8.png -------------------------------------------------------------------------------- /assets/screenshot-9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kylephillips/wp-nested-pages/5b5bb4acd518f9db55e4676b8a103605cc92499f/assets/screenshot-9.png -------------------------------------------------------------------------------- /assets/scss/_alerts.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Alerts 3 | // -------------------------------------------------- 4 | 5 | // error in quick edit form 6 | .np-quickedit-error { 7 | border-left: 4px solid #dd3d36; 8 | padding: 4px 0 4px 8px; 9 | margin-bottom: 10px; 10 | background-color: $gray-lighter; 11 | } 12 | 13 | .np-quickedit-info { 14 | border-left: 4px solid $blue; 15 | padding: 4px 0 4px 8px; 16 | margin-bottom: 10px; 17 | background-color: $gray-lighter; 18 | } -------------------------------------------------------------------------------- /assets/scss/_bulk-edit.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Bulk Edit Form 3 | // -------------------------------------------------- 4 | .nestedpages { 5 | .nestedpages-bulk-edit { 6 | display: none; 7 | background-color: $white; 8 | margin: 0; 9 | border: 1px solid $border; 10 | border-bottom: 0; 11 | h3 { 12 | padding: 1em; 13 | margin: 0; 14 | } 15 | 16 | .np-bulk-edit-link-info { 17 | padding: 0 1em; 18 | } 19 | 20 | .fields { 21 | padding: 1em; 22 | margin: 0; 23 | } 24 | 25 | .np-taxonomies { 26 | margin: 0; 27 | } 28 | 29 | .np-bulk-titles { 30 | @include unstyled; 31 | background-color: $gray-lighter; 32 | border: 1px solid $border-light; 33 | border-left: 0; 34 | border-right: 0; 35 | padding: .5em; 36 | li { 37 | padding: 0; 38 | margin: .5em 0 0 0; 39 | &:first-child { 40 | margin: 0; 41 | } 42 | } 43 | .np-remove { 44 | display: inline-block; 45 | background-color: $black; 46 | width: 22px; 47 | height: 22px; 48 | border-radius: 50%; 49 | text-align: center; 50 | text-decoration: none; 51 | color: $white; 52 | line-height: 18px; 53 | font-size: 1.5em; 54 | margin-right: .3em; 55 | &:hover { 56 | background-color: darken($white, 50%); 57 | } 58 | } 59 | } //.np-bulk-titles 60 | 61 | .np-taxonomies { 62 | display: block; 63 | padding: 1em; 64 | } 65 | 66 | .np-bulk-footer { 67 | padding: 10px; 68 | text-align: right; 69 | background-color: #404040; 70 | @include clearfix; 71 | .pull-left { 72 | float: left; 73 | } 74 | } 75 | 76 | } // .nestedpages-bulk-edit 77 | } // .nestedpages -------------------------------------------------------------------------------- /assets/scss/_buttons.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Buttons 3 | // -------------------------------------------------- 4 | .np-btn { 5 | text-decoration: none; 6 | color: #555; 7 | display: inline-block; 8 | background-color: #f7f7f7; 9 | border: 1px solid $border; 10 | padding: 5px 8px; 11 | transition: all 200ms ease; 12 | vertical-align: top; 13 | &:hover { 14 | background-color: $blue; 15 | color: $white; 16 | transition: all 200ms ease; 17 | border-color: $blue; 18 | } 19 | &:disabled, &.disabled { 20 | opacity: .6; 21 | cursor: not-allowed; 22 | } 23 | &.has-icon { 24 | padding: 0; 25 | } 26 | } 27 | 28 | .np-btn-half { 29 | float: left; 30 | width: 47%; 31 | padding: 3px 0px; 32 | text-align: center; 33 | &.btn-right { 34 | float: right; 35 | } 36 | } 37 | 38 | 39 | .np-btn-trash { 40 | background-color: $red; 41 | border-color: $red; 42 | color: $white; 43 | &:hover { 44 | background-color: darken($red, 15%); 45 | border-color: darken($red, 15%); 46 | } 47 | } -------------------------------------------------------------------------------- /assets/scss/_columns.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Columns 3 | // -------------------------------------------------- 4 | .nestedpages-columns-header { 5 | display: table; 6 | border-collapse: collapse; 7 | width: 100%; 8 | box-sizing: border-box; 9 | background-color: darken($white, 6%); 10 | border: 1px solid $border; 11 | border-bottom: 0; 12 | .cell { 13 | font-weight: bold; 14 | text-align: left; 15 | display: table-cell; 16 | padding: 1em .5em 1em 0; 17 | box-sizing: border-box; 18 | vertical-align: middle; 19 | } 20 | .toggle-spacer { 21 | span { 22 | display: block; 23 | width: 50px; 24 | } 25 | } 26 | .bulk-spacer { 27 | span { 28 | display: block; 29 | width: 33px; 30 | } 31 | } 32 | .thumbnail-spacer { 33 | span { 34 | display: block; 35 | width: 100%; 36 | } 37 | } 38 | .title { 39 | width: 30%; 40 | padding: 0 5px; 41 | } 42 | } 43 | 44 | // Post Rows 45 | .nestedpages-row-columns { 46 | display: table; 47 | width: 100%; 48 | border-collapse: collapse; 49 | box-sizing: border-box; 50 | .cell { 51 | text-align: left; 52 | display: table-cell; 53 | padding: 1em .5em 1em 0; 54 | box-sizing: border-box; 55 | vertical-align: middle; 56 | } 57 | .title-cell { 58 | width: 30%; 59 | position: relative; 60 | } 61 | .title-cell-spacer { 62 | position: absolute; 63 | display: block; 64 | width: 100%; 65 | } 66 | } 67 | 68 | @media (max-width:767px) 69 | { 70 | .nestedpages-row-columns .custom-column, 71 | .nestedpages-columns-header { 72 | display: none; 73 | } 74 | .nestedpages-row-columns .title-cell.cell { 75 | width: 100%; 76 | } 77 | } 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /assets/scss/_datepicker.scss: -------------------------------------------------------------------------------- 1 | // 2 | // JQuery UI Datepicker for Nested Pages 3 | // -------------------------------------------------- 4 | .nestedpages-datepicker.ui-datepicker { 5 | background-color: $white; 6 | font-size: 90%; 7 | box-shadow: 0px 0px 3px 0px rgba(0,0,0,.4); 8 | border-radius: 3px; 9 | padding: 0; 10 | width: auto; 11 | 12 | // Prev & Next 13 | .ui-datepicker-next, .ui-datepicker-prev { 14 | position: absolute; 15 | color: $white; 16 | top: 0px; 17 | right: 0px; 18 | cursor: pointer; 19 | display: block; 20 | width: 35px; 21 | height: 35px; 22 | font-size: 0; 23 | background: url('../images/datepicker-arrow-next.png'); 24 | background-position: center; 25 | opacity: 1; 26 | transition: opacity 200ms ease; 27 | &:hover { 28 | opacity: .5; 29 | transition: opacity 200ms ease; 30 | } 31 | } 32 | 33 | .ui-datepicker-calendar { 34 | margin: 0; 35 | } 36 | 37 | .ui-datepicker-prev { 38 | right: auto; 39 | left: 0px; 40 | background: url('../images/datepicker-arrow-prev.png'); 41 | } 42 | 43 | .ui-datepicker-header { 44 | padding: 0; 45 | } 46 | 47 | // Month & Year 48 | .ui-datepicker-title { 49 | background-color: darken($white, 90%); 50 | text-align: center; 51 | color: $white; 52 | height: 35px; 53 | line-height: 35px; 54 | border-radius: 3px 3px 0 0; 55 | margin: 0; 56 | } 57 | 58 | // Calendar Table 59 | .ui-datepicker-calendar { 60 | border-collapse: collapse; 61 | table { 62 | width: auto; 63 | } 64 | th, td { 65 | text-align: center; 66 | width: 30px; 67 | height: 30px; 68 | } 69 | // Days 70 | th { 71 | background-color: darken($white, 10%); 72 | text-transform: uppercase; 73 | font-size: 11px; 74 | letter-spacing: 1px; 75 | padding: 0; 76 | } 77 | td { 78 | border-top: 1px solid darken($white, 8%); 79 | border-left: 1px solid darken($white, 8%); 80 | padding: 0; 81 | &:first-child { 82 | border-left: 0; 83 | } 84 | } 85 | a { 86 | text-align: center; 87 | padding: 0; 88 | text-decoration: none; 89 | color: $black; 90 | display: block; 91 | background-color: $white; 92 | transition: all 200ms ease; 93 | width: 30px; 94 | height: 30px; 95 | line-height: 30px; 96 | &.ui-state-active { 97 | background-color: darken($white, 90%); 98 | color: $white; 99 | } 100 | &:hover { 101 | background-color: $black; 102 | color: $white; 103 | transition: all 200ms ease; 104 | } 105 | } 106 | } 107 | 108 | } -------------------------------------------------------------------------------- /assets/scss/_dropdowns.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Dropdowns 3 | // -------------------------------------------------- 4 | .nestedpages-dropdown { 5 | position: relative; 6 | z-index: 3; 7 | display: inline-block; 8 | } 9 | 10 | .nestedpages-dropdown-content { 11 | display: none; 12 | position: absolute; 13 | top: 40px; 14 | left: 0; 15 | z-index: 2; 16 | background-color: $white; 17 | width: 250px; 18 | text-align: left; 19 | border-radius: 2px; 20 | box-shadow: 1px 1px 3px 0px rgba($black, .4); 21 | &.padded { 22 | padding: 1em; 23 | } 24 | p { 25 | line-height: 1.4; 26 | margin: 0; 27 | } 28 | &.right { 29 | left: auto; 30 | right: 0; 31 | } 32 | ul { 33 | @include unstyled; 34 | @include clearfix; 35 | li { 36 | float: none; 37 | width: 100%; 38 | margin: 0; 39 | line-height: 1; 40 | a { 41 | display: block; 42 | color: $black; 43 | text-decoration: none; 44 | } 45 | &:first-child a { 46 | border: 0; 47 | } 48 | } 49 | } 50 | &.active { 51 | display: block; 52 | } 53 | } 54 | 55 | // .icon-arrow_drop_down { 56 | // width: 0; 57 | // height: 0; 58 | // display: inline-block; 59 | // border-left: 4px solid transparent; 60 | // border-right: 4px solid transparent; 61 | // border-top: 4px solid $black; 62 | // position: relative; 63 | // top: -2px; 64 | // } 65 | 66 | // .icon-arrow_drop_up { 67 | // width: 0; 68 | // height: 0; 69 | // position: relative; 70 | // top: -2px; 71 | // display: inline-block; 72 | // border-left: 4px solid transparent; 73 | // border-right: 4px solid transparent; 74 | // border-bottom: 4px solid $black; 75 | // } -------------------------------------------------------------------------------- /assets/scss/_expand-handle.scss: -------------------------------------------------------------------------------- 1 | // Expand/Contract Button 2 | .wppages-handle-expand { 3 | float: left; 4 | background-color: darken($white, 5%); 5 | font-size: 18px; 6 | width: 46px; 7 | height: 46px; 8 | margin-right: 5px; 9 | text-align: center; 10 | border-right: 1px solid $border; 11 | div { 12 | background-color: $white; 13 | border: 1px solid $border; 14 | width: 24px; 15 | height: 24px; 16 | line-height: 24px; 17 | border-radius: 15px; 18 | margin-top: 9px; 19 | cursor: pointer; 20 | transition: all 200ms ease; 21 | &:hover { 22 | background-color: $blue; 23 | border-color: $blue; 24 | color: $white; 25 | transition: all 200ms ease; 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /assets/scss/_form-modal.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Modal Forms 3 | // -------------------------------------------------- 4 | .np-modal-form { 5 | 6 | .form-interior { 7 | @include clearfix; 8 | background: url('../images/border.png') repeat-y; 9 | background-position: center; 10 | padding: 5px 0; 11 | } 12 | 13 | .form-control { 14 | @include clearfix; 15 | margin-bottom: 10px; 16 | } 17 | 18 | .checkbox { 19 | margin-bottom: 10px; 20 | } 21 | 22 | .left { 23 | float: left; 24 | width: 45%; 25 | } 26 | 27 | .right { 28 | float: right; 29 | width: 45%; 30 | padding-top: 18px; 31 | } 32 | 33 | label { 34 | display: block; 35 | } 36 | 37 | input[type="text"], 38 | select { 39 | width: 100%; 40 | } 41 | 42 | .buttons { 43 | clear: both; 44 | } 45 | 46 | } // .np-modal-form 47 | 48 | .np-new-child-form.in-modal { 49 | padding-bottom: 50px; 50 | } -------------------------------------------------------------------------------- /assets/scss/_inline-modals.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Inline Modal Forms 3 | // -------------------------------------------------- 4 | 5 | // Overlay 6 | .np-inline-overlay { 7 | position: fixed; 8 | width: 100%; 9 | height: 100%; 10 | background-color: rgba(0,0,0,.4); 11 | z-index: 998; 12 | top: 0; 13 | right: 0; 14 | bottom: 0; 15 | left: 0; 16 | opacity: 0; 17 | transition: opacity 200ms ease; 18 | &.active { 19 | opacity: 1; 20 | transition: opacity 200ms ease; 21 | } 22 | } 23 | 24 | .np-inline-modal { 25 | position: relative; 26 | z-index: 999; 27 | background-color: $white; 28 | border-radius: 0; 29 | box-shadow: 0px 0px 4px 0px rgba(0,0,0,.5); 30 | } -------------------------------------------------------------------------------- /assets/scss/_loading.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Loading Indication 3 | // -------------------------------------------------- 4 | #nested-loading { 5 | display: none; 6 | float: right; 7 | width: 30px; 8 | height: 30px; 9 | margin: 0px 20px 0 0; 10 | position: relative; 11 | svg { 12 | width: 30px; 13 | height: 30px; 14 | animation: nestedpagesRotate linear 1.25s infinite; 15 | position: absolute; 16 | left: 0; 17 | top: 0; 18 | line-height: .1; 19 | path { 20 | fill: darken($white, 40%); 21 | } 22 | } 23 | } 24 | 25 | .np-qe-loading, 26 | .np-default-loading, 27 | .np-menu-search-loading { 28 | display: none; 29 | float: right; 30 | width: 25px; 31 | height: 25px; 32 | margin: 2px 10px 0 0; 33 | position: relative; 34 | animation: nestedpagesRotate linear 1.25s infinite; 35 | svg { 36 | position: absolute; 37 | left: 0; 38 | top: 0; 39 | width: 25px; 40 | height: 25px; 41 | path { 42 | fill: $white; 43 | } 44 | } 45 | } 46 | 47 | .np-default-loading { 48 | float: none; 49 | margin: 0; 50 | svg path { 51 | fill: darken($white, 40%); 52 | } 53 | } 54 | 55 | @keyframes nestedpagesRotate { 56 | 0% { 57 | transform: rotate(0deg); 58 | transform-origin: center center; 59 | } 60 | 50% { 61 | transform: rotate(180deg); 62 | transform-origin: center center; 63 | } 64 | 100% { 65 | transform: rotate(360deg); 66 | transform-origin: center center; 67 | } 68 | } -------------------------------------------------------------------------------- /assets/scss/_mixins.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Mixins 3 | // -------------------------------------------------- 4 | @mixin clearfix { 5 | zoom: 1; 6 | &:before, 7 | &:after { 8 | content: " "; /* 1 */ 9 | display: table; /* 2 */ 10 | } 11 | &:after { 12 | clear: both; 13 | } 14 | } 15 | 16 | @mixin unstyled { 17 | list-style-type: none; 18 | margin: 0; 19 | padding: 0; 20 | } 21 | 22 | // WP Dashboard Styling 23 | @mixin wptable { 24 | // box-shadow: 0 1px 1px rgba(0,0,0,.06); 25 | border: 1px solid $border-light; 26 | border-radius: 3px; 27 | } 28 | 29 | @mixin translate($x, $y) { 30 | -webkit-transform: translate($x, $y); 31 | -ms-transform: translate($x, $y); // IE9 only 32 | -o-transform: translate($x, $y); 33 | transform: translate($x, $y); 34 | } 35 | 36 | @mixin transition-transform($transition...) { 37 | -webkit-transition: -webkit-transform $transition; 38 | -moz-transition: -moz-transform $transition; 39 | -o-transition: -o-transform $transition; 40 | transition: transform $transition; 41 | } -------------------------------------------------------------------------------- /assets/scss/_modals.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Modals 3 | // -------------------------------------------------- 4 | .nestedpages-modal-backdrop { 5 | position: fixed; 6 | width: 100%; 7 | height: 100%; 8 | background-color: rgba(0,0,0,.85); 9 | top: 0; 10 | left: 0; 11 | z-index: $z-index-modal-backdrop; 12 | visibility: hidden; 13 | opacity: 0; 14 | transition: all 500ms ease; 15 | &.active { 16 | visibility: visible; 17 | opacity: 1; 18 | transition: all 200ms ease; 19 | } 20 | } 21 | 22 | .nestedpages-modal-content { 23 | z-index: $z-index-modal-content; 24 | position: fixed; 25 | top: -200%; 26 | width: 90%; 27 | height: 80%; 28 | margin: 10px auto 0px auto; 29 | background-color: $white; 30 | left: 5%; 31 | transition: all 500ms ease; 32 | &.small { 33 | width: 400px; 34 | left: 50%; 35 | margin-left: -200px; 36 | } 37 | &.short { 38 | height: auto; 39 | } 40 | &.active { 41 | top: 50%; 42 | transition: all 200ms ease; 43 | transform: translateY(-50%); 44 | } 45 | h5 { 46 | font-size: 1.1em; 47 | } 48 | 49 | .modal-body { 50 | position: relative; 51 | padding: 10px; 52 | &.new-child { 53 | padding: 0; 54 | } 55 | } 56 | 57 | // Footer (for actions) 58 | .modal-footer { 59 | padding: 10px; 60 | text-align: right; 61 | background-color: #404040; 62 | @include clearfix; 63 | .modal-close { 64 | float: left; 65 | } 66 | } 67 | 68 | .modal-open .modal { 69 | overflow-x: hidden; 70 | overflow-y: auto; 71 | } 72 | 73 | // Actual modal 74 | .modal-content { 75 | position: relative; 76 | background-color: $white; 77 | box-shadow: 0 3px 9px rgba(0,0,0,.5); 78 | background-clip: padding-box; 79 | outline: 0; 80 | } 81 | } 82 | 83 | .nestedpages-modal-full { 84 | width: 94%; 85 | height: 94%; 86 | transform: none; 87 | left: 3%; 88 | &.active { 89 | top: 3%; 90 | } 91 | .modal-header { 92 | position: absolute; 93 | top: 0; 94 | left: 0; 95 | width: 100%; 96 | height: 50px; 97 | z-index: 4; 98 | @include clearfix; 99 | padding-left: .5em; 100 | } // .modal-header 101 | .modal-content-interior { 102 | padding-top: 50px; 103 | height: 100%; 104 | .modal-content-body { 105 | height: 100%; 106 | max-height: 100%; 107 | } 108 | } 109 | } // .modal-full 110 | 111 | .np-trash-modal { 112 | text-align: center; 113 | .modal-body { 114 | padding-bottom: 30px; 115 | } 116 | .button-primary { 117 | margin-left: 10px; 118 | } 119 | } 120 | 121 | .clone-modal { 122 | h3 { 123 | margin: 0; 124 | padding: 8px; 125 | background-color: $border-light; 126 | } 127 | .form-control { 128 | margin: 0 !important; 129 | padding: 5px 10px; 130 | } 131 | } -------------------------------------------------------------------------------- /assets/scss/_settings.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Settings styles 3 | // -------------------------------------------------- 4 | 5 | // Settings Table 6 | .nestedpages-settings-table { 7 | border: 1px solid darken($white, 15%); 8 | border-collapse: collapse; 9 | thead th { 10 | background-color: darken($white, 15%); 11 | padding: 5px; 12 | } 13 | tbody tr td { 14 | padding: 5px; 15 | border-left: 1px solid darken($white, 12%); 16 | &:first-child { 17 | border: 0; 18 | } 19 | } 20 | tbody tr:nth-child(odd) td { 21 | background-color: $white; 22 | } 23 | i { 24 | font-size: 20px; 25 | } 26 | .np-btn { 27 | cursor: pointer; 28 | text-decoration: none; 29 | color: #555; 30 | display: inline-block; 31 | background-color: #f7f7f7; 32 | border: 1px solid $border; 33 | padding: 5px 8px; 34 | transition: all 200ms ease; 35 | &:hover { 36 | background-color: $blue; 37 | color: $white; 38 | transition: all 200ms ease; 39 | border-color: $blue; 40 | } 41 | &:disabled, &.disabled { 42 | opacity: .6; 43 | cursor: not-allowed; 44 | } 45 | } 46 | .np-btn-trash { 47 | background-color: $red; 48 | border-color: $red; 49 | color: $white; 50 | &:hover { 51 | background-color: darken($red, 15%); 52 | border-color: darken($red, 15%); 53 | } 54 | } 55 | } 56 | 57 | .nestedpages-settings-general-wrapper { 58 | padding-top: 1em; 59 | display: flex; 60 | justify-content: space-between; 61 | align-items: flex-start; 62 | .nestedpages-settings-table { 63 | width: 67%; 64 | border: 0; 65 | .row { 66 | border-left: 1px solid $border; 67 | border-right: 1px solid $border; 68 | &.submit { 69 | border: 0; 70 | } 71 | } 72 | } 73 | .nestedpages-settings-support { 74 | width: 30%; 75 | background-color: $white; 76 | text-align: center; 77 | @include wptable; 78 | .inner { 79 | padding: 2em; 80 | } 81 | .or { 82 | margin: 2em 0; 83 | } 84 | } 85 | h3 { 86 | color: rgba(255,45,32,1); 87 | margin: 0 0 3px 0; 88 | font-size: 20px; 89 | } 90 | h4 { 91 | margin: 0; 92 | } 93 | .support-button { 94 | display: block; 95 | background-color: rgba(255,45,32,.1); 96 | border: 1px solid rgba(255,45,32,.15 ); 97 | text-align: center; 98 | color: $black; 99 | text-decoration: none; 100 | padding: 1em; 101 | border-radius: 4px; 102 | font-weight: bold; 103 | font-size: 16px; 104 | transition: all 200ms ease; 105 | svg { 106 | display: inline-block; 107 | vertical-align: middle; 108 | margin-right: 8px; 109 | .heart { 110 | fill: #ff2d20; 111 | } 112 | } 113 | &:hover { 114 | background-color: rgba(255,45,32,1); 115 | transition: all 200ms ease; 116 | color: $white; 117 | svg .heart { 118 | fill: $white; 119 | } 120 | } 121 | } // .support-button 122 | } 123 | 124 | @media (max-width: 768px) 125 | { 126 | .nestedpages-settings-general-wrapper { 127 | display: block; 128 | .nestedpages-settings-table, 129 | .nestedpages-settings-support { 130 | width: 100%; 131 | } 132 | } 133 | } -------------------------------------------------------------------------------- /assets/scss/_tabs.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Tabs 3 | // -------------------------------------------------- 4 | .np-tab-pane { 5 | display: none; 6 | } 7 | .ul.np-tabs, 8 | .nestedpages ul.np-tabs { 9 | background: transparent; 10 | text-align: left; 11 | @include unstyled; 12 | border-bottom: 1px solid darken($white, 20%); 13 | margin-bottom: 10px; 14 | font-size: 0; 15 | line-height: 0; 16 | li { 17 | font-size: 13px; 18 | line-height: 1.4em; 19 | display: inline-block; 20 | margin: 0 0 0 6px; 21 | border: 0; 22 | a { 23 | background-color: darken($white, 10%); 24 | display: block; 25 | text-decoration: none; 26 | padding: 8px 15px; 27 | color: $black; 28 | border: 1px solid darken($white, 20%); 29 | border-bottom: 0; 30 | &:hover { 31 | background-color: $white; 32 | } 33 | &:focus, &:active { 34 | outline: none; 35 | box-shadow: none; 36 | } 37 | } 38 | &.active a { 39 | font-weight: bold; 40 | position: relative; 41 | color: $black; 42 | background-color: $white; 43 | &:after { 44 | position: absolute; 45 | width: 100%; 46 | height: 2px; 47 | content: ''; 48 | background-color: $white; 49 | left: 0; 50 | bottom: -1px; 51 | } 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /assets/scss/_toggle-all.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Toggle Visibility of pages 3 | // -------------------------------------------------- 4 | 5 | .nestedpages-listing-title { 6 | @include clearfix; 7 | } 8 | 9 | // Clear fix for admin notices 10 | .nestedpages .updated { 11 | clear: both; 12 | } 13 | 14 | // Toggle Button 15 | .nestedpages-toggleall.np-btn { 16 | float: right; 17 | } 18 | 19 | // Publish Toggle 20 | .np-toggle-publish { 21 | color: darken($white, 40%); 22 | &.active { 23 | color: $black; 24 | font-weight: bold; 25 | } 26 | } 27 | 28 | // Sync Menu 29 | .np-sync-menu-cont { 30 | float: right; 31 | margin: 0px 15px 0px 0px; 32 | button { 33 | position: relative; 34 | top: -5px; 35 | padding: 5px 8px; 36 | } 37 | label { 38 | position: relative; 39 | top: 4px; 40 | } 41 | } 42 | 43 | .nestedpages-top-toggles { 44 | float: right; 45 | @include clearfix; 46 | padding-top: 10px; 47 | } -------------------------------------------------------------------------------- /assets/scss/_top-tools.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Links/Tools Above the Page Listing 3 | // -------------------------------------------------- 4 | 5 | .nestedpages-tools { 6 | @include clearfix; 7 | clear: both; 8 | clear: both; 9 | margin: 20px 0px 5px 0px; 10 | .subsubsub { 11 | margin: 0; 12 | } 13 | .np-tools-primary { 14 | clear: left; 15 | padding-top: 8px; 16 | } 17 | .select { 18 | float: left; 19 | margin-left: 5px; 20 | &:first-child { 21 | margin-left: 0; 22 | } 23 | } 24 | .np-tools-sort { 25 | float: left; 26 | } 27 | .np-tools-search { 28 | float: right; 29 | } 30 | } -------------------------------------------------------------------------------- /assets/scss/_variables.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Variables 3 | // -------------------------------------------------- 4 | $black: #333; 5 | $white: #ffffff; 6 | $gray-lighter: #f9f9f9; // WP BG 7 | 8 | $border: #e1e1e1; 9 | $border-light: #e5e5e5; 10 | 11 | $blue: #0074a2; // WP Blue 12 | $red: #e14d43; // WP red 13 | 14 | $z-index-modal-backdrop: 9999; 15 | $z-index-modal-content: 99999; -------------------------------------------------------------------------------- /assets/scss/_wpml-translation-modal.scss: -------------------------------------------------------------------------------- 1 | // 2 | // WPML Inline Modal for displaying translations for a post 3 | // -------------------------------------------------- 4 | .np-wpml-translations-modal { 5 | .np-qe-loading { 6 | margin: 1em 0; 7 | text-align: center; 8 | float: left; 9 | svg path { 10 | fill: darken($white, 40%); 11 | } 12 | } 13 | &.loading { 14 | .np-translations-table { 15 | display: none; 16 | } 17 | .np-qe-loading { 18 | display: block; 19 | } 20 | } 21 | .np-translations-table { 22 | display: table; 23 | border-collapse: collapse; 24 | width: 100%; 25 | tr { 26 | td { 27 | padding: 10px; 28 | border: 1px solid $border; 29 | &:first-child { 30 | border-left: 0; 31 | padding-left: 0; 32 | } 33 | &:last-child { 34 | border-right: 0; 35 | } 36 | } 37 | &:last-child td { 38 | border-bottom: 0; 39 | } 40 | } // tr 41 | } // .np-translations-table 42 | } // .np-wpml-translations-modal -------------------------------------------------------------------------------- /assets/scss/nestedpages.scss: -------------------------------------------------------------------------------- 1 | // 2 | // WP Pages Styles 3 | // -------------------------------------------------- 4 | @import "_variables.scss"; 5 | @import "_mixins.scss"; 6 | 7 | @import "_buttons.scss"; 8 | @import "_alerts.scss"; 9 | @import "_modals.scss"; 10 | @import "_inline-modals.scss"; 11 | @import "_toggle-all.scss"; 12 | @import "_tabs.scss"; 13 | @import "_datepicker.scss"; 14 | @import "_dropdowns.scss"; 15 | @import "_loading.scss"; 16 | 17 | @import "_top-tools.scss"; 18 | @import "_expand-handle.scss"; 19 | @import "_page-listing.scss"; 20 | @import "_quick-edit.scss"; 21 | @import "_form-modal.scss"; 22 | @import "_link-form.scss"; 23 | @import "_bulk-edit.scss"; 24 | @import "_columns.scss"; 25 | 26 | @import "_wpml-translation-modal.scss"; 27 | 28 | @import "_settings.scss"; 29 | @import "_settings-tables.scss"; 30 | @import "_settings-admin-nav-menu.scss"; 31 | 32 | @import "_dark.scss"; -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kylephillips/nestedpages", 3 | "description": "Nested Pages for WordPress", 4 | "keywords": ["wordpress", "plugin", "nested pages"], 5 | "homepage": "https://nestedpages.com", 6 | "license": "GPL", 7 | "authors": [ 8 | { 9 | "name": "Kyle Phillips", 10 | "homepage": "https://github.com/kylephillips" 11 | } 12 | ], 13 | "type": "wordpress-plugin", 14 | "require": { 15 | "php": ">=5.4.0", 16 | "composer/installers": "v1.0.6" 17 | }, 18 | "autoload": { 19 | "psr-4" : { 20 | "NestedPages\\" : "app/" 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var sass = require('gulp-sass')(require('sass')); 3 | var autoprefix = require('gulp-autoprefixer'); 4 | var livereload = require('gulp-livereload'); 5 | var minifycss = require('gulp-minify-css'); 6 | var concat = require('gulp-concat'); 7 | var uglify = require('gulp-uglify'); 8 | 9 | // Paths 10 | var scss = 'assets/scss/**/*'; 11 | var css = 'assets/css/'; 12 | 13 | var js_source = [ 14 | 'assets/js/lib/nestedpages.formatter.js', 15 | 'assets/js/lib/nestedpages.dropdowns.js', 16 | 'assets/js/lib/nestedpages.modals.js', 17 | 'assets/js/lib/nestedpages.check-all.js', 18 | 'assets/js/lib/nestedpages.bulk-actions.js', 19 | 'assets/js/lib/nestedpages.hidden-item-count.js', 20 | 'assets/js/lib/nestedpages.menu-toggle.js', 21 | 'assets/js/lib/nestedpages.page-toggle.js', 22 | 'assets/js/lib/nestedpages.nesting.js', 23 | 'assets/js/lib/nestedpages.sync-menu-setting.js', 24 | 'assets/js/lib/nestedpages.new-post.js', 25 | 'assets/js/lib/nestedpages.quickedit-post.js', 26 | 'assets/js/lib/nestedpages.quickedit-link.js', 27 | 'assets/js/lib/nestedpages.clone.js', 28 | 'assets/js/lib/nestedpages.tabs.js', 29 | 'assets/js/lib/nestedpages-factory.js', 30 | 'assets/js/lib/nestedpages.menu-links.js', 31 | 'assets/js/lib/nestedpages.menu-search.js', 32 | 'assets/js/lib/nestedpages.trash.js', 33 | 'assets/js/lib/nestedpages.confirm-delete.js', 34 | 'assets/js/lib/nestedpages.manual-sync.js', 35 | 'assets/js/lib/nestedpages.post-search.js', 36 | 'assets/js/lib/nestedpages.move-post.js', 37 | 'assets/js/lib/nestedpages.trash-with-children.js', 38 | 'assets/js/lib/nestedpages.wpml.js' 39 | ]; 40 | var js_source_settings = [ 41 | 'assets/js/lib/nestedpages.settings-reset.js', 42 | 'assets/js/lib/nestedpages.userprefs-reset.js', 43 | 'assets/js/lib/nestedpages.settings-admin-customization.js', 44 | 'assets/js/lib/nestedpages.settings.js' 45 | ]; 46 | var js_compiled = 'assets/js/'; 47 | 48 | /** 49 | * Minify the admin styles and output 50 | */ 51 | var styles = function(){ 52 | return gulp.src(scss) 53 | .pipe(sass({sourceComments: 'map', sourceMap: 'sass', style: 'compact'})) 54 | .pipe(autoprefix('last 5 version')) 55 | .pipe(minifycss({keepBreaks: false})) 56 | .pipe(gulp.dest(css)) 57 | .pipe(livereload()); 58 | } 59 | 60 | /** 61 | * Concatenate and minify scripts 62 | */ 63 | var scripts = function(){ 64 | return gulp.src(js_source) 65 | .pipe(concat('nestedpages.min.js')) 66 | .pipe(uglify()) 67 | .pipe(gulp.dest(js_compiled)); 68 | }; 69 | 70 | /** 71 | * Output unminified JS for dev environment 72 | */ 73 | var scripts_dev = function(){ 74 | return gulp.src(js_source) 75 | .pipe(concat('nestedpages.js')) 76 | .pipe(uglify()) 77 | .pipe(gulp.dest(js_compiled)); 78 | }; 79 | 80 | /** 81 | * Output the settings JS and output 82 | */ 83 | var scripts_settings = function(){ 84 | return gulp.src(js_source_settings) 85 | .pipe(concat('nestedpages.settings.min.js')) 86 | .pipe(uglify()) 87 | .pipe(gulp.dest(js_compiled)); 88 | }; 89 | 90 | /** 91 | * Watch Task 92 | */ 93 | gulp.task('watch', function(){ 94 | livereload.listen(); 95 | gulp.watch(scss, gulp.series(styles)); 96 | gulp.watch(js_source, gulp.series(scripts, scripts_dev)); 97 | gulp.watch(js_source_settings, gulp.series(scripts_settings)); 98 | }); 99 | 100 | /** 101 | * Default 102 | */ 103 | gulp.task('default', gulp.series(styles, scripts, scripts_dev, scripts_settings, 'watch')); -------------------------------------------------------------------------------- /languages/index.php: -------------------------------------------------------------------------------- 1 | The Nested Pages plugin requires'.$flag.' version '.$version.' or greater.

','Plugin Activation Error', array( 'response'=>200, 'back_link'=>TRUE ) ); 48 | } 49 | 50 | if( !class_exists('Bootstrap') ) : 51 | define('NESTEDPAGES_DIR', __DIR__); 52 | define('NESTEDPAGES_URI', __FILE__); 53 | 54 | nestedpages_check_versions(); 55 | require_once(NESTEDPAGES_DIR . '/vendor/autoload.php'); 56 | require_once(NESTEDPAGES_DIR . '/app/NestedPages.php'); 57 | NestedPages::init(); 58 | endif; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "devDependencies": { 3 | "gulp": "4.0.2", 4 | "gulp-autoprefixer": "7.0.1", 5 | "gulp-concat": "^2.6.1", 6 | "gulp-livereload": "4.0.2", 7 | "gulp-minify-css": "1.2.4", 8 | "gulp-sass": "5.1.0", 9 | "gulp-uglify": "3.0.2", 10 | "gulp-watch": "5.0.1", 11 | "pump": "3.0.0", 12 | "sass": "^1.70.0" 13 | } 14 | } 15 | --------------------------------------------------------------------------------