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