Version 5.0.14

This commit is contained in:
Neo committed 2025-12-19 19:17:57 -08:00
1 parent 5efe744c5f
commit dbd774b30c
364 files changed
+7048 -1377

No files matched your search

+1 -1
View File
@@ -2048,7 +2048,7 @@ abstract class Comment extends Content
{
$ipAddressColumn = static::$databaseColumnMap['ip_address'];
$menu->addSeparator();
$menu->add( new ContentMenuLink( url: Url::internal( 'app=core&module=modcp&controller=modcp&tab=ip_tools&ip=' . $this->$ipAddressColumn ), languageString: Member::loggedIn()->language()->addToStack( 'ip_address' ) . ' ' . $this->$ipAddressColumn ) );
$menu->add( new ContentMenuLink( url: Url::internal( 'app=core&module=modcp&controller=modcp&tab=ip_tools&ip=' . $this->$ipAddressColumn, 'front', 'modcp_ip_tools' ), languageString: Member::loggedIn()->language()->addToStack( 'ip_address' ) . ' ' . $this->$ipAddressColumn ) );
}
foreach( $this->ui( 'menuItems', array(), TRUE ) as $key => $link )
+4 -2
View File
@@ -204,6 +204,7 @@ class Controller extends CoverPhotoController
/* We also want to update the views if we have a page parameter */
if( IPS::classUsesTrait( $item, 'IPS\Content\ViewUpdates' ) and isset( $class::$databaseColumnMap['views'] ) AND isset( Request::i()->page ) AND Request::i()->page )
{
/* @var $item ViewUpdates */
$item->updateViews();
}
@@ -297,6 +298,7 @@ class Controller extends CoverPhotoController
$idColumn = $class::$databaseColumnId;
if ( IPS::classUsesTrait( $class, 'IPS\Content\ViewUpdates' ) AND isset( $class::$databaseColumnMap['views'] ) )
{
/* @var $item ViewUpdates */
$item->updateViews();
}
else
@@ -4775,7 +4777,7 @@ class Controller extends CoverPhotoController
'promotype' => 'promote'
] );
Webhook::fire( 'content_promoted', $item );
Webhook::fire( 'content_promoted', $promote );
Output::i()->redirect( $objectToPromote->url(), Member::loggedIn()->language()->addToStack( 'promote_complete' ) );
}
@@ -5024,7 +5026,7 @@ class Controller extends CoverPhotoController
$promote->setText( $values['promote_internal_text'] );
$promote->save();
Webhook::fire( 'content_promoted', $item );
Webhook::fire( 'content_promoted', $promote );
/* Redirect back to the list */
Output::i()->redirect( Url::internal( 'app=core&module=modcp&controller=modcp&tab=featured', 'front', 'modcp_featured' ), 'saved' );
+6 -3
View File
@@ -139,11 +139,14 @@ class ModeratorPermissions
public function getPermissions( array $toggles ): array
{
$class = static::$class;
$containerNodeClass = $class::$containerNodeClass;
$return = array();
$return[ $containerNodeClass::$modPerm ] = array( 'Node', array( 'class' => $containerNodeClass, 'zeroVal' => 'all', 'multiple' => TRUE ) );
if( isset( $class::$containerNodeClass ) )
{
$containerNodeClass = $class::$containerNodeClass;
$return[ $containerNodeClass::$modPerm ] = array( 'Node', array( 'class' => $containerNodeClass, 'zeroVal' => 'all', 'multiple' => TRUE ) );
}
foreach ( $this->actions as $k )
{
+9 -2
View File
@@ -21,6 +21,7 @@ use IPS\Dispatcher;
use IPS\Extensions\PermissionsAbstract;
use IPS\IPS;
use IPS\Member;
use IPS\Member\Group;
use IPS\Node\Model;
use IPS\Settings;
@@ -121,11 +122,17 @@ class Permissions
*
* @param string $permission
* @param object $object
* @param Member|null $member
* @param Member|Group|null $member
* @return int
*/
public static function can( string $permission, object $object, ?Member $member=null ) : int
public static function can( string $permission, object $object, Member|Group|null $member=null ) : int
{
/* Rather than do a breaking change, skip this check if we pass in a group */
if( $member instanceof Group )
{
return static::PERM_DEFAULT;
}
/* Only allow these extensions to run on the front-end or from the API */
if( !Dispatcher::hasInstance() or ( Dispatcher::i()->controllerLocation != 'front' and Dispatcher::i()->controllerLocation != 'api' ) )
{
+10 -1
View File
@@ -16,6 +16,7 @@ namespace IPS\Content\Search;
use IPS\Application;
use IPS\Content;
use IPS\Member;
use function get_class;
use function is_string;
use function in_array;
@@ -36,19 +37,27 @@ class SearchContent
/**
* Returns all searchable classes
*
* @param Member|bool $member Member to check against or false to skip
* @return array
*/
public static function searchableClasses() : array
public static function searchableClasses( Member|bool $member=false ) : array
{
if( static::$_searchableClasses === null )
{
$classes = array();
foreach( Application::allExtensions( 'core', 'SearchContent', false, null, null, false ) as $extension )
{
/** @var $extension SearchContentAbstract */
$classes = array_merge( $classes, $extension::supportedClasses() );
}
static::$_searchableClasses = $classes;
}
if( $member !== false )
{
return array_intersect( Content::routedClasses( $member ), static::$_searchableClasses );
}
return static::$_searchableClasses;
}
+2 -1
View File
@@ -353,8 +353,9 @@ class Tag extends Model
$this->text = $values['tag_text'];
$this->save();
}
/* Do we need to update the tag name? */
elseif( $this->text != $values['tag_text'] )
if( $this->text != $values['tag_text'] )
{
Task::queue( 'core', 'UpdateTaggedItems', [ 'tag' => $this->text, 'new' => $values['tag_text'] ], 5, [ 'tag' ] );
}
+1 -1
View File
@@ -42,7 +42,7 @@ abstract class Store extends AbstractData
* Available Data Store Methods
* - MUST always return 'Database' and 'FileSystem' options
*
* @return array
* @return array<string, class-string<\IPS\Data\Store>>
*/
public static function availableMethods(): array
{
+1 -1
View File
@@ -35,7 +35,7 @@ class DateTime extends \DateTime
/**
* Create from timestamp
*
* @param int $timestamp UNIX Timestamp
* @param int$ timestamp UNIX Timestamp
* @param bool $bypassTimezone Ignore timezone (useful for things like rfc1123() which forces to GMT anyways)
* @return DateTime
*/
+22 -9
View File
@@ -356,11 +356,21 @@ class Admin extends Standard
/* 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'] ) )
/* Sometimes we cheat and have extra query string parameters in here, so make sure we only get what we need */
$amp = strpos( $item['do'], '&' );
if( $amp !== false )
{
$item['menu_checks']['do'] = array( $item['menu_checks']['do'] );
$do = substr( $item['do'], 0, $amp );
}
if( !empty( $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'][] = $do;
}
$item['menu_checks']['do'][] = $item['do'];
}
if( isset( $item['menu_checks'] ) AND is_array( $item['menu_checks'] ) )
@@ -523,14 +533,17 @@ class Admin extends Standard
if ( \IPS\IN_DEV )
{
$currentApp = Request::i()->appKey ?? 'core';
foreach ( $this->menu['tabs']['developer'] as $module => $controllers )
if ( isset( $this->menu['tabs']['developer'] ) )
{
foreach ( $controllers as $key => $url )
foreach ( $this->menu['tabs']['developer'] as $module => $controllers )
{
parse_str( $url, $params );
$params['module'] = 'developer';
$params['appKey'] = $currentApp;
$this->menu['tabs']['developer'][$module][$key] = http_build_query( $params );
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 );
}
}
}
}
+1 -1
View File
@@ -143,7 +143,7 @@ abstract class Email
/**
* Available Outgoing Email Handlers, in order of preference
*
* @return string[]
* @return array<string, class-string<\IPS\Email>>
*/
public static function outgoingHandlers(): array
{
+2 -2
View File
@@ -319,7 +319,7 @@ abstract class Dynamic extends Chart
}
elseif ( is_numeric( Request::i()->start[ $this->identifier ] ) )
{
$this->start = DateTime::ts( Request::i()->start[ $this->identifier ] );
$this->start = DateTime::ts( (int) Request::i()->start[ $this->identifier ] );
}
else
{
@@ -354,7 +354,7 @@ abstract class Dynamic extends Chart
}
elseif ( is_numeric( Request::i()->end[ $this->identifier ] ) )
{
$this->end = DateTime::ts( Request::i()->end[ $this->identifier ] );
$this->end = DateTime::ts( (int) Request::i()->end[ $this->identifier ] );
}
else
{
+11 -1
View File
@@ -10,6 +10,8 @@
namespace IPS\Helpers\Form\Captcha;
use ErrorException;
use InvalidArgumentException;
use IPS\Http\Request\Exception;
use IPS\Http\Url;
use IPS\Log;
@@ -50,7 +52,15 @@ class Turnstile implements CaptchaInterface
*/
public function getHtml(): string
{
return Theme::i()->getTemplate( 'forms', 'core', 'global' )->captchaTurnstile( Bridge::i()->getTurnstileCredentials()['site_key'], preg_replace( '/^(.+?)\..*$/', '$1', Member::loggedIn()->language()->short ) );
try
{
return Theme::i()->getTemplate( 'forms', 'core', 'global' )->captchaTurnstile( Bridge::i()->getTurnstileCredentials()['site_key'], preg_replace( '/^(.+?)\..*$/', '$1', Member::loggedIn()->language()->short ) );
}
catch( ErrorException $e )
{
Log::log( $e->getMessage(), 'turnstile' );
return '';
}
}
/**
+3 -2
View File
@@ -680,9 +680,10 @@ class Form
{
foreach ( $elements as $_name => $element )
{
/* @var FormAbstract $element */
if ( isset( $element->options['togglesOn'] ) )
{
if ( !$element->value )
if ( !$element->value or in_array( $element->htmlId, $htmlIdsToIgnoreBecauseTheyAreHiddenByToggles ) )
{
$htmlIdsToIgnoreBecauseTheyAreHiddenByToggles = array_merge( $htmlIdsToIgnoreBecauseTheyAreHiddenByToggles, $element->options['togglesOn'] );
}
@@ -726,7 +727,7 @@ class Form
$match = is_array( $element->value ) ? in_array( $toggleValue, $element->value ) : $toggleValue == $element->value;
}
if ( !$match )
if ( !$match or in_array( $element->htmlId, $htmlIdsToIgnoreBecauseTheyAreHiddenByToggles ) )
{
$htmlIdsToIgnoreBecauseTheyAreHiddenByToggles = array_merge( $htmlIdsToIgnoreBecauseTheyAreHiddenByToggles, $toggleHtmlIds );
}
+17 -5
View File
@@ -67,7 +67,7 @@ class Node extends FormAbstract
'where' => array(), // where clause to control which results to display
'disabledIds' => array(), // Array of disabled IDs
'noParentNodes' => 'Custom' // If a value is provided, subnodes of this class which have no parent node will be added into a pseudo-group with the title provided. e.g. custom packages in Nexus do not belong to any package group
'autoPopulate' => FALSE // Whether or not to autopopulate children of root nodes (defaults to TRUE which means the children are loaded, use FALSE to only show the parent nodes by default until they are clicked on)
'autoPopulate' => FALSE // Whether to autopopulate children of root nodes (defaults to TRUE which means the children are loaded, use FALSE to only show the parent nodes by default until they are clicked on)
'clubs' => TRUE, // If TRUE, will also show nodes inside clubs that the user can access. Defaults to FALSE.
'nodeGroups' => FALSE, // If TRUE, will show node groups for easier selection. Defaults to FALSE.
'maxResults' => 200, // Maximum number of nodes to load. NULL to load all nodes or FALSE to respect node class definition. Defaults to FALSE.
@@ -120,9 +120,21 @@ class Node extends FormAbstract
}
$this->options['url'] = $this->options['url']->setQueryString( '_nodeSelectName', $this->name );
if ( $this->options['clubs'] and ( !Settings::i()->clubs or !IPS::classUsesTrait( $this->options['class'], 'IPS\Content\ClubContainer' ) ) )
if ( $this->options['clubs'] )
{
$this->options['clubs'] = FALSE;
if( ( !Settings::i()->clubs or !IPS::classUsesTrait( $this->options['class'], 'IPS\Content\ClubContainer' ) ) )
{
$this->options['clubs'] = FALSE;
}
else
{
/* Make sure we have clubs to show */
$totalClubNodes = (int) Db::i()->select( 'count(*)', 'core_clubs_node_map', [ 'node_class=?', $this->options['class'] ] )->first();
if( !$totalClubNodes )
{
$this->options['clubs'] = false;
}
}
}
if( $this->options['nodeGroups'] and ! ( IPS::classUsesTrait( $this->options['class'], Grouping::class ) and Member::loggedIn()->isModerator() ) )
@@ -143,7 +155,7 @@ class Node extends FormAbstract
*
* @return string
* @note $selectable controls whether the node is selectable, not whether it is provided as an option or not.
* @note $showable controls whether or not the node should be displayed at all. On the front end this is based on view permissions, and on the backend we display all nodes regardless of view permissions.
* @note $showable controls whether the node should be displayed at all. On the front end this is based on view permissions, and on the backend we display all nodes regardless of view permissions.
*/
public function html(): string
{
@@ -258,7 +270,7 @@ class Node extends FormAbstract
$nodes = array();
$children = array();
$noParentNodes = array();
if( isset( $nodeClass::$ownerTypes ) and $nodeClass::$ownerTypes !== NULL and $this->options['forceOwner'] !== FALSE )
if( !empty( $nodeClass::$ownerTypes ) and $this->options['forceOwner'] !== FALSE )
{
$nodes = $nodeClass::loadByOwner( $this->options['forceOwner'] ?: Member::loggedIn(), $this->options['where'] );
if ( $this->options['clubs'] )
+11
View File
@@ -19,6 +19,7 @@ use IPS\Content\Comment;
use IPS\Content\Filter;
use IPS\Content\Item;
use IPS\Content\Reaction;
use IPS\Content\Search\Index;
use IPS\Content\Taggable;
use IPS\DateTime;
use IPS\Db;
@@ -1053,6 +1054,16 @@ class Content extends Table
if( $modified )
{
$object->setTags( $currentTags );
/* And make sure we index it! */
if( $object instanceof Item and $object::$firstCommentRequired )
{
Index::i()->index( $object->firstComment() );
}
else
{
Index::i()->index( $object );
}
}
}
catch( OutOfRangeException ){}
+1 -1
View File
@@ -687,7 +687,7 @@ class Friendly extends Internal
}
}
$queryString = http_build_query( $queryStringParts );
$queryString = $queryStringParts;
/* Return */
return $component;
+37 -32
View File
@@ -131,24 +131,29 @@ class Login
/**
* Get methods
*
* @return array<Handler>
* @return array<Handler>
*/
public static function methods(): array
{
$return = array();
foreach ( static::getStore() as $row )
$return = [];
$allowedHandlerClasses = Handler::handlerClasses();
foreach( static::getStore() as $row )
{
try
/* Does the class exist in the allowed handlers array, which contains all the handlers that are enabled? */
if( in_array( $row[ 'login_classname' ], $allowedHandlerClasses ) )
{
$return[ $row['login_id'] ] = Handler::constructFromData( $row );
/* Check login method is supported */
if( !$return[ $row['login_id'] ]->isSupported() )
try
{
$handler = Handler::constructFromData( $row );
if( $handler->isSupported() )
{
$return[ $row[ 'login_id' ] ] = $handler;
}
}
catch( OutOfRangeException $e )
{
unset( $return[ $row['login_id'] ] );
}
}
catch ( OutOfRangeException $e ) { }
}
return $return;
}
@@ -156,13 +161,13 @@ class Login
/**
* Login method Store
*
* @return array
* @return array
*/
public static function getStore(): array
{
if ( !isset( Store::i()->loginMethods ) )
{
Store::i()->loginMethods = iterator_to_array( Db::i()->select( '*', 'core_login_methods', array( 'login_enabled=1' ), 'login_order' )->setKeyField( 'login_id' ) );
Store::i()->loginMethods = iterator_to_array( Db::i()->select( '*', 'core_login_methods', ['login_enabled=1'], 'login_order' )->setKeyField( 'login_id' ) );
}
return Store::i()->loginMethods;
@@ -171,7 +176,7 @@ class Login
/**
* Get methods
*
* @return array
* @return array<int, Handler>
*/
protected function _methods(): array
{
@@ -195,11 +200,11 @@ class Login
/**
* Get methods which use a username and password
*
* @return array
* @return array<int, Handler>
*/
public function usernamePasswordMethods(): array
{
$return = array();
$return = [];
foreach ( $this->_methods() as $method )
{
if ( $method->type() === static::TYPE_USERNAME_PASSWORD )
@@ -213,7 +218,7 @@ class Login
/**
* Get methods which use a button
*
* @return array
* @return array<int, Handler>
*/
public function buttonMethods(): array
{
@@ -268,8 +273,8 @@ class Login
{
$leastOffensiveException = NULL;
$success = NULL;
$fails = array();
$failsNoAccount = array();
$fails = [];
$failsNoAccount = [];
foreach ( $this->usernamePasswordMethods() as $method )
{
@@ -450,8 +455,8 @@ class Login
if( $member->member_id )
{
Email::buildFromTemplate( 'core', 'account_locked', array( $member, $location, $unlockTime ?? NULL ), Email::TYPE_TRANSACTIONAL )->send( $member );
$member->logHistory( 'core', 'login', array( 'type' => 'lock', 'count' => $member->failedLoginCount( Request::i()->ipAddress() ), 'unlockTime' => $unlockTime?->getTimestamp() ) );
Email::buildFromTemplate( 'core', 'account_locked', [$member, $location, $unlockTime??NULL], Email::TYPE_TRANSACTIONAL )->send( $member );
$member->logHistory( 'core', 'login', ['type' => 'lock', 'count' => $member->failedLoginCount( Request::i()->ipAddress() ), 'unlockTime' => $unlockTime?->getTimestamp()] );
}
}
@@ -459,7 +464,7 @@ class Login
{
$diffValue = $unlockTime->diff( new DateTime() )->format('%i') ?: 1;
throw new Exception( Member::loggedIn()->language()->addToStack( 'login_err_locked_unlock', FALSE, array( 'pluralize' => array( $diffValue ) ) ), Exception::ACCOUNT_LOCKED );
throw new Exception( Member::loggedIn()->language()->addToStack( 'login_err_locked_unlock', FALSE, ['pluralize' => [$diffValue]] ), Exception::ACCOUNT_LOCKED );
}
else
{
@@ -512,7 +517,7 @@ class Login
{
if( $admin )
{
return Member::loggedIn()->language()->addToStack( 'member_name_exists_admin', FALSE, array('sprintf' => array( $handler->_title ) ) );
return Member::loggedIn()->language()->addToStack( 'member_name_exists_admin', FALSE, ['sprintf' => [$handler->_title]] );
}
else
{
@@ -540,16 +545,16 @@ class Login
$existingMember = Member::load( $email, 'email' );
if ( $existingMember->member_id and ( !$exclude or $exclude->member_id != $existingMember->member_id ) )
{
$url = Url::internal( "app=core&module=system&controller=lostpass", "front", "lostpassword" );
return Member::loggedIn()->language()->addToStack( 'member_email_exists', FALSE, array( 'sprintf' => array( (string) $url ) ) );
$url = Url::internal( 'app=core&module=system&controller=lostpass', 'front', 'lostpassword' );
return Member::loggedIn()->language()->addToStack( 'member_email_exists', FALSE, ['sprintf' => [(string) $url]] );
}
/* Check if someone requested to change to this email */
try
{
$test = Db::i()->select( '*', 'core_validating', [ 'email_chg=? and new_email=?', 1, $email ] )->first();
$url = Url::internal( "app=core&module=system&controller=lostpass", "front", "lostpassword" );
return Member::loggedIn()->language()->addToStack( 'member_email_exists', FALSE, array( 'sprintf' => array( (string) $url ) ) );
$url = Url::internal( 'app=core&module=system&controller=lostpass', 'front', 'lostpassword' );
return Member::loggedIn()->language()->addToStack( 'member_email_exists', FALSE, ['sprintf' => [(string) $url]] );
}
catch( UnderflowException ){}
@@ -560,12 +565,12 @@ class Login
{
if( $admin )
{
return Member::loggedIn()->language()->addToStack( 'member_email_exists_admin', FALSE, array('sprintf' => array( $handler->_title ) ) );
return Member::loggedIn()->language()->addToStack( 'member_email_exists_admin', FALSE, ['sprintf' => [$handler->_title]] );
}
else
{
$url = Url::internal( "app=core&module=system&controller=lostpass", "front", "lostpassword" );
return Member::loggedIn()->language()->addToStack( 'member_email_exists', FALSE, array( 'sprintf' => array( (string) $url ) ) );
$url = Url::internal( 'app=core&module=system&controller=lostpass', 'front', 'lostpassword' );
return Member::loggedIn()->language()->addToStack( 'member_email_exists', FALSE, ['sprintf' => [(string) $url]] );
}
}
}
@@ -666,7 +671,7 @@ class Login
static::$_registrationType = 'disabled';
}
if ( in_array( static::$_registrationType, array( 'normal', 'full' ) ) and !Handler::findMethod( 'IPS\Login\Handler\Standard' ) )
if ( in_array( static::$_registrationType, ['normal', 'full'] ) and !Handler::findMethod( 'IPS\Login\Handler\Standard' ) )
{
static::$_registrationType = 'disabled';
}
@@ -699,14 +704,14 @@ class Login
Request::i()->clearLoginCookies();
/* Destroy the session (we have to explicitly reset the session cookie, see http://php.net/manual/en/function.session-destroy.php) */
$_SESSION = array();
$_SESSION = [];
$params = session_get_cookie_params();
setcookie( session_name(), '', time() - 42000, $params["path"], $params["domain"], $params["secure"], $params["httponly"] );
session_destroy();
session_start();
/* Member sync callback */
Event::fire( 'onLogout', Member::loggedIn(), array( $redirectUrl ?: Url::internal('') ) );
Event::fire( 'onLogout', Member::loggedIn(), [$redirectUrl ?: Url::internal( '' )] );
/* Check SSO Extensions for overloads */
if( Session::i() instanceof Front )
+25
View File
@@ -370,6 +370,7 @@ class Member extends ActiveRecord
$this->member_group_id = Settings::i()->guest_group;
$this->mgroup_others = '';
$this->joined = time();
$this->marked_site_read = time();
$this->ip_address = Request::i()->ipAddress();
$this->timezone = 'UTC';
$this->allow_admin_mails = ( Settings::i()->updates_consent_default == 'enabled' );
@@ -3791,6 +3792,30 @@ class Member extends ActiveRecord
{
return (boolean) $this->group['gbw_post_highlight'];
}
/**
* Return the group ID that has highlighting enabled
*
* @return int|null
*/
public function highlightedGroup() : ?int
{
if( !$this->hasHighlightedReplies() )
{
return null;
}
foreach( $this->groups as $groupId )
{
$options = Group::load( $groupId )->g_bitoptions->asArray();
if( $options['gbw_post_highlight'] )
{
return $groupId;
}
}
return null;
}
/**
* Get output for API
+24 -1
View File
@@ -18,11 +18,13 @@ use IPS\Application;
use IPS\Data\Store;
use IPS\Db;
use IPS\Extensions\ProfileStepsAbstract;
use IPS\File;
use IPS\Helpers\Form;
use IPS\Helpers\Form\Radio;
use IPS\Helpers\Form\Translatable;
use IPS\Helpers\Form\YesNo;
use IPS\Lang;
use IPS\Login;
use IPS\Member;
use IPS\Node\Model;
use IPS\Patterns\ActiveRecordIterator;
@@ -612,7 +614,15 @@ class ProfileStep extends Model
}
$form->add( new YesNo( 'profile_step_required', $this->required, FALSE, array(), NULL, NULL, NULL, 'step_required' ) );
$form->add( new YesNo( 'profile_step_registration', $this->registration, false, array(), null, null, null, 'step_registration' ) );
$form->add( new YesNo( 'profile_step_registration', $this->registration, false, array(
'disabled' => ( Login::registrationType() != 'full' )
), null, null, null, 'step_registration' ) );
if( Login::registrationType() != 'full' )
{
$form->addMessage( 'profile_step_no_registration', 'ipsMessage ipsMessage--warning' );
}
}
/**
@@ -623,6 +633,14 @@ class ProfileStep extends Model
public function formatFormValues( array $values ): array
{
$return = array();
/* Do we have an editor field to clear? */
$autoSaveKey = null;
$choiceType = static::actionMultipleChoice( $values['profile_step_completion_act'] );
if( $choiceType === null )
{
$autoSaveKey = 'core-step-' . $values['profile_step_completion_act'] . ( $this->id ?: 0 );
}
if ( !$this->id )
{
@@ -638,6 +656,11 @@ class ProfileStep extends Model
$this->save();
}
if( $autoSaveKey )
{
File::claimAttachments( $autoSaveKey, $this->id, null, null, true );
}
if ( isset( $values[ 'profile_step_subaction_' . $values['profile_step_completion_act'] ] ) )
{
+1 -1
View File
@@ -2020,7 +2020,7 @@ JS;
{
if( $this->globalSearchMenuOptions === NULL )
{
foreach( SearchContent::searchableClasses() as $class )
foreach( SearchContent::searchableClasses( Member::loggedIn() ) as $class )
{
if( in_array( 'IPS\Content\Item', class_parents( $class ) ) )
{
-1
View File
@@ -58,7 +58,6 @@ class Bridge
'frontDispatcherFinish',
'refreshRealTimeToken',
'liveTopicCreateFormFromCalendar',
'bounceMemberViewRequestEmailUnblock',
'dispatcherFinish',
'postAchievement'
);
+86 -1
View File
@@ -61,12 +61,17 @@ use function array_key_exists;
use function chr;
use function count;
use function defined;
use function explode;
use function function_exists;
use function implode;
use function in_array;
use function intval;
use function is_array;
use function is_string;
use function json_decode;
use function md5;
use function mt_rand;
use function preg_quote;
use function str_replace;
use function substr;
use const IPS\DEFAULT_REQUEST_TIMEOUT;
@@ -1040,6 +1045,11 @@ class Parser
$return[] = 'ipsEmbeddedVideo';
$return[] = 'ipsEmbeddedVideo_limited';
$return[] = 'ipsEmbeddedOther';
$return[] = 'ipsEmbeddedOther--google-maps';
$return[] = 'ipsEmbeddedOther--iframely';
$return[] = 'iframely-embed';
$return[] = 'iframely-player';
$return[] = 'iframely-responsive';
$return[] = 'ipsEmbeddedOther_limited';
$return[] = 'ipsRawIframe';
$return[] = 'ipsEmbedded__wrap';
@@ -2461,6 +2471,57 @@ class Parser
'bsky.app' => [ 'https://embed.bsky.app/oembed', static::EMBED_TWEET ]
);
/* Can we support iframely? */
if ( Settings::i()->iframely_enabled and Settings::i()->iframely_api_key and ( Settings::i()->iframely_url_whitelist or Settings::i()->iframely_facebook_enabled or Settings::i()->iframely_instagram_enabled ) )
{
$patterns = [];
$builtins = [];
if ( Settings::i()->iframely_facebook_enabled )
{
$builtins[] = "facebook\\.com";
$builtins[] = "fb\\.watch";
}
if ( Settings::i()->iframely_instagram_enabled )
{
$builtins[] = "instagram\\.com";
$builtins[] = "instagr\\.am";
}
if ( !empty( $builtins ) )
{
$patterns[] = "((?:www\\.)?(?:" . implode( "|", $builtins ) . "))";
}
if ( Settings::i()->iframely_url_whitelist )
{
$sym1 = md5( mt_rand() );
$sym2 = md5( mt_rand() );
foreach ( explode( ',', Settings::i()->iframely_url_whitelist ) as $domainPattern )
{
if ( mb_strpos( $domainPattern, '*' ) !== false )
{
$patterns[] = "(" . str_replace( [ $sym1, $sym2 ], [ '\.', '.*?' ], preg_quote( str_replace( array( '.', '*' ), array( $sym1, $sym2 ), $domainPattern ) ) ) . ")";
}
else
{
$patterns[] = "(" . preg_quote( $domainPattern ) . ")";
}
}
}
if ( !empty( $patterns ) )
{
$services["/^" . implode( "|", $patterns ) . "$/i"] = [
(string) Url::external( "https://iframe.ly/api/oembed" )->setQueryString([ 'key' => Settings::i()->iframely_api_key, 'omit_script' => 1, 'card' => 0, 'omit_css' => 0 ]),
static::EMBED_STATUS
];
}
}
/* Can we support Facebook and Instagram oembeds? */
if( $token = static::getFacebookToken() )
{
@@ -2632,7 +2693,25 @@ class Parser
{
foreach( $oembedServices as $k => $v )
{
if( mb_strpos( $k, '*' ) !== FALSE )
// The pattern might be a regex expression (see iframely)
if ( preg_match( '/^\\/(.*[^\\\\])?\\/[iuxasmdg]*?$/i', $k ) )
{
try
{
// Since this is used for iframely, where domain patterns are intended to be exact, we use the raw domain without the wwww. subhost removed
if ( preg_match( $k, $url->data['host'] ) )
{
$entry = $v;
$entryKey = $k;
break;
}
}
catch( Exception )
{
continue;
}
}
else if( mb_strpos( $k, '*' ) !== FALSE )
{
if( preg_match( "/^" . str_replace( array( '.', '*' ), array( '\\.', '.*?' ), $k ) . "$/i", $domain ) )
{
@@ -2779,6 +2858,12 @@ class Parser
{
$response['type'] = 'rich';
}
/* Iframely rich embeds don't need our wrapper. */
if ( str_starts_with( $endpoint, "https://iframe.ly" ) and !in_array( @$response['type'], [ 'video', 'photo' ] ) and isset( $response['html'] ) )
{
return Theme::i()->getTemplate( 'embed', 'core', 'global' )->iframely( $response['html'] );
}
/* We need a type otherwise we can't embed */
if( !isset( $response['type'] ) )
+6 -2
View File
@@ -529,8 +529,12 @@ class Setting extends Model
)
) ) );
$form->add( new Number( 'themeeditor_setting_min', $this->data['min'] ?? null, false, array(), null, null, null, 'themeeditor_setting_min' ) );
$form->add( new Number( 'themeeditor_setting_max', $this->data['max'] ?? null, null, array(), null, null, null, 'themeeditor_setting_max' ) );
$form->add( new Number( 'themeeditor_setting_min', $this->data['min'] ?? null, false, array(
'decimals' => 2
), null, null, null, 'themeeditor_setting_min' ) );
$form->add( new Number( 'themeeditor_setting_max', $this->data['max'] ?? null, null, array(
'decimals' => 2
), null, null, null, 'themeeditor_setting_max' ) );
$form->add( new Number( 'themeeditor_setting_step', $this->data['step'] ?? 1, false, array(
'decimals' => 2
), null, null, null, 'themeeditor_setting_step' ) );
+9 -1
View File
@@ -97,11 +97,19 @@ class Atom extends SimpleXML
foreach( $namespaces as $prefix => $namespace )
{
if( empty( $prefix ) )
{
continue;
}
if( $elements = $item->children( $prefix, true ) )
{
foreach( $elements as $name => $element )
{
$articles[ $key ][ $name ] = (string) $element;
if( !isset( $articles[ $key ][ $prefix . ":" . $name ] ) )
{
$articles[ $key ][ $prefix . ":" . $name ] = (string) $element;
}
}
}
}