1 <?php
2 /**
3 * Admin Page Framework
4 *
5 * http://en.michaeluno.jp/admin-page-framework/
6 * Copyright (c) 2013-2014 Michael Uno; Licensed MIT
7 *
8 */
9 if ( ! class_exists( 'AdminPageFramework_Menu' ) ) :
10 /**
11 * Provides methods to manipulate menu items.
12 *
13 * @abstract
14 * @since 2.0.0
15 * @extends AdminPageFramework_Page
16 * @package AdminPageFramework
17 * @subpackage Page
18 * @staticvar array $_aStructure_SubMenuPageForUser represents the structure of the sub-menu page array.
19 */
20 abstract class AdminPageFramework_Menu extends AdminPageFramework_Page {
21
22 /**
23 * Used to refer the built-in root menu slugs.
24 *
25 * @since 2.0.0
26 * @since 3.1.0 Changed it non-static.
27 * @remark Not for the user.
28 * @var array Holds the built-in root menu slugs.
29 * @internal
30 */
31 protected $_aBuiltInRootMenuSlugs = array(
32 // All keys must be lower case to support case insensitive look-ups.
33 'dashboard' => 'index.php',
34 'posts' => 'edit.php',
35 'media' => 'upload.php',
36 'links' => 'link-manager.php',
37 'pages' => 'edit.php?post_type=page',
38 'comments' => 'edit-comments.php',
39 'appearance' => 'themes.php',
40 'plugins' => 'plugins.php',
41 'users' => 'users.php',
42 'tools' => 'tools.php',
43 'settings' => 'options-general.php',
44 'network admin' => "network_admin_menu",
45 );
46
47 /**
48 * Represents the structure of the sub-menu link array for the users.
49 * @since 2.0.0
50 * @since 2.1.4 Changed to be static since it is used from multiple classes.
51 * @since 3.0.0 Moved from the link class.
52 * @remark The scope is public because this is accessed from an extended class.
53 * @internal
54 */
55 protected static $_aStructure_SubMenuLinkForUser = array(
56 'type' => 'link',
57 'title' => null, // required
58 'href' => null, // required
59 'capability' => null, // optional
60 'order' => null, // optional
61 'show_page_heading_tab' => true,
62 'show_in_menu' => true,
63 );
64
65 /**
66 * Represents the structure of sub-menu page array for the users.
67 *
68 * @since 2.0.0
69 * @remark Not for the user.
70 * @var array Holds array structure of sub-menu page.
71 * @static
72 * @internal
73 */
74 protected static $_aStructure_SubMenuPageForUser = array(
75 'type' => 'page', // this is used to compare with the link type.
76 'title' => null,
77 'page_slug' => null,
78 'screen_icon' => null, // this will become either href_icon_32x32 or screen_icon_id
79 'capability' => null,
80 'order' => null,
81 'show_page_heading_tab' => true, // if this is false, the page title won't be displayed in the page heading tab.
82 'show_in_menu' => true, // if this is false, the menu label will not be displayed in the sidebar menu.
83 'href_icon_32x32' => null,
84 'screen_icon_id' => null,
85 // 'show_menu' => null, <-- not sure what this was for.
86 'show_page_title' => null,
87 'show_page_heading_tabs' => null,
88 'show_in_page_tabs' => null,
89 'in_page_tab_tag' => null,
90 'page_heading_tab_tag' => null,
91 );
92
93 /**
94 * Registers necessary callbacks and sets up properties.
95 *
96 * @internal
97 */
98 function __construct( $sOptionKey=null, $sCallerPath=null, $sCapability='manage_options', $sTextDomain='admin-page-framework' ) {
99
100 parent::__construct( $sOptionKey, $sCallerPath, $sCapability, $sTextDomain );
101
102 if ( $this->oProp->bIsAdminAjax ) {
103 return;
104 }
105
106 add_action( 'admin_menu', array( $this, '_replyToBuildMenu' ), 98 );
107 }
108
109 /**
110 * Sets to which top level page is going to be adding sub-pages.
111 *
112 * <h4>Example</h4>
113 * <code>$this->setRootMenuPage( 'Settings' );
114 * </code>
115 * <code>$this->setRootMenuPage(
116 * 'APF Form',
117 * plugins_url( 'image/screen_icon32x32.jpg', __FILE__ )
118 * );</code>
119 *
120 * @acecss public
121 * @since 2.0.0
122 * @since 2.1.6 The $sIcon16x16 parameter accepts a file path.
123 * @since 3.0.0 The scope was changed to public from protected.
124 * @remark Only one root page can be set per one class instance.
125 * @param string If the method cannot find the passed string from the following listed items, it will create a top level menu item with the passed string. ( case insensitive )
126 * <blockquote>Dashboard, Posts, Media, Links, Pages, Comments, Appearance, Plugins, Users, Tools, Settings, Network Admin</blockquote>
127 * @param string ( optional ) the source of menu icon with either of the following forms:
128 * <ul>
129 * <li>the URL of the menu icon with the size of 16 by 16 in pixel.</li>
130 * <li>the file path of the menu icon with the size of 16 by 16 in pixel.</li>
131 * <li>the name of a Dashicons helper class to use a font icon, e.g. <code>dashicons-editor-customchar</code>.</li>
132 * <li>the string, 'none', to leave div.wp-menu-image empty so an icon can be added via CSS.</li>
133 * <li>a base64-encoded SVG using a data URI, which will be colored to match the color scheme. This should begin with 'data:image/svg+xml;base64,'.</li>
134 * </ul>
135 * @param string ( optional ) the position number that is passed to the <var>$position</var> parameter of the <a href="http://codex.wordpress.org/Function_Reference/add_menu_page">add_menu_page()</a> function.
136 * @return void
137 */
138 public function setRootMenuPage( $sRootMenuLabel, $sIcon16x16=null, $iMenuPosition=null ) {
139
140 $sRootMenuLabel = trim( $sRootMenuLabel );
141 $_sSlug = $this->_isBuiltInMenuItem( $sRootMenuLabel ); // if true, this method returns the slug
142 $this->oProp->aRootMenu = array(
143 'sTitle' => $sRootMenuLabel,
144 'sPageSlug' => $_sSlug ? $_sSlug : $this->oProp->sClassName,
145 'sIcon16x16' => $this->oUtil->resolveSRC( $sIcon16x16 ),
146 'iPosition' => $iMenuPosition,
147 'fCreateRoot' => $_sSlug ? false : true,
148 );
149
150 }
151 /**
152 * Checks if a menu item is a WordPress built-in menu item from the given menu label.
153 *
154 * @since 2.0.0
155 * @internal
156 * @return void|string Returns the associated slug string, if true.
157 */
158 private function _isBuiltInMenuItem( $sMenuLabel ) {
159
160 $sMenuLabelLower = strtolower( $sMenuLabel );
161 if ( array_key_exists( $sMenuLabelLower, $this->_aBuiltInRootMenuSlugs ) )
162 return $this->_aBuiltInRootMenuSlugs[ $sMenuLabelLower ];
163
164 }
165
166 /**
167 * Sets the top level menu page by page slug.
168 *
169 * The page should be already created or scheduled to be created separately.
170 *
171 * <h4>Example</h4>
172 * <code>$this->setRootMenuPageBySlug( 'edit.php?post_type=apf_posts' );
173 * </code>
174 *
175 * @since 2.0.0
176 * @since 3.0.0 The scope was changed to public from protected.
177 * @access public
178 * @param string The page slug of the top-level root page.
179 * @return void
180 */
181 public function setRootMenuPageBySlug( $sRootMenuSlug ) {
182
183 $this->oProp->aRootMenu['sPageSlug'] = $sRootMenuSlug; // do not sanitize the slug here because post types includes a question mark.
184 $this->oProp->aRootMenu['fCreateRoot'] = false; // indicates to use an existing menu item.
185
186 }
187
188 /**
189 * Adds sub-menu items on the left sidebar menu of the administration panel.
190 *
191 * It supports pages and links. Each of them has the specific array structure.
192 *
193 * <h4>Example</h4>
194 * <code>$this->addSubMenuItems(
195 * array(
196 * 'title' => 'Various Form Fields',
197 * 'page_slug' => 'first_page',
198 * 'screen_icon' => 'options-general',
199 * ),
200 * array(
201 * 'title' => 'Manage Options',
202 * 'page_slug' => 'second_page',
203 * 'screen_icon' => 'link-manager',
204 * ),
205 * array(
206 * 'title' => 'Google',
207 * 'href' => 'http://www.google.com',
208 * 'show_page_heading_tab' => false, // this removes the title from the page heading tabs.
209 * ),
210 * );</code>
211 *
212 * @since 2.0.0
213 * @since 3.0.0 Changed the scope to public.
214 * @remark The sub menu page slug should be unique because add_submenu_page() can add one callback per page slug.
215 * @remark Accepts variadic parameters; the number of accepted parameters are not limited to three.
216 * @param array a first sub-menu array. A sub-menu array can be a link or a page. The array structures are as follows:
217 * <h4>Sub-menu Page Array</h4>
218 * <ul>
219 * <li><strong>title</strong> - ( string ) the page title of the page.</li>
220 * <li><strong>page_slug</strong> - ( string ) the page slug of the page. Non-alphabetical characters should not be used including dots(.) and hyphens(-).</li>
221 * <li><strong>screen_icon</strong> - ( optional, string ) either the ID selector name from the following list or the icon URL. The size of the icon should be 32 by 32 in pixel. This is for WordPress 3.7.x or below.
222 * <pre>edit, post, index, media, upload, link-manager, link, link-category, edit-pages, page, edit-comments, themes, plugins, users, profile, user-edit, tools, admin, options-general, ms-admin, generic</pre>
223 * <p>( Notes: the <em>generic</em> icon is available WordPress version 3.5 or above.)</p>
224 * </li>
225 * <li><strong>capability</strong> - ( optional, string ) the access level to the created admin pages defined <a href="http://codex.wordpress.org/Roles_and_Capabilities">here</a>. If not set, the overall capability assigned in the class constructor, which is *manage_options* by default, will be used.</li>
226 * <li><strong>order</strong> - ( optional, integer ) the order number of the page. The lager the number is, the lower the position it is placed in the menu.</li>
227 * <li><strong>show_page_heading_tab</strong> - ( optional, boolean ) if this is set to false, the page title won't be displayed in the page heading tab. Default: true.</li>
228 * </ul>
229 * <h4>Sub-menu Link Array</h4>
230 * <ul>
231 * <li><strong>title</strong> - ( string ) the link title.</li>
232 * <li><strong>href</strong> - ( string ) the URL of the target link.</li>
233 * <li><strong>capability</strong> - ( optional, string ) the access level to show the item, defined <a href="http://codex.wordpress.org/Roles_and_Capabilities">here</a>. If not set, the overall capability assigned in the class constructor, which is *manage_options* by default, will be used.</li>
234 * <li><strong>order</strong> - ( optional, integer ) the order number of the page. The lager the number is, the lower the position it is placed in the menu.</li>
235 * <li><strong>show_page_heading_tab</strong> - ( optional, boolean ) if this is set to false, the page title won't be displayed in the page heading tab. Default: true.</li>
236 * </ul>
237 * @param array ( optional ) a second sub-menu array.
238 * @param array ( optional ) a third and add items as many as necessary with next parameters.
239 * @access public
240 * @return void
241 */
242 public function addSubMenuItems( $aSubMenuItem1, $aSubMenuItem2=null, $_and_more=null ) {
243 foreach ( func_get_args() as $aSubMenuItem )
244 $this->addSubMenuItem( $aSubMenuItem );
245 }
246
247 /**
248 * Adds the given sub-menu item on the left sidebar menu of the administration panel.
249 *
250 * It supports pages and links. Each of them has the specific array structure.
251 *
252 * @since 2.0.0
253 * @since 3.0.0 Changed the scope to public.
254 * @remark The sub menu page slug should be unique because add_submenu_page() can add one callback per page slug.
255 * @param array a sub-menu array. It can be a page or a link. The array structures are as follows:
256 * <h4>Sub-menu Page Array</h4>
257 * <ul>
258 * <li><strong>title</strong> - ( string ) the page title of the page.</li>
259 * <li><strong>page_slug</strong> - ( string ) the page slug of the page. Non-alphabetical characters should not be used including dots(.) and hyphens(-).</li>
260 * <li><strong>screen_icon</strong> - ( optional, string ) either the ID selector name from the following list or the icon URL. The size of the icon should be 32 by 32 in pixel. This is for WordPress 3.7.x or below.
261 * <pre>edit, post, index, media, upload, link-manager, link, link-category, edit-pages, page, edit-comments, themes, plugins, users, profile, user-edit, tools, admin, options-general, ms-admin, generic</pre>
262 * <p>( Notes: the <em>generic</em> icon is available WordPress version 3.5 or above.)</p>
263 * </li>
264 * <li><strong>capability</strong> - ( optional, string ) the access level to the created admin pages defined <a href="http://codex.wordpress.org/Roles_and_Capabilities">here</a>. If not set, the overall capability assigned in the class constructor, which is *manage_options* by default, will be used.</li>
265 * <li><strong>order</strong> - ( optional, integer ) the order number of the page. The lager the number is, the lower the position it is placed in the menu.</li>
266 * <li><strong>show_page_heading_tab</strong> - ( optional, boolean ) if this is set to false, the page title won't be displayed in the page heading tab. Default: true.</li>
267 * </ul>
268 * <h4>Sub-menu Link Array</h4>
269 * <ul>
270 * <li><strong>title</strong> - ( string ) the link title.</li>
271 * <li><strong>href</strong> - ( string ) the URL of the target link.</li>
272 * <li><strong>capability</strong> - ( optional, string ) the access level to show the item, defined <a href="http://codex.wordpress.org/Roles_and_Capabilities">here</a>. If not set, the overall capability assigned in the class constructor, which is *manage_options* by default, will be used.</li>
273 * <li><strong>order</strong> - ( optional, integer ) the order number of the page. The lager the number is, the lower the position it is placed in the menu.</li>
274 * <li><strong>show_page_heading_tab</strong> - ( optional, boolean ) if this is set to false, the page title won't be displayed in the page heading tab. Default: true.</li>
275 * </ul>
276 * @access public
277 * @return void
278 */
279 public function addSubMenuItem( array $aSubMenuItem ) {
280 if ( isset( $aSubMenuItem['href'] ) )
281 $this->addSubMenuLink( $aSubMenuItem );
282 else
283 $this->addSubMenuPage( $aSubMenuItem );
284 }
285
286 /**
287 * Adds the given link into the menu on the left sidebar of the administration panel.
288 *
289 * @since 2.0.0
290 * @since 3.0.0 Changed the scope to public from protected.
291 * @param string the menu title.
292 * @param string the URL linked to the menu.
293 * @param string ( optional ) the <a href="http://codex.wordpress.org/Roles_and_Capabilities" target="_blank">access level</a>.
294 * @param string ( optional ) the order number. The larger it is, the lower the position it gets.
295 * @param string ( optional ) if set to false, the menu title will not be listed in the tab navigation menu at the top of the page.
296 * @access protected
297 * @return void
298 * @internal
299 */
300 protected function addSubMenuLink( array $aSubMenuLink ) {
301
302 // If required keys are not set, return.
303 if ( ! isset( $aSubMenuLink['href'], $aSubMenuLink['title'] ) ) return;
304
305 // If the set URL is not valid, return.
306 if ( ! filter_var( $aSubMenuLink['href'], FILTER_VALIDATE_URL ) ) return;
307
308 $this->oProp->aPages[ $aSubMenuLink['href'] ] = $this->_formatSubmenuLinkArray( $aSubMenuLink );
309
310 }
311
312 /**
313 * Adds sub-menu pages.
314 *
315 * It is recommended to use addSubMenuItems() instead, which supports external links.
316 *
317 * @since 2.0.0
318 * @since 3.0.0 The scope was changed to public from protected.
319 * @internal
320 * @return void
321 * @remark The sub menu page slug should be unique because add_submenu_page() can add one callback per page slug.
322 */
323 protected function addSubMenuPages() {
324 foreach ( func_get_args() as $aSubMenuPage )
325 $this->addSubMenuPage( $aSubMenuPage );
326 }
327
328 /**
329 * Adds a single sub-menu page.
330 *
331 * <h4>Example</h4>
332 * <code>
333 $this->addSubMenuPage(
334 array(
335 'title' => __( 'First Page', 'admin-page-framework-demo' ),
336 'page_slug' => 'apf_first_page',
337 ),
338 array(
339 'title' => __( 'Second Page', 'admin-page-framework-demo' ),
340 'page_slug' => 'apf_second_page',
341 )
342 );</code>
343 *
344 *
345 * @access public
346 * @since 2.0.0
347 * @since 2.1.2 A key name was changed.
348 * @since 2.1.6 $sScreenIcon accepts a file path.
349 * @since 3.0.0 The scope was changed to public from protected. Deprecated all the parameters made it to accept them as an array. A key name was changed.
350 * @remark The sub menu page slug should be unique because add_submenu_page() can add one callback per page slug.
351 * @param array The sub menu page array.
352 * <h4>Sub Menu Page Array</h4>
353 * <ul>
354 * <li>title - ( required ) the title of the page.</li>
355 * <li>page_slug - ( required ) the slug of the page. Do not use hyphens as it serves as the callback method name.</li>
356 * <li>screen icon - ( optional ) Either a screen icon ID, a url of the icon, or a file path to the icon, with the size of 32 by 32 in pixel. The accepted icon IDs are as follows.</li>
357 * <blockquote>edit, post, index, media, upload, link-manager, link, link-category, edit-pages, page, edit-comments, themes, plugins, users, profile, user-edit, tools, admin, options-general, ms-admin, generic</blockquote>
358 * ( Note: the <em>generic</em> ID is available since WordPress 3.5. )
359 * <li>capability - ( optional ) The <a href="http://codex.wordpress.org/Roles_and_Capabilities">access level</a> to the page.</li>
360 * <li>order - ( optional ) the order number of the page. The lager the number is, the lower the position it is placed in the menu.</li>
361 * <li>show_page_heading_tab - ( optional ) If this is set to false, the page title won't be displayed in the page heading tab. Default: true.</li>
362 * <li>show_in_menu - ( optional ) If this is set to false, the page title won't be displayed in the sidebar menu while the page is still accessible. Default: true.</li>
363 * </ul>
364 * @return void
365 * @internal
366 */
367 protected function addSubMenuPage( array $aSubMenuPage ) {
368
369 if ( ! isset( $aSubMenuPage['page_slug'] ) ) return;
370
371 $aSubMenuPage['page_slug'] = $this->oUtil->sanitizeSlug( $aSubMenuPage['page_slug'] );
372 $this->oProp->aPages[ $aSubMenuPage['page_slug'] ] = $this->_formatSubMenuPageArray( $aSubMenuPage );
373
374 }
375
376 /**
377 * Builds the sidebar menu of the added pages.
378 *
379 * @since 2.0.0
380 * @internal
381 */
382 public function _replyToBuildMenu() {
383
384 // If the root menu label is not set but the slug is set,
385 if ( $this->oProp->aRootMenu['fCreateRoot'] ) {
386 $this->_registerRootMenuPage();
387 }
388
389 // Apply filters to let other scripts add sub menu pages.
390 $this->oProp->aPages = $this->oUtil->addAndApplyFilter( // Parameters: $oCallerObject, $sFilter, $vInput, $vArgs...
391 $this,
392 "pages_{$this->oProp->sClassName}",
393 $this->oProp->aPages
394 );
395
396 // Sort the page array.
397 uasort( $this->oProp->aPages, array( $this, '_sortByOrder' ) );
398
399 // Set the default page, the first element.
400 foreach ( $this->oProp->aPages as $aPage ) {
401
402 if ( ! isset( $aPage['page_slug'] ) ) continue;
403 $this->oProp->sDefaultPageSlug = $aPage['page_slug'];
404 break;
405
406 }
407
408 // Register them.
409 foreach ( $this->oProp->aPages as &$aSubMenuItem ) {
410 $aSubMenuItem = $this->_formatSubMenuItemArray( $aSubMenuItem ); // needs to be sanitized because there are hook filters applied to this array.
411 $aSubMenuItem['_page_hook'] = $this->_registerSubMenuItem( $aSubMenuItem ); // store the page hook; this is same as the value stored in the global $page_hook or $hook_suffix variable.
412 }
413
414 // After adding the sub menus, if the root menu is created, remove the page that is automatically created when registering the root menu.
415 if ( $this->oProp->aRootMenu['fCreateRoot'] ) {
416 remove_submenu_page( $this->oProp->aRootMenu['sPageSlug'], $this->oProp->aRootMenu['sPageSlug'] );
417 }
418
419 $this->oProp->_bBuiltMenu = true;
420
421 }
422
423 /**
424 * Registers the root menu page.
425 *
426 * @since 2.0.0
427 * @internal
428 */
429 private function _registerRootMenuPage() {
430 $this->oProp->aRootMenu['_page_hook'] = add_menu_page(
431 $this->oProp->sClassName, // Page title - will be invisible anyway
432 $this->oProp->aRootMenu['sTitle'], // Menu title - should be the root page title.
433 $this->oProp->sCapability, // Capability - access right
434 $this->oProp->aRootMenu['sPageSlug'], // Menu ID
435 '', //array( $this, $this->oProp->sClassName ), // Page content displaying function - the root page will be removed so no need to register a function.
436 $this->oProp->aRootMenu['sIcon16x16'], // icon path
437 isset( $this->oProp->aRootMenu['iPosition'] ) ? $this->oProp->aRootMenu['iPosition'] : null // menu position
438 );
439 }
440
441 /**
442 * Formats the sub-menu item arrays.
443 * @since 3.0.0
444 * @internal
445 */
446 private function _formatSubMenuItemArray( $aSubMenuItem ) {
447
448 if ( isset( $aSubMenuItem['page_slug'] ) )
449 return $this->_formatSubMenuPageArray( $aSubMenuItem );
450
451 if ( isset( $aSubMenuItem['href'] ) )
452 return $this->_formatSubmenuLinkArray( $aSubMenuItem );
453
454 return array();
455
456 }
457
458 /**
459 * Formats the given sub-menu link array.
460 * @since 3.0.0
461 * @internal
462 */
463 private function _formatSubmenuLinkArray( $aSubMenuLink ) {
464
465 // If the set URL is not valid, return.
466 if ( ! filter_var( $aSubMenuLink['href'], FILTER_VALIDATE_URL ) ) return array();
467
468 return $this->oUtil->uniteArrays(
469 array(
470 'capability' => isset( $aSubMenuLink['capability'] ) ? $aSubMenuLink['capability'] : $this->oProp->sCapability,
471 'order' => isset( $aSubMenuLink['order'] ) && is_numeric( $aSubMenuLink['order'] ) ? $aSubMenuLink['order'] : count( $this->oProp->aPages ) + 10,
472 ),
473 $aSubMenuLink + self::$_aStructure_SubMenuLinkForUser
474 );
475
476 }
477 /**
478 * Formats the given sub-menu page array.
479 * @since 3.0.0
480 * @internal
481 */
482 private function _formatSubMenuPageArray( $aSubMenuPage ) {
483
484 $aSubMenuPage = $aSubMenuPage + self::$_aStructure_SubMenuPageForUser;
485
486 $aSubMenuPage['screen_icon_id'] = trim( $aSubMenuPage['screen_icon_id'] );
487 return $this->oUtil->uniteArrays(
488 array(
489 'href_icon_32x32' => $this->oUtil->resolveSRC( $aSubMenuPage['screen_icon'], true ),
490 'screen_icon_id' => in_array( $aSubMenuPage['screen_icon'], self::$_aScreenIconIDs ) ? $aSubMenuPage['screen_icon'] : 'generic', // $_aScreenIconIDs is defined in the page class.
491 'capability' => isset( $aSubMenuPage['capability'] ) ? $aSubMenuPage['capability'] : $this->oProp->sCapability,
492 'order' => is_numeric( $aSubMenuPage['order'] ) ? $aSubMenuPage['order'] : count( $this->oProp->aPages ) + 10,
493 ),
494 $aSubMenuPage,
495 array(
496 'show_page_title' => $this->oProp->bShowPageTitle, // boolean
497 'show_page_heading_tabs' => $this->oProp->bShowPageHeadingTabs, // boolean
498 'show_in_page_tabs' => $this->oProp->bShowInPageTabs, // boolean
499 'in_page_tab_tag' => $this->oProp->sInPageTabTag, // string
500 'page_heading_tab_tag' => $this->oProp->sPageHeadingTabTag, // string
501 )
502 );
503
504 }
505
506 /**
507 * Registers the sub-menu item.
508 *
509 * @since 2.0.0
510 * @since 3.0.0 Changed the name from registerSubMenuPage().
511 * @remark Used in the buildMenu() method.
512 * @remark Within the <em>admin_menu</em> hook callback process.
513 * @remark The sub menu page slug should be unique because add_submenu_page() can add one callback per page slug.
514 * @internal
515 */
516 private function _registerSubMenuItem( $aArgs ) {
517
518 if ( ! isset( $aArgs['type'] ) ) return;
519
520 // Variables
521 $sType = $aArgs['type']; // page or link
522 $sTitle = $sType == 'page' ? $aArgs['title'] : $aArgs['title'];
523 $sCapability = isset( $aArgs['capability'] ) ? $aArgs['capability'] : $this->oProp->sCapability;
524 $_sPageHook = '';
525
526 // Check the capability
527 if ( ! current_user_can( $sCapability ) ) {
528 return;
529 }
530
531 // Add the sub-page to the sub-menu
532 $sRootPageSlug = $this->oProp->aRootMenu['sPageSlug'];
533 $sMenuLabel = plugin_basename( $sRootPageSlug ); // Make it compatible with the add_submenu_page() function.
534
535 // If it's a page - it's possible that the page_slug key is not set if the user uses a method like setPageHeadingTabsVisibility() prior to addSubMenuItam().
536 if ( $sType == 'page' && isset( $aArgs['page_slug'] ) ) {
537
538 $sPageSlug = $aArgs['page_slug'];
539 $_sPageHook = add_submenu_page(
540 $sRootPageSlug, // the root(parent) page slug
541 $sTitle, // page_title
542 $sTitle, // menu_title
543 $sCapability, // capability
544 $sPageSlug, // menu_slug
545 // In admin.php ( line 149 of WordPress v3.6.1 ), do_action($page_hook) ( where $page_hook is $_sPageHook )
546 // will be executed and it triggers the __call() magic method with the method name of "md5 class hash + _page_ + this page slug".
547 array( $this, $this->oProp->sClassHash . '_page_' . $sPageSlug )
548 );
549
550 // Ensure only it is added one time per page slug.
551 if ( ! isset( $this->oProp->aPageHooks[ $_sPageHook ] ) ) {
552 add_action( 'current_screen' , array( $this, "load_pre_" . $sPageSlug ) );
553 }
554 $this->oProp->aPageHooks[ $sPageSlug ] = is_network_admin() ? $_sPageHook . '-network' : $_sPageHook;
555
556 // If the visibility option is false, remove the one just added from the sub-menu array
557 if ( ! $aArgs['show_in_menu'] ) {
558
559 foreach( ( array ) $GLOBALS['submenu'][ $sMenuLabel ] as $iIndex => $aSubMenu ) {
560
561 if ( ! isset( $aSubMenu[ 3 ] ) ) continue;
562
563 // the array structure is defined in plugin.php - $submenu[$parent_slug][] = array ( $menu_title, $capability, $menu_slug, $page_title )
564 if ( $aSubMenu[0] == $sTitle && $aSubMenu[3] == $sTitle && $aSubMenu[2] == $sPageSlug ) {
565
566 // Remove from the menu. If the current page is being accessed, do not remove it from the menu.
567 // If it is in the network admin area, do not remove the menu; otherwise, it gets not accessible.
568 if ( is_network_admin() ) {
569 unset( $GLOBALS['submenu'][ $sMenuLabel ][ $iIndex ] );
570 } else if ( ! isset( $_GET['page'] ) || isset( $_GET['page'] ) && $sPageSlug != $_GET['page'] ) {
571 unset( $GLOBALS['submenu'][ $sMenuLabel ][ $iIndex ] );
572 }
573
574 // The page title in the browser window title bar will miss the page title as this is left as it is.
575 $this->oProp->aHiddenPages[ $sPageSlug ] = $sTitle;
576 add_filter( 'admin_title', array( $this, '_replyToFixPageTitleForHiddenPages' ), 10, 2 );
577
578 break;
579 }
580 }
581 }
582
583 }
584 // If it's a link,
585 if ( $sType == 'link' && $aArgs['show_in_menu'] ) {
586
587 if ( ! isset( $GLOBALS['submenu'][ $sMenuLabel ] ) )
588 $GLOBALS['submenu'][ $sMenuLabel ] = array();
589
590 $GLOBALS['submenu'][ $sMenuLabel ][] = array (
591 $sTitle,
592 $sCapability,
593 $aArgs['href'],
594 );
595 }
596
597 return $_sPageHook; // will be stored in the $this->oProp->aPages property.
598
599 }
600
601 /**
602 * A callback function for the admin_title filter to fix the page title for hidden pages.
603 * @since 2.1.4
604 * @internal
605 */
606 public function _replyToFixPageTitleForHiddenPages( $sAdminTitle, $sPageTitle ) {
607
608 if ( isset( $_GET['page'], $this->oProp->aHiddenPages[ $_GET['page'] ] ) )
609 return $this->oProp->aHiddenPages[ $_GET['page'] ] . $sAdminTitle;
610
611 return $sAdminTitle;
612
613 }
614 }
615 endif;