Files
mediawiki-theme/includes/NextBestNetworkTemplate.php
D. Rimron-Soutter ea5dbcb05a Initial commit
2025-11-13 16:59:49 +00:00

1352 lines
46 KiB
PHP

<?php
/**
* NextBestNetwork - Tweaked version of Vector, using Twitter Bootstrap.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
* @ingroup Skins
*/
use MediaWiki\Linker\Linker;
use MediaWiki\MediaWikiServices;
use MediaWiki\Session\SessionManager;
use MediaWiki\Title\Title;
/**
* QuickTemplate subclass for Vector
* @ingroup Skins
*/
class NextBestNetworkTemplate extends BaseTemplate {
/* Functions */
/**
* Outputs the entire contents of the (X)HTML page
*/
public function execute() {
$this->data['namespace_urls'] = $this->data['content_navigation']['namespaces'];
$this->data['view_urls'] = $this->data['content_navigation']['views'];
$this->data['action_urls'] = $this->data['content_navigation']['actions'];
$this->data['variant_urls'] = $this->data['content_navigation']['variants'];
$this->data['watch_urls'] = [];
if( $GLOBALS['wgNextBestNetworkSkinUseRealnames'] == true && $this->data['username'] ) {
$this->data['username'] = NextBestNetworkHooks::getRealname( $this->data['username'] );
}
$this->data['advanced'] = MediaWikiServices::getInstance()->getUserOptionsLookup()->getOption( $this->getSkin()->getUser(), 'nextbestnetwork-advanced' );
// Remove the watch/unwatch star from the "actions" menu
if ( $this->config->get( 'NextBestNetworkSkinUseIconWatch' ) ) {
if ( method_exists( MediaWikiServices::class, 'getWatchlistManager' ) ) {
// MediaWiki 1.36+
$watchlistManager = MediaWikiServices::getInstance()->getWatchlistManager();
$watched = $watchlistManager->isWatched( $this->getSkin()->getUser(), $this->getSkin()->getRelevantTitle() );
} else {
$watched = $this->getSkin()->getUser()->isWatched( $this->getSkin()->getRelevantTitle() );
}
$mode = $watched ? 'unwatch' : 'watch';
if ( isset( $this->data['action_urls'][$mode] ) ) {
$this->data['watch_urls'][$mode] = $this->data['action_urls'][$mode];
unset( $this->data['action_urls'][$mode] );
}
}
$this->data['pageLanguage'] =
$this->getSkin()->getTitle()->getPageLanguage()->getHtmlCode();
//set userStateClass
if ( $this->data['loggedin'] ) {
$this->data['userstateclass'] = "user-loggedin";
} else {
$this->data['userstateclass'] = "user-loggedout";
}
if ( ( isset( $this->config->get( 'GroupPermissions' )['*']['edit'] ) && $this->config->get( 'GroupPermissions' )['*']['edit'] ) || $this->data['loggedin'] ) {
$this->data['userstateclass'] .= " editable";
} else {
$this->data['userstateclass'] .= " not-editable";
}
//set 'namespace' and 'title_formatted' variables
$this->data['title_formatted'] = $this->data['title'];
$this->data['namespace'] = str_replace( "_", " ", $this->getSkin()->getTitle()->getNsText() );
// the following is obsolete since MW 1.39, namespace and separator have their own spans
if( strpos( $this->data['title_formatted'], $this->data['namespace'] . ':' ) === 0 ) {
$this->data['title_formatted'] = '<span class="namespace">' . $this->data['namespace'] . ":</span> " . str_replace( $this->data['namespace'] . ':', '', $this->data['title_formatted'] );
}
call_user_func_array( $this->config->get( 'NextBestNetworkSkinPageRenderer' ), [ $this ] );
?>
</body>
</html>
<?php
}
/**
* Render the whole page
*
* Copy this function and use $wgNextBestNetworkSkinPageRenderer to create
* completely custom page layouts
*
* @param $skin Skin skin object
*/
public static function renderPage( $skin ) {
// load defaults for layout without sidebar
$main_offset = $skin->getConfig( 'NextBestNetworkSkinGridNone' )['mainoffset'];
$main_width = $skin->getConfig( 'NextBestNetworkSkinGridNone' )['mainwidth'];
$left_width = 0;
$left_offset = 0;
$right_width = 0;
$right_offset = 0;
// TODO: check for situational emptiness of sidebar (e.g. on special pages)
if( true ) {
$sidebar_left = $skin->checkVisibility( 'sidebar-left' ) && !$skin->checkEmptiness( 'sidebar-left' );
$sidebar_right = $skin->checkVisibility( 'sidebar-right' ) && !$skin->checkEmptiness( 'sidebar-right' );
if( $sidebar_left && $sidebar_right ) { // both sidebars
$left_offset = $skin->getConfig( 'NextBestNetworkSkinGridBoth' )['leftoffset'];
$left_width = $skin->getConfig( 'NextBestNetworkSkinGridBoth' )['leftwidth'];
$main_offset = $skin->getConfig( 'NextBestNetworkSkinGridBoth' )['mainoffset'];
$main_width = $skin->config->get( 'NextBestNetworkSkinGridBoth' )['mainwidth'];
$right_offset = $skin->getConfig( 'NextBestNetworkSkinGridBoth' )['rightoffset'];
$right_width = $skin->getConfig( 'NextBestNetworkSkinGridBoth' )['rightwidth'];
}
if( $sidebar_left XOR $sidebar_right ) { // only one of the sidebars
if( $sidebar_left ) {
$left_offset = $skin->getConfig( 'NextBestNetworkSkinGridLeft' )['leftoffset'];
$left_width = $skin->getConfig( 'NextBestNetworkSkinGridLeft' )['leftwidth'];
$main_offset = $skin->getConfig( 'NextBestNetworkSkinGridLeft' )['mainoffset'];
$main_width = $skin->getConfig( 'NextBestNetworkSkinGridLeft' )['mainwidth'];
} else {
$main_offset = $skin->getConfig( 'NextBestNetworkSkinGridRight' )['mainoffset'];
$main_width = $skin->getConfig( 'NextBestNetworkSkinGridRight' )['mainwidth'];
$right_offset = $skin->getConfig( 'NextBestNetworkSkinGridRight' )['rightoffset'];
$right_width = $skin->getConfig( 'NextBestNetworkSkinGridRight' )['rightwidth'];
}
}
}
$contentclass = $skin->data['userstateclass'];
$contentclass .= ' ' . wfMessage( 'nextbestnetwork-container-class' )->escaped();
$contentclass .= ( $skin->checkVisibility( 'navbar' ) ) ? ' with-navbar' : ' without-navbar';
if( false !== stripos( wfMessage( 'nextbestnetwork-navbar-class' ), 'fixed' ) ) {
$contentclass .= ' with-navbar-fixed';
}
$mainclass = 'col-md-' . $main_width;
if( $main_offset > 0 ) {
$mainclass .= ' offset-md-' . $main_offset;
} elseif( $main_width == 12 ) {
$mainclass = 'col';
}
call_user_func_array( $skin->getConfig( 'NextBestNetworkSkinNavbarRenderer' ), [ $skin ] );
?>
<main role="main">
<div id="mw-page-base"></div>
<div id="mw-head-base"></div>
<a id="top"></a>
<div id="contentwrapper" class="<?php echo $contentclass; ?>">
<div class="row">
<div id="maincontentwrapper" class="<?php echo $mainclass; ?>">
<?php if( !$skin->checkEmptiness( 'subnav' ) ) { $skin->renderSubnav( $mainclass ); } ?>
<!-- content -->
<main role="main">
<?php $skin->renderContent(); ?>
</main>
<!-- /content -->
</div>
<?php
if( !$skin->checkEmptiness( 'sidebar-left' ) ) {
$leftclass = 'col-md-' . $left_width;
$skin->renderSidebar( 'left', $leftclass );
}
if( !$skin->checkEmptiness( 'sidebar-right' ) ) {
$rightclass = 'col-md-' . $right_width;
$skin->renderSidebar( 'right', $rightclass );
}
?>
</div>
</div>
</main>
<?php
$skin->renderFooter();
}
public function getConfig( $config ) {
return $this->config->get($config);
}
/**
* Render one or more navigations elements by name, automatically reversed by css
* when UI is in RTL mode
*
* @param $elements
* @param String $context
*/
protected function renderNavigation( $elements, $context = '' ) {
// If only one element was given, wrap it in an array, allowing more
// flexible arguments
if ( !is_array( $elements ) ) {
$elements = [ $elements ];
// If there's a series of elements, reverse them when in RTL mode
} elseif ( $this->data['rtl'] ) {
$elements = array_reverse( $elements );
}
// Render elements
foreach ( $elements as $name => $element ) {
if ( !$this->checkVisibility( $element ) ) {
return [];
}
// was this element defined in LocalSettings?
if ( isset( $this->config->get( 'NextBestNetworkSkinNavigationalElements' )[ $element ] ) ) {
return call_user_func( $this->config->get( 'NextBestNetworkSkinNavigationalElements' )[ $element ], $this, $context );
}
// is it a special element with special non-buttonesque rendering?
if ( isset( $this->config->get( 'NextBestNetworkSkinSpecialElements' )[ $element ] ) ) {
return [ [ 'special' => $element ] ];
}
switch ( $element ) {
case 'EDIT':
$views = $this->data['view_urls'];
if(count( $views ) > 0) {
unset( $views['view'] );
if( isset( $views['formedit'] ) ) {
$link = $views['formedit'];
} else {
$link = array_shift( $views );
}
$link['icon'] = wfMessage( 'nextbestnetwork-edit-icon' )->plain();
$link['text'] = wfMessage( 'nextbestnetwork-edit', $link['text'] )->plain();
unset( $link['class'] ); // interferes with btn classing
if( $link['id'] == 'ca-ve-edit' ) {
$link['id'] = 'ca-edit';
}
return [ $link ];
}
return [];
break;
case 'EDIT-EXT':
$views = $this->data['view_urls'];
if(count( $views ) > 0) {
unset( $views['view'] );
if ( $this->checkVisibility( 'EDIT-EXT-special' ) ) {
$items = $views;
if(count($this->data['action_urls']) > 0) {
$items[] = []; #divider
$actions = $this->renderNavigation( 'ACTIONS' );
$items = array_merge( $items, $actions[0]['items'] );
}
if( strpos( $context, 'navbar' ) !== false ) {
$button = [
'href' => '#',
'html' => wfMessage( 'nextbestnetwork-edit-ext-nav' )->plain(),
'id' => 'ca-edit-ext',
'items' => $items
];
} else {
if( isset( $items['formedit'] ) ) {
$link = $items['formedit'];
unset( $items['formedit'] );
} else {
$link = array_shift( $items );
}
$button = [
'href' => $link['href'],
'href_implicit' => false,
'id' => 'ca-edit',
'icon' => wfMessage( 'nextbestnetwork-edit-ext-icon' )->plain(),
'text' => wfMessage( 'nextbestnetwork-edit-ext', $this->data['namespace'] )->plain(),
'name' => 'ca-edit-ext'
];
if( isset( $items['edit'] ) ) {
$items['edit']['id'] = 'ca-edit-source';
}
$button['items'] = $items;
}
} else {
$link = array_shift( $views );
$button = [
'href' => $link['href'],
'id' => 'ca-edit',
'icon' => wfMessage( 'nextbestnetwork-edit-ext-icon' )->plain(),
'text' => wfMessage( 'nextbestnetwork-edit-ext', $this->data['namespace'] )->plain()
];
}
return [ $button ];
}
return [];
break;
case 'PAGE':
$items = array_merge( $this->data['namespace_urls'], $this->data['view_urls'] );
$text = wfMessage( 'namespaces' );
foreach ( $items as $key => $link ) {
if ( array_key_exists( 'context', $link ) && $link['context'] == 'subject' ) {
$text = $link['text'];
}
if (preg_match('/^ca-(view|edit)$/', $link['id'])) {
unset( $items[$key] );
}
}
return [[
'href' => '#',
'text' => $text,
'id' => 'n-namespaces',
'items' => $items
]];
break;
case 'NAMESPACES':
$items = $this->data['namespace_urls'];
$text = wfMessage( 'namespaces' );
/*
foreach ( $items as $key => $link ) {
if ( array_key_exists( 'context', $link ) && $link['context'] == 'subject' ) {
$text = $link['text'];
}
if (
array_key_exists( 'attributes', $link ) && false !== strpos( $link['attributes'], 'selected' )
|| array_key_exists( 'class', $link ) && false !== strpos( $link['class'], 'selected' )
) {
unset( $items[$key] );
}
}
*/
return [[
'href' => '#',
'text' => $text,
'id' => 'n-namespaces',
'items' => $items
]];
break;
case 'TALK':
$items = $this->data['namespace_urls'];
foreach ( $items as $key => &$link ) {
if( isset( $link['context'] ) ) {
$link['icon'] = wfMessage( 'nextbestnetwork-namespace-' . $link['context'] . '-icon' )->plain();
}
if (
array_key_exists( 'attributes', $link ) && false !== strpos( $link['attributes'], 'selected' )
|| array_key_exists( 'class', $link ) && false !== strpos( $link['class'], 'selected' )
) {
unset( $items[$key] );
} else {
unset( $link['class'] ); // interferes with btn classing
}
}
return $items;
break;
case 'TOOLBOX':
$items = array_reverse($this->get('sidebar')['TOOLBOX']);
$divideditems = [];
$html = (wfMessage( 'nextbestnetwork-toolbox' )->plain() == "") ? wfMessage( 'toolbox' )->plain() : wfMessage( 'nextbestnetwork-toolbox' )->plain();
foreach($items as $key => $item) {
if(!isset( $item['text'] ) ) {
$item['text'] = wfMessage( isset( $item['msg'] ) ? $item['msg'] : $key )->text();
}
if(preg_match( '/specialpages|whatlinkshere/', $key )) {
$divideditems[] = [];
}
$divideditems[$key] = $item;
}
return [[
'href' => '#',
'html' => $html,
'id' => 't-tools',
'items' => $divideditems
]];
break;
case 'TOOLBOX-EXT':
$items = array_reverse($this->get('sidebar')['TOOLBOX']);
$divideditems = [];
$html = (wfMessage( 'nextbestnetwork-toolbox' )->plain() == "") ? wfMessage( 'toolbox' )->plain() : wfMessage( 'nextbestnetwork-toolbox' )->plain();
foreach($items as $key => $item) {
if(!isset( $item['text'] ) ) {
$item['text'] = wfMessage( isset( $item['msg'] ) ? $item['msg'] : $key )->text();
}
if(preg_match( '/specialpages|whatlinkshere/', $key )) {
$divideditems[] = [];
}
$divideditems[$key] = $item;
}
$divideditems[] = [];
$divideditems['recent'] = [
'text' => wfMessage( 'recentchanges' )->plain(),
'href' => Title::newFromText( 'Special:RecentChanges' )->getLocalURL(),
'id' => 't-recentchanges',
];
return [[
'href' => '#',
'html' => $html,
'id' => 't-tools',
'items' => $divideditems
]];
break;
case 'VARIANTS':
$theMsg = 'variants';
$items = $this->data['variant_urls'];
if (count($items) > 0) {
return [[
'href' => '#',
'text' => wfMessage( 'variants' ),
'id' => 'ca-variants',
'items' => $items
]];
}
break;
case 'VIEWS':
$items = $this->data['view_urls'];
if (count($items) > 0) {
return [[
'href' => '#',
'text' => wfMessage( 'views' ),
'id' => 'ca-views',
'items' => $items
]];
}
break;
case 'HISTORY':
$button = null;
$views = $this->data['view_urls'];
if( isset( $views['history'] ) ) {
if (
array_key_exists( 'attributes', $views['history'] ) && false !== strpos( $views['history']['attributes'], 'selected' )
|| array_key_exists( 'class', $views['history'] ) && false !== strpos( $views['history']['class'], 'selected' )
) {
$button = array_shift( $this->data['namespace_urls'] );
} else {
$button = $views['history'];
$button['icon'] = wfMessage( 'nextbestnetwork-history-icon' )->plain();
}
}
if( !is_null( $button ) ) {
$button['options'] = [ 'wrapperid' => $button['id'] ];
unset( $button['id'] );
unset( $button['class'] );
return [ $button ];
}
break;
case 'ACTIONS':
$items = array_reverse($this->data['action_urls']);
if (count($items) > 0) {
return [[
'href' => '#',
'text' => wfMessage( 'actions' ),
'id' => 'ca-actions',
'items' => $items
]];
}
break;
case 'WATCH':
$button = null;
$actions = $this->data['action_urls'];
if( isset( $actions['watch'] ) ) {
$button = $actions['watch'];
} else if( isset( $actions['unwatch'] ) ) {
$button = $actions['unwatch'];
}
if( !is_null( $button ) ) {
$button['options'] = [ 'wrapperid' => $button['id'] ];
unset( $button['id'] );
return [ $button ];
}
break;
case 'ICONWATCH':
$button = null;
$watch = $this->data['watch_urls'];
if( isset( $watch['watch'] ) ) {
$button = $watch['watch'];
} else if( isset( $watch['unwatch'] ) ) {
$button = $watch['unwatch'];
}
if( !is_null( $button ) ) {
$button['class'] .= 'icon';
$button['options'] = [
'wrapperid' => $button['id'],
'wrapperclass' => 'nav icon'
];
if( strpos( $context, 'nav' ) !== false ) {
$button['class'] .= ' nav-link';
$button['options']['wrapperclass'] .= ' nav-item';
}
unset( $button['id'] );
return [ $button ];
}
break;
case 'PERSONAL':
$items = $this->getPersonalTools();
$divideditems = [];
foreach($items as $key => $item) {
if(!isset( $item['text'] ) ) {
$item['text'] = wfMessage( isset( $item['msg'] ) ? $item['msg'] : $key )->text();
}
if(!isset( $item['href'] ) ) {
$item['href'] = $item['links'][0]['href'];
}
if(preg_match( '/preferences|logout/', $key )) {
$divideditems[] = [];
}
unset( $item['links'] );
$divideditems[$key] = $item;
}
if ( array_key_exists( 'login', $divideditems ) ) {
if( array_key_exists( 'createaccount', $divideditems ) ) { // If ConfirmAccount Extension is used
return [ $divideditems['login'], $divideditems['createaccount'] ];
}
$divideditems['login']['text'] = wfMessage( 'nextbestnetwork-login' )->plain();
return [ $divideditems['login'] ];
}
if ( array_key_exists( 'login-private', $divideditems ) ) {
if( array_key_exists( 'createaccount', $divideditems ) ) { // If ConfirmAccount Extension is used
return [ $divideditems['login-private'], $divideditems['createaccount'] ];
}
$divideditems['login-private']['text'] = wfMessage( 'nextbestnetwork-login' )->plain();
return [ $divideditems['login-private'] ];
}
if (count($items) > 0) {
$personal = [
'href' => '#',
'html' => '<span class="nextbestnetwork-username">' . wfMessage( 'nextbestnetwork-personaltools-text', $this->data['username'] )->text() . '</span>',
'icon' => wfMessage( 'nextbestnetwork-personaltools-icon' )->text(),
'id' => 'pt-personaltools',
'items' => $divideditems
];
if( isset( $GLOBALS['wgNextBestNetworkSkinUserImageProperty'] ) && $GLOBALS['wgNextBestNetworkSkinUserImageProperty'] !== false ) {
$userImages = \SMW\StoreFactory::getStore()->getPropertyValues( \SMW\DIWikiPage::newFromTitle( $this->getSkin()->getUser()->getUserPage() ), \SMW\DIProperty::newFromUserLabel( $GLOBALS['wgNextBestNetworkSkinUserImageProperty'] ) );
//die(var_dump( \SMW\DIProperty::newFromUserLabel( $GLOBALS['wgNextBestNetworkSkinUserImageProperty'] ) ) );
if ( !empty( $userImages ) ) {
$userImage = reset( $userImages );
if ( is_a( $userImage, '\SMW\DIWikiPage' ) && $userImage->getNamespace() === NS_FILE ) {
$imagePage = new WikiFilePage( $userImage->getTitle() );
$personal['html'] = '<img src="' . $imagePage->getFile()->createThumb( 31, 31 ) . '" class="nextbestnetwork-user-image">';
unset( $personal['icon'] );
}
}
}
return [$personal];
}
break;
case 'PERSONAL-EXT':
$items = $this->getPersonalTools();
$divideditems = [];
foreach($items as $key => $item) {
if(!isset( $item['text'] ) ) {
$item['text'] = wfMessage( isset( $item['msg'] ) ? $item['msg'] : $key )->text();
}
if(!isset( $item['href'] ) ) {
$item['href'] = isset( $item['links'][0]['href'] ) ? $item['links'][0]['href'] : '';
}
if(preg_match( '/preferences|logout/', $key )) {
$divideditems[] = [];
}
unset( $item['links'] );
$divideditems[$key] = $item;
}
if ( array_key_exists( 'login', $divideditems ) ) {
return [ [ 'special' => 'LOGIN-EXT' ] ];
}
if ( array_key_exists( 'login-private', $divideditems ) ) {
return [ [ 'special' => 'LOGIN-EXT' ] ];
}
if (count($items) > 0) {
return [[
'href' => '#',
'html' => '<span class="nextbestnetwork-username">' . wfMessage( 'nextbestnetwork-personaltools-text', $this->data['username'] )->text() . '</span>',
'icon' => wfMessage( 'nextbestnetwork-personaltools-icon' )->text(),
'id' => 'pt-personaltools-ext',
'items' => $divideditems
]];
}
break;
case 'LOGIN':
$items = $this->getPersonalTools();
if ( array_key_exists( 'login', $items ) ) {
if( array_key_exists( 'createaccount', $items ) ) { // If ConfirmAccount Extension is used
return [ $items['login'], $items['createaccount'] ];
}
$items['login']['links'][0]['text'] = wfMessage( 'nextbestnetwork-login' )->plain();
return [ $items['login'] ];
}
if ( array_key_exists( 'login-private', $items ) ) {
if( array_key_exists( 'createaccount', $items ) ) { // If ConfirmAccount Extension is used
return [ $items['login-private'], $items['createaccount'] ];
}
$items['login-private']['links'][0]['text'] = wfMessage( 'nextbestnetwork-login' )->plain();
return [ $items['login-private'] ];
}
return [];
break;
case 'SIDEBAR':
$sidebar = [];
foreach ( $this->data['sidebar'] as $name => $content ) {
if( !in_array( $name , ['TOOLBOX', 'SEARCH', 'LANGUAGES'] ) ) {
if ( empty ( $content ) ) {
if( strpos( $name, '|' ) !== false ) {
$parser = MediaWikiServices::getInstance()->getParserFactory()->create();
$parser->setOutputType(Parser::OT_HTML);
$sidebarItem = NextBestNetworkHooks::parseButtonLink( $name, $parser, false );
$sidebar[] = $sidebarItem[0];
continue;
}
// navigational keywords
$navigation = $this->renderNavigation( $name );
if( is_array( $navigation ) ) {
if( isset( $navigation[0] ) ) {
$sidebar[] = $navigation[0];
}
continue;
}
}
$msgObj = wfMessage( $name );
$name = htmlspecialchars( $msgObj->exists() ? $msgObj->text() : $name );
$sidebar[] = [
'href' => '#',
'text' => $name,
'items' => $content
];
}
}
return $sidebar;
break;
case 'LANGUAGES':
$items = $this->data['language_urls'];
if (is_array($items) && count($items) > 0 && $items) {
return [[
'href' => '#',
'text' => wfMessage( 'otherlanguages' ),
'id' => 'p-otherlanguages',
'items' => $items
]];
}
return [];
break;
default:
return $element;
break;
}
}
}
/**
* Check navigational sections for content
*
* @param $item String
*/
public function checkEmptiness( $item ) {
return wfMessage( 'nextbestnetwork-' . $item )->isDisabled();
}
/**
* Elements can be hidden for anonymous or logged in users or for everybody who has not opted
* to show the advanced features in their preferences
*
* @param $item String
*/
public function checkVisibility( $item ) {
$hookContainer = MediaWikiServices::getInstance()->getHookContainer();
return (
(
!$this->checkVisibilitySetting( $item, $this->config->get( 'NextBestNetworkSkinHideNonAdvanced' ) ) ||
$this->data['advanced'] // not hidden for non-advanced OR advanced
) &&
(
!$this->checkVisibilitySetting( $item, $this->config->get( 'NextBestNetworkSkinHideAnon' ) ) ||
$this->data['loggedin'] // not hidden for anonymous users OR non-anonymous user
) &&
(
!$this->checkVisibilitySetting( $item, $this->config->get( 'NextBestNetworkSkinHideLoggedin' ) ) ||
!$this->data['loggedin'] // not hidden for logged-in users OR anonymous user
) &&
!$this->checkVisibilitySetting( $item, $this->config->get( 'NextBestNetworkSkinHideAll' ) ) // not hidden for all
&&
!$this->checkVisibilityGroups( $item ) // not hidden for all OR user is in exempted group
&&
false !== $hookContainer->run( 'SkinNextBestNetworkCheckVisibility', [ $this, $item ] ) // not hidden via hook
);
}
/**
* Check if an element has an entry in a configuration option and if it's set to true
* (i.e. the element should be hidden)
*
* @param $item Element to be tested
* @param $setting Configuration option to be searched
*
* @return Boolean returns true, if the element is hidden
*/
public function checkVisibilitySetting( $item, $setting ) {
// this is for backwards compatibility
if( in_array( $item, $setting, true ) ) {
return true;
}
if( array_key_exists( $item, $setting ) ) {
return $setting[$item] ? true : false;
}
return false;
}
/**
* Check if an element has an entry in $wgNextBestNetworkSkinExcept or if the user is
* in the corresponding group
*
* @param $item Element to be tested
*
* @return Boolean returns true, if the element is hidden
*/
public function checkVisibilityGroups( $item ) {
// has the option been used?
if( !$this->config->has( 'NextBestNetworkSkinHideExcept' ) ) {
return false;
}
$group_settings = $this->config->get( 'NextBestNetworkSkinHideExcept' );
// has the option been set for this item?
if( isset( $group_settings[$item] ) && is_array( $group_settings[$item] ) ) {
$userGroupManager = MediaWikiServices::getInstance()->getUserGroupManager();
$groups = $userGroupManager->getUserEffectiveGroups($this->getSkin()->getUser());
// is the user in the exempted group?
if( count( array_intersect( $group_settings[$item], $groups ) ) > 0 ) {
return false;
}
return true;
}
return false;
}
/**
* Get a navigational element's content as defined in the respective message or customized with {{#nextbestnetworknav}}
*
* @param $item Element whose content should be returned
*
* @return String Element's content
*/
public function getNavContent( $item ) {
if( isset( $GLOBALS['wgNextBestNetworkSkinCustomNav'][$item] ) ) {
return $GLOBALS['wgNextBestNetworkSkinCustomNav'][$item];
}
return wfMessage( 'nextbestnetwork-' . $item )->plain();
}
/**
* Render Subnavigation
*/
public function renderSubnav() {
$options = $this->getParsingOptions( 'subnav' );
if( !wfMessage( 'nextbestnetwork-subnav' )->isDisabled() && $this->checkVisibility( 'subnav' ) ) { ?>
<!-- subnav -->
<div id="page-header">
<ul class="<?php $this->msg( 'nextbestnetwork-subnav-class' ) ?>">
<?php $this->buildItems( $this->getNavContent( 'subnav' ), $options, 'subnav' ); ?>
</ul>
</div>
<!-- /subnav -->
<?php }
}
/**
* Render Navbar
*
* @param $skin
*/
public static function renderNavbar($skin) {
$navbar_class = $skin->getMsg( 'nextbestnetwork-navbar-class' );
if ( $skin->checkVisibility( 'navbar' ) ) { ?>
<header>
<nav id="mw-navigation" class="<?php echo $navbar_class; ?>">
<div class="<?php echo wfMessage( 'nextbestnetwork-container-class' )->plain(); ?>">
<?php if ( $skin->checkVisibility( 'navbar-brand' ) ) {
$skin->renderBrand();
} ?>
<button type="button" class="navbar-toggler" data-bs-toggle="collapse" data-bs-target="#navbar" aria-controls="navbar" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div id="navbar" class="collapse navbar-collapse">
<?php if ( $skin->checkVisibility( 'navbar-left' ) ) { ?>
<ul class="navbar-nav me-auto">
<?php $skin->renderNavbarElement( 'left' ); ?>
</ul>
<?php } ?>
<?php if ( $skin->checkVisibility( 'navbar-right' ) ) { ?>
<ul class="navbar-nav">
<?php $skin->renderNavbarElement( 'right' ); ?>
</ul>
<?php } ?>
</div>
</div>
</nav>
</header>
<?php }
}
/**
* Render Navbarelement
*
* @param $side string
*/
public function renderNavbarElement( $side ) {
$element = 'navbar-' . $side;
$options = $this->getParsingOptions( $element );
$options['wrapperclass'] = 'nav-item';
$options['class'] = 'nav-link';
$this->buildItems( $this->getNavContent( $element ), $options, $element );
}
/**
* Render Sidebar
*
* @param $side string
* @param $class string
*/
public function renderSidebar( $side, $class = '' ) {
$element = 'sidebar-' . $side;
$options = $this->getParsingOptions( $element );
$classes = $class;
if( wfMessage( 'nextbestnetwork-sidebar-class' )->exists() ) {
$classes .= ' ' . $this->getMsg( 'nextbestnetwork-sidebar-class' );
}
if( wfMessage( 'nextbestnetwork-' . $element . '-class' )->exists() ) {
$classes .= ' ' . $this->getMsg( 'nextbestnetwork-' . $element . '-class' );
}
/* TODO: can we move these criteria elsewhere? rather there should be some handling for empty sidebars */
if ( ( true || count( $this->data['view_urls'] ) > 0 || $this->data['isarticle'] ) && $this->checkVisibility( $element ) ) { ?>
<!-- <?php echo $element;?> -->
<div id="<?php echo $element; ?>" class="<?php echo $classes; ?>">
<?php $this->buildItems( $this->getNavContent( $element ), $options, $element ); ?>
</div>
<!-- /<?php echo $element;?> -->
<?php }
}
/**
* Render Content
*/
public function renderContent() {
?>
<div class="mw-body" id="content">
<div id="mw-js-message" style="display:none;"<?php $this->html( 'userlangattributes' ) ?>></div>
<?php if ( $this->data['sitenotice'] ) { ?>
<!-- sitenotice -->
<div id="siteNotice"><?php $this->html( 'sitenotice' ) ?></div>
<!-- /sitenotice -->
<?php } ?>
<?php if ( $this->checkVisibility( 'firstHeading' ) ) { ?>
<h1 id="firstHeading" class="firstHeading page-header" lang="<?php
$this->data['pageLanguage'] = $this->getSkin()->getTitle()->getPageLanguage()->getHtmlCode();
$this->text( 'pageLanguage' );
?>"><span dir="auto"><?php $this->html( 'title_formatted' ) ?></span></h1>
<?php } ?>
<?php $this->html( 'prebodyhtml' ) ?>
<!-- bodyContent -->
<div id="bodyContent">
<?php if ( $this->data['isarticle'] ) { ?>
<div id="siteSub"><?php $this->msg( 'tagline' ) ?></div>
<?php } ?>
<div id="contentSub"<?php $this->html( 'userlangattributes' ) ?>><?php $this->html( 'subtitle' ) ?></div>
<?php if ( $this->data['undelete'] ) { ?>
<div id="contentSub2"><?php $this->html( 'undelete' ) ?></div>
<?php } ?>
<?php if ( $this->data['newtalk'] ) { ?>
<div class="usermessage"><?php $this->html( 'newtalk' ) ?></div>
<?php } ?>
<div id="jump-to-nav" class="mw-jump">
<?php $this->msg( 'jumpto' ) ?>
<a href="#mw-navigation"><?php $this->msg( 'jumptonavigation' ) ?></a><?php $this->msg( 'comma-separator' ) ?>
<a href="#p-search"><?php $this->msg( 'jumptosearch' ) ?></a>
</div>
<?php $this->html( 'bodycontent' ) ?>
<?php if ( $this->data['printfooter'] ) { ?>
<div class="printfooter">
<?php $this->html( 'printfooter' ); ?>
</div>
<?php } ?>
<?php if ( $this->data['catlinks'] ) { ?>
<?php $this->html( 'catlinks' ); ?>
<?php } ?>
<?php if ( $this->data['dataAfterContent'] ) { ?>
<?php $this->html( 'dataAfterContent' ); ?>
<?php } ?>
<div class="visualClear"></div>
<?php $this->html( 'debughtml' ); ?>
</div>
<!-- /bodyContent -->
</div>
<?php
}
public function renderFooter() {
$options = $this->getParsingOptions( 'footer' );
if ( $this->checkVisibility( 'footer' ) ) { ?>
<footer id="footer" role="contentinfo" class="footer <?php $this->msg( 'nextbestnetwork-footer-class' ); ?>"<?php $this->html( 'userlangattributes' ) ?>>
<div class="<?php $this->msg( 'nextbestnetwork-container-class' ); ?>">
<div class="row">
<?php $this->buildItems( $this->getNavContent( 'footer' ), $options, 'footer' ); ?>
</div>
</div>
</footer>
<?php }
}
/**
* Get options for navigational sections
*
* Options can be set via system messages
*
* @param $element string
*/
private function getParsingOptions( $element ) {
$options = [];
$available_options = [
'btnclass',
'wrapper',
'wrapperclass',
'dropdownclass'
];
foreach( $available_options as $option ) {
$msg = wfMessage( 'nextbestnetwork-' . $element . '-' . $option );
if( $msg->exists() ) {
/* the btnclass option's name for the parser is different */
if( $option === 'btnclass' ) {
$option = 'class';
}
$options[$option] = $msg->parse();
}
}
return $options;
}
/**
* Build Items for navbar, subnav, sidebar
*
* @param $items String
* @param $options Array
* @param $context String
*/
public function buildItems( $items, $options, $context ) {
$buttons = [];
$customItems = [];
$navbarItems = preg_split( '/[\n,]/', $items );
foreach( $navbarItems as $navbarItem ) {
$navbarItem = trim( $navbarItem );
$navbarItem = $this->renderNavigation( $navbarItem, $context );
if ( is_array( $navbarItem ) ) {
$this->renderCustomNavigation( $buttons, $customItems );
if(count($navbarItem) !== 0) {
$buttons = array_merge( $buttons, $navbarItem );
}
} else {
$customItems[] = $navbarItem;
}
}
$this->renderCustomNavigation( $buttons, $customItems );
foreach( $buttons as $button ) {
/* standard button rendering */
if( !isset( $button['special'] ) ) {
$button_options = [];
if( isset( $button['options'] ) ) {
$button_options = $button['options'];
unset( $button['options'] );
}
echo NextBestNetworkHooks::renderButtons( [ $button ], array_merge( $options, $button_options ) );
}
/* special cases */
else {
call_user_func_array( $this->config->get( 'NextBestNetworkSkinSpecialElements' )[$button['special']], [ $this, $context, $options ] );
}
}
}
/**
* Render navigations elements that renderNavigation hasn't dealt with
*
* @param $buttons array
* @param $customItems array
*/
private function renderCustomNavigation( &$buttons, &$customItems ) {
$parser = MediaWikiServices::getInstance()->getParserFactory()->create();
$parser->setOutputType(Parser::OT_HTML);
$parser->setOptions( ParserOptions::newFromContext( $this->getSkin()->getContext() ) );
$parser->setTitle( $this->getSkin()->getTitle() );
$parser->clearState();
if( count( $customItems ) !== 0 ) {
$newButtons = NextBestNetworkHooks::parseButtons( implode( chr(10), $customItems ), $parser, false );
$buttons = array_merge( $buttons, $newButtons );
$customItems = [];
}
}
/**
* Render firstheading
*/
public function renderFirstHeading( $skin, $context ) {
echo '<div class="nextbestnetworkFirstHeading">' . $skin->data[ 'title_formatted' ] . '</div>';
}
/**
* Render TOC
*/
public function renderTOC( $skin, $context ) {
if( $context == 'sidebar-left' || $context == 'sidebar-right' ) {
echo '<div id="nextbestnetworkTOC"></div>';
} else {
echo '<li class="nav dropdown" id="nextbestnetworkDropdownTOC"><a id="n-toc" class="dropdown-toggle" data-bs-toggle="dropdown" href="#">' . wfMessage( 'Toc' )->text() . '<span class="caret"></span></a><ul class="dropdown-menu pull-right" role="menu" id="nextbestnetworkTOC"><li><a href="#">' . wfMessage( 'nextbestnetwork-toc-top' )->text() . '</a></li><li class="divider"></li></ul></li>';
}
}
/**
* Render logo
*/
public function renderLogo( $skin, $context ) {
$mainPageLink = $skin->data['nav_urls']['mainpage']['href'];
$toolTip = Xml::expandAttributes( Linker::tooltipAndAccesskeyAttribs( 'p-logo' ) );
echo '
<a id="p-logo" href="' . htmlspecialchars( $skin->data['nav_urls']['mainpage']['href'] ) . '" ' . Xml::expandAttributes( Linker::tooltipAndAccesskeyAttribs( 'p-logo' ) ) . '>
<img src="';
$skin->text( 'logopath' );
echo '" alt="';
$skin->html( 'sitename' );
echo '"></a>';
}
/**
* Render Login-ext
*/
public function renderLoginExt( $skin, $context ) {
SessionManager::getGlobalSession()->persist();
//build path for form action
$returntotitle = $skin->getSkin()->getTitle();
$returnto = $returntotitle->getFullText();
if ( $returntotitle->equals( SpecialPage::getTitleFor( 'Userlogin' ) )
|| $returntotitle->equals( SpecialPage::getTitleFor( 'Userlogout' ) )
|| !$returntotitle->exists() ) {
$returnto = Title::newMainPage()->getFullText();
}
$returnto = $this->getSkin()->getRequest()->getVal( 'returnto', $returnto );
if ( $this->config->has( 'NextBestNetworkReturnto' ) && $returnto == Title::newMainPage()->getFullText() ) {
$returnto = $this->config->get( 'NextBestNetworkReturnto' );
}
$action = $GLOBALS['wgScript'] . '?title=Special:UserLogin&amp;action=submitlogin&amp;type=login&amp;returnto=' . $returnto;
//create login token if it doesn't exist
if( !$this->getSkin()->getRequest()->getSession()->getToken( '', 'login' ) ) $this->getSkin()->getRequest()->getSession()->resetToken( 'login' );
$this->getSkin()->getUser()->setCookies();
$dropdown['class'] = ' dropdown-toggle';
if( strpos( $context, 'navbar' ) === 0 ) {
$dropdown['class'] .= ' nav-link';
}
$dropdown['data-bs-toggle'] = 'dropdown';
$dropdown['text'] = $this->getMsg( 'nextbestnetwork-login' )->text();
$dropdown['html'] = $dropdown['text'] . ' <b class="caret"></b>';
$dropdown['href'] = '#';
$dropdown['type'] = 'button';
$dropdown['id'] = 'n-login-ext';
$renderedDropdown = NextBestNetworkHooks::makeLink( $dropdown);
$wrapperclass = ( $context == 'footer' ) ? 'dropup' : 'nav-item dropdown';
echo '<li class="' . $wrapperclass . '">
' . $renderedDropdown . '
<div class="dropdown-menu" role="menu" aria-labelledby="' . $this->getMsg( 'nextbestnetwork-login' )->text() . '" id="loginext">
<form action="' . $action . '" method="post" name="userloginext" class="clearfix p-1">
<div class="form-group">
<label for="wpName2" class="hidden-xs">
' . $this->getMsg( 'userlogin-yourname' )->text() . '
</label>';
echo Html::input( 'wpName', null, 'text', [
'class' => 'form-control input-sm',
'id' => 'wpName2',
'tabindex' => '101',
'placeholder' => $this->getMsg( 'userlogin-yourname-ph' )->text()
] );
echo '</div>
<div class="form-group">
<label for="wpPassword2" class="hidden-xs">
' . $this->getMsg( 'userlogin-yourpassword' )->text() . '
</label>';
echo Html::input( 'wpPassword', null, 'password', [
'class' => 'form-control input-sm',
'id' => 'wpPassword2',
'tabindex' => '102',
'placeholder' => $this->getMsg( 'userlogin-yourpassword-ph' )->text()
] );
echo '</div>
<div class="form-group">
<button type="submit" name="wpLoginAttempt" tabindex="103" id="wpLoginAttempt2" class="float-end btn btn-primary btn-block">
' . $this->getMsg( 'pt-login-button' )->text() . '
</button>
</div>
<input type="hidden" id="wpEditToken" value="+\" name="wpEditToken">
<input type="hidden" value="Special:UserLogin" name="title">
<input name="authAction" type="hidden" value="login">
<input name="force" type="hidden">
<input type="hidden" value="' . $this->getSkin()->getRequest()->getSession()->getToken( '', 'login' ) . '" name="wpLoginToken">
</form>';
if( $this->getSkin()->getUser()->isAllowed( 'createaccount' ) ) {
echo '<div class="nav center mb-2" id="tw-createaccount">
<a href="' . $GLOBALS['wgScript'] . '?title=special:userlogin&amp;type=signup">
' . $this->getMsg( 'createaccount' )->text() . '
</a>
</div>';
} else if( array_key_exists( 'createaccount', $this->getPersonalTools() ) ) { // ConfirmAccount Extension
echo '<div class="nav center mb-2" id="tw-requestaccount">
<a href="' . $GLOBALS['wgScript'] . '?title=special:requestaccount">
' . $this->getMsg( 'requestaccount-login' )->text() . '
</a>
</div>';
}
echo '
</div>
</li>';
/*
echo '<script>
$( document ).ready( function() {
$( "#n-login" ).click( function() {
if( ! $( this ).parent().hasClass( "open" ) ) {
setTimeout( \'$( "#wpName2" ).focus();\', 500 );
}
});
});
</script>';
*/
}
/**
* Render search
*/
public function renderSearch( $skin, $context ) {
if( $context == 'subnav' ) {
echo '<li class="nav dropdown">';
}
if( strpos( $context, 'navbar' ) === 0 ) {
echo '</ul>';
}
echo '
<form ';
if( $context == 'navbar-left' ) {
echo 'class="navbar-form navbar-left" ';
}
if( $context == 'navbar-right' ) {
echo 'class="navbar-form navbar-right" ';
}
echo 'action="';
$skin->text( 'wgScript' );
echo '" id="searchform">
<div class="form-inline">';
echo $skin->makeSearchInput( [
'id' => 'searchInput',
'class' => 'search-query form-control',
'placeholder' => $skin->getMsg( 'search' )->text()
] );
echo $skin->makeSearchButton( 'go', [
'id' => 'mw-searchButton',
'class' => 'searchButton btn d-none'
] );
echo '
</div>
</form>';
if( $context == 'navbar-left' ) {
echo '<ul class="nav navbar-nav">';
}
if( $context == 'navbar-right' ) {
echo '<ul class="nav navbar-nav navbar-right">';
}
if( $context == 'subnav' ) {
echo '</li>';
} else {
echo '
<li class="nav-item dropdown">
<button class="btn btn-link nav-link px-0 px-lg-2 py-2 dropdown-toggle d-flex align-items-center" id="bd-theme" type="button" aria-expanded="false" data-bs-toggle="dropdown" data-bs-display="static" aria-label="Toggle theme">
<i class="bi bi-circle-half"></i>
<span class="d-lg-none ms-2" id="bd-theme-text">Toggle theme</span>
</button>
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="bd-theme-text">
<li>
<button type="button" class="dropdown-item d-flex align-items-center" data-bs-theme-value="light" aria-pressed="false">
<i class="bi bi-sun-fill"></i>
Light
<i class="bi bi-check2 d-none" aria-hidden="true"></i>
</button>
</li>
<li>
<button type="button" class="dropdown-item d-flex align-items-center" data-bs-theme-value="dark" aria-pressed="false">
<i class="bi bi-moon-stars-fill"></i>
Dark
<i class="bi bi-check2 d-none" aria-hidden="true"></i>
</button>
</li>
<li>
<button type="button" class="dropdown-item d-flex align-items-center active" data-bs-theme-value="auto" aria-pressed="true">
<i class="bi bi-circle-half"></i>
Auto
<i class="bi bi-check2 d-none" aria-hidden="true"></i>
</button>
</li>
</ul>
</li>
';
}
}
/**
* Render brand (linking to mainpage)
*/
public function renderBrand() {
$brandmsg = wfMessage( 'nextbestnetwork-navbar-brand' );
if( !$brandmsg->isDisabled() ) {
$brand = $brandmsg->text();
if( isset( $GLOBALS['wgNextBestNetworkSkinCustomNav']['navbar-brand'] ) ) {
$brand = htmlspecialchars( $GLOBALS['wgNextBestNetworkSkinCustomNav']['navbar-brand'] );
}
/* is it a file? */
$brandimageTitle = Title::newFromText( $brand );
if ( ! is_null( $brandimageTitle ) && $brandimageTitle->exists() ) {
$wikiPageFactory = MediaWikiServices::getInstance()->getWikiPageFactory();
$brandimageWikiPage = $wikiPageFactory->newFromTitle( $brandimageTitle );
if ( method_exists( $brandimageWikiPage, 'getFile' ) ) {
$brandimage = $brandimageWikiPage->getFile()->getFullUrl();
$brand = '<img src="' . $brandimage . '" alt="' . $this->data['sitename'] . '" />';
}
}
$brandurl = $this->data['nav_urls']['mainpage']['href'];
if( isset( $GLOBALS['wgNextBestNetworkSkinCustomNav']['navbar-brand-url'] ) ) {
$brandurl = Title::newFromText( $GLOBALS['wgNextBestNetworkSkinCustomNav']['navbar-brand-url'] )->getLocalURL();
}
echo '<a href="' . htmlspecialchars( $brandurl ) . '" class="navbar-brand">' . $brand . '</a>';
}
}
/**
* Render standard MediaWiki footer
*/
public function renderStandardFooter( $options ) {
$options = $this->getParsingOptions( 'footer-standard' );
$widget_class = 'col-12 col-sm footer-widget';
foreach ( $this->getFooterLinks() as $category => $links ) {
if ( $this->checkVisibility( 'footer-' . $category ) ) {
echo '<div class="'.$widget_class.'"><ul id="footer-' . $category . '">';
foreach ( $links as $link ) {
if ( $this->checkVisibility( 'footer-' . $category . '-' . $link ) ) {
echo '<li id="footer-' . $category . '-' . $link . '">';
$this->html( $link );
echo '</li>';
}
}
echo '</ul></div>';
}
}
if ( $this->checkVisibility( 'footer-custom' ) ) {
if ( wfMessage ( 'nextbestnetwork-footer-custom' )->plain() !== "" ) {
echo '<div class="'.$widget_class.'"><ul id="footer-custom">';
$this->buildItems( wfMessage ( 'nextbestnetwork-footer-custom' )->plain(), $options, 'footer' );
echo '</ul></div>';
}
}
$footericons = $this->get('footericons');
if ( count( $footericons ) > 0 && $this->checkVisibility( 'footer-icons' ) ) {
echo '<div class="'.$widget_class.'"><ul id="footer-icons">';
foreach ( $footericons as $blockName => $footerIcons ) {
if ( $this->checkVisibility( 'footer-' . $blockName . 'ico' ) ) {
echo '<li id="footer-' . htmlspecialchars( $blockName ) . 'ico">';
foreach ( $footerIcons as $icon ) {
if($this->config->get( 'NextBestNetworkSkinFooterIcons' ) ) {
echo $this->getSkin()->makeFooterIcon( $icon );
} else {
echo '<span>' . $this->getSkin()->makeFooterIcon( $icon, 'withoutImage' ) . '</span>';
}
}
echo '</li>';
}
}
echo '</ul></div>';
}
}
}