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