Version 5.0.13
This commit is contained in:
1 parent
b89858b2ff
commit
5efe744c5f
1407 files changed
+69690
-30177
No files matched your search
@@ -51,10 +51,15 @@ use function is_dir;
|
||||
use function is_file;
|
||||
use function is_string;
|
||||
use function json_encode;
|
||||
use function mb_strtolower;
|
||||
use function md5;
|
||||
use function mkdir;
|
||||
use function preg_match;
|
||||
use function rtrim;
|
||||
use function str_ends_with;
|
||||
use function substr;
|
||||
use function unlink;
|
||||
use function urlencode;
|
||||
use const IPS\CACHEBUST_KEY;
|
||||
use const IPS\IPS_FILE_PERMISSION;
|
||||
use const IPS\IPS_FOLDER_PERMISSION;
|
||||
@@ -891,10 +896,38 @@ class Javascript extends ActiveRecord
|
||||
'javascript_app, javascript_location, javascript_position'
|
||||
)->setKeyField('javascript_id') );
|
||||
|
||||
/* Web Components are not loaded along with the rest of dev/js/framework */
|
||||
$locationRows = [];
|
||||
$componentRows = [];
|
||||
foreach ( $rows as $id => $row )
|
||||
{
|
||||
if ( $fileName === 'framework.js' and $row['javascript_type'] === 'component' )
|
||||
{
|
||||
$componentRows[$id] = $row;
|
||||
}
|
||||
else
|
||||
{
|
||||
$locationRows[$id] = $row;
|
||||
}
|
||||
}
|
||||
|
||||
/* Write it */
|
||||
$obj = static::_writeJavascriptFromResultset( $rows, $fileName, $app, 'root' );
|
||||
$obj = static::_writeJavascriptFromResultset( $locationRows, $fileName, $app, 'root' );
|
||||
|
||||
$map[ $app ][ md5( $app .'-' . 'root' . '-' . $fileName ) ] = $obj ? (string) $obj : null;
|
||||
|
||||
/* Web Components should have their own file */
|
||||
foreach ( $componentRows as $id => $row )
|
||||
{
|
||||
$componentName = explode( '.', $row['javascript_name'] )[0];
|
||||
if ( !preg_match( "/^[a-zA-Z][a-zA-Z0-9]*$/", $componentName ) )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
$row['javascript_content'] = static::wrapWebComponentContents( $row['javascript_content'], $componentName );
|
||||
$componentObj = static::_writeJavascriptFromResultset( [ $id => $row ], "component_" . $row['javascript_name'], $app, 'root' );
|
||||
$map[ $app ][ md5( $app . '-root-' . "component_" . $row['javascript_name'] ) ] = $componentObj ? (string) $componentObj : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1170,7 +1203,7 @@ class Javascript extends ActiveRecord
|
||||
*
|
||||
* @param string $file Filename
|
||||
* @param string|null $app Application
|
||||
* @param string|null $location Location (e.g. 'admin', 'front')
|
||||
* @param string|null $location Location (e.g. 'admin', 'front', 'components')
|
||||
* @return array URL to JS files
|
||||
*/
|
||||
public static function inDevJs( string $file, string $app=NULL, string $location=NULL ): array
|
||||
@@ -1189,6 +1222,16 @@ class Javascript extends ActiveRecord
|
||||
$location = '/';
|
||||
}
|
||||
}
|
||||
|
||||
// Loading a web component? we then need to load from the file directly
|
||||
if ( $location === "components" )
|
||||
{
|
||||
if ( str_ends_with( $file, '.js' ) )
|
||||
{
|
||||
$file = mb_substr( $file, 0, -3 );
|
||||
}
|
||||
return array( Url::baseUrl() . "/applications/core/interface/js/webcomponents.php?component=" . urlencode( $file ) );
|
||||
}
|
||||
|
||||
$app = $app ?: ( Dispatcher::i()->application ? Dispatcher::i()->application->directory : NULL );
|
||||
$location = $location ?: Dispatcher::i()->controllerLocation;
|
||||
@@ -1223,7 +1266,7 @@ class Javascript extends ActiveRecord
|
||||
{
|
||||
return static::_directoryJs( ROOT_PATH . "/dev/js/" . mb_substr( $file, 0, -3 ) );
|
||||
}
|
||||
|
||||
|
||||
$path = ROOT_PATH . "/dev/js";
|
||||
}
|
||||
else
|
||||
@@ -1407,8 +1450,9 @@ class Javascript extends ActiveRecord
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( $file->isDir() )
|
||||
|
||||
// Skip the /framework/common/components directory
|
||||
if ( $file->isDir() && !( str_ends_with( $path, 'dev/js/framework/common' ) and rtrim( $file->getFilename(), '/' ) == 'components' ) )
|
||||
{
|
||||
$return = static::_directoryJs( $path . '/' . $file->getFileName(), $return );
|
||||
}
|
||||
@@ -1508,6 +1552,10 @@ class Javascript extends ActiveRecord
|
||||
{
|
||||
$type = 'mixins';
|
||||
}
|
||||
else if ( mb_strstr( $path, '/components/' ) )
|
||||
{
|
||||
$type = 'component';
|
||||
}
|
||||
else if ( mb_strstr( $path, '/ui/' ) )
|
||||
{
|
||||
$type = 'ui';
|
||||
@@ -1572,4 +1620,80 @@ class Javascript extends ActiveRecord
|
||||
|
||||
return $map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap contents with the web components
|
||||
*
|
||||
* @param string $contents
|
||||
* @param string $component The component's file name
|
||||
* @return string
|
||||
*/
|
||||
protected static function wrapWebComponentContents( string $contents, string $component ) : string
|
||||
{
|
||||
/* Start with some light validation to make sure the component is a valid string */
|
||||
if ( !preg_match( '/^[a-zA-Z][a-zA-Z0-9]*$/', $component ) )
|
||||
{
|
||||
throw new InvalidArgumentException( "The name {$component} is invalid. Expected an alphanumeric string (camel case)" );
|
||||
}
|
||||
|
||||
$encodedComponent = json_encode( $component );
|
||||
$errorStatement = <<<JS
|
||||
Debug.log(`The source file for the component \${{$encodedComponent}} does not contain a class that extends HTMLElement. If the component extends a class other than HTMLElement, ips.ui.registerWebComponent() will need to be invoked directly.`);
|
||||
JS;
|
||||
if ( preg_match( "/class\s+([a-zA-Z][a-zA-Z0-9]*)\s+extends\s+HTMLElement/", $contents, $matches ) )
|
||||
{
|
||||
$classStatement = <<<JS
|
||||
ips.ui.registerWebComponent( {$encodedComponent}, {$matches[1]} );
|
||||
Debug.log(`Submitted the web component constructor, {$matches[1]}, for \${{$encodedComponent}}`);
|
||||
JS;
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
$classStatement = $errorStatement;
|
||||
}
|
||||
|
||||
return <<<JS
|
||||
;(function() {
|
||||
"use strict";
|
||||
{$contents}
|
||||
|
||||
{$classStatement}
|
||||
})();
|
||||
JS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the JS that defines a Web Component
|
||||
*
|
||||
* @param string $component
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function generateWebComponent( string $component ) : string
|
||||
{
|
||||
/* Start with some light validation to make sure the component is a valid string */
|
||||
if ( !preg_match( '/^[a-zA-Z][a-zA-Z0-9]*$/', $component ) )
|
||||
{
|
||||
throw new InvalidArgumentException( "The name {$component} is invalid. Expected an alphanumeric string (camel case)" );
|
||||
}
|
||||
|
||||
if ( \IPS\IN_DEV )
|
||||
{
|
||||
$path = Application::getRootPath() . '/dev/js/framework/common/components/' . $component . '.js';
|
||||
if ( file_exists( $path ) and $contents = file_get_contents( $path ) )
|
||||
{
|
||||
return static::wrapWebComponentContents( $contents, $component );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$path = Application::getRootPath() . "static/js/global/root_component_" . $component . 'js';
|
||||
if ( file_exists( $path ) and $contents = file_get_contents( $path ) )
|
||||
{
|
||||
return $contents;
|
||||
}
|
||||
}
|
||||
throw new OutOfRangeException( "Cannot find file $path" );
|
||||
}
|
||||
}
|
||||
+72
-21
@@ -258,6 +258,11 @@ class Output
|
||||
* @brief Additional CSS classes to add to body tag
|
||||
*/
|
||||
public array $bodyClasses = array();
|
||||
|
||||
/**
|
||||
* @brief Additional data attributes to add to body tag
|
||||
*/
|
||||
public array $bodyAttributes = array();
|
||||
|
||||
/**
|
||||
* @brief Elements that can be hidden from view
|
||||
@@ -279,11 +284,6 @@ class Output
|
||||
*/
|
||||
public ?string $base = NULL;
|
||||
|
||||
/**
|
||||
* @brief Allow default widgets with this output
|
||||
*/
|
||||
public bool $allowDefaultWidgets = TRUE;
|
||||
|
||||
/**
|
||||
* @brief Allow page caching. This can be set at any point during controller execution to override defaults
|
||||
*/
|
||||
@@ -313,6 +313,9 @@ class Output
|
||||
{
|
||||
if ( Dispatcher::hasInstance() and Dispatcher::i()->controllerLocation !== 'setup' )
|
||||
{
|
||||
/* Additional security: https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cross-Origin-Opener-Policy */
|
||||
$this->httpHeaders['Cross-Origin-Opener-Policy'] = "same-origin";
|
||||
|
||||
if ( Settings::i()->clickjackprevention == 'csp' )
|
||||
{
|
||||
$this->httpHeaders['Content-Security-Policy'] = Settings::i()->csp_header;
|
||||
@@ -380,6 +383,7 @@ class Output
|
||||
* @li admin.js or front.js (these are controllers, templates and models which are used everywhere for that location)
|
||||
* @li app.js (this is all models for a single application)
|
||||
* @li {location}_{section}.js (this is all controllers and templates for this section called ad-hoc when needed)
|
||||
* @li {component}.js - Load a Web Component from the Web Component files; Must set $location to "components" when using this option
|
||||
*
|
||||
* @param string $file Filename
|
||||
* @param string|null $app Application
|
||||
@@ -391,7 +395,7 @@ class Output
|
||||
$file = trim( $file, '/' );
|
||||
|
||||
/* Legacy code support. The following directories were moved to /static */
|
||||
if ( ( $app === "core" or $app === null) and $location === "interface" and preg_match( "/^(?:codemirror|fontawesome|themeeditor)/", $file ) )
|
||||
if ( ( $app === "core" or $app === null) and $location === "interface" and preg_match( "/^(?:codemirror|fontawesome)/", $file ) )
|
||||
{
|
||||
$file = "static/" . $file;
|
||||
}
|
||||
@@ -412,7 +416,12 @@ class Output
|
||||
}
|
||||
|
||||
$fileObj = null;
|
||||
if ( $app === null OR $app === 'global' )
|
||||
if ( $location === 'components' )
|
||||
{
|
||||
$fileName = "component_" . $file;
|
||||
$fileObj = static::_getJavascriptFileObject( 'global', 'root', $fileName );
|
||||
}
|
||||
else if ( $app === null OR $app === 'global' )
|
||||
{
|
||||
if ( in_array( $file, static::$globalJavascript ) )
|
||||
{
|
||||
@@ -894,15 +903,6 @@ class Output
|
||||
$faulty = Member::loggedIn()->language()->addToStack( 'faulty_app', FALSE, array( 'sprintf' => array( $app->_title, Url::internal( 'app=core&module=applications&controller=applications', 'admin' ) ) ) );
|
||||
}
|
||||
|
||||
/* Send default Cache Headers if this is a front-end page. */
|
||||
if ( !isset( $httpHeaders['Cache-Control'] ) AND ( $httpStatusCode == 404 OR $httpStatusCode == 403 ) )
|
||||
{
|
||||
if ( CACHE_PAGE_TIMEOUT AND $this->pageCaching AND Dispatcher::hasInstance() AND Dispatcher::i()->controllerLocation === 'front' AND !Member::loggedIn()->member_id )
|
||||
{
|
||||
$httpHeaders += static::getCacheHeaders( time(), CACHE_PAGE_TIMEOUT);
|
||||
}
|
||||
}
|
||||
|
||||
/* Send output */
|
||||
if ( Application::appIsEnabled( 'cms' ) and isset( Settings::i()->cms_error_page ) and Settings::i()->cms_error_page )
|
||||
{
|
||||
@@ -910,6 +910,7 @@ class Output
|
||||
}
|
||||
else
|
||||
{
|
||||
Output::i()->setBodyAttributes();
|
||||
Output::i()->sidebar['enabled'] = FALSE;
|
||||
$this->sendOutput(Theme::i()->getTemplate('global', 'core')->globalTemplate($title, Theme::i()->getTemplate('global', 'core')->error($title, $message, $code, $extra, $member, $faulty, $httpStatusCode), array('app' => Dispatcher::i()->application ? Dispatcher::i()->application->directory : NULL, 'module' => Dispatcher::i()->module ? Dispatcher::i()->module->key : NULL, 'controller' => Dispatcher::i()->controller)), $httpStatusCode, 'text/html', $httpHeaders, FALSE );
|
||||
}
|
||||
@@ -1422,7 +1423,13 @@ JS;
|
||||
*/
|
||||
public function redirect( Url|string $url, ?string $message='', int $httpStatusCode=301, bool $forceScreen=FALSE ) : void
|
||||
{
|
||||
if( Request::i()->isAjax() )
|
||||
/* We have to cache here instead of waiting for sendOutput because this method can close the session */
|
||||
if ( DataLayer::hasInstance() and !$this->bypassDataLayer and Dispatcher::hasInstance() and Dispatcher::i()->controllerLocation == 'front' )
|
||||
{
|
||||
DataLayer::i()->cache();
|
||||
}
|
||||
|
||||
if( Request::i()->isAjax() and !Request::i()->bypassAjaxRedirect() )
|
||||
{
|
||||
if( !empty( $message ) )
|
||||
{
|
||||
@@ -1519,6 +1526,7 @@ JS;
|
||||
$this->title = Settings::i()->board_name;
|
||||
|
||||
Output::i()->sidebar['enabled'] = FALSE;
|
||||
Output::i()->setBodyAttributes();
|
||||
|
||||
DispatcherFront::i()->checkMfa();
|
||||
|
||||
@@ -1569,6 +1577,7 @@ JS;
|
||||
$this->title = Settings::i()->board_name;
|
||||
|
||||
Output::i()->sidebar['enabled'] = FALSE;
|
||||
Output::i()->setBodyAttributes();
|
||||
|
||||
/* Unset page token */
|
||||
unset( Output::i()->jsVars['page_token'] );
|
||||
@@ -1618,13 +1627,12 @@ JS;
|
||||
return true;
|
||||
}
|
||||
|
||||
if( isset( $this->sidebar['widgets']['sidebar'] ) and count( $this->sidebar['widgets']['sidebar'] ) )
|
||||
if( isset( $this->sidebar['widgetareas']['sidebar'] ) and !empty( $this->sidebar['widgetareas']['sidebar'] ) )
|
||||
{
|
||||
return true;
|
||||
return (bool) $this->sidebar['widgetareas']['sidebar']->totalVisibleWidgets();
|
||||
}
|
||||
|
||||
|
||||
if( isset( $this->sidebar['widgetareas']['sidebar'] ) and !empty( $this->sidebar['widgetareas']['sidebar'] ) )
|
||||
if( isset( $this->sidebar['widgets']['sidebar'] ) and count( $this->sidebar['widgets']['sidebar'] ) )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -1945,6 +1953,49 @@ JS;
|
||||
$this->jsonLd = array_merge( $this->jsonLd, $jsonLd );
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the data attributes that will be applied to the body tag
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setBodyAttributes() : void
|
||||
{
|
||||
if( !empty( $this->globalControllers ) )
|
||||
{
|
||||
$this->bodyAttributes['controller'] = implode( ",", $this->globalControllers );
|
||||
}
|
||||
|
||||
if( $this->inlineMessage )
|
||||
{
|
||||
$this->bodyAttributes['message'] = $this->inlineMessage;
|
||||
}
|
||||
|
||||
$this->bodyAttributes['pageApp'] = Dispatcher::i()->application->directory;
|
||||
$this->bodyAttributes['pageLocation'] = 'front';
|
||||
|
||||
/* The module may not necessarily be set, in the event of an error (for example) */
|
||||
if( isset( Dispatcher::i()->module ) )
|
||||
{
|
||||
$this->bodyAttributes['pageModule'] = Dispatcher::i()->module->key;
|
||||
}
|
||||
|
||||
$this->bodyAttributes['pageController'] = Dispatcher::i()->controller;
|
||||
if( isset( Request::i()->id ) )
|
||||
{
|
||||
$this->bodyAttributes['id'] = (int) Request::i()->id;
|
||||
}
|
||||
|
||||
if( isset( Dispatcher::i()->dispatcherController ) and !Dispatcher::i()->dispatcherController->isContentPage )
|
||||
{
|
||||
$this->bodyAttributes['nocontent'] = '';
|
||||
}
|
||||
|
||||
if( $this->pageName )
|
||||
{
|
||||
$this->bodyAttributes['pageName'] = $this->pageName;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Global search menu options
|
||||
*/
|
||||
|
||||
@@ -12,11 +12,8 @@ namespace IPS\Output\Plugin;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
|
||||
use Exception;
|
||||
use IPS\Application;
|
||||
use IPS\Data\Cache;
|
||||
use IPS\IPS;
|
||||
use IPS\Member;
|
||||
use IPS\Theme;
|
||||
use function array_pop;
|
||||
use function count;
|
||||
@@ -69,7 +66,11 @@ class Resource
|
||||
return '"../../applications/' . $app . '/interface/' . $data . '"';
|
||||
}
|
||||
|
||||
return '"../resources/' . $app . '_' . $hash . '_' . $name . '"';
|
||||
/* If we're building from install or upgrade, we need to use the relative paths otherwise the theme will use the building site's URL in the static files */
|
||||
if ( $location !== 'admin' )
|
||||
{
|
||||
return '"../resources/' . $app . '_' . $hash . '_' . $name . '"';
|
||||
}
|
||||
}
|
||||
|
||||
return "\\IPS\\Theme::i()->resource( \"{$data}\", \"{$app}\", '{$location}', {$noProtocol} )";
|
||||
|
||||
Reference in new issue
Block a user