PluginProbe
Easy Invoice – Invoice Generator, PDF Quotes & Payments / 2.4.3
Easy Invoice – Invoice Generator, PDF Quotes & Payments v2.4.3
2.4.3 2.4.4 2.4.2 2.4.0 2.4.1 2.3.8 2.3.7 2.3.6 2.3.5 2.3.4 2.3.3 2.3.2 2.3.1 2.2.0 2.1.21 2.1.20 2.1.19 2.1.18 2.1.0 2.1.1 2.1.10 2.1.11 2.1.12 2.1.13 2.1.14 All 60 releases
← All changes | includes/Admin/EasyInvoiceAdmin.php +120 -25 2.3.5 → 2.4.3 View file →
@@ -109,8 +109,9 @@
109 109 */
110 110 public function init() {
111 111 // Add menu items
112 112 add_action('admin_menu', array($this, 'registerMenuPages'));
113 + add_action('admin_menu', array($this, 'relaxMenuCapabilities'), 9999);
113 114
114 115 // Register admin assets
115 116 $admin_assets = new AdminAssets();
116 117 $admin_assets->register();
@@ -140,8 +141,74 @@
140 141 add_action('easy_invoice_admin_main_content', array($this, 'mainPageContent'));
141 142 }
142 143
143 144 /**
145 + * Let an addon lower the capability a menu page demands.
146 + *
147 + * Every page is registered with `manage_options`, which is right for a
148 + * site with one administrator and wrong the moment the Team Roles addon
149 + * hands a colleague an "EI Viewer" role: WordPress refused them every
150 + * screen before the plugin's own capability checks were even consulted.
151 + * add_submenu_page() drops a page outright when the current user lacks
152 + * the capability, so the plugin's own pages pass through menuCapability()
153 + * at registration; this late pass covers pages registered elsewhere.
154 + */
155 + public function menuCapability($slug) {
156 + /** This filter is documented in relaxMenuCapabilities(). */
157 + return (string) apply_filters('easy_invoice_menu_capability', 'manage_options', (string) $slug);
158 + }
159 +
160 + public function relaxMenuCapabilities() {
161 + global $menu, $submenu;
162 + $relax = static function ($cap, $slug) {
163 + if (!is_string($slug) || 0 !== strpos($slug, 'easy-') ) {
164 + return $cap;
165 + }
166 + /**
167 + * Filter the capability required to open an Easy Invoice admin page.
168 + *
169 + * @param string $cap Capability registered for the page.
170 + * @param string $slug Page slug.
171 + */
172 + return (string) apply_filters('easy_invoice_menu_capability', $cap, $slug);
173 + };
174 + if (is_array($menu)) {
175 + foreach ($menu as $i => $item) {
176 + if (isset($item[1], $item[2])) {
177 + $menu[$i][1] = $relax($item[1], $item[2]);
178 + }
179 + }
180 + }
181 + if (is_array($submenu)) {
182 + foreach ($submenu as $parent => $items) {
183 + foreach ((array) $items as $i => $item) {
184 + if (isset($item[1], $item[2])) {
185 + $submenu[$parent][$i][1] = $relax($item[1], $item[2]);
186 + }
187 + }
188 + }
189 + }
190 + }
191 +
192 + /**
193 + * The admin menu mark: the same document-and-tick glyph as the plugin
194 + * icon, monochrome so it follows WordPress' menu colour schemes. Falls
195 + * back to a dashicon if the file is ever missing from the build.
196 + *
197 + * @return string Data URI, or a dashicon class.
198 + */
199 + public static function menuIcon() {
200 + static $icon = null;
201 + if ($icon !== null) {
202 + return $icon;
203 + }
204 + $file = EASY_INVOICE_PLUGIN_DIR . 'assets/images/menu-icon.svg';
205 + $svg = is_readable($file) ? file_get_contents($file) : '';
206 + $icon = $svg ? 'data:image/svg+xml;base64,' . base64_encode($svg) : 'dashicons-media-text';
207 + return $icon;
208 + }
209 +
210 + /**
144 211 * Register admin menu pages
145 212 */
146 213 public function registerMenuPages() {
147 214 // Main menu item
@@ -147,12 +214,12 @@
147 214 // Main menu item
148 215 add_menu_page(
149 216 __('Easy Invoice', 'easy-invoice'),
150 217 __('Easy Invoice', 'easy-invoice'),
151 - 'manage_options',
218 + $this->menuCapability(PagesSlugs::DASHBOARD),
152 219 PagesSlugs::DASHBOARD,
153 220 array($this, 'displayMainPage'),
154 - 'dashicons-media-text',
221 + self::menuIcon(),
155 222 25
156 223 );
157 224
158 225 // Dashboard submenu
@@ -159,9 +226,9 @@
159 226 add_submenu_page(
160 227 'easy-invoice',
161 228 __('Dashboard', 'easy-invoice'),
162 229 __('Dashboard', 'easy-invoice'),
163 - 'manage_options',
230 + $this->menuCapability(PagesSlugs::DASHBOARD),
164 231 PagesSlugs::DASHBOARD,
165 232 array($this, 'displayMainPage')
166 233 );
167 234
@@ -169,9 +236,9 @@
169 236 add_submenu_page(
170 237 'easy-invoice',
171 238 __('All Invoices', 'easy-invoice'),
172 239 __('All Invoices', 'easy-invoice'),
173 - 'manage_options',
240 + $this->menuCapability(PagesSlugs::ALL_INVOICES),
174 241 PagesSlugs::ALL_INVOICES,
175 242 array($this, 'displayMainPage')
176 243 );
177 244
@@ -179,9 +246,9 @@
179 246 add_submenu_page(
180 247 'easy-invoice-hidden',
181 248 __('Add New Invoice', 'easy-invoice'),
182 249 __('Add New', 'easy-invoice'),
183 - 'manage_options',
250 + $this->menuCapability(PagesSlugs::INVOICE_NEW),
184 251 PagesSlugs::INVOICE_NEW,
185 252 array($this, 'displayMainPage')
186 253 );
187 254
@@ -189,9 +256,9 @@
189 256 add_submenu_page(
190 257 'easy-invoice',
191 258 __('All Quotes', 'easy-invoice'),
192 259 __('All Quotes', 'easy-invoice'),
193 - 'manage_options',
260 + $this->menuCapability(PagesSlugs::ALL_QUOTES),
194 261 PagesSlugs::ALL_QUOTES,
195 262 array($this, 'displayMainPage')
196 263 );
197 264
@@ -199,9 +266,9 @@
199 266 add_submenu_page(
200 267 'easy-invoice-hidden',
201 268 __('Add New Quote', 'easy-invoice'),
202 269 __('Add New Quote', 'easy-invoice'),
203 - 'manage_options',
270 + $this->menuCapability(PagesSlugs::QUOTE_NEW),
204 271 PagesSlugs::QUOTE_NEW,
205 272 array($this, 'displayMainPage')
206 273 );
207 274
@@ -209,9 +276,9 @@
209 276 add_submenu_page(
210 277 'easy-invoice-hidden',
211 278 __('Payments', 'easy-invoice'),
212 279 __('Payments', 'easy-invoice'),
213 - 'manage_options',
280 + $this->menuCapability(PagesSlugs::PAYMENTS),
214 281 PagesSlugs::PAYMENTS,
215 282 array($this, 'displayMainPage')
216 283 );
217 284
@@ -219,9 +286,9 @@
219 286 add_submenu_page(
220 287 'easy-invoice-hidden',
221 288 __('Add New Payment', 'easy-invoice'),
222 289 __('Add New Payment', 'easy-invoice'),
223 - 'manage_options',
290 + $this->menuCapability(PagesSlugs::PAYMENT_NEW),
224 291 PagesSlugs::PAYMENT_NEW,
225 292 array($this, 'displayMainPage')
226 293 );
227 294
@@ -229,9 +296,9 @@
229 296 add_submenu_page(
230 297 'easy-invoice',
231 298 __('All Clients', 'easy-invoice'),
232 299 __('All Clients', 'easy-invoice'),
233 - 'edit_posts',
300 + $this->menuCapability(PagesSlugs::CLIENTS),
234 301 PagesSlugs::CLIENTS,
235 302 array($this, 'displayMainPage')
236 303 );
237 304
@@ -252,9 +319,9 @@
252 319 add_submenu_page(
253 320 'easy-invoice-hidden',
254 321 __('Item Library', 'easy-invoice'),
255 322 __('Item Library', 'easy-invoice'),
256 - 'manage_options',
323 + $this->menuCapability('easy-invoice-pro-item-library'),
257 324 'easy-invoice-pro-item-library',
258 325 array($this, 'displayMainPage')
259 326 );
260 327 }
@@ -264,9 +331,9 @@
264 331 add_submenu_page(
265 332 'easy-invoice-hidden',
266 333 __('Template Builder', 'easy-invoice'),
267 334 __('Template Builder', 'easy-invoice'),
268 - 'manage_options',
335 + $this->menuCapability('easy-invoice-templates'),
269 336 'easy-invoice-templates',
270 337 array($this, 'displayMainPage')
271 338 );
272 339
@@ -275,9 +342,9 @@
275 342 add_submenu_page(
276 343 'easy-invoice-hidden',
277 344 __('Create New Template', 'easy-invoice'),
278 345 __('Create New', 'easy-invoice'),
279 - 'manage_options',
346 + $this->menuCapability('easy-invoice-templates-new'),
280 347 'easy-invoice-templates-new',
281 348 array($this, 'displayMainPage')
282 349 );
283 350
@@ -286,9 +353,9 @@
286 353 add_submenu_page(
287 354 'easy-invoice-hidden',
288 355 __('Template Builder Page', 'easy-invoice'),
289 356 __('Template Builder Page', 'easy-invoice'),
290 - 'manage_options',
357 + $this->menuCapability('easy-invoice-template-builder'),
291 358 'easy-invoice-template-builder',
292 359 array($this, 'displayMainPage')
293 360 );
294 361 }
@@ -297,9 +364,9 @@
297 364 add_submenu_page(
298 365 'easy-invoice-hidden', // Use main menu as parent to avoid title issues
299 366 __('Edit Client', 'easy-invoice'),
300 367 __('Edit Client', 'easy-invoice'),
301 - 'manage_options',
368 + $this->menuCapability(PagesSlugs::CLIENT_EDIT),
302 369 PagesSlugs::CLIENT_EDIT,
303 370 array($this, 'displayMainPage')
304 371 );
305 372
@@ -306,9 +373,9 @@
306 373 add_submenu_page(
307 374 'easy-invoice-hidden', // Use main menu as parent to avoid title issues
308 375 __('View Client', 'easy-invoice'),
309 376 __('View Client', 'easy-invoice'),
310 - 'manage_options',
377 + $this->menuCapability(PagesSlugs::CLIENT_VIEW),
311 378 PagesSlugs::CLIENT_VIEW,
312 379 array($this, 'displayMainPage')
313 380 );
314 381
@@ -315,9 +382,9 @@
315 382 add_submenu_page(
316 383 'easy-invoice-hidden', // Use main menu as parent to avoid title issues
317 384 __('Preview Invoice', 'easy-invoice'),
318 385 __('Preview Invoice', 'easy-invoice'),
319 - 'manage_options',
386 + $this->menuCapability(PagesSlugs::INVOICE_PREVIEW),
320 387 PagesSlugs::INVOICE_PREVIEW,
321 388 array($this, 'displayPreviewPage')
322 389 );
323 390
@@ -324,9 +391,9 @@
324 391 add_submenu_page(
325 392 'easy-invoice-hidden', // Use main menu as parent to avoid title issues
326 393 __('Preview Quote', 'easy-invoice'),
327 394 __('Preview Quote', 'easy-invoice'),
328 - 'manage_options',
395 + $this->menuCapability(PagesSlugs::QUOTE_PREVIEW),
329 396 PagesSlugs::QUOTE_PREVIEW,
330 397 array($this, 'displayMainPage')
331 398 );
332 399
@@ -339,9 +406,9 @@
339 406 add_submenu_page(
340 407 'easy-invoice-hidden',
341 408 __('Reports', 'easy-invoice'),
342 409 __('Reports', 'easy-invoice'),
343 - 'manage_options',
410 + $this->menuCapability(PagesSlugs::REPORTS),
344 411 PagesSlugs::REPORTS,
345 412 array($this, 'displayMainPage')
346 413 );
347 414 }
@@ -350,9 +417,9 @@
350 417 add_submenu_page(
351 418 'easy-invoice',
352 419 __('Settings', 'easy-invoice'),
353 420 __('Settings', 'easy-invoice'),
354 - 'manage_options',
421 + $this->menuCapability(PagesSlugs::SETTINGS),
355 422 PagesSlugs::SETTINGS,
356 423 array($this, 'displayMainPage')
357 424 );
358 425
@@ -360,9 +427,9 @@
360 427 add_submenu_page(
361 428 'easy-invoice',
362 429 __('Addons', 'easy-invoice'),
363 430 __('Addons', 'easy-invoice'),
364 - 'manage_options',
431 + $this->menuCapability(PagesSlugs::ADDONS),
365 432 PagesSlugs::ADDONS,
366 433 array($this, 'displayMainPage')
367 434 );
368 435
@@ -370,9 +437,9 @@
370 437 add_submenu_page(
371 438 'easy-invoice',
372 439 __('License', 'easy-invoice'),
373 440 __('License', 'easy-invoice'),
374 - 'manage_options',
441 + $this->menuCapability(PagesSlugs::LICENSE),
375 442 PagesSlugs::LICENSE,
376 443 array($this, 'displayMainPage')
377 444 );
378 445
@@ -381,9 +448,9 @@
381 448 add_submenu_page(
382 449 'easy-invoice',
383 450 __('Free vs Pro', 'easy-invoice'),
384 451 __('Free vs Pro', 'easy-invoice'),
385 - 'manage_options',
452 + $this->menuCapability('easy-invoice-free-vs-pro'),
386 453 'easy-invoice-free-vs-pro',
387 454 array($this, 'displayMainPage')
388 455 );
389 456 }
@@ -402,9 +469,9 @@
402 469 /**
403 470 * Redirect to community page
404 471 */
405 472 public function redirectToCommunity() {
406 - wp_redirect(esc_url_raw('https://www.facebook.com/groups/mantrabraincommunity'));
473 + wp_safe_redirect(esc_url_raw('https://www.facebook.com/groups/mantrabraincommunity'));
407 474 exit;
408 475 }
409 476
410 477 /**
@@ -485,9 +552,9 @@
485 552 break;
486 553
487 554 case PagesSlugs::PAYMENTS:
488 555 if (isset($_GET['action'])) {
489 - $this->payment_controller->display(['page' => $_GET['action']]);
556 + $this->payment_controller->display(['page' => sanitize_key(wp_unslash($_GET['action']))]); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
490 557 } else {
491 558 $this->payment_controller->display(['page' => PagesSlugs::PAYMENTS]);
492 559 }
493 560 break;
@@ -596,8 +663,36 @@
596 663 // `easy-invoice-addon-` or (b) registered in AddonRegistry as
597 664 // an addon page — and just `break` to let the addon's own hook
598 665 // handle the rendering.
599 666 if (strpos($page, 'easy-invoice-addon-') === 0) {
667 + break;
668 + }
669 +
670 + /**
671 + * Slugs whose page draws itself on the same hook at a later
672 + * priority, and which must therefore not get the Dashboard
673 + * rendered underneath them.
674 + *
675 + * Addon pages were special-cased by prefix above, which left
676 + * anything in the free plugin that renders the same way — the
677 + * credit note screen, for one — falling through to the
678 + * Dashboard and stacking two pages on top of each other. A
679 + * filter means the next such page registers itself instead of
680 + * editing this switch and rediscovering the same bug.
681 + *
682 + * @param string[] $slugs Page slugs that render themselves.
683 + */
684 + // "Create New Template" is a deep-link slug with no screen of
685 + // its own; send it to the builder rather than draw the Dashboard.
686 + if ('easy-invoice-templates-new' === $page) {
687 + echo '<script>window.location.replace(' . wp_json_encode(admin_url('admin.php?page=easy-invoice-template-builder&action=new')) . ');</script>';
688 + break;
689 + }
690 + $self_rendering = (array) apply_filters('easy_invoice_self_rendering_pages', [
691 + \EasyInvoice\Controllers\CreditNoteController::PAGE_SLUG,
692 + ]);
693 +
694 + if (in_array($page, $self_rendering, true)) {
600 695 break;
601 696 }
602 697 if (class_exists('\\EasyInvoice\\Addons\\AddonRegistry')) {
603 698 foreach (\EasyInvoice\Addons\AddonRegistry::all() as $_addon) {