Version 5.0.0 beta 2
This commit is contained in:
1 parent
15c7beabc5
commit
f61b0da85d
181 files changed
+3182
-1533
No files matched your search
@@ -15,6 +15,7 @@ interface Browsers {
|
||||
const BROWSER = 'Browser';
|
||||
const BUNJALLOO = 'Bunjalloo';
|
||||
const CAMINO = 'Camino';
|
||||
const CHATGPT_USER = 'ChatGPT-User';
|
||||
const CHROME = 'Chrome';
|
||||
const CURL = 'curl';
|
||||
const EDGE = 'Edge';
|
||||
@@ -24,17 +25,20 @@ interface Browsers {
|
||||
const GOOGLEBOT = 'Googlebot';
|
||||
const GOOGLEBOT_IMAGE = 'Googlebot-Image';
|
||||
const GOOGLEBOT_VIDEO = 'Googlebot-Video';
|
||||
const GPTBOT = 'GPTBot';
|
||||
const HEADLESSCHROME = 'HeadlessChrome';
|
||||
const IEMOBILE = 'IEMobile';
|
||||
const IMESSAGEBOT = 'iMessageBot';
|
||||
const KINDLE = 'Kindle';
|
||||
const LYNX = 'Lynx';
|
||||
const MASTODON = 'Mastodon';
|
||||
const MIDORI = 'Midori';
|
||||
const MIUIBROWSER = 'MiuiBrowser';
|
||||
const MSIE = 'MSIE';
|
||||
const MSNBOT_MEDIA = 'msnbot-media';
|
||||
const NETFRONT = 'NetFront';
|
||||
const NINTENDOBROWSER = 'NintendoBrowser';
|
||||
const OAI_SEARCHBOT = 'OAI-SearchBot';
|
||||
const OCULUSBROWSER = 'OculusBrowser';
|
||||
const OPERA = 'Opera';
|
||||
const PUFFIN = 'Puffin';
|
||||
@@ -42,6 +46,7 @@ interface Browsers {
|
||||
const SAILFISHBROWSER = 'SailfishBrowser';
|
||||
const SAMSUNGBROWSER = 'SamsungBrowser';
|
||||
const SILK = 'Silk';
|
||||
const SLACKBOT = 'Slackbot';
|
||||
const TELEGRAMBOT = 'TelegramBot';
|
||||
const TIZENBROWSER = 'TizenBrowser';
|
||||
const TWITTERBOT = 'Twitterbot';
|
||||
|
||||
+21
-15
@@ -90,7 +90,7 @@ REGEX
|
||||
} elseif( $platform == 'Adr' ) {
|
||||
$platform = 'Android';
|
||||
} elseif( $platform === null ) {
|
||||
if(preg_match_all('%(?P<platform>Android)[:/ ]%ix', $u_agent, $result)) {
|
||||
if( preg_match_all('%(?P<platform>Android)[:/ ]%ix', $u_agent, $result) ) {
|
||||
$platform = $result[PLATFORM][0];
|
||||
}
|
||||
}
|
||||
@@ -98,18 +98,19 @@ REGEX
|
||||
preg_match_all(<<<'REGEX'
|
||||
%(?P<browser>Camino|Kindle(\ Fire)?|Firefox|Iceweasel|IceCat|Safari|MSIE|Trident|AppleWebKit|
|
||||
TizenBrowser|(?:Headless)?Chrome|YaBrowser|Vivaldi|IEMobile|Opera|OPR|Silk|Midori|(?-i:Edge)|EdgA?|CriOS|UCBrowser|Puffin|
|
||||
OculusBrowser|SamsungBrowser|SailfishBrowser|XiaoMi/MiuiBrowser|
|
||||
Baiduspider|Applebot|Facebot|Googlebot|YandexBot|bingbot|Lynx|Version|Wget|curl|
|
||||
Valve\ Steam\ Tenfoot|
|
||||
OculusBrowser|SamsungBrowser|SailfishBrowser|XiaoMi/MiuiBrowser|YaApp_Android|
|
||||
Baiduspider|Applebot|Facebot|Googlebot|YandexBot|bingbot|Lynx|Version|Wget|curl|ChatGPT-User|GPTBot|OAI-SearchBot|
|
||||
Valve\ Steam\ Tenfoot|Mastodon|
|
||||
NintendoBrowser|PLAYSTATION\ (?:\d|Vita)+)
|
||||
\)?;?
|
||||
(?:[:/ ](?P<version>[0-9A-Z.]+)|/[A-Z]*)%ix
|
||||
(?:[:/ ](?P<version>[0-9A-Z.]+)|/[A-Z]*)
|
||||
%ix
|
||||
REGEX
|
||||
, $u_agent, $result);
|
||||
|
||||
// If nothing matched, return null (to avoid undefined index errors)
|
||||
if( !isset($result[BROWSER][0], $result[BROWSER_VERSION][0]) ) {
|
||||
if( preg_match('%^(?!Mozilla)(?P<browser>[A-Z0-9\-]+)(/(?P<version>[0-9A-Z.]+))?%ix', $u_agent, $result) ) {
|
||||
if( preg_match('%^(?!Mozilla)(?P<browser>[A-Z0-9\-]+)([/ :](?P<version>[0-9A-Z.]+))?%ix', $u_agent, $result) ) {
|
||||
return [ PLATFORM => $platform ?: null, BROWSER => $result[BROWSER], BROWSER_VERSION => empty($result[BROWSER_VERSION]) ? null : $result[BROWSER_VERSION] ];
|
||||
}
|
||||
|
||||
@@ -125,7 +126,7 @@ REGEX
|
||||
|
||||
$lowerBrowser = array_map('strtolower', $result[BROWSER]);
|
||||
|
||||
$find = function ( $search, &$key = null, &$value = null ) use ( $lowerBrowser ) {
|
||||
$find = function( $search, &$key = null, &$value = null ) use ( $lowerBrowser ) {
|
||||
$search = (array)$search;
|
||||
|
||||
foreach( $search as $val ) {
|
||||
@@ -141,7 +142,7 @@ REGEX
|
||||
return false;
|
||||
};
|
||||
|
||||
$findT = function ( array $search, &$key = null, &$value = null ) use ( $find ) {
|
||||
$findT = function( array $search, &$key = null, &$value = null ) use ( $find ) {
|
||||
$value2 = null;
|
||||
if( $find(array_keys($search), $key, $value2) ) {
|
||||
$value = $search[$value2];
|
||||
@@ -154,7 +155,7 @@ REGEX
|
||||
|
||||
$key = 0;
|
||||
$val = '';
|
||||
if( $findT([ 'OPR' => 'Opera', 'Facebot' => 'iMessageBot', 'UCBrowser' => 'UC Browser', 'YaBrowser' => 'Yandex', 'Iceweasel' => 'Firefox', 'Icecat' => 'Firefox', 'CriOS' => 'Chrome', 'Edg' => 'Edge', 'EdgA' => 'Edge', 'XiaoMi/MiuiBrowser' => 'MiuiBrowser' ], $key, $browser) ) {
|
||||
if( $findT([ 'OPR' => 'Opera', 'Facebot' => 'iMessageBot', 'UCBrowser' => 'UC Browser', 'YaBrowser' => 'Yandex', 'YaApp_Android' => 'Yandex', 'Iceweasel' => 'Firefox', 'Icecat' => 'Firefox', 'CriOS' => 'Chrome', 'Edg' => 'Edge', 'EdgA' => 'Edge', 'XiaoMi/MiuiBrowser' => 'MiuiBrowser' ], $key, $browser) ) {
|
||||
$version = is_numeric(substr($result[BROWSER_VERSION][$key], 0, 1)) ? $result[BROWSER_VERSION][$key] : null;
|
||||
} elseif( $find('Playstation Vita', $key, $platform) ) {
|
||||
$platform = 'PlayStation Vita';
|
||||
@@ -168,7 +169,7 @@ REGEX
|
||||
} elseif( $find('NintendoBrowser', $key) || $platform == 'Nintendo 3DS' ) {
|
||||
$browser = 'NintendoBrowser';
|
||||
$version = $result[BROWSER_VERSION][$key];
|
||||
} elseif( $find('Kindle', $key, $platform) ) {
|
||||
} elseif( $find([ 'Kindle' ], $key, $platform) ) {
|
||||
$browser = $result[BROWSER][$key];
|
||||
$version = $result[BROWSER_VERSION][$key];
|
||||
} elseif( $find('Opera', $key, $browser) ) {
|
||||
@@ -187,7 +188,7 @@ REGEX
|
||||
}
|
||||
}
|
||||
}
|
||||
} elseif( $find([ 'Applebot', 'IEMobile', 'Edge', 'Midori', 'Vivaldi', 'OculusBrowser', 'SamsungBrowser', 'Valve Steam Tenfoot', 'Chrome', 'HeadlessChrome', 'SailfishBrowser' ], $key, $browser) ) {
|
||||
} elseif( $find([ 'Googlebot', 'Applebot', 'IEMobile', 'Edge', 'Midori', 'Vivaldi', 'OculusBrowser', 'SamsungBrowser', 'Valve Steam Tenfoot', 'Chrome', 'HeadlessChrome', 'SailfishBrowser' ], $key, $browser) ) {
|
||||
$version = $result[BROWSER_VERSION][$key];
|
||||
} elseif( $rv_result && $find('Trident') ) {
|
||||
$browser = 'MSIE';
|
||||
@@ -200,12 +201,17 @@ REGEX
|
||||
$platform = 'BlackBerry';
|
||||
} elseif( $platform == 'BlackBerry' || $platform == 'PlayBook' ) {
|
||||
$browser = 'BlackBerry Browser';
|
||||
} else {
|
||||
$find('Safari', $key, $browser) || $find('TizenBrowser', $key, $browser);
|
||||
} elseif( $find('Safari', $key, $browser) || $find('TizenBrowser', $key, $browser) ) {
|
||||
$version = $result[BROWSER_VERSION][$key];
|
||||
} elseif( count($result[BROWSER]) ) {
|
||||
$key = count($result[BROWSER]) - 1;
|
||||
$browser = $result[BROWSER][$key];
|
||||
$version = $result[BROWSER_VERSION][$key];
|
||||
}
|
||||
|
||||
$find('Version', $key);
|
||||
$version = $result[BROWSER_VERSION][$key];
|
||||
if( $find('Version', $key) ) {
|
||||
$version = $result[BROWSER_VERSION][$key];
|
||||
}
|
||||
} elseif( $pKey = preg_grep('/playstation \d/i', $result[BROWSER]) ) {
|
||||
$pKey = reset($pKey);
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ use IPS\core\FrontNavigation;
|
||||
use IPS\Data\Cache;
|
||||
use IPS\Data\Store;
|
||||
use IPS\Db\Exception;
|
||||
use Exception as PHPException;
|
||||
use IPS\Helpers\Form;
|
||||
use IPS\Helpers\Form\Text;
|
||||
use IPS\Helpers\Form\YesNo;
|
||||
@@ -2607,7 +2608,7 @@ EOF;
|
||||
Db::i()->update( 'core_theme_editor_categories', $category, [ 'cat_key=?', $key ] );
|
||||
}
|
||||
|
||||
Lang::saveCustom( 'core', 'themeeditor_cat_' . $categoryId, $name );
|
||||
Lang::saveCustom( 'core', 'themeeditor_cat_' . $key, $name, false, $name );
|
||||
|
||||
$category['cat_id'] = $categoryId;
|
||||
$currentCategories[ $key ] = $category;
|
||||
@@ -2678,10 +2679,10 @@ EOF;
|
||||
], [ 'setting_key=?', $key ] );
|
||||
}
|
||||
|
||||
Lang::saveCustom( 'core', 'themeeditor_var_' . $settingId, $setting['name'] );
|
||||
Lang::saveCustom( 'core', 'themeeditor_var_' . $key, $setting['name'], false, $setting['name'] );
|
||||
if( isset( $setting['desc'] ) )
|
||||
{
|
||||
Lang::saveCustom( 'core', 'themeeditor_var_' . $settingId . '_desc', $setting['desc'] );
|
||||
Lang::saveCustom( 'core', 'themeeditor_var_' . $key . '_desc', $setting['desc'], false, $setting['desc'] );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5277,7 +5278,7 @@ EOF;
|
||||
$block->layouts = $block->getSupportedLayouts();
|
||||
$blocks[ $widget['key'] ] = $block;
|
||||
}
|
||||
catch( Exception $e )
|
||||
catch( PHPException $e )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -566,7 +566,6 @@ class Controller extends CoverPhotoController
|
||||
$showing = intval( Request::i()->showing ?: 0 );
|
||||
foreach ( $newComments as $newComment )
|
||||
{
|
||||
$output[] = Output::i()->replaceEmojiWithImages( $newComment->html() );
|
||||
$lastId = ( $newComment->$commentIdColumn > $lastId ) ? $newComment->$commentIdColumn : $lastId;
|
||||
if ( ++$showing >= $perPage )
|
||||
{
|
||||
|
||||
@@ -4525,7 +4525,7 @@ abstract class Item extends Content
|
||||
}
|
||||
else
|
||||
{
|
||||
Output::i()->json( array( 'type' => 'merge', 'id' => $lastComment->$commentIdColumn, 'page' => $newPageCount, 'total' => $this->mapped('num_comments'), 'content' => Output::i()->replaceEmojiWithImages( $newContent ) ) );
|
||||
Output::i()->json( array( 'type' => 'merge', 'id' => $lastComment->$commentIdColumn, 'page' => $newPageCount, 'total' => $this->mapped('num_comments'), 'content' => $newContent ) );
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -4557,7 +4557,7 @@ abstract class Item extends Content
|
||||
|
||||
if ( $currentPageCount != $newPageCount )
|
||||
{
|
||||
Output::i()->json( array( 'type' => 'redirect', 'page' => $newPageCount, 'total' => $this->mapped('num_comments'), 'content' => Output::i()->replaceEmojiWithImages( $comment->html() ), 'url' => (string) $comment->url('find') ) );
|
||||
Output::i()->json( array( 'type' => 'redirect', 'page' => $newPageCount, 'total' => $this->mapped('num_comments'), 'content' => $comment->html(), 'url' => (string) $comment->url('find') ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -4596,7 +4596,7 @@ abstract class Item extends Content
|
||||
'id' => $comment->$commentIdColumn,
|
||||
'page' => $newPageCount,
|
||||
'total' => $this->mapped('num_comments'),
|
||||
'content' => Output::i()->replaceEmojiWithImages( $output ),
|
||||
'content' => $output,
|
||||
'message' => $message,
|
||||
'postedByLoggedInMember' => true,
|
||||
/* This is used on the front end */
|
||||
|
||||
@@ -390,13 +390,14 @@ trait Reactable
|
||||
else
|
||||
{
|
||||
/* Set the data in $this->reputation to save queries later */
|
||||
$this->reputation = array();
|
||||
$_reputation = array();
|
||||
foreach( Db::i()->select( '*', 'core_reputation_index', $this->getReactionWhereClause() )->join( 'core_reactions', 'reaction=reaction_id' ) AS $reaction )
|
||||
{
|
||||
$this->reputation[ $reaction['member_id'] ] = $reaction['reaction'];
|
||||
$_reputation[ $reaction['member_id'] ] = $reaction['reaction'];
|
||||
$this->_reactions[ $reaction['member_id'] ][] = $reaction['reaction'];
|
||||
$this->_reactionCount += $reaction['rep_rating'];
|
||||
}
|
||||
$this->reputation = $_reputation;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -39,17 +39,6 @@ abstract class Result
|
||||
*/
|
||||
public static function preDisplay( string $content ): string
|
||||
{
|
||||
/* We cannot use images (or *anything* other than text) in search results as it messes with the truncation
|
||||
logic. All HTML (including emoticons) are already stripped before they go into the search index
|
||||
for this reason, but Emoji (because they're text) won't be. We can't swap the Emoji back out
|
||||
with one of the alternative Emoji style images though because that re-introduces images, so we
|
||||
just strip them out (making them behave like emoticons) */
|
||||
|
||||
if ( Settings::i()->emoji_style == 'twemoji' )
|
||||
{
|
||||
$content = preg_replace( '/(?:' . Parser::getEmojiRegex() . '|\x{200d})+/u', '', $content );
|
||||
}
|
||||
|
||||
/* We need to strip the image labels as returned by the AI image scanner */
|
||||
return preg_replace('#<i\s+?l="([^<]+?)</i>#', '', $content);
|
||||
}
|
||||
|
||||
@@ -269,7 +269,7 @@ class Select implements Iterator, Countable
|
||||
/**
|
||||
* @brief Key Field
|
||||
*/
|
||||
protected ?string $keyField = NULL;
|
||||
protected mixed $keyField = NULL;
|
||||
|
||||
/**
|
||||
* @brief Key Table
|
||||
@@ -279,7 +279,7 @@ class Select implements Iterator, Countable
|
||||
/**
|
||||
* @brief Value Field
|
||||
*/
|
||||
protected ?string $valueField = NULL;
|
||||
protected mixed $valueField = NULL;
|
||||
|
||||
/**
|
||||
* @brief Value Table
|
||||
|
||||
@@ -108,8 +108,6 @@ abstract class Standard extends Dispatcher
|
||||
Output::i()->jsVars['ipb_url_filter_option'] = Settings::i()->ipb_url_filter_option;
|
||||
Output::i()->jsVars['url_filter_any_action'] = Settings::i()->url_filter_any_action;
|
||||
Output::i()->jsVars['bypass_profanity'] = intval( Member::loggedIn()->group['g_bypass_badwords'] );
|
||||
Output::i()->jsVars['emoji_style'] = ( Settings::i()->getFromConfGlobal('sql_utf8mb4') === TRUE ) ? Settings::i()->emoji_style : 'disabled';
|
||||
Output::i()->jsVars['emoji_shortcodes'] = (bool) Settings::i()->emoji_shortcodes;
|
||||
Output::i()->jsVars['emoji_cache'] = (int) Settings::i()->emoji_cache;
|
||||
Output::i()->jsVars['image_jpg_quality'] = (int) Settings::i()->image_jpg_quality ?: 85;
|
||||
Output::i()->jsVars['cloud2'] = (bool) CIC2;
|
||||
@@ -327,6 +325,11 @@ abstract class Standard extends Dispatcher
|
||||
*/
|
||||
public bool $destruct = true;
|
||||
|
||||
/**
|
||||
* @brief Flag to check if we are in the destructor
|
||||
*/
|
||||
public bool $inDestructor = false;
|
||||
|
||||
/**
|
||||
* Destructor
|
||||
* Runs tasks
|
||||
@@ -343,6 +346,7 @@ abstract class Standard extends Dispatcher
|
||||
|
||||
if ( $this->runTasks and Settings::i()->task_use_cron == 'normal' and !Request::i()->isAjax() )
|
||||
{
|
||||
$this->inDestructor = true;
|
||||
try
|
||||
{
|
||||
$task = Task::queued();
|
||||
|
||||
@@ -444,7 +444,7 @@ class Amazon extends File
|
||||
) );
|
||||
|
||||
/* Turn on task */
|
||||
Db::i()->update( 'core_tasks', array( 'enabled' => 1 ), array( '`key`=?', 's3_delete' ) );
|
||||
Db::i()->update( 'core_tasks', array( 'enabled' => 1 ), array( '`key`=?', 's3Delete' ) );
|
||||
|
||||
/* Log deletion request */
|
||||
$realContainer = $this->container;
|
||||
|
||||
@@ -51,6 +51,11 @@ abstract class File
|
||||
* @var string|null
|
||||
*/
|
||||
public ?string $contextInfo;
|
||||
|
||||
/**
|
||||
* @var mixed|null
|
||||
*/
|
||||
public mixed $screenshot = null;
|
||||
|
||||
/**
|
||||
* Get available storage handlers
|
||||
@@ -402,7 +407,7 @@ abstract class File
|
||||
* @throws DomainException
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public static function createFromUploads( string $storageLocation, ?string $fieldName=NULL, ?array $allowedFileTypes=NULL, int|float $maxFileSize=NULL, ?int $totalMaxSize=NULL, int $flags=0, array|callable|null $callback=NULL, ?string $container=NULL, bool $obscure=TRUE ) : array
|
||||
public static function createFromUploads( string $storageLocation, ?string $fieldName=NULL, ?array $allowedFileTypes=NULL, int|float $maxFileSize=NULL, int|float $totalMaxSize=NULL, int $flags=0, array|callable|null $callback=NULL, ?string $container=NULL, bool $obscure=TRUE ) : array
|
||||
{
|
||||
/* Do we have any uploads? */
|
||||
if( empty( $_FILES ) )
|
||||
|
||||
@@ -289,9 +289,6 @@ class Editor extends FormAbstract
|
||||
/* Clean resources in ACP */
|
||||
$value = $this->value;
|
||||
Output::i()->parseFileObjectUrls( $value );
|
||||
|
||||
/* Fix Emoji */
|
||||
$value = Output::i()->replaceEmojiWithImages( $value );
|
||||
|
||||
// Todo we can probably can be deleted. The <br> is now saved, and we also have CSS that makes the empty <p></p> tags hidden. Needs Ehren's signoff :)
|
||||
/* The editor will replace <p></p> (which doesn't display anything in a normal post) with <p><br></p> (which does)
|
||||
|
||||
@@ -5516,7 +5516,8 @@ class Member extends ActiveRecord
|
||||
{
|
||||
if ( $this->member_id )
|
||||
{
|
||||
if ( $by === NULL and Dispatcher::hasInstance() )
|
||||
/* Set this only if this was not called by the task system, otherwise we'll set the member who triggered the task as moderator */
|
||||
if ( Dispatcher::hasInstance() and !Dispatcher::i()->inDestructor and $by === NULL )
|
||||
{
|
||||
$by = Session::i()->member; // Not \IPS\Member::loggedIn() because if this is an admin logged in as a member, we want to log that the action was done by the admin
|
||||
}
|
||||
@@ -6652,9 +6653,12 @@ class Member extends ActiveRecord
|
||||
/* PMs are treated extra */
|
||||
Db::i()->update( 'core_message_posts', array( 'msg_ip_address' => '' ), array( "msg_ip_address != '' AND msg_date <= " . $time ) );
|
||||
|
||||
foreach ( Application::allExtensions( 'core', 'pruneIpAddresses', FALSE, 'core' ) as $key => $extension )
|
||||
foreach ( Application::allExtensions( 'core', 'IpAddresses', FALSE, 'core' ) as $key => $extension )
|
||||
{
|
||||
$extension->pruneIpAddresses( $time );
|
||||
if( method_exists( $extension, 'pruneIpAddresses' ) )
|
||||
{
|
||||
$extension->pruneIpAddresses( $time );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+10
-5
@@ -2313,7 +2313,12 @@ abstract class Model extends ActiveRecord
|
||||
{
|
||||
if( static::$_allPermissions === null )
|
||||
{
|
||||
static::$_allPermissions = iterator_to_array(
|
||||
static::$_allPermissions = [];
|
||||
}
|
||||
|
||||
if( !isset( static::$_allPermissions[ get_called_class() ] ) )
|
||||
{
|
||||
static::$_allPermissions[ get_called_class() ] = iterator_to_array(
|
||||
Db::i()->select( array( 'perm_type_id', 'perm_id', 'perm_view', 'perm_2', 'perm_3', 'perm_4', 'perm_5', 'perm_6', 'perm_7' ), 'core_permission_index', array( "app=? AND perm_type=?", static::$permApp, static::$permType ) )
|
||||
->setKeyField( 'perm_type_id' )
|
||||
);
|
||||
@@ -2331,11 +2336,11 @@ abstract class Model extends ActiveRecord
|
||||
{
|
||||
if ( $this->_permissions === NULL )
|
||||
{
|
||||
if( !isset( static::permissionStore()[ $this->_id ] ) )
|
||||
if( !isset( static::permissionStore()[ get_called_class() ][ $this->_id ] ) )
|
||||
{
|
||||
try
|
||||
{
|
||||
static::$_allPermissions[ $this->_id ] = Db::i()->select( array( 'perm_type_id', 'perm_id', 'perm_view', 'perm_2', 'perm_3', 'perm_4', 'perm_5', 'perm_6', 'perm_7' ), 'core_permission_index', array( "app=? AND perm_type=? and perm_type_id=?", static::$permApp, static::$permType, $this->_id ) )->first();
|
||||
static::$_allPermissions[ get_called_class() ][ $this->_id ] = Db::i()->select( array( 'perm_type_id', 'perm_id', 'perm_view', 'perm_2', 'perm_3', 'perm_4', 'perm_5', 'perm_6', 'perm_7' ), 'core_permission_index', array( "app=? AND perm_type=? and perm_type_id=?", static::$permApp, static::$permType, $this->_id ) )->first();
|
||||
}
|
||||
catch( UnderflowException )
|
||||
{
|
||||
@@ -2346,11 +2351,11 @@ abstract class Model extends ActiveRecord
|
||||
'perm_view' => ''
|
||||
) );
|
||||
|
||||
static::$_allPermissions[ $this->_id ] = array( 'perm_id' => $permId, 'perm_view' => '', 'perm_2' => NULL, 'perm_3' => NULL, 'perm_4' => NULL, 'perm_5' => NULL, 'perm_6' => NULL, 'perm_7' => NULL );
|
||||
static::$_allPermissions[ get_called_class() ][ $this->_id ] = array( 'perm_id' => $permId, 'perm_view' => '', 'perm_2' => NULL, 'perm_3' => NULL, 'perm_4' => NULL, 'perm_5' => NULL, 'perm_6' => NULL, 'perm_7' => NULL );
|
||||
}
|
||||
}
|
||||
|
||||
$this->_permissions = static::permissionStore()[ $this->_id ];
|
||||
$this->_permissions = static::permissionStore()[ get_called_class() ][ $this->_id ];
|
||||
}
|
||||
|
||||
return $this->_permissions;
|
||||
|
||||
@@ -945,12 +945,6 @@ class Output
|
||||
$this->parseFileObjectUrls( $output );
|
||||
}
|
||||
|
||||
/* Replace emoji */
|
||||
if ( $output and $parseEmoji and Dispatcher::hasInstance() and Dispatcher::i()->controllerLocation !== 'setup' )
|
||||
{
|
||||
$output = $this->replaceEmojiWithImages( $output );
|
||||
}
|
||||
|
||||
/* Combine some common checks for re-use */
|
||||
$canCache = function( $contentType, $obj ) {
|
||||
if( $obj->pageCaching and
|
||||
@@ -1355,68 +1349,7 @@ class Output
|
||||
/* ___base_url___ is a bit dramatic but it prevents accidental replacements with tags called base_url if a third party app or hook uses it */
|
||||
$output = str_replace( '<___base_url___>', rtrim( Settings::i()->base_url, '/' ), $output );
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace emoji unicode with images
|
||||
*
|
||||
* @param string $output The output containing emojis as unicode
|
||||
* @return string
|
||||
*/
|
||||
public function replaceEmojiWithImages( string $output ): string
|
||||
{
|
||||
if ( Settings::i()->emoji_style == 'twemoji' )
|
||||
{
|
||||
return preg_replace_callback( '/<span class="ipsEmoji">(.+?)<\/span>/', function( $matches ) {
|
||||
$hex = bin2hex( mb_convert_encoding( $matches[1], 'UTF-32', 'UTF-8' ) );
|
||||
$hexLength = strlen( $hex ) / 8;
|
||||
$chunks = array();
|
||||
for ( $i = 0; $i < $hexLength; ++$i )
|
||||
{
|
||||
$tmp = substr( $hex, $i * 8, 8 );
|
||||
|
||||
$copy = false;
|
||||
$len = strlen( $tmp );
|
||||
$res = '';
|
||||
for ( $j = 0; $j < $len; ++$j )
|
||||
{
|
||||
$ch = $tmp[ $j ];
|
||||
if ( !$copy )
|
||||
{
|
||||
if ( $ch != '0' )
|
||||
{
|
||||
$copy = true;
|
||||
}
|
||||
else if ( ( $i + 1 ) == $len )
|
||||
{
|
||||
$res = '0';
|
||||
}
|
||||
}
|
||||
if ( $copy )
|
||||
{
|
||||
$res .= $ch;
|
||||
}
|
||||
}
|
||||
|
||||
$chunks[ $i ] = $res;
|
||||
}
|
||||
|
||||
$image = implode( '-', $chunks );
|
||||
|
||||
if ( strstr( $image, '200d' ) === FALSE or $image === '1f441-fe0f-200d-1f5e8-fe0f' )
|
||||
{
|
||||
$image = str_replace( '-fe0f', '', $image );
|
||||
}
|
||||
if ( in_array( $image, array( '0031-20e3', '0030-20e3', '0032-20e3', '0034-20e3', '0035-20e3', '0036-20e3', '0037-20e3', '0038-20e3', '0033-20e3', '0039-20e3', '0023-20e3', '002a-20e3', '00a9', '00ae' ) ) )
|
||||
{
|
||||
$image = str_replace( '00', '', $image );
|
||||
}
|
||||
|
||||
return '<img src="https://cdn.jsdelivr.net/gh/twitter/twemoji@14.0.2/assets/72x72/' . $image . '.png" class="ipsEmoji" alt="' . $matches[1] . '" loading="lazy">';
|
||||
}, $output );
|
||||
}
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show Offline
|
||||
*
|
||||
|
||||
@@ -138,7 +138,7 @@ class CustomTemplate extends Model
|
||||
*/
|
||||
public static function all(): ActiveRecordIterator
|
||||
{
|
||||
return new ActiveRecordIterator( Db::i()->select( '*', static::$databaseTable, NULL, static::$databasePrefix . static::$databaseColumnOrder ), 'IPS\Theme\CustomTemplate' );
|
||||
return new ActiveRecordIterator( Db::i()->select( '*', static::$databaseTable, Db::i()->in( 'template_app', array_keys( Application::enabledApplications() ) ), static::$databasePrefix . static::$databaseColumnOrder ), 'IPS\Theme\CustomTemplate' );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -272,7 +272,12 @@ class CustomTemplate extends Model
|
||||
*/
|
||||
public static function pathToArray( string $path ): array
|
||||
{
|
||||
[ $app, $location, $group, $templateName ] = explode( '/', $path );
|
||||
$bits = explode( "/", $path );
|
||||
$app = array_shift( $bits );
|
||||
$location = array_shift( $bits );
|
||||
$group = array_shift( $bits );
|
||||
$templateName = implode( "_", $bits );
|
||||
//[ $app, $location, $group, $templateName ] = explode( '/', $path );
|
||||
|
||||
return [
|
||||
'app' => $app,
|
||||
@@ -576,9 +581,12 @@ EOF;
|
||||
|
||||
if ( $this->hookpoint and $this->hookpoint_type )
|
||||
{
|
||||
[ $path, $hook ] = explode( ':', $this->hookpoint );
|
||||
$pathData = CustomTemplate::pathToArray( $path );
|
||||
|
||||
$badge = array(
|
||||
0 => 'style2',
|
||||
1 => $this->hookpoint . ' ' . $this->hookpoint_type
|
||||
1 => implode( "/", $pathData ) . ':' . $hook . ' ' . $this->hookpoint_type
|
||||
);
|
||||
}
|
||||
else if ( $this->key )
|
||||
|
||||
@@ -24,6 +24,7 @@ use IPS\Helpers\Form\Text;
|
||||
use IPS\Helpers\Form\Translatable;
|
||||
use IPS\Http\Url;
|
||||
use IPS\Lang;
|
||||
use IPS\Member;
|
||||
use IPS\Node\Model;
|
||||
use IPS\Request;
|
||||
use IPS\Settings;
|
||||
@@ -98,6 +99,39 @@ class Category extends Model
|
||||
*/
|
||||
public static ?string $subnodeClass = Setting::class;
|
||||
|
||||
/**
|
||||
* [Node] Get Title
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get__title(): string
|
||||
{
|
||||
return Member::loggedIn()->language()->addToStack( static::$titleLangPrefix . $this->key, NULL, array( 'escape' => TRUE ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the title for a node using the specified language object
|
||||
* This is commonly used where we cannot use the logged in member's language, such as sending emails
|
||||
*
|
||||
* @param Lang $language Language object to fetch the title with
|
||||
* @param array $options What options to use for language parsing
|
||||
* @return string
|
||||
*/
|
||||
public function getTitleForLanguage( Lang $language, array $options=array() ): string
|
||||
{
|
||||
return $language->addToStack( static::$titleLangPrefix . $this->key, NULL, $options );
|
||||
}
|
||||
|
||||
/**
|
||||
* [Node] Get Title language key, not added to a language stack
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
protected function get__titleLanguageKey(): ?string
|
||||
{
|
||||
return static::$titleLangPrefix . $this->key;
|
||||
}
|
||||
|
||||
/**
|
||||
* [Node] Get Icon for tree
|
||||
*
|
||||
@@ -365,7 +399,7 @@ class Category extends Model
|
||||
$return['cat_app'] = $values['themeeditor_cat_app']->directory;
|
||||
}
|
||||
|
||||
Lang::saveCustom( 'core', static::$titleLangPrefix . $this->id, $values['themeeditor_cat_name'] );
|
||||
Lang::saveCustom( 'core', static::$titleLangPrefix . $this->key, $values['themeeditor_cat_name'] );
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
@@ -127,6 +127,49 @@ class Setting extends Model
|
||||
*/
|
||||
protected static bool $loadFromCache = true;
|
||||
|
||||
/**
|
||||
* [Node] Get Title
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get__title(): string
|
||||
{
|
||||
return Member::loggedIn()->language()->addToStack( static::$titleLangPrefix . $this->key, NULL, array( 'escape' => TRUE ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the title for a node using the specified language object
|
||||
* This is commonly used where we cannot use the logged in member's language, such as sending emails
|
||||
*
|
||||
* @param Lang $language Language object to fetch the title with
|
||||
* @param array $options What options to use for language parsing
|
||||
* @return string
|
||||
*/
|
||||
public function getTitleForLanguage( Lang $language, array $options=array() ): string
|
||||
{
|
||||
return $language->addToStack( static::$titleLangPrefix . $this->key, NULL, $options );
|
||||
}
|
||||
|
||||
/**
|
||||
* [Node] Get Title language key, not added to a language stack
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
protected function get__titleLanguageKey(): ?string
|
||||
{
|
||||
return static::$titleLangPrefix . $this->key;
|
||||
}
|
||||
|
||||
/**
|
||||
* [Node] Get content table description
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
protected function get_description(): ?string
|
||||
{
|
||||
return Member::loggedIn()->language()->addToStack( static::$titleLangPrefix . $this->key . static::$descriptionLangSuffix );
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to load cached data
|
||||
*
|
||||
@@ -600,8 +643,8 @@ class Setting extends Model
|
||||
$return['setting_app'] = $values['themeeditor_setting_app']->directory;
|
||||
}
|
||||
|
||||
Lang::saveCustom( 'core', static::$titleLangPrefix . $this->id, $values['themeeditor_setting_name'] );
|
||||
Lang::saveCustom( 'core', static::$titleLangPrefix . $this->id . '_desc', $values['themeeditor_setting_desc'] );
|
||||
Lang::saveCustom( 'core', static::$titleLangPrefix . $this->key, $values['themeeditor_setting_name'] );
|
||||
Lang::saveCustom( 'core', static::$titleLangPrefix . $this->key . '_desc', $values['themeeditor_setting_desc'] );
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
@@ -656,6 +656,21 @@ class Area
|
||||
}
|
||||
}
|
||||
|
||||
/* Make sure that the widget actually exists, otherwise ignore it.
|
||||
This can be an issue if a widget was added and now no longer exists in the system. */
|
||||
try
|
||||
{
|
||||
if( !Widget::isValidWidget( Application::load( $block['app'] ), $block['key'] ) )
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
catch( Exception )
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/* Load configuration */
|
||||
if ( empty( $block['configuration'] ) or !is_array( $block['configuration'] ) )
|
||||
{
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @brief Polymorphic
|
||||
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) Invision Power Services, Inc.
|
||||
* @license https://www.invisioncommunity.com/legal/standards/
|
||||
* @package Invision Community
|
||||
* @subpackage
|
||||
* @since Oct 2024
|
||||
*/
|
||||
|
||||
namespace IPS\Widget;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
|
||||
use function defined;
|
||||
use function header;
|
||||
|
||||
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
interface Polymorphic
|
||||
{
|
||||
/**
|
||||
* Get the base key. When the class name is dynamically generated, we need to use the base key to look in the database and find the actual base configuration
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getBaseKey() : string;
|
||||
}
|
||||
@@ -494,7 +494,7 @@ abstract class Widget
|
||||
*/
|
||||
public function getSupportedLayouts() : array
|
||||
{
|
||||
if ( !isset( static::$baseWidgetConfig[ $this::class ]['layouts'] ) OR empty( static::$baseWidgetConfig[ $this::class ]['layouts'] ) )
|
||||
if ( !isset( static::$baseWidgetConfig[ $this::class ]['layouts'] ) OR !is_array( static::$baseWidgetConfig[ $this::class ]['layouts'] ) )
|
||||
{
|
||||
$layouts = ['table'];
|
||||
if ( $this->isCustomizableWidget() )
|
||||
@@ -718,6 +718,17 @@ abstract class Widget
|
||||
}
|
||||
|
||||
$class = '\IPS\\' . $parent->directory . '\widgets\\' . $widgetKey;
|
||||
$baseWidgetKey = $widgetKey;
|
||||
if ( class_exists( $class ) and in_array( "IPS\\Widget\\Polymorphic", class_implements( $class ) ) )
|
||||
{
|
||||
$baseWidgetKey = $class::getBaseKey();
|
||||
}
|
||||
|
||||
/* Is this a valid widget? */
|
||||
if( !static::isValidWidget( $parent, $baseWidgetKey ) )
|
||||
{
|
||||
throw new OutOfRangeException;
|
||||
}
|
||||
|
||||
/* Return */
|
||||
if ( class_exists( $class ) )
|
||||
@@ -732,6 +743,37 @@ abstract class Widget
|
||||
|
||||
throw new OutOfRangeException;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure that the widget actually is supported by the app
|
||||
*
|
||||
* @param Application|string $app
|
||||
* @param string $widgetKey
|
||||
* @return bool
|
||||
*/
|
||||
public static function isValidWidget( Application|string $app, string $widgetKey ) : bool
|
||||
{
|
||||
/* Is this a valid widget? */
|
||||
try
|
||||
{
|
||||
$cache = Store::i()->widgets;
|
||||
}
|
||||
catch( OutOfRangeException )
|
||||
{
|
||||
static::appDefaults( $app );
|
||||
$cache = Store::i()->widgets;
|
||||
}
|
||||
|
||||
$app = ( $app instanceof Application ) ? $app->directory : $app;
|
||||
|
||||
$class = '\IPS\\' . $app . '\widgets\\' . $widgetKey;
|
||||
if ( class_exists( $class ) and in_array( "IPS\\Widget\\Polymorphic", class_implements( $class ) ) )
|
||||
{
|
||||
$widgetKey = $class::getBaseKey();
|
||||
}
|
||||
|
||||
return array_key_exists( $widgetKey, $cache[ $app ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Widget Types
|
||||
@@ -1183,11 +1225,12 @@ abstract class Widget
|
||||
/**
|
||||
* Get default widgets for an application
|
||||
*
|
||||
* @param Application $app The application
|
||||
* @param Application|string $app The application
|
||||
* @return array
|
||||
*/
|
||||
public static function appDefaults( Application $app ): array
|
||||
public static function appDefaults( Application|string $app ): array
|
||||
{
|
||||
$app = is_string( $app ) ? Application::load( $app ) : $app;
|
||||
if ( !isset( Store::i()->widgets ) )
|
||||
{
|
||||
$widgets = array();
|
||||
|
||||
Reference in new issue
Block a user