PluginProbe
Auto Submenu / 1.0.1
Auto Submenu v1.0.1
1.0.7 trunk 0.1 0.2 0.3.0 0.3.1 0.3.2 1.0.0 1.0.1 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6
auto-submenu / auto-submenu.php

auto-submenu.php in Auto Submenu 1.0.1, at auto-submenu.php

322 lines 11.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /*
3 * Plugin Name: Auto Submenu - Free
4 * Plugin URI: https://wordpress.telodelic.nl/auto-submenu
5 * Description: Dynamic menus: Add a page to your menu and then let WordPress automatically add the child pages.
6 * Version: 1.0.1
7 * Author: Diana van de Laarschot
8 * Author URI: https://wordpress.telodelic.nl
9 * License: GPL v2 or later
10 * License URI: https://www.gnu.org/licenses/gpl-2.0.html
11 * Requires at least: 5.4
12 * Tested up to: 6.4
13 * Requires PHP: 7.4
14 * Text Domain: auto-submenu
15 */
16
17 /* Copyright 2024 Diana van de Laarschot (email : mail@telodelic.nl)
18
19 This program is free software; you can redistribute it and/or modify
20 it under the terms of the GNU General Public License, version 2, as
21 published by the Free Software Foundation.
22
23 This program is distributed in the hope that it will be useful,
24 but WITHOUT ANY WARRANTY; without even the implied warranty of
25 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26 GNU General Public License for more details.
27
28 You should have received a copy of the GNU General Public License
29 along with this program; if not, write to the Free Software
30 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
31 */
32
33 require('src/asm-functions.php');
34
35 class ASM_Manager_Free
36 {
37 const OPTION_NAME = 'asm_options';
38 const VERSION = '1.0.0';
39 const CSS_VERSION = '1.0.0';
40 const JS_VERSION = '1.0.0';
41
42 protected $options = null;
43 protected $defaults = array('version' => self::VERSION);
44
45 /**
46 * Constructor
47 */
48 function __construct()
49 {
50 // For use in Appearance > Menus and Theme > Customize:
51 add_filter('gettext', array(&$this, 'asm_gettext'), 10, 3);
52 add_action('publish_page', array(&$this, 'asm_publish_page')); // Checkbox: Automatically add new top-level and all its child pages to this menu (Auto Submenu)
53
54 // For use via Block Editor:
55 add_action('enqueue_block_editor_assets', array(&$this, 'asm_enqueue_block_editor_assets_js'));
56 add_action('block_core_navigation_render_inner_blocks', array(&$this, 'asm_block_core_navigation_render_inner_blocks'));
57
58 // Add button to plugin description
59 add_filter('plugin_row_meta', array(&$this, 'asm_plugin_row_meta'), 10, 2);
60
61 // Auto-update if a new plugin version is available via central wordpress.org repository
62 add_filter('auto_update_plugin', array(&$this, 'asm_auto_update_plugin'), 10, 2);
63 } // function
64
65 function ASM_Manager_Free()
66 {
67 $this->get_options();
68 $this->__construct();
69 } // function
70
71 function asm_block_core_navigation_render_inner_blocks_recursive(&$result_parent, &$original_parent, &$original_item, &$taxonomies)
72 {
73 // We need to work on the inner array. (Array methods will throw if it's a WP_Block)
74 if (is_a($original_item, 'WP_Block')) {
75 $original_item = $original_item->parsed_block;
76 }
77 $result_item = array_merge($original_item, array("innerBlocks" => array()));
78 if (array_key_exists("attrs", $original_item)) {
79 $get_attribute_value = fn($i, $k, $def) => is_array($i) && array_key_exists("attrs", $i) && array_key_exists($k, $i["attrs"]) ? $i["attrs"][$k] : $def;
80 // Augment page object with a list of its posts: Append posts to $result
81 if ($get_attribute_value($original_item, "asm_unfold", false)) {
82 $id = $get_attribute_value($original_item, "id", null);
83 $asm_item_depth = $get_attribute_value($original_item, "asm_item_depth", 10);
84 $asm_item_depth = ($asm_item_depth <= 0 || $asm_item_depth >= 3) ? 3 : $asm_item_depth;
85
86 // Add child pages as submenu under page
87 (new ASM_Functions_Free())->asm_block_editor_add_submenu_child_pages($result_parent, $original_item, $id, $asm_item_depth);
88 } else {
89 // Append to correct parent
90 $original_parent["innerBlocks"][] = &$result_item;
91 }
92
93 if (is_array($original_item["innerBlocks"])) {
94 foreach ($original_item["innerBlocks"] as $key => &$innerBlock) {
95 $this->asm_block_core_navigation_render_inner_blocks_recursive($result_item, $original_item, $innerBlock, $taxonomies);
96 }
97 }
98 }
99 }
100
101 // Workaround. Dropdown carets are not generated for parent items
102 // Reported here: https://core.trac.wordpress.org/ticket/60572#ticket
103 function decorate_block_recursive(&$menu_item)
104 {
105 $menu_item = new WP_Block($menu_item, array("showSubmenuIcon" => 1)); // This is the actual fix, 'activate' the context attribute to make WordPress honor it.
106
107 if (count($menu_item->parsed_block["innerBlocks"]) > 0) {
108 foreach ($menu_item->parsed_block["innerBlocks"] as $key => &$innerBlock) {
109 $this->decorate_block_recursive($innerBlock);
110 }
111 }
112 }
113
114 function asm_block_core_navigation_render_inner_blocks($items)
115 {
116 $this->get_options();
117
118 $result = array("innerBlocks" => array());
119
120 $taxonomies = get_taxonomies(array('show_in_nav_menus' => true), 'objects');
121 foreach ($items as $key => $item) {
122 $this->asm_block_core_navigation_render_inner_blocks_recursive($result, $result, $item->parsed_block, $taxonomies);
123 }
124
125 // Workaround. Dropdown carets are not generated for parent items
126 // Reported here: https://core.trac.wordpress.org/ticket/60572#ticket
127 foreach ($result["innerBlocks"] as $key => &$innerBlock) {
128 $this->decorate_block_recursive($innerBlock);
129 }
130
131 return new WP_Block_List($result["innerBlocks"]);
132 }
133
134 function asm_auto_update_plugin($update, $item)
135 {
136 return($item->slug == 'auto-submenu') ? true : $update;
137 } // function
138
139 private function get_options()
140 {
141 // already did the checks
142 if (isset($this->options)) {
143 return $this->options;
144 }
145
146 // first call, get the options
147 $options = get_option(self::OPTION_NAME);
148
149 // options exist
150 if ($options !== false) {
151 $new_version = version_compare($options['version'], self::VERSION, '<');
152 $desync = array_diff_key($this->defaults, $options) !== array_diff_key($options, $this->defaults);
153
154 // update options if version changed, or we have missing/extra (out of sync) option entries
155 if ($new_version || $desync) {
156 $new_options = array();
157
158 // check for new options and set defaults if necessary
159 foreach ($this->defaults as $option => $value) {
160 $new_options[$option] = isset($options[$option]) ? $options[$option] : $value;
161 }
162
163 // update version info
164 $new_options['version'] = self::VERSION;
165
166 update_option(self::OPTION_NAME, $new_options);
167 $this->options = $new_options;
168 } else // no update required
169 {
170 $this->options = $options;
171 }
172 } else // either new install or version from before versioning existed
173 {
174 update_option(self::OPTION_NAME, $this->defaults);
175 $this->options = $this->defaults;
176 }
177
178 return $this->options;
179 }
180
181 static function asm_uninstall()
182 {
183 // We're uninstalling, so delete all custom fields on nav_menu_items that the ASM plugin added
184 // Unless the premium version is active
185 $installed_plugins = get_plugins();
186 if (!(array_key_exists('auto-submenu-premium', $installed_plugins) || in_array('auto-submenu-premium', $installed_plugins, true))) {
187 // Premium version not installed, safe to delete all fields
188 delete_metadata('nav_menu_item', null, '_asm-unfold', '', true);
189 delete_metadata('nav_menu_item', null, '_asm-orderby', '', true);
190 delete_metadata('nav_menu_item', null, '_asm-order', '', true);
191 delete_metadata('nav_menu_item', null, '_asm-item-depth', '', true);
192 delete_metadata('nav_menu_item', null, '_asm-item-titles', '', true);
193 }
194 } // function
195
196 /*
197 * Add JS for ASM fields site-editor.php (Appearance > Editor, the block editor)
198 */
199 function asm_enqueue_block_editor_assets_js( /* no $hook parameter in this case */)
200 {
201 wp_enqueue_script(
202 'asm_enqueue_block_editor_assets_core_navigation_link_js',
203 plugins_url('src/blocks/asm_core_navigation_link/build/index.js', __FILE__),
204 ['wp-blocks'],
205 self::JS_VERSION,
206 true
207 );
208 } // function
209
210 function asm_gettext($translated, $original, $textdomain)
211 {
212 // Some users expect to see a checkbox for this option.
213 // Change the text of the checkbox, clearly showing that the plugin is active and the functionality behaves differently now.
214 // https://wordpress.org/support/topic/no-automatically-add-new-child-pages-option-in-menus-when-plugin-activated/
215 if ('Automatically add new top-level pages to this menu' == $translated) {
216 $translated = 'When publishing a top-level page, automatically add it to this menu. When publishing a child page, add it if the parent is in the menu (Auto Submenu)';
217 }
218
219 return $translated;
220 }
221
222 /**
223 * When publishing a top-level page or child page, automatically add it to this menu (Auto Submenu)
224 */
225 function asm_publish_page($post_id)
226 {
227 // Theme supports custom menus?
228 if (!current_theme_supports('menus')) {
229 return;
230 }
231
232 // Published page has parent?
233 $post = get_post( $post_id );
234 if ( ! $post->post_parent ) {
235 return;
236 }
237
238 // Get menus with auto_add enabled
239 $auto_add = get_option('nav_menu_options');
240 if (empty($auto_add) || !is_array($auto_add) || !isset($auto_add['auto_add'])) {
241 return;
242 }
243 $auto_add = $auto_add['auto_add'];
244 if (empty($auto_add) || !is_array($auto_add)) {
245 return;
246 }
247
248 // Loop through the menus to find page parent
249 foreach ($auto_add as $menu_id) {
250 $menu_items = wp_get_nav_menu_items($menu_id, array('post_status' => 'publish,draft'));
251 if (!is_array($menu_items)) {
252 continue;
253 }
254
255 // Assumption of current implementation: Top level pages are added to the menu only once.
256 $menu_child = NULL;
257 $menu_parent = NULL;
258
259 // Find the child menu item and the parent menu item for this child page
260 foreach ($menu_items as $menu_item) {
261 if ($menu_item->object_id == $post->ID) {
262 $menu_child = $menu_item;
263 }
264 if ($menu_item->object_id == $post->post_parent) {
265 $menu_parent = $menu_item;
266 }
267 }
268
269 // Parent not in menu, abort.
270 if ( !$menu_parent ){
271 break;
272 }
273
274 if ( $menu_child ) {
275 // Is child menu item already under correct parent? (If so, do nothing)
276 if ( $menu_child->menu_item_parent != $menu_parent->ID ) {
277 // Update the existing child menu item, move to correct parent
278 wp_update_nav_menu_item( $menu_id, $menu_child->db_id, array(
279 'menu-item-position' => $post->menu_order,
280 'menu-item-object-id' => $post->ID,
281 'menu-item-object' => $post->post_type,
282 'menu-item-parent-id' => $menu_parent->ID,
283 'menu-item-type' => 'post_type',
284 'menu-item-status' => 'publish'
285 ) );
286 }
287 } else {
288 // No menu item yet. Add new menu item for child page
289 wp_update_nav_menu_item( $menu_id, 0, array(
290 'menu-item-position' => $post->menu_order,
291 'menu-item-object-id' => $post->ID,
292 'menu-item-object' => $post->post_type,
293 'menu-item-parent-id' => $menu_parent->ID,
294 'menu-item-type' => 'post_type',
295 'menu-item-status' => 'publish'
296 ) );
297 }
298 }
299 }
300
301 /*
302 * Add a button to the plugin description on the plugins page
303 */
304 function asm_plugin_row_meta($links, $file)
305 {
306 if (plugin_basename(__FILE__) == $file) {
307 $row_meta = array(
308 'docs' => '<a href="' . esc_url('https://wordpress.telodelic.nl/auto-submenu/') . '" target="_blank" aria-label="' . esc_attr__('Go Premium', 'auto-submenu') . '" style="color:green;">' . esc_html__('Go Premium', 'auto-submenu') . '</a>'
309 );
310 return array_merge($links, $row_meta);
311 }
312
313 return $links;
314 }
315 }
316
317 $asm_manager_free = new ASM_Manager_Free();
318
319 // Register the uninstall hook. Should be done after the class has been defined.
320 register_uninstall_hook(__FILE__, array('ASM_Manager_Free', 'asm_uninstall'));
321
322 ?>