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_Page' ) ) :
10 /**
11 * Provides methods to render admin page elements.
12 *
13 * @abstract
14 * @extends AdminPageFramework_Page_MetaBox
15 * @since 2.0.0
16 * @since 2.1.0 Extends AdminPageFramework_HelpPane_Page.
17 * @since 3.0.0 No longer extends AdminPageFramework_HelpPane_Page.
18 * @package AdminPageFramework
19 * @subpackage Page
20 * @staticvar array $_aScreenIconIDs stores the ID selector names for screen icons.
21 * @staticvar array $_aHookPrefixes stores the prefix strings for filter and action hooks.
22 * @staticvar array $_aStructure_InPageTabElements represents the array structure of an in-page tab array.
23 */
24 abstract class AdminPageFramework_Page extends AdminPageFramework_Page_MetaBox {
25
26 /**
27 * Stores the ID selector names for screen icons. <em>generic</em> is not available in WordPress v3.4.x.
28 *
29 * @since 2.0.0
30 * @var array
31 * @static
32 * @access protected
33 * @internal
34 */
35 protected static $_aScreenIconIDs = array(
36 'edit', 'post', 'index', 'media', 'upload', 'link-manager', 'link', 'link-category',
37 'edit-pages', 'page', 'edit-comments', 'themes', 'plugins', 'users', 'profile',
38 'user-edit', 'tools', 'admin', 'options-general', 'ms-admin', 'generic',
39 );
40
41 /**
42 * Represents the array structure of an in-page tab array.
43 *
44 * @since 2.0.0
45 * @var array
46 * @static
47 * @access private
48 * @internal
49 */
50 private static $_aStructure_InPageTabElements = array(
51 'page_slug' => null,
52 'tab_slug' => null,
53 'title' => null,
54 'order' => null,
55 'show_in_page_tab' => true,
56 'parent_tab_slug' => null, // this needs to be set if the above show_in_page_tab is false so that the framework can mark the parent tab to be active when the hidden page is accessed.
57 );
58
59 /**
60 * Registers necessary hooks and sets up properties.
61 *
62 * @internal
63 */
64 function __construct( $sOptionKey=null, $sCallerPath=null, $sCapability='manage_options', $sTextDomain='admin-page-framework' ) {
65
66 parent::__construct( $sOptionKey, $sCallerPath, $sCapability, $sTextDomain );
67
68 if ( $this->oProp->bIsAdminAjax ) {
69 return;
70 }
71 add_action( "load_after_{$this->oProp->sClassName}", array( $this, '_replyToFinalizeInPageTabs' ), 19 ); // must be called before the _replyToRegisterSettings() method
72
73 }
74
75 /**
76 * Adds in-page tabs.
77 *
78 * The parameters accept in-page tab arrays and they must have the following array keys.
79 *
80 * <h4>Example</h4>
81 * <code>$this->addInPageTabs(
82 * array(
83 * 'page_slug' => 'myfirstpage'
84 * 'tab_slug' => 'firsttab',
85 * 'title' => __( 'Text Fields', 'my-text-domain' ),
86 * ),
87 * array(
88 * 'page_slug' => 'myfirstpage'
89 * 'tab_slug' => 'secondtab',
90 * 'title' => __( 'Selectors and Checkboxes', 'my-text-domain' ),
91 * )
92 * );</code>
93 *
94 * <code>$this->addInPageTabs(
95 * 'myfirstpage', // sets the target page slug
96 * array(
97 * 'tab_slug' => 'firsttab',
98 * 'title' => __( 'Text Fields', 'my-text-domain' ),
99 * ),
100 * array(
101 * 'tab_slug' => 'secondtab',
102 * 'title' => __( 'Selectors and Checkboxes', 'my-text-domain' ),
103 * )
104 * );</code>
105 * @since 2.0.0
106 * @since 3.0.0 Changed the scope to public. Added page slug target support.
107 * @param array $aTab1 The in-page tab array.
108 * <h4>In-Page Tab Array</h4>
109 * <ul>
110 * <li><strong>page_slug</strong> - ( string ) the page slug that the tab belongs to.</li>
111 * <li><strong>tab_slug</strong> - ( string ) the tab slug. Non-alphabetical characters should not be used including dots(.) and hyphens(-).</li>
112 * <li><strong>title</strong> - ( string ) the title of the tab.</li>
113 * <li><strong>order</strong> - ( optional, integer ) the order number of the tab. The lager the number is, the lower the position it is placed in the menu.</li>
114 * <li><strong>show_in_page_tab</strong> - ( optional, boolean ) default: false. If this is set to false, the tab title will not be displayed in the tab navigation menu; however, it is still accessible from the direct URL.</li>
115 * <li><strong>parent_tab_slug</strong> - ( optional, string ) this needs to be set if the above show_in_page_tab is true so that the parent tab will be emphasized as active when the hidden page is accessed.</li>
116 * </ul>
117 * @param array $aTab2 Another in-page tab array.
118 * @param array $_and_more Add in-page tab arrays as many as necessary to the next parameters.
119 * @param string (optional) $sPageSlug If the passed parameter item is a string, it will be stored as the target page slug so that it will be applied to the next passed tab arrays as the page_slug element.
120 * @remark Accepts variadic parameters; the number of accepted parameters are not limited to three.
121 * @remark In-page tabs are different from page-heading tabs which is automatically added with page titles.
122 * @return void
123 */
124 public function addInPageTabs( $aTab1, $aTab2=null, $_and_more=null ) {
125 foreach( func_get_args() as $asTab ) $this->addInPageTab( $asTab );
126 }
127
128 /**
129 * Adds an in-page tab.
130 *
131 * The singular form of the addInPageTabs() method, which takes only one parameter.
132 *
133 * @since 2.0.0
134 * @since 3.0.0 Changed the scope to public.
135 * @param array|string $asInPageTab The in-page tab array or the target page slug. If the target page slug is set, the page_slug key can be omitted from next calls.
136 * @remark Use this method to add in-page tabs to ensure the array holds all the necessary keys.
137 * @remark In-page tabs are different from page-heading tabs which are automatically added with page titles.
138 * @return void
139 */
140 public function addInPageTab( $asInPageTab ) {
141
142 static $__sTargetPageSlug; // stores the target page slug which will be applied when no page slug is specified.
143 if ( ! is_array( $asInPageTab ) ) {
144 $__sTargetPageSlug = is_string( $asInPageTab ) ? $asInPageTab : $__sTargetPageSlug; // set the target page slug
145 return;
146 }
147
148 $aInPageTab = $this->oUtil->uniteArrays( $asInPageTab, self::$_aStructure_InPageTabElements, array( 'page_slug' => $__sTargetPageSlug ) ); // avoid undefined index warnings.
149 $__sTargetPageSlug = $aInPageTab['page_slug']; // set the target page slug for next calls
150 if ( ! isset( $aInPageTab['page_slug'], $aInPageTab['tab_slug'] ) ) return; // check the required keys.
151
152 $iCountElement = isset( $this->oProp->aInPageTabs[ $aInPageTab['page_slug'] ] ) ? count( $this->oProp->aInPageTabs[ $aInPageTab['page_slug'] ] ) : 0;
153 $aInPageTab = array( // sanitize some elements
154 'page_slug' => $this->oUtil->sanitizeSlug( $aInPageTab['page_slug'] ),
155 'tab_slug' => $this->oUtil->sanitizeSlug( $aInPageTab['tab_slug'] ),
156 'order' => is_numeric( $aInPageTab['order'] ) ? $aInPageTab['order'] : $iCountElement + 10,
157 ) + $aInPageTab;
158
159 $this->oProp->aInPageTabs[ $aInPageTab['page_slug'] ][ $aInPageTab['tab_slug'] ] = $aInPageTab;
160
161 }
162
163 /**
164 * Sets whether the page title is displayed or not.
165 *
166 * <h4>Example</h4>
167 * <code>$this->setPageTitleVisibility( false ); // disables the page title.
168 * </code>
169 *
170 * @since 2.0.0
171 * @since 3.0.0 Changed the scope to public.
172 * @param boolean $bShow If false, the page title will not be displayed.
173 * @return void
174 */
175 public function setPageTitleVisibility( $bShow=true, $sPageSlug='' ) {
176
177 $sPageSlug = $this->oUtil->sanitizeSlug( $sPageSlug );
178 if ( $sPageSlug ) {
179 $this->oProp->aPages[ $sPageSlug ]['show_page_title'] = $bShow;
180 return;
181 }
182 $this->oProp->bShowPageTitle = $bShow;
183 foreach( $this->oProp->aPages as &$aPage )
184 $aPage['show_page_title'] = $bShow;
185
186 }
187
188 /**
189 * Sets whether page-heading tabs are displayed or not.
190 *
191 * <h4>Example</h4>
192 * <code>$this->setPageHeadingTabsVisibility( false ); // disables the page heading tabs by passing false.
193 * </code>
194 *
195 * @since 2.0.0
196 * @since 3.0.0 Changed the scope to public.
197 * @param boolean $bShow If false, page-heading tabs will be disabled; otherwise, enabled.
198 * @param string $sPageSlug The page to apply the visibility setting. If not set, it applies to all the pages.
199 * @remark Page-heading tabs and in-page tabs are different. The former displays page titles and the latter displays tab titles.
200 * @remark If the second parameter is omitted, it sets the default value.
201 */
202 public function setPageHeadingTabsVisibility( $bShow=true, $sPageSlug='' ) {
203
204 $sPageSlug = $this->oUtil->sanitizeSlug( $sPageSlug );
205 if ( $sPageSlug ) {
206 $this->oProp->aPages[ $sPageSlug ]['show_page_heading_tabs'] = $bShow;
207 return;
208 }
209 $this->oProp->bShowPageHeadingTabs = $bShow;
210 foreach( $this->oProp->aPages as &$aPage )
211 $aPage['show_page_heading_tabs'] = $bShow;
212
213 }
214
215 /**
216 * Sets whether in-page tabs are displayed or not.
217 *
218 * Sometimes, it is required to disable in-page tabs in certain pages. In that case, use the second parameter.
219 *
220 * @since 2.1.1
221 * @since 3.0.0 Changed the scope to public. Changed the name from showInPageTabs() to setInPageTabsVisibility().
222 * @param boolean $bShow If false, in-page tabs will be disabled.
223 * @param string $sPageSlug The page to apply the visibility setting. If not set, it applies to all the pages.
224 * @remark If the second parameter is omitted, it sets the default value.
225 */
226 public function setInPageTabsVisibility( $bShow=true, $sPageSlug='' ) {
227
228 $sPageSlug = $this->oUtil->sanitizeSlug( $sPageSlug );
229 if ( $sPageSlug ) {
230 $this->oProp->aPages[ $sPageSlug ]['show_in_page_tabs'] = $bShow;
231 return;
232 }
233 $this->oProp->bShowInPageTabs = $bShow;
234 foreach( $this->oProp->aPages as &$aPage )
235 $aPage['show_in_page_tabs'] = $bShow;
236
237 }
238
239 /**
240 * Sets in-page tab's HTML tag.
241 *
242 * <h4>Example</h4>
243 * <code>$this->setInPageTabTag( 'h2' );
244 * </code>
245 *
246 * @since 2.0.0
247 * @since 3.0.0 Changed the scope to public.
248 * @param string $sTag The HTML tag that encloses each in-page tab title. Default: h3.
249 * @param string $sPageSlug The page slug that applies the setting.
250 * @remark If the second parameter is omitted, it sets the default value.
251 */
252 public function setInPageTabTag( $sTag='h3', $sPageSlug='' ) {
253
254 $sPageSlug = $this->oUtil->sanitizeSlug( $sPageSlug );
255 if ( $sPageSlug ) {
256 $this->oProp->aPages[ $sPageSlug ]['in_page_tab_tag'] = $sTag;
257 return;
258 }
259 $this->oProp->sInPageTabTag = $sTag;
260 foreach( $this->oProp->aPages as &$aPage )
261 $aPage['in_page_tab_tag'] = $sTag;
262
263 }
264
265 /**
266 * Sets page-heading tab's HTML tag.
267 *
268 * <h4>Example</h4>
269 * <code>$this->setPageHeadingTabTag( 'h2' );
270 * </code>
271 *
272 * @since 2.1.2
273 * @since 3.0.0 Changed the scope to public.
274 * @param string $sTag The HTML tag that encloses the page-heading tab title. Default: h2.
275 * @param string $sPageSlug The page slug that applies the setting.
276 * @remark If the second parameter is omitted, it sets the default value.
277 */
278 public function setPageHeadingTabTag( $sTag='h2', $sPageSlug='' ) {
279
280 $sPageSlug = $this->oUtil->sanitizeSlug( $sPageSlug );
281 if ( $sPageSlug ) {
282 $this->oProp->aPages[ $sPageSlug ]['page_heading_tab_tag'] = $sTag;
283 return;
284 }
285 $this->oProp->sPageHeadingTabTag = $sTag;
286 foreach( $this->oProp->aPages as &$aPage )
287 $aPage[ $sPageSlug ]['page_heading_tab_tag'] = $sTag;
288
289 }
290
291 /*
292 * Internal Methods
293 */
294
295 /**
296 * Renders the admin page.
297 *
298 * @remark This is not intended for the users to use.
299 * @since 2.0.0
300 * @access protected
301 * @return void
302 * @internal
303 */
304 protected function _renderPage( $sPageSlug, $sTabSlug=null ) {
305
306 // Do actions before rendering the page. In this order, global -> page -> in-page tab
307 $this->oUtil->addAndDoActions(
308 $this, // the caller object
309 $this->oUtil->getFilterArrayByPrefix( 'do_before_', $this->oProp->sClassName, $sPageSlug, $sTabSlug, true ), // the action hooks
310 $this // the argument 1
311 );
312 ?>
313 <div class="wrap">
314 <?php
315 // Screen icon, page heading tabs(page title), and in-page tabs.
316 $sContentTop = $this->_getScreenIcon( $sPageSlug );
317 $sContentTop .= $this->_getPageHeadingTabs( $sPageSlug, $this->oProp->sPageHeadingTabTag );
318 $sContentTop .= $this->_getInPageTabs( $sPageSlug, $this->oProp->sInPageTabTag );
319
320 // Apply filters in this order, in-page tab -> page -> global.
321 echo $this->oUtil->addAndApplyFilters( $this, $this->oUtil->getFilterArrayByPrefix( 'content_foot_', $this->oProp->sClassName, $sPageSlug, $sTabSlug, false ), $sContentTop );
322
323 ?>
324 <div class="admin-page-framework-container">
325 <?php
326 $this->oUtil->addAndDoActions(
327 $this, // the caller object
328 $this->oUtil->getFilterArrayByPrefix( 'do_form_', $this->oProp->sClassName, $sPageSlug, $sTabSlug, true ), // the action hooks
329 $this // the argument 1
330 );
331 $this->_printFormOpeningTag( $this->oProp->bEnableForm ); // <form ... >
332 ?>
333 <div id="poststuff">
334 <div id="post-body" class="metabox-holder columns-<?php echo $this->_getNumberOfColumns(); ?>">
335 <?php
336 $this->_printMainContent( $sPageSlug, $sTabSlug );
337 $this->_printMetaBox( 'side', 1 ); // defined in the parrent class.
338 $this->_printMetaBox( 'normal', 2 );
339 $this->_printMetaBox( 'advanced', 3 );
340 ?>
341 </div><!-- #post-body -->
342 </div><!-- #poststuff -->
343
344 <?php echo $this->_printFormClosingTag( $sPageSlug, $sTabSlug, $this->oProp->bEnableForm ); // </form> ?>
345 </div><!-- .admin-page-framework-container -->
346
347 <?php
348 // Apply the content_bottom filters.
349 echo $this->oUtil->addAndApplyFilters( $this, $this->oUtil->getFilterArrayByPrefix( 'content_bottom_', $this->oProp->sClassName, $sPageSlug, $sTabSlug, false ), '' ); // empty string
350 ?>
351 </div><!-- .wrap -->
352 <?php
353 // Do actions after rendering the page.
354 $this->oUtil->addAndDoActions(
355 $this, // the caller object
356 $this->oUtil->getFilterArrayByPrefix( 'do_after_', $this->oProp->sClassName, $sPageSlug, $sTabSlug, true ), // the action hooks
357 $this // the argument 1
358 );
359
360 }
361
362 /**
363 * Renders the main content of the admin page.
364 * @since 3.0.0
365 */
366 private function _printMainContent( $sPageSlug, $sTabSlug ) {
367
368 /* Check if a sidebar meta box is registered */
369 $_bIsSideMetaboxExist = ( isset( $GLOBALS['wp_meta_boxes'][ $GLOBALS['page_hook'] ][ 'side' ] ) && count( $GLOBALS['wp_meta_boxes'][ $GLOBALS['page_hook'] ][ 'side' ] ) > 0 );
370
371 echo "<!-- main admin page content -->";
372 echo "<div class='admin-page-framework-content'>";
373 if ( $_bIsSideMetaboxExist ) {
374 echo "<div id='post-body-content'>";
375 }
376
377 /* Capture the output buffer */
378 ob_start(); // start buffer
379
380 // Render the form elements.
381 if ( $this->oProp->bEnableForm ) {
382
383 // do_settings_sections( $sPageSlug ); // deprecated
384 if ( $this->oForm->isPageAdded( $sPageSlug ) ) {
385
386 $this->aFieldErrors = isset( $this->aFieldErrors ) ? $this->aFieldErrors : $this->_getFieldErrors( $sPageSlug );
387 $oFieldsTable = new AdminPageFramework_FormTable( $this->oProp->aFieldTypeDefinitions, $this->aFieldErrors, $this->oMsg );
388 $this->oForm->setCurrentPageSlug( $sPageSlug );
389 $this->oForm->setCurrentTabSlug( $sTabSlug );
390 $this->oForm->applyConditions();
391 $this->oForm->applyFiltersToFields( $this, $this->oProp->sClassName ); // applies filters to the conditioned field definition arrays.
392 $this->oForm->setDynamicElements( $this->oProp->aOptions ); // will update $this->oForm->aConditionedFields
393 echo $oFieldsTable->getFormTables( $this->oForm->aConditionedSections, $this->oForm->aConditionedFields, array( $this, '_replyToGetSectionHeaderOutput' ), array( $this, '_replyToGetFieldOutput' ) );
394
395 }
396
397 }
398
399 $_sContent = ob_get_contents(); // assign the content buffer to a variable
400 ob_end_clean(); // end buffer and remove the buffer
401
402 // Apply the content filters.
403 echo $this->oUtil->addAndApplyFilters( $this, $this->oUtil->getFilterArrayByPrefix( 'content_', $this->oProp->sClassName, $sPageSlug, $sTabSlug, false ), $_sContent );
404
405 // Do the page actions.
406 $this->oUtil->addAndDoActions(
407 $this, // the caller object
408 $this->oUtil->getFilterArrayByPrefix( 'do_', $this->oProp->sClassName, $sPageSlug, $sTabSlug, true ), // the action hooks
409 $this // the argument 1
410 );
411
412 if ( $_bIsSideMetaboxExist )
413 echo "</div><!-- #post-body-content -->";
414 echo "</div><!-- .admin-page-framework-content -->";
415 }
416
417 /**
418 * Retrieves the form opening tag.
419 *
420 * @since 2.0.0
421 * @since 3.1.0 Changed to echo the output. Changed to remove disallowed query keys in the target action url.
422 * @internal
423 * @return void
424 */
425 private function _printFormOpeningTag( $fEnableForm=true ) {
426
427 if ( ! $fEnableForm ) {
428 return;
429 }
430
431 echo "<form "
432 . $this->oUtil->generateAttributes(
433 array(
434 'method' => 'post',
435 'enctype' => $this->oProp->sFormEncType,
436 'id' => 'admin-page-framework-form',
437 'action' => wp_unslash( remove_query_arg( 'settings-updated', $this->oProp->sTargetFormPage ) ),
438 )
439 )
440 . ">";
441 settings_fields( $this->oProp->sOptionKey );
442
443 }
444 /**
445 * Retrieves the form closing tag.
446 *
447 * @since 2.0.0
448 * @since 3.1.0 Prints out the output.
449 * @internal
450 * @return void
451 */
452 private function _printFormClosingTag( $sPageSlug, $sTabSlug, $fEnableForm=true ) {
453
454 if ( ! $fEnableForm ) {
455 return;
456 }
457
458 $_sNonce = '_admin_page_framework_form_nonce_' . uniqid();
459 $this->oUtil->setTransient( 'form_' . md5( $this->oProp->sClassName . get_current_user_id() ), $_sNonce, 60*60 ); // 60 minutes
460 echo "<input type='hidden' name='page_slug' value='{$sPageSlug}' />" . PHP_EOL
461 . "<input type='hidden' name='tab_slug' value='{$sTabSlug}' />" . PHP_EOL
462 . "<input type='hidden' name='_is_admin_page_framework' value='{$_sNonce}' />" . PHP_EOL
463 . "</form><!-- End Form -->" . PHP_EOL;
464
465 }
466
467 /**
468 * Retrieves the screen icon output as HTML.
469 *
470 * @remark the screen object is supported in WordPress 3.3 or above.
471 * @since 2.0.0
472 */
473 private function _getScreenIcon( $sPageSlug ) {
474
475 // If the icon path is explicitly set, use it.
476 if ( isset( $this->oProp->aPages[ $sPageSlug ]['href_icon_32x32'] ) )
477 return '<div class="icon32" style="background-image: url(' . $this->oProp->aPages[ $sPageSlug ]['href_icon_32x32'] . ');"><br /></div>';
478
479 // If the screen icon ID is explicitly set, use it.
480 if ( isset( $this->oProp->aPages[ $sPageSlug ]['screen_icon_id'] ) )
481 return '<div class="icon32" id="icon-' . $this->oProp->aPages[ $sPageSlug ]['screen_icon_id'] . '"><br /></div>';
482
483 // Retrieve the screen object for the current page.
484 $oScreen = get_current_screen();
485 $sIconIDAttribute = $this->_getScreenIDAttribute( $oScreen );
486
487 $sClass = 'icon32';
488 if ( empty( $sIconIDAttribute ) && $oScreen->post_type )
489 $sClass .= ' ' . sanitize_html_class( 'icon32-posts-' . $oScreen->post_type );
490
491 if ( empty( $sIconIDAttribute ) || $sIconIDAttribute == $this->oProp->sClassName )
492 $sIconIDAttribute = 'generic'; // the default value
493
494 return '<div id="icon-' . $sIconIDAttribute . '" class="' . $sClass . '"><br /></div>';
495
496 }
497 /**
498 * Retrieves the screen ID attribute from the given screen object.
499 *
500 * @since 2.0.0
501 */
502 private function _getScreenIDAttribute( $oScreen ) {
503
504 if ( ! empty( $oScreen->parent_base ) )
505 return $oScreen->parent_base;
506
507 if ( 'page' == $oScreen->post_type )
508 return 'edit-pages';
509
510 return esc_attr( $oScreen->base );
511
512 }
513
514 /**
515 * Retrieves the output of page heading tab navigation bar as HTML.
516 *
517 * @since 2.0.0
518 * @return string the output of page heading tabs.
519 */
520 private function _getPageHeadingTabs( $sCurrentPageSlug, $sTag='h2', $aOutput=array() ) {
521
522 // If the page title is disabled, return an empty string.
523 if ( ! $this->oProp->aPages[ $sCurrentPageSlug ][ 'show_page_title' ] ) return "";
524
525 $sTag = $this->oProp->aPages[ $sCurrentPageSlug ][ 'page_heading_tab_tag' ]
526 ? $this->oProp->aPages[ $sCurrentPageSlug ][ 'page_heading_tab_tag' ]
527 : $sTag;
528
529 // If the page heading tab visibility is disabled, or only one page is registered, return the title.
530 if ( ! $this->oProp->aPages[ $sCurrentPageSlug ][ 'show_page_heading_tabs' ] || count( $this->oProp->aPages ) == 1 )
531 return "<{$sTag}>" . $this->oProp->aPages[ $sCurrentPageSlug ]['title'] . "</{$sTag}>";
532
533 foreach( $this->oProp->aPages as $aSubPage ) {
534
535 // For added sub-pages
536 if ( isset( $aSubPage['page_slug'] ) && $aSubPage['show_page_heading_tab'] ) {
537 // Check if the current tab number matches the iteration number. If not match, then assign blank; otherwise put the active class name.
538 $sClassActive = $sCurrentPageSlug == $aSubPage['page_slug'] ? 'nav-tab-active' : '';
539 $aOutput[] = "<a class='nav-tab {$sClassActive}' "
540 . "href='" . $this->oUtil->getQueryAdminURL( array( 'page' => $aSubPage['page_slug'], 'tab' => false ), $this->oProp->aDisallowedQueryKeys )
541 . "'>"
542 . $aSubPage['title']
543 . "</a>";
544 }
545
546 // For added menu links
547 if (
548 isset( $aSubPage['href'] )
549 && $aSubPage['type'] == 'link'
550 && $aSubPage['show_page_heading_tab']
551 )
552 $aOutput[] =
553 "<a class='nav-tab link' "
554 . "href='{$aSubPage['href']}'>"
555 . $aSubPage['title']
556 . "</a>";
557
558 }
559 return "<div class='admin-page-framework-page-heading-tab'><{$sTag} class='nav-tab-wrapper'>"
560 . implode( '', $aOutput )
561 . "</{$sTag}></div>";
562
563 }
564
565 /**
566 * Retrieves the output of in-page tab navigation bar as HTML.
567 *
568 * @since 2.0.0
569 * @return string the output of in-page tabs.
570 */
571 private function _getInPageTabs( $sCurrentPageSlug, $sTag='h3', $aOutput=array() ) {
572
573 // If in-page tabs are not set, return an empty string.
574 if ( empty( $this->oProp->aInPageTabs[ $sCurrentPageSlug ] ) ) {
575 return implode( '', $aOutput );
576 }
577
578 // Determine the current tab slug.
579 $sCurrentTabSlug = isset( $_GET['tab'] ) ? $_GET['tab'] : $this->oProp->getDefaultInPageTab( $sCurrentPageSlug );
580 $sCurrentTabSlug = $this->_getParentTabSlug( $sCurrentPageSlug, $sCurrentTabSlug );
581
582 $sTag = $this->oProp->aPages[ $sCurrentPageSlug ][ 'in_page_tab_tag' ]
583 ? $this->oProp->aPages[ $sCurrentPageSlug ][ 'in_page_tab_tag' ]
584 : $sTag;
585
586 // If the in-page tabs' visibility is set to false, returns the title.
587 if ( ! $this->oProp->aPages[ $sCurrentPageSlug ][ 'show_in_page_tabs' ] ) {
588 return isset( $this->oProp->aInPageTabs[ $sCurrentPageSlug ][ $sCurrentTabSlug ]['title'] )
589 ? "<{$sTag}>{$this->oProp->aInPageTabs[ $sCurrentPageSlug ][ $sCurrentTabSlug ]['title']}</{$sTag}>"
590 : "";
591 }
592
593 // Get the actual string buffer.
594 foreach( $this->oProp->aInPageTabs[ $sCurrentPageSlug ] as $sTabSlug => $aInPageTab ) {
595
596 // If it's hidden and its parent tab is not set, skip
597 if ( ! $aInPageTab['show_in_page_tab'] && ! isset( $aInPageTab['parent_tab_slug'] ) ) { continue; }
598
599 // The parent tab means the root tab when there is a hidden tab that belongs to it. Also check it the specified parent tab exists.
600 $sInPageTabSlug = isset( $aInPageTab['parent_tab_slug'], $this->oProp->aInPageTabs[ $sCurrentPageSlug ][ $aInPageTab['parent_tab_slug'] ] )
601 ? $aInPageTab['parent_tab_slug']
602 : $aInPageTab['tab_slug'];
603
604 // Check if the current tab slug matches the iteration slug. If not match, assign blank; otherwise, put the active class name.
605 $bIsActiveTab = ( $sCurrentTabSlug == $sInPageTabSlug );
606
607 $aOutput[ $sInPageTabSlug ] = "<a class='nav-tab " . ( $bIsActiveTab ? "nav-tab-active" : "" ) . "' "
608 . "href='" . $this->oUtil->getQueryAdminURL( array( 'page' => $sCurrentPageSlug, 'tab' => $sInPageTabSlug ), $this->oProp->aDisallowedQueryKeys )
609 . "'>"
610 . $this->oProp->aInPageTabs[ $sCurrentPageSlug ][ $sInPageTabSlug ]['title'] // "{$aInPageTab['title']}"
611 . "</a>";
612
613 }
614
615 return empty( $aOutput )
616 ? ""
617 : "<div class='admin-page-framework-in-page-tab'><{$sTag} class='nav-tab-wrapper in-page-tab'>"
618 . implode( '', $aOutput )
619 . "</{$sTag}></div>";
620
621 }
622
623 /**
624 * Retrieves the parent tab slug from the given tab slug.
625 *
626 * @since 2.0.0
627 * @since 2.1.2 If the parent slug has the show_in_page_tab to be true, it returns an empty string.
628 * @return string the parent tab slug.
629 */
630 private function _getParentTabSlug( $sPageSlug, $sTabSlug ) {
631
632 $sParentTabSlug = isset( $this->oProp->aInPageTabs[ $sPageSlug ][ $sTabSlug ]['parent_tab_slug'] )
633 ? $this->oProp->aInPageTabs[ $sPageSlug ][ $sTabSlug ]['parent_tab_slug']
634 : $sTabSlug;
635
636 return isset( $this->oProp->aInPageTabs[ $sPageSlug ][ $sParentTabSlug ]['show_in_page_tab'] ) && $this->oProp->aInPageTabs[ $sPageSlug ][ $sParentTabSlug ]['show_in_page_tab']
637 ? $sParentTabSlug
638 : '';
639
640 }
641
642 /**
643 * Finalizes the in-page tab property array.
644 *
645 * This finalizes the added in-page tabs and sets the default in-page tab for each page.
646 * Also this sorts the in-page tab property array.
647 * This must be done before registering settings sections because the default tab needs to be determined in the process.
648 *
649 * @since 2.0.0
650 * @remark A callback for the <em>admin_menu</em> hook. It must be called earlier than _replyToRegisterSettings() method.
651 * @return void
652 */
653 public function _replyToFinalizeInPageTabs() {
654
655 if ( ! $this->oProp->isPageAdded() ) { return; }
656
657 foreach( $this->oProp->aPages as $sPageSlug => $aPage ) {
658
659 if ( ! isset( $this->oProp->aInPageTabs[ $sPageSlug ] ) ) { continue; }
660
661 // Apply filters to let modify the in-page tab array.
662 $this->oProp->aInPageTabs[ $sPageSlug ] = $this->oUtil->addAndApplyFilter( // Parameters: $oCallerObject, $sFilter, $vInput, $vArgs...
663 $this,
664 "tabs_{$this->oProp->sClassName}_{$sPageSlug}",
665 $this->oProp->aInPageTabs[ $sPageSlug ]
666 );
667 // Added in-page arrays may be missing necessary keys so merge them with the default array structure.
668 foreach( $this->oProp->aInPageTabs[ $sPageSlug ] as &$aInPageTab ) {
669 $aInPageTab = $aInPageTab + self::$_aStructure_InPageTabElements;
670 $aInPageTab['order'] = is_null( $aInPageTab['order'] ) ? 10 : $aInPageTab['order'];
671 }
672
673 // Sort the in-page tab array.
674 uasort( $this->oProp->aInPageTabs[ $sPageSlug ], array( $this, '_sortByOrder' ) );
675
676 // Set the default tab for the page.
677 // Read the value as reference; otherwise, a strange bug occurs. It may be due to the variable name, $aInPageTab, is also used as reference in the above foreach.
678 foreach( $this->oProp->aInPageTabs[ $sPageSlug ] as $sTabSlug => &$aInPageTab ) {
679
680 if ( ! isset( $aInPageTab['tab_slug'] ) ) { continue; }
681
682 // Regardless of whether it's a hidden tab, it is stored as the default in-page tab.
683 $this->oProp->aDefaultInPageTabs[ $sPageSlug ] = $aInPageTab['tab_slug'];
684
685 break; // The first iteration item is the default one.
686 }
687 }
688
689 }
690
691 }
692 endif;