PluginProbe
Bulk Menu Edit / trunk
Bulk Menu Edit vtrunk
trunk 1.3.1 1.3.2
bulk-menu-edit / bulk-menu-edit.php

bulk-menu-edit.php in Bulk Menu Edit trunk, at bulk-menu-edit.php

205 lines 6.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Plugin Name: Bulk Menu Edit
4 * Plugin URI: https://wordpress.org/plugins/bulk-menu-edit/
5 * Description: Remove multiple menu items in one single click
6 * Version: 1.3.3
7 * Tags: menu, edit, bulk, items
8 * Requires at least: 5.0 or higher
9 * Requires PHP: 5.6
10 * Tested up to: 7.0
11 * Author: M . Code
12 * Text Domain: bulk-menu-edit
13 * Domain Path: /languages
14 * License: GPL v2 or later
15 * License URI: https://www.gnu.org/licenses/gpl-2.0.html
16 * Contributors: M . Code
17 * Donate link: https://ko-fi.com/devloper
18 */
19
20 // Exit if accessed directly
21 if (!defined('ABSPATH')) {
22 exit;
23 }
24
25 if (!class_exists('BulkMenuEdit')):
26 class BulkMenuEdit
27 {
28 private $rate_limit_key = 'bme_last_request_';
29 private static $instance = null;
30
31 public static function get_instance()
32 {
33 if (null === self::$instance) {
34 self::$instance = new self();
35 }
36 return self::$instance;
37 }
38
39 public function __construct()
40 {
41 if (!defined('BME_ASSETS_URL')) {
42 define('BME_ASSETS_URL', plugin_dir_url(__FILE__) . 'assets/');
43 }
44 add_action('admin_enqueue_scripts', array($this, 'bme_enqueue_files'));
45 add_action('wp_ajax_remove_menu_items', array($this, 'bme_ajax_call'));
46
47 }
48
49 private function check_rate_limit()
50 {
51 $user_id = get_current_user_id();
52 if (!$user_id) {
53 return false;
54 }
55
56 $key = $this->rate_limit_key . $user_id;
57 $attempts = (int) get_transient($key . '_count') ?: 0;
58 $last_request = get_transient($key);
59
60 if ($last_request !== false) {
61 $time_passed = time() - $last_request;
62 if ($time_passed < 2 || $attempts > 10) {
63 return false;
64 }
65 }
66
67 set_transient($key, time(), 60);
68 set_transient($key . '_count', $attempts + 1, 60);
69 return true;
70 }
71
72 private function verify_menu_item_ownership($menu_item_id)
73 {
74 if (!current_user_can('edit_theme_options')) {
75 return false;
76 }
77
78 $menu_item = get_post($menu_item_id);
79 if (!$menu_item || $menu_item->post_type !== 'nav_menu_item') {
80 return false;
81 }
82
83 $menu_id = wp_get_post_terms($menu_item_id, 'nav_menu', array('fields' => 'ids'));
84 if (empty($menu_id)) {
85 return false;
86 }
87
88 $menu = wp_get_nav_menu_object($menu_id[0]);
89 return $menu && current_user_can('edit_theme_options');
90 }
91
92 public function bme_enqueue_files()
93 {
94 $screen = get_current_screen();
95 if (!$screen || $screen->base !== 'nav-menus') {
96 return;
97 }
98
99 if (!current_user_can('edit_theme_options')) {
100 return;
101 }
102
103 $text_domain = 'bulk-menu-edit';
104 $version = '1.3';
105
106 wp_register_script('bulk-menu-js', BME_ASSETS_URL . 'bulk-menu.js', array('jquery', 'wp-i18n'), $version, true);
107 wp_set_script_translations('bulk-menu-js', $text_domain, plugin_dir_path(__FILE__) . 'languages');
108 wp_enqueue_script('bulk-menu-js');
109
110 wp_enqueue_style('bulk-menu-css', BME_ASSETS_URL . 'bulk-menu.css', array(), $version, 'all');
111
112 wp_localize_script('bulk-menu-js', 'bulkMenuEdit', array(
113 'ajaxurl' => esc_url_raw(admin_url('admin-ajax.php')),
114 'nonce' => wp_create_nonce('bulk_menu_edit_nonce')
115 ));
116 }
117
118 public function bme_ajax_call()
119 {
120 try {
121 if (!check_ajax_referer('bulk_menu_edit_nonce', 'nonce', false)) {
122 wp_send_json_error('Invalid security token');
123 return;
124 }
125
126 if (!current_user_can('edit_theme_options')) {
127 wp_send_json_error('Insufficient permissions');
128 return;
129 }
130
131 if (!$this->check_rate_limit()) {
132 wp_send_json_error('Please wait before making another request');
133 return;
134 }
135
136 if (!isset($_POST['data']) || !is_array($_POST['data'])) {
137 wp_send_json_error('Invalid data format');
138 return;
139 }
140
141 $menu_items = $_POST['data'];
142 global $wpdb;
143
144 $wpdb->query('START TRANSACTION');
145
146 try {
147 if (empty($_POST['data'])) {
148 throw new Exception('No menu items provided');
149 }
150
151 $deleted_items = array();
152 $skipped_items = array();
153
154 foreach ($menu_items as $data) {
155 if (!isset($data['value']) || !is_string((string) $data['value'])) {
156 $skipped_items[] = 'invalid_format';
157 continue;
158 }
159
160 $menu_item_id = filter_var($data['value'], FILTER_VALIDATE_INT);
161 if ($menu_item_id === false || $menu_item_id <= 0) {
162 $skipped_items[] = $data['value'];
163 continue;
164 }
165
166 if (!$this->verify_menu_item_ownership($menu_item_id)) {
167 $skipped_items[] = $menu_item_id;
168 continue;
169 }
170
171 $deleted_items[] = $menu_item_id;
172 }
173
174 foreach ($deleted_items as $item_id) {
175 $result = wp_delete_post($item_id, true);
176 if (!$result) {
177 throw new Exception(sprintf('Failed to delete menu item %d', $item_id));
178 }
179 }
180
181 $wpdb->query('COMMIT');
182 wp_send_json_success(array(
183 'message' => 'Menu items deleted successfully',
184 'deleted_count' => count($deleted_items),
185 'skipped_count' => count($skipped_items)
186 ));
187
188 } catch (Exception $e) {
189 $wpdb->query('ROLLBACK');
190 throw $e;
191 }
192
193 } catch (Exception $e) {
194 wp_send_json_error($e->getMessage());
195 }
196 }
197 }
198
199 // Initialize plugin using singleton pattern
200 function bulk_menu_edit_init()
201 {
202 return BulkMenuEdit::get_instance();
203 }
204 add_action('plugins_loaded', 'bulk_menu_edit_init');
205 endif;