Version 5.0.0 beta 1
This commit is contained in:
1 parent
25ddeb65d6
commit
15c7beabc5
6736 files changed
+627902
-497943
No files matched your search
+344
-164
@@ -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();
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user