Version 5.0.0 beta 1

This commit is contained in:
Neo committed 2025-12-19 16:27:35 -08:00
1 parent 25ddeb65d6
commit 15c7beabc5
6736 files changed
+627902 -497943

No files matched your search

+344 -164
View File
@@ -11,64 +11,102 @@
namespace IPS\Dispatcher;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use DateInterval;
use DomainException;
use IPS\Application;
use IPS\DateTime;
use IPS\Db;
use IPS\Developer;
use IPS\Helpers\Menu\Link;
use IPS\Http\Url;
use IPS\Lang;
use IPS\Member;
use IPS\Application\Module;
use IPS\Output;
use IPS\Platform\Bridge;
use IPS\Request;
use IPS\Session;
use IPS\Settings;
use IPS\Theme;
use UnderflowException;
use function array_merge;
use function count;
use function defined;
use function in_array;
use function is_array;
use function substr;
use const IPS\CIC;
use const IPS\DEMO_MODE;
use const IPS\ENFORCE_ACCESS;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* Admin CP Dispatcher
*/
class _Admin extends \IPS\Dispatcher\Standard
class Admin extends Standard
{
/**
* Controller Location
*/
public $controllerLocation = 'admin';
public string $controllerLocation = 'admin';
/**
* @brief Cached Menu
*/
protected $menu = NULL;
protected ?array $menu = NULL;
/**
* @brief Search Keywords
*/
public $searchKeywords = array();
public array $searchKeywords = array();
/**
* @brief ACP Restrictions (for search keyword editing)
*/
public $moduleRestrictions = array();
public array $moduleRestrictions = array();
/**
* @brief ACP Restriction for the current menu item (for search keyword editing)
*/
public $menuRestriction = NULL;
public ?string $menuRestriction = NULL;
const PLATFORM_PERMISSION_UPGRADE_NEEDED = -1;
const PLATFORM_PERMISSION_FUTURE_EXPIRES = -2;
/**
* Cache built URL map
* @var null
*/
public static mixed $permissionUrls = NULL;
/**
* Init
*
* @return void
*/
public function init()
public function init() : void
{
\IPS\Output::i()->sidebar['appmenu'] = '';
Output::i()->sidebar['appmenu'] = '';
/* Sync stuff when in developer mode */
if ( \IPS\IN_DEV )
{
\IPS\Developer::sync();
Developer::sync();
}
if ( \IPS\Member::loggedIn()->member_id )
if ( Member::loggedIn()->member_id )
{
/* Build the menu */
$menu = $this->buildMenu();
/* Do we need to figure out the default? */
if ( !isset( \IPS\Request::i()->app ) )
if ( !isset( Request::i()->app ) )
{
foreach ( $menu['tabs'] as $app => $appData )
{
@@ -77,7 +115,7 @@ class _Admin extends \IPS\Dispatcher\Standard
parse_str( $menu['defaults'][ $app ], $defaultQueryString );
foreach ( $defaultQueryString as $k => $v )
{
\IPS\Request::i()->$k = $v;
Request::i()->$k = $v;
}
break;
}
@@ -89,54 +127,66 @@ class _Admin extends \IPS\Dispatcher\Standard
static::baseCss();
static::baseJs();
/* Loader extension */
foreach( Application::allExtensions( 'core', 'Loader' ) as $loader )
{
foreach( $loader->js() as $js )
{
Output::i()->jsFiles = array_merge( Output::i()->jsFiles, $js );
}
foreach( $loader->css() as $css )
{
Output::i()->cssFiles = array_merge( Output::i()->cssFiles, $css );
}
}
/* Stuff needed for output */
if ( !\IPS\Request::i()->isAjax() )
if ( !Request::i()->isAjax() )
{
/* Special grouped CSS files */
\IPS\Output::i()->cssFiles = array_merge( \IPS\Output::i()->cssFiles, \IPS\Theme::i()->css( 'core.css', 'core', 'admin' ) );
\IPS\Output::i()->cssFiles = array_merge( \IPS\Output::i()->cssFiles, \IPS\Theme::i()->css( 'responsive.css', 'core', 'front' ) );
\IPS\Output::i()->cssFiles = array_merge( \IPS\Output::i()->cssFiles, \IPS\Theme::i()->css( 'responsive.css', 'core', 'admin' ) );
Output::i()->cssFiles = array_merge( Output::i()->cssFiles, Theme::i()->css( 'core/core.css', 'core', 'admin' ) );
/* JS */
\IPS\Output::i()->globalControllers[] = 'core.admin.core.app';
\IPS\Output::i()->jsFiles = array_merge( \IPS\Output::i()->jsFiles, \IPS\Output::i()->js( 'admin.js' ) );
\IPS\Output::i()->jsFiles = array_merge( \IPS\Output::i()->jsFiles, \IPS\Output::i()->js( 'jquery/jquery-ui.js', 'core', 'interface' ) );
\IPS\Output::i()->jsFiles = array_merge( \IPS\Output::i()->jsFiles, \IPS\Output::i()->js( 'jquery/jquery-touchpunch.js', 'core', 'interface' ) );
\IPS\Output::i()->jsFiles = array_merge( \IPS\Output::i()->jsFiles, \IPS\Output::i()->js( 'jquery/jquery.menuaim.js', 'core', 'interface' ) );
\IPS\Output::i()->jsFiles = array_merge( \IPS\Output::i()->jsFiles, \IPS\Output::i()->js( 'jquery/jquery.nestedSortable.js', 'core', 'interface' ) );
\IPS\Output::i()->jsFiles = array_merge( \IPS\Output::i()->jsFiles, \IPS\Output::i()->js( 'admin_core.js', 'core', 'front' ) );
Output::i()->globalControllers[] = 'core.admin.core.app';
Output::i()->jsFiles = array_merge( Output::i()->jsFiles, Output::i()->js( 'admin.js' ) );
Output::i()->jsFiles = array_merge( Output::i()->jsFiles, Output::i()->js( 'jquery/jquery-ui.js', 'core', 'interface' ) );
Output::i()->jsFiles = array_merge( Output::i()->jsFiles, Output::i()->js( 'jquery/jquery-touchpunch.js', 'core', 'interface' ) );
Output::i()->jsFiles = array_merge( Output::i()->jsFiles, Output::i()->js( 'jquery/jquery.menuaim.js', 'core', 'interface' ) );
Output::i()->jsFiles = array_merge( Output::i()->jsFiles, Output::i()->js( 'jquery/jquery.nestedSortable.js', 'core', 'interface' ) );
Output::i()->jsFiles = array_merge( Output::i()->jsFiles, Output::i()->js( 'admin_core.js', 'core', 'front' ) );
if ( \IPS\Member::loggedIn()->member_id )
if ( Member::loggedIn()->member_id )
{
/* These are just defaults in case we hit an immediate error, e.g. app or controller doesn't exist */
\IPS\Output::i()->sidebar['sidebar'] = \IPS\Theme::i()->getTemplate( 'global', 'core' )->sidebar( array(), 'core_overview' );
\IPS\Output::i()->sidebar['appmenu'] = \IPS\Theme::i()->getTemplate( 'global', 'core' )->appmenu( $menu, 'core', 'core_overview_dashboard' );
\IPS\Output::i()->sidebar['mobilenav'] = \IPS\Theme::i()->getTemplate( 'global', 'core' )->mobileNavigation( $menu, 'core' );
Output::i()->sidebar['sidebar'] = Theme::i()->getTemplate( 'global', 'core' )->sidebar( array(), 'core_overview' );
Output::i()->sidebar['appmenu'] = Theme::i()->getTemplate( 'global', 'core' )->appmenu( $menu, 'core', 'core_overview_dashboard' );
Output::i()->sidebar['mobilenav'] = Theme::i()->getTemplate( 'global', 'core' )->mobileNavigation( $menu, 'core' );
Output::i()->sidebar['quickLinks'] = $this->_getAcpQuickLinks();
}
}
/* Check we're logged in and we have ACP access */
if( ( !\IPS\Member::loggedIn()->member_id or !\IPS\Member::loggedIn()->isAdmin() )
and ( \IPS\Request::i()->module !== 'system' or \IPS\Request::i()->controller !== 'login' )
and ( !\IPS\ENFORCE_ACCESS )
if( ( !Member::loggedIn()->member_id or !Member::loggedIn()->isAdmin() )
and ( Request::i()->module !== 'system' or Request::i()->controller !== 'login' )
and ( !ENFORCE_ACCESS )
)
{
/* Make sure the right protocol is used. IIS, for example, does not like protocol relative URL's in redirects. (Ref: 970629) */
$protocol = \IPS\Http\Url::PROTOCOL_HTTP;
if ( \substr( \IPS\Settings::i()->base_url, 0, 5 ) == 'https' )
$protocol = Url::PROTOCOL_HTTP;
if ( substr( Settings::i()->base_url, 0, 5 ) == 'https' )
{
$protocol = \IPS\Http\Url::PROTOCOL_HTTPS;
$protocol = Url::PROTOCOL_HTTPS;
}
$url = \IPS\Http\Url::internal( "app=core&module=system&controller=login", 'admin', NULL, array(), $protocol );
$url = Url::internal( "app=core&module=system&controller=login", 'admin', NULL, array(), $protocol );
if ( \IPS\Session::i()->error )
if ( Session::i()->error and is_object( Session::i()->error ) )
{
$url = $url->setQueryString( 'error', \IPS\Session::i()->error->getMessage() );
$url = $url->setQueryString( 'error', Session::i()->error );
}
if( !\IPS\Request::i()->isAjax() )
if( !Request::i()->isAjax() )
{
/* If someone calls this from command line, while it wouldn't work, the key won't be set */
if( isset( $_SERVER['QUERY_STRING'] ) )
@@ -150,7 +200,7 @@ class _Admin extends \IPS\Dispatcher\Standard
$url = $url->setQueryString( 'ref', base64_encode( $previous ) );
}
\IPS\Output::i()->redirect( $url );
Output::i()->redirect( $url );
}
/* Init */
@@ -158,65 +208,75 @@ class _Admin extends \IPS\Dispatcher\Standard
{
parent::init();
}
catch ( \DomainException $e )
catch ( DomainException $e )
{
\IPS\Output::i()->error( $e->getMessage(), '2S100/' . $e->getCode(), $e->getCode() === 4 ? 403 : 404, '' );
Output::i()->error( $e->getMessage(), '2S100/' . $e->getCode(), $e->getCode() === 4 ? 403 : 404, '' );
}
/* Loader extension - check for redirects */
foreach( Application::allExtensions( 'core', 'Loader' ) as $loader )
{
/* First come first serve here */
if( $redirect = $loader->checkForRedirect() )
{
Output::i()->redirect( $redirect );
}
}
/* Unless there is a flag telling us we have specifically added CSRF checks, assume any AdminCP action which contains more than app/module/controller/id (i.e. anything with "do") requires CSRF-protection */
if ( !isset( $this->classname::$csrfProtected ) and array_diff( array_keys( \IPS\Request::i()->url()->queryString ), array( 'app', 'module', 'controller', 'id' ) ) )
if ( !isset( $this->classname::$csrfProtected ) and array_diff( array_keys( Request::i()->url()->queryString ), array( 'app', 'module', 'controller', 'id' ) ) )
{
\IPS\Session::i()->csrfCheck();
Session::i()->csrfCheck();
}
\IPS\Db::i()->readWriteSeparation = FALSE;
Db::i()->readWriteSeparation = FALSE;
if ( isset( $this->classname::$allowRWSeparation ) and $this->classname::$allowRWSeparation )
{
\IPS\Db::i()->readWriteSeparation = TRUE;
Db::i()->readWriteSeparation = TRUE;
}
/* If we are in recovery mode, but not actually doing the recovery process, or logging in, then we need them to remove the constant */
if ( \IPS\RECOVERY_MODE === TRUE AND !\in_array( $this->controller, array( 'recovery', 'login' ) ) )
if ( \IPS\RECOVERY_MODE AND !in_array( $this->controller, array( 'recovery', 'login' ) ) )
{
\IPS\Output::i()->error( 'recovery_mode_remove_constant', '1S107/3', 403, '' );
Output::i()->error( 'recovery_mode_remove_constant', '1S107/3', 403, '' );
}
/* Permission Check */
if (
(
$this->module->key !== 'system' or
!\in_array( $this->controller, array( 'login', 'language', 'theme', 'livesearch', 'editor', 'ajax' ) )
!in_array( $this->controller, array( 'login', 'language', 'theme', 'livesearch', 'editor', 'ajax' ) )
) and
/* Every admin can view and manage his own acp notification */
( $this->module->key !== 'overview' or $this->controller !== 'notifications' ) and
(
$this->module->key !== 'members' or
$this->controller !== 'members' or
!\in_array( \IPS\Request::i()->do, array( 'adminDetails', 'adminEmail', 'adminPassword' ) )
!in_array( Request::i()->do, array( 'adminDetails', 'adminEmail', 'adminPassword' ) )
) and
/* This is slightly hacky, but the upgrader was moved to the system module, however the ACP restriction is still set to overview.
To avoid unintentionally removing restrictions via an upgrade by moving the restriction, we reference the overview module for the restriction check instead */
!\IPS\Member::loggedIn()->hasAcpRestriction( $this->application, ( $this->application->directory === 'core' and $this->module->key === 'system' and $this->controller === 'upgrade' ) ? \IPS\Application\Module::get( 'core', 'overview', 'admin' ) : $this->module )
!Member::loggedIn()->hasAcpRestriction( $this->application, ( $this->application->directory === 'core' and $this->module->key === 'system' and $this->controller === 'upgrade' ) ? Application\Module::get( 'core', 'overview', 'admin' ) : $this->module )
)
{
\IPS\Output::i()->error( 'no_module_permission', '2S107/1', 403, '' );
Output::i()->error( 'no_module_permission', '2S107/1', 403, '' );
}
if( ! \IPS\Application::appIsEnabled( $this->application->directory ) )
if( ! Application::appIsEnabled( $this->application->directory ) )
{
\IPS\Output::i()->error( 'requested_route_404', '2S107/5', 404, '' );
Output::i()->error( 'requested_route_404', '2S107/5', 404, '' );
}
/* Support is not available for demos */
if ( \IPS\DEMO_MODE AND $this->module->application === 'core' AND $this->module->key === 'support' )
if ( DEMO_MODE AND $this->module->application === 'core' AND $this->module->key === 'support' )
{
\IPS\Output::i()->error( 'demo_mode_function_blocked', '1S107/4', 403, '' );
Output::i()->error( 'demo_mode_function_blocked', '1S107/4', 403, '' );
}
/* ACP search keywords */
if ( \IPS\IN_DEV )
{
foreach ( \IPS\Db::i()->select( '*', 'core_acp_search_index' ) as $word )
foreach ( Db::i()->select( '*', 'core_acp_search_index' ) as $word )
{
$this->searchKeywords[ $word['url'] ]['lang_key'] = $word['lang_key'];
$this->searchKeywords[ $word['url'] ]['restriction'] = $word['restriction'];
@@ -245,7 +305,7 @@ class _Admin extends \IPS\Dispatcher\Standard
{
foreach ( $appMenu[ $this->module->key ] as $menuItem )
{
if ( $menuItem['restriction'] and $menuItem['controller'] == $this->controller and ( !$menuItem['do'] or ( isset( \IPS\Request::i()->do ) and $menuItem['do'] == \IPS\Request::i()->do ) ) )
if ( $menuItem['restriction'] and $menuItem['controller'] == $this->controller and ( !$menuItem['do'] or ( isset( Request::i()->do ) and $menuItem['do'] == Request::i()->do ) ) )
{
$this->menuRestriction = $menuItem['restriction'];
}
@@ -254,10 +314,10 @@ class _Admin extends \IPS\Dispatcher\Standard
}
/* More stuff needed for output */
if ( !\IPS\Request::i()->isAjax() )
if ( !Request::i()->isAjax() )
{
/* Menu and base navigation */
if ( \IPS\Member::loggedIn()->member_id )
if ( Member::loggedIn()->member_id )
{
/* Work out what tab we're on */
$currentTab = NULL;
@@ -288,23 +348,45 @@ class _Admin extends \IPS\Dispatcher\Standard
$additionalChecksPass = TRUE;
if( isset( $item['menu_checks'] ) AND \is_array( $item['menu_checks'] ) )
/* If we set a do=value in the menu, add that to the list of menu checks */
if( isset( $item['do'] ) and $item['do'] )
{
if( isset( $item['menu_checks']['do'] ) and !is_array( $item['menu_checks']['do'] ) )
{
$item['menu_checks']['do'] = array( $item['menu_checks']['do'] );
}
$item['menu_checks']['do'][] = $item['do'];
}
if( isset( $item['menu_checks'] ) AND is_array( $item['menu_checks'] ) )
{
foreach( $item['menu_checks'] as $key => $value )
{
if( !isset( \IPS\Request::i()->$key ) OR \IPS\Request::i()->$key != $value )
{
$additionalChecksPass = FALSE;
break;
}
if( isset( Request::i()->$key ) )
{
if( is_array( $value ) and !in_array( Request::i()->$key, $value ) )
{
$additionalChecksPass = false;
break;
}
elseif( !is_array( $value ) and Request::i()->$key != $value )
{
$additionalChecksPass = false;
break;
}
}
else
{
$additionalChecksPass = false;
break;
}
}
}
if ( $additionalChecksPass === TRUE and ( $item['controller'] === $this->controller or ( isset( $item['subcontrollers'] ) and \in_array( $this->controller, explode( ",", $item['subcontrollers'] ) ) ) ) )
if ( $additionalChecksPass === TRUE and ( $item['controller'] === $this->controller or ( isset( $item['subcontrollers'] ) and in_array( $this->controller, explode( ",", $item['subcontrollers'] ) ) ) ) )
{
$controllerForKey = ( isset( $item['menu_controller'] ) ) ? $item['menu_controller'] : $item['controller'];
$currentItem = $this->application->directory . "_" . $moduleKey . "_" . $controllerForKey;
$currentItem = $item['activemenuitem'] ?? ( $this->application->directory . "_" . $moduleKey . "_" . $controllerForKey );
if( $currentTab != $item['tab'] )
{
@@ -323,10 +405,10 @@ class _Admin extends \IPS\Dispatcher\Standard
/* Display */
if ( isset( $menu['tabs'][ $currentTab ] ) )
{
\IPS\Output::i()->sidebar['sidebar'] = \IPS\Theme::i()->getTemplate( 'global', 'core' )->sidebar( $menu['tabs'][ $currentTab ], $this->application->directory . '_' . $this->module->key );
Output::i()->sidebar['sidebar'] = Theme::i()->getTemplate( 'global', 'core' )->sidebar( $menu['tabs'][ $currentTab ], $this->application->directory . '_' . $this->module->key );
}
\IPS\Output::i()->sidebar['appmenu'] = \IPS\Theme::i()->getTemplate( 'global', 'core' )->appmenu( $menu, $currentTab, $currentItem );
\IPS\Output::i()->sidebar['mobilenav'] = \IPS\Theme::i()->getTemplate( 'global', 'core' )->mobileNavigation( $menu, $currentTab, $currentItem );
Output::i()->sidebar['appmenu'] = Theme::i()->getTemplate( 'global', 'core' )->appmenu( $menu, $currentTab, $currentItem );
Output::i()->sidebar['mobilenav'] = Theme::i()->getTemplate( 'global', 'core' )->mobileNavigation( $menu, $currentTab, $currentItem );
}
}
}
@@ -334,23 +416,23 @@ class _Admin extends \IPS\Dispatcher\Standard
/**
* Build Menu
*
* @param bool $rebuild If TRUE, will rebuild
* @return array
* @param bool $rebuild If TRUE, will rebuild
* @return array|null
*/
public function buildMenu( $rebuild=FALSE )
public function buildMenu(bool $rebuild=FALSE ): ?array
{
$acpTabOrder = $this->_getAcpTabOrder();
if ( $this->menu === NULL or $rebuild === TRUE )
{
$this->menu = array( 'tabs' => array(), 'defaults' => array() );
foreach ( \IPS\Application::applications() as $app )
$this->menu = array( 'tabs' => array(), 'defaults' => array(), 'badges' );
foreach ( Application::applications() as $app )
{
if ( \IPS\Application::appIsEnabled( $app->directory ) and \IPS\Application::load( $app->directory )->canAccess() )
if ( Application::appIsEnabled( $app->directory ) and Application::load( $app->directory )->canAccess() )
{
$appMenu = $app->acpMenu();
if ( $acpTabOrder !== NULL and isset( $acpTabOrder[ $app->directory ] ) and $app->directory == 'nexus' )
{
uksort( $appMenu, function( $a, $b ) use ( $acpTabOrder, $app )
@@ -361,18 +443,18 @@ class _Admin extends \IPS\Dispatcher\Standard
foreach ( $appMenu as $moduleKey => $items )
{
foreach ( $items as $itemKey => $item )
{
if ( isset( $item['callback'] ) and !eval( $item['callback'] ) )
{
continue;
}
$moduleUrl = ( isset( $item['module_url'] ) ) ? $item['module_url'] : $moduleKey;
$moduleToCheck = ( isset( $item['restriction_module'] ) ) ? $item['restriction_module'] : $moduleKey;
if ( \IPS\Member::loggedIn()->hasAcpRestriction( $app, $moduleToCheck ) )
{
foreach ( $items as $itemKey => $item )
{
if ( isset( $item['callback'] ) and !eval( $item['callback'] ) )
{
continue;
}
$moduleUrl = ( isset( $item['module_url'] ) ) ? $item['module_url'] : $moduleKey;
$moduleToCheck = ( isset( $item['restriction_module'] ) ) ? $item['restriction_module'] : $moduleKey;
if ( Member::loggedIn()->hasAcpRestriction( $app, $moduleToCheck ) )
{
if( !$item['restriction'] )
{
$canAccess = TRUE;
@@ -392,7 +474,7 @@ class _Admin extends \IPS\Dispatcher\Standard
foreach( $restrictions as $restrictionKey )
{
if( \IPS\Member::loggedIn()->hasAcpRestriction( $app, $moduleToCheck, $restrictionKey ) )
if( Member::loggedIn()->hasAcpRestriction( $app, $moduleToCheck, $restrictionKey ) )
{
$canAccess = TRUE;
break;
@@ -400,28 +482,60 @@ class _Admin extends \IPS\Dispatcher\Standard
}
}
if ( $canAccess )
{
$this->menu['tabs'][ $item['tab'] ][ "{$app->directory}_{$moduleKey}" ][ $itemKey ] = "app={$app->directory}&module={$moduleUrl}&controller={$item['controller']}" . ( $item['do'] ? "&do={$item['do']}" : '' );
}
}
}
if( isset( $item['activemenuitem'] ) and $item['activemenuitem'] )
{
$groupKey = substr( $item['activemenuitem'], 0, strrpos( $item['activemenuitem'], '_' ) );
$itemKey = substr( $item['activemenuitem'], strrpos( $item['activemenuitem'], '_' ) + 1 );
}
else
{
$groupKey = $app->directory . "_" . $moduleKey;
}
if ( $canAccess )
{
$this->menu['tabs'][ $item['tab'] ][ "{$groupKey}" ][ $itemKey ] = "app={$app->directory}&module={$moduleUrl}&controller={$item['controller']}" . ( $item['do'] ? "&do={$item['do']}" : '' );
}
/* Custom badge */
if( isset( $item['badge'] ) and $item['badge'] )
{
$this->menu['badges'][ "{$groupKey}_{$itemKey}" ] = $item['badge'];
}
}
}
}
}
}
}
/* Developer Center */
if( \IPS\IN_DEV )
{
$currentApp = Request::i()->appKey ?? 'core';
foreach( $this->menu['tabs']['developer'] as $module => $controllers )
{
foreach( $controllers as $key => $url )
{
parse_str( $url, $params );
$params['module'] = 'developer';
$params['appKey'] = $currentApp;
$this->menu['tabs']['developer'][ $module ][ $key ] = http_build_query( $params );
}
}
}
if ( $acpTabOrder !== NULL )
{
$_apps = array_keys( $acpTabOrder );
uksort( $this->menu['tabs'], function($a, $b) use ( $_apps )
{
if( !\in_array( $a, $_apps ) )
if( !in_array( $a, $_apps ) )
{
return 1;
}
if( !\in_array( $b, $_apps ) )
if( !in_array( $b, $_apps ) )
{
return -1;
}
@@ -437,12 +551,12 @@ class _Admin extends \IPS\Dispatcher\Standard
{
uksort( $this->menu['tabs'][ $app ], function($a, $b) use ( $submenu )
{
if( !\in_array( $a, $submenu ) )
if( !in_array( $a, $submenu ) )
{
return 1;
}
if( !\in_array( $b, $submenu ) )
if( !in_array( $b, $submenu ) )
{
return -1;
}
@@ -475,47 +589,71 @@ class _Admin extends \IPS\Dispatcher\Standard
}
}
return $this->menu;
return Bridge::i()->alterAcpMenu( $this->menu );
}
/**
* Build an array of quick links for the top menu
*
* @return array|null
*/
protected function _getAcpQuickLinks() : ?array
{
$links = [];
if( Member::loggedIn()->hasAcpRestriction( 'core', 'support', 'get_support' ) )
{
$links[] = new Link( Url::internal( "app=core&module=support&controller=support&_new=1" ), 'support' );
$links[] = new Link( Url::internal( "app=core&module=support&controller=systemLogs" ), 'system_logs' );
$links[] = new Link( Url::internal( "app=core&module=support&controller=support&do=clearCaches" )->csrf(), 'health_clear_caches_button', dataAttributes: [ 'data-role' => 'clearCaches' ] );
}
if( Member::loggedIn()->hasAcpRestriction( 'core', 'settings', 'advanced_manage_tasks' ) )
{
$links[] = new Link( Url::internal( "app=core&module=settings&controller=advanced&do=tasks" ), 'task_manager' );
}
return count( $links ) ? $links : null;
}
/**
* @brief Cached ACP tab order
*/
protected $acpTabOrder = NULL;
protected ?array $acpTabOrder = NULL;
/**
* Figure out the ACP tab order
*
* @return array
* @return array|null
*/
public function _getAcpTabOrder()
public function _getAcpTabOrder(): ?array
{
if( $this->acpTabOrder !== NULL )
{
return $this->acpTabOrder;
}
if ( isset( \IPS\Request::i()->cookie['acpTabs'] ) AND !\IPS\Settings::i()->acp_menu_cookie_rebuild )
if ( isset( Request::i()->cookie['acpTabs'] ) AND ! Settings::i()->acp_menu_cookie_rebuild )
{
$this->acpTabOrder = json_decode( \IPS\Request::i()->cookie['acpTabs'], TRUE );
$this->acpTabOrder = json_decode( Request::i()->cookie['acpTabs'], TRUE );
}
else
{
if ( \IPS\Settings::i()->acp_menu_cookie_rebuild )
if ( Settings::i()->acp_menu_cookie_rebuild )
{
\IPS\Settings::i()->changeValues( array( 'acp_menu_cookie_rebuild' => 0 ) );
Settings::i()->changeValues( array( 'acp_menu_cookie_rebuild' => 0 ) );
}
try
{
$this->acpTabOrder = json_decode( \IPS\Db::i()->select( 'data', 'core_acp_tab_order', array( 'id=?', \IPS\Member::loggedIn()->member_id ) )->first(), TRUE );
$this->acpTabOrder = json_decode( Db::i()->select( 'data', 'core_acp_tab_order', array( 'id=?', Member::loggedIn()->member_id ) )->first(), TRUE );
}
catch( \UnderflowException $ex )
catch( UnderflowException $ex )
{
$this->acpTabOrder = array( 'core' => array(), 'community' => array(), 'members' => array(), 'nexus' => array(), 'cms' => array(), 'stats' => array(), 'customization' => array() );
}
\IPS\Request::i()->setCookie( 'acpTabs', json_encode( $this->acpTabOrder ) );
Request::i()->setCookie( 'acpTabs', json_encode( $this->acpTabOrder ) );
}
return $this->acpTabOrder;
@@ -524,60 +662,24 @@ class _Admin extends \IPS\Dispatcher\Standard
/**
* Display a link in a custom format
*
* @param string $url The URL from the menu system
* @param string $url The URL from the menu system
* @return string|null
*/
public function acpMenuCustom( $url ): ?string
public function acpMenuCustom( string $url ): ?string
{
if ( !\IPS\Application::appIsEnabled( 'cloud' ) AND $url == 'app=core&module=smartcommunity&controller=cloud' )
{
$url = \IPS\Http\Url::internal( $url, 'admin' );
$title = \IPS\Member::loggedIn()->language()->addToStack( "menu__core_smartcommunity_features" );
return <<<EOF
<a href='{$url}' style="opacity:30%">{$title} <i class="fa fa-lock"></i></a>
EOF;
}
return NULL;
return Bridge::i()->acpMenuCustom( $url );
}
/**
* Do we have permission to use this module?
*
* @param \IPS\Application $app Application
* @param \IPS\Module|string $module Module
* @param Application $app Application
* @param string|Module $module Module
* @return bool
*/
public function hasPermission( $app, $module )
public function hasPermission( Application $app, Module|string $module ): bool
{
return \IPS\Member::loggedIn()->hasAcpRestriction( $app, $module );
}
/**
* Check ACP Permission
*
* @param string $key Permission Key
* @param \IPS\Application|null $app Application (NULL will default to current)
* @param \IPS\Module|string|null $module Module (NULL will default to current)
* @param boolean $return Return boolean (true/false) instead of throwing an error
* @return void
*/
public function checkAcpPermission( $key, $app=NULL, $module=NULL, $return=FALSE )
{
if ( !\IPS\Member::loggedIn()->hasAcpRestriction( ( $app ?: $this->application ), ( $module ?: $this->module ), $key ) )
{
if ( $return )
{
return FALSE;
}
\IPS\Output::i()->error( 'no_module_permission', '2S107/2', 403, '' );
}
if ( $return )
{
return TRUE;
}
return Member::loggedIn()->hasAcpRestriction( $app, $module );
}
/**
@@ -587,23 +689,23 @@ EOF;
*/
final public static function showSwitchLink(): bool
{
if ( \IPS\CIC )
if ( CIC )
{
return false;
}
/* Don't show if installation was less than a month ago */
if ( time() < ( (int) \IPS\Settings::i()->board_start + ( 86400 * 30 ) ) )
if ( time() < ( (int) Settings::i()->board_start + ( 86400 * 30 ) ) )
{
return false;
}
if ( ! isset( \IPS\Request::i()->cookie['acpLinkSnooze'] ) )
if ( ! isset( Request::i()->cookie['acpLinkSnooze'] ) )
{
return true;
}
$value = json_decode( \IPS\Request::i()->cookie['acpLinkSnooze'], true );
$value = json_decode( Request::i()->cookie['acpLinkSnooze'], true );
if ( $value['hits'] < 3 )
{
@@ -632,4 +734,82 @@ EOF;
return true;
}
/**
* Base CSS
*
* @return void
*/
public static function baseCss() : void
{
parent::baseCss();
/* Stuff for output */
if ( !Request::i()->isAjax() )
{
Output::i()->cssFiles = array_merge( Output::i()->cssFiles, Theme::i()->css( 'core/core.css', 'core', 'admin' ) );
}
if ( count( Lang::languages() ) > 1 )
{
Output::i()->cssFiles = array_merge( Output::i()->cssFiles, Theme::i()->css( 'flags.css', 'core', 'global' ) );
}
}
/**
* Finish
*
* @return void
*/
public function finish() : void
{
if( !Request::i()->isAjax() )
{
$dismiss = [];
$updated = false;
if ( isset( Request::i()->cookie['acpDeprecations'] ) )
{
$dismiss = json_decode( Request::i()->cookie['acpDeprecations'], TRUE );
if ( ! is_array( $dismiss ) )
{
$dismiss = [];
}
}
if ( isset( Request::i()->deprecationDismiss ) )
{
$updated = true;
$dismiss[ Request::i()->deprecationDismiss ] = DateTime::create()->add( new DateInterval( 'P1M' ) )->getTimestamp();
}
foreach ( $dismiss as $key => $value )
{
if ( $value < time() )
{
$updated = true;
unset( $dismiss[ $key ] );
}
}
if ( $updated )
{
Request::i()->setCookie( 'acpDeprecations', json_encode( $dismiss ), DateTime::create()->add( new DateInterval( 'P1Y' ) ) );
}
if ( isset( Request::i()->deprecationDismiss ) )
{
Output::i()->redirect( Request::i()->url()->stripQueryString( 'deprecationDismiss' ) );
}
}
/* Loader Extension */
foreach( Application::allExtensions( 'core', 'Loader' ) as $loader )
{
$loader->onFinish();
}
parent::finish();
}
}