Version 4.3.6

This commit is contained in:
Neo committed 2025-12-19 05:49:12 -08:00
1 parent fe1acf984a
commit 1a0c7fd3c2
609 files changed
+14707 -5726

No files matched your search

-4
View File
@@ -339,10 +339,6 @@ abstract class _Controller
{
$url = \IPS\Http\Url::external( rtrim( \IPS\Settings::i()->base_url, '/' ) . '/api/index.php?' );
}
if ( \IPS\Request::i()->isSecure() )
{
$url = $url->setScheme('https');
}
$pieces[1] = $url . $pieces[1];
}
+4 -3
View File
@@ -288,6 +288,7 @@ class _OAuthClient extends \IPS\Node\Model
public function formatFormValues( $values )
{
/* Normalise the settings */
$originalClientType = $values['oauth_client_type'];
if ( $values['oauth_client_type'] === 'invision' )
{
$values['oauth_client_type'] = 'confidential';
@@ -375,7 +376,7 @@ class _OAuthClient extends \IPS\Node\Model
unset( $values['oauth_client_name'] );
/* Redirect URIs */
if ( $values['oauth_client_type'] === 'public' or in_array( 'authorization_code', $values['oauth_grant_types'] ) or in_array( 'implicit', $values['oauth_grant_types'] ) )
if ( \in_array( $originalClientType, array( 'public', 'invision', 'wordpress' ) ) or \in_array( 'authorization_code', $values['oauth_grant_types'] ) or \in_array( 'implicit', $values['oauth_grant_types'] ) )
{
$values['oauth_redirect_uris'] = json_encode( array_map( function( $url ) { return (string) $url; }, $values['oauth_redirect_uris'] ) );
}
@@ -494,7 +495,7 @@ class _OAuthClient extends \IPS\Node\Model
'access_token' => $accessToken,
'access_token_expires' => $this->access_token_length ? ( time() + ( $this->access_token_length * 3600 ) ) : NULL,
'refresh_token' => NULL,
'refresh_token_expires' => $this->access_token_length ? ( time() + ( $this->access_token_length * 3600 ) ) : NULL,
'refresh_token_expires' => $this->refresh_token_length ? ( time() + ( $this->refresh_token_length * 86400 ) ) : NULL,
'scope' => $scopes ? json_encode( $scopes ) : NULL,
'authorization_code' => $authorizationCode,
'issued' => time()
@@ -510,7 +511,7 @@ class _OAuthClient extends \IPS\Node\Model
if ( $this->refresh_token_length )
{
$data['refresh_token_expires'] = time() + ( $this->access_token_length * 86400 );
$data['refresh_token_expires'] = time() + ( $this->refresh_token_length * 86400 );
}
else
{
+1 -1
View File
@@ -75,7 +75,7 @@ class _PaginatedResponse extends Response
$this->page = (int) $page;
$this->select = $select;
$this->activeRecordClass = $activeRecordClass;
$this->count = $count;
$this->count = (int) $count;
$this->resultsPerPage = $perPage ? (int) $perPage : 25;
$this->authorizedMember = $authorizedMember;
}
+75 -4
View File
@@ -448,9 +448,9 @@ class _Application extends \IPS\Node\Model
{
if( !empty( $data['longversion'] ) and $data['longversion'] > $this->long_version and ( !$skipSameHumanVersion or $data['version'] != $this->version ) )
{
if( $data['released'] AND intval($data['released']) == $data['released'] AND \strlen($data['released']) == 10 )
if( $data['released'] AND ( (int) $data['released'] != $data['released'] OR \strlen($data['released']) != 10 ) )
{
$data['released'] = (string) \IPS\DateTime::ts( $data['released'] )->localeDate();
$data['released'] = strtotime( $data['released'] );
}
$update[] = $data;
@@ -798,6 +798,11 @@ EOF;
{
continue;
}
if ( method_exists( $classname, 'deprecated' ) )
{
continue;
}
}
catch( \ErrorException $e )
{
@@ -2348,6 +2353,7 @@ EOF;
unset( \IPS\Data\Store::i()->themes );
\IPS\Theme::removeTemplates( $this->directory );
\IPS\Theme::removeCss( $this->directory );
\IPS\Theme::clearFiles( \IPS\Theme::CSS );
\IPS\Theme::removeResources( $this->directory );
\IPS\Theme::resetAllCacheKeys();
}
@@ -2689,9 +2695,21 @@ EOF;
/* Get our local definition of this table */
try
{
$localDefinition = \IPS\Db::i()->getTableDefinition( $tableName );
$localDefinition = \IPS\Db::i()->getTableDefinition( $tableName, FALSE, TRUE );
$originalDefinition = $localDefinition; #Store this before it is normalised and engine stripped
$localDefinition = \IPS\Db::i()->normalizeDefinition( $localDefinition );
/* Now we have to add the correct colation for text columns to our compare definition to flag any columns that don't have the correct charset/collation */
$tableDefinition['columns'] = array_map( function( $column ){
if( in_array( mb_strtoupper( $column['type'] ), array( 'CHAR', 'VARCHAR', 'TINYTEXT', 'TEXT', 'MEDIUMTEXT', 'LONGTEXT', 'ENUM', 'SET' ) ) )
{
$column['collation'] = \IPS\Db::i()->collation;
}
return $column;
}, $tableDefinition['columns'] );
/* And store our definition */
$compareDefinition = \IPS\Db::i()->normalizeDefinition( $tableDefinition );
$tableDefinition = \IPS\Db::i()->updateDefinitionIndexLengths( $tableDefinition );
@@ -2748,7 +2766,7 @@ EOF;
$differences = array();
foreach ( $columnData as $k => $v )
{
if ( $v != $localDefinition['columns'][ $columnName ][ $k ] )
if ( isset( $localDefinition['columns'][ $columnName ][ $k ] ) AND $v != $localDefinition['columns'][ $columnName ][ $k ] )
{
$differences[ $k ] = array( 'is' => $localDefinition['columns'][ $columnName ][ $k ], 'shouldBe' => $v );
}
@@ -4263,4 +4281,57 @@ EOF;
{
return ( \IPS\Member::loggedIn()->hasAcpRestriction( 'core', 'applications', 'app_manage' ) );
}
/**
* Get any third parties this app uses for the privacy policy
*
* @return array( title => language bit, description => language bit, privacyUrl => privacy policy URL )
*/
public function privacyPolicyThirdParties()
{
/* Apps can overload this */
return array();
}
/**
* Get any settings that are uploads
*
* @return array
*/
public function uploadSettings()
{
/* Apps can overload this */
return array();
}
/**
* Search
*
* @param string $column Column to search
* @param string $query Search query
* @param string|null $order Column to order by
* @param mixed $where Where clause
* @return array
*/
public static function search( $column, $query, $order=NULL, $where=array() )
{
if ( $column === '_title' )
{
$return = array();
foreach( \IPS\Member::loggedIn()->language()->words as $k => $v )
{
if ( preg_match( '/^__app_([a-z]*)$/', $k, $matches ) and mb_strpos( mb_strtolower( $v ), mb_strtolower( $query ) ) !== FALSE )
{
try
{
$application = static::load( $matches[1] );
$return[ $application->_id ] = $application;
}
catch ( \OutOfRangeException $e ) { }
}
}
return $return;
}
return parent::search( $column, $query, $order, $where );
}
}
+1 -1
View File
@@ -193,7 +193,7 @@ class _Module extends \IPS\Node\Model implements \IPS\Node\Permissions
{
try
{
$module = static::load( $matches[2], 'sys_module_key', count( $where ) ? array_merge( array( array( 'sys_module_application=?', $matches[1] ) ), array( $where ) ) : array( array( 'sys_module_application=?', $matches[1] ) ) );
$module = static::load( $matches[2], 'sys_module_key', count( $where ) ? array_merge( array( array( 'sys_module_application=? and sys_module_area=?', $matches[1], 'front' ) ), array( $where ) ) : array( array( 'sys_module_application=?', $matches[1] ) ) );
$return[ $module->_id ] = $module;
}
catch ( \OutOfRangeException $e ) { }
+2 -2
View File
@@ -209,11 +209,11 @@ class _CommentController extends \IPS\Api\Controller
$class = $this->class;
if ( in_array( 'IPS\Content\Review', class_parents( $class ) ) )
{
$comment = $class::create( $item, $content, FALSE, intval( \IPS\Request::i()->rating ), $author->member_id ? NULL : $author->real_name, $author, $date, ( !$this->member and \IPS\Request::i()->ip_address ) ?: \IPS\Request::i()->ipAddress(), $hidden );
$comment = $class::create( $item, $content, FALSE, intval( \IPS\Request::i()->rating ), $author->member_id ? NULL : $author->real_name, $author, $date, ( !$this->member and \IPS\Request::i()->ip_address ) ? \IPS\Request::i()->ip_address : \IPS\Request::i()->ipAddress(), $hidden );
}
else
{
$comment = $class::create( $item, $content, FALSE, $author->member_id ? NULL : $author->real_name, NULL, $author, $date, ( !$this->member and \IPS\Request::i()->ip_address ) ?: \IPS\Request::i()->ipAddress(), $hidden );
$comment = $class::create( $item, $content, FALSE, $author->member_id ? NULL : $author->real_name, NULL, $author, $date, ( !$this->member and \IPS\Request::i()->ip_address ) ? \IPS\Request::i()->ip_address : \IPS\Request::i()->ipAddress(), $hidden );
}
/* Index */
+1 -1
View File
@@ -382,7 +382,7 @@ class _ItemController extends \IPS\Api\Controller
}
$commentClass = $item::$commentClass;
$post = $commentClass::create( $item, $postContents, TRUE, $author->member_id ? NULL : $author->real_name, NULL, $author, $date );
$post = $commentClass::create( $item, $postContents, TRUE, $author->member_id ? NULL : $author->real_name, NULL, $author, $date, ( !$this->member and \IPS\Request::i()->ip_address ) ? \IPS\Request::i()->ip_address : \IPS\Request::i()->ipAddress() );
if ( isset( $class::$databaseColumnMap['first_comment_id'] ) )
{
+22 -9
View File
@@ -130,12 +130,15 @@ trait ClubContainer
{
$this->setPermissionsToClub( $club );
}
$followApp = $itemClass::$application;
$followArea = mb_strtolower( mb_substr( $nodeClass, mb_strrpos( $nodeClass, '\\' ) + 1 ) );
$time = time();
\IPS\Db::i()->insert( 'core_follow', \IPS\Db::i()->select( "MD5( CONCAT( '{$followApp};{$followArea};{$this->_id};', follow_member_id ) ) AS follow_id, '{$followApp}' AS follow_app, '{$followArea}' AS follow_area, '{$this->_id}' AS follow_rel_id, follow_member_id, follow_is_anon, '{$time}' AS follow_added, follow_notify_do, follow_notify_meta, follow_notify_freq, 0 AS follow_notify_sent, follow_visible", 'core_follow', array(
'follow_app=? AND follow_area=? AND follow_rel_id=?', 'core', 'club', $club->id ) ) );
if ( !$haveId )
{
$followApp = $itemClass::$application;
$followArea = mb_strtolower( mb_substr( $nodeClass, mb_strrpos( $nodeClass, '\\' ) + 1 ) );
$time = time();
\IPS\Db::i()->insert( 'core_follow', \IPS\Db::i()->select( "MD5( CONCAT( '{$followApp};{$followArea};{$this->_id};', follow_member_id ) ) AS follow_id, '{$followApp}' AS follow_app, '{$followArea}' AS follow_area, '{$this->_id}' AS follow_rel_id, follow_member_id, follow_is_anon, '{$time}' AS follow_added, follow_notify_do, follow_notify_meta, follow_notify_freq, 0 AS follow_notify_sent, follow_visible", 'core_follow', array(
'follow_app=? AND follow_area=? AND follow_rel_id=?', 'core', 'club', $club->id ) ) );
}
}
/**
@@ -299,12 +302,22 @@ trait ClubContainer
{
if ( \IPS\Settings::i()->clubs )
{
$clubIdColumn = $this->clubIdColumn();
$clubIdColumn = $this->clubIdColumn();
$itemClass = $itemClass ?: static::$contentItemClass;
$title = $itemClass::$title;
if ( $this->$clubIdColumn and $club = $this->club() )
{
if ( in_array( $club->memberStatus( $member ), array( \IPS\Member\Club::STATUS_LEADER, \IPS\Member\Club::STATUS_MODERATOR ) ) )
if ( \in_array( $club->memberStatus( $member ), array( \IPS\Member\Club::STATUS_LEADER, \IPS\Member\Club::STATUS_MODERATOR ) ) )
{
if ( in_array( $type, explode( ',', \IPS\Settings::i()->clubs_modperms ) ) )
if ( \in_array( $type, explode( ',', \IPS\Settings::i()->clubs_modperms ) ) )
{
return TRUE;
}
}
elseif ( $member->modPermission( "can_{$type}_{$title}" ) and \is_array( $member->modPermission( static::$modPerm ) ) and $member->modPermission('can_access_all_clubs') )
{
if ( \in_array( $type, explode( ',', \IPS\Settings::i()->clubs_modperms ) ) )
{
return TRUE;
}
+41 -75
View File
@@ -184,20 +184,6 @@ abstract class _Comment extends \IPS\Content
}
catch( \BadMethodCallException $e ) { }
/* Update the container */
if ( !$obj->hidden() AND !$item->hidden() )
{
try
{
$container = $item->container();
if ( $container->_comments !== NULL )
{
$container->setLastComment( $obj );
$container->save();
}
}
catch ( \BadMethodCallException $e ) { }
}
/* Update member's last post and daily post limits */
if( $obj->author()->member_id )
@@ -253,10 +239,14 @@ abstract class _Comment extends \IPS\Content
* @note We do not save here, as saving is done in user-land code after
* @param bool $first Is this the first comment?
* @param bool $edit Are we editing or merging (true) or is this a new comment (false)?
* @param string|NULL $content The content to check - useful for if the content needs to be checked first, before it gets saved to the database.
* @return bool Whether to send unapproved notifications
*/
protected function checkProfanityFilters( $first=FALSE, $edit=TRUE )
public function checkProfanityFilters( $first=FALSE, $edit=TRUE, $content=NULL )
{
/* Set our content */
$content = $content ?: $this->content();
/* We need our item */
$item = $this->item();
@@ -265,25 +255,29 @@ abstract class _Comment extends \IPS\Content
/* Pass this through our profanity filters to see if it needs to be mod queued */
$sendNotifications = FALSE;
/* Do we need to recount item comments and container comments? */
$recount = FALSE;
try
{
$hiddenByFilter = FALSE;
if ( !$this->hidden() AND ( ! $item::$firstCommentRequired or ( $item::$firstCommentRequired and !$first ) ) AND !$member->group['g_bypass_badwords'] )
{
$hiddenByFilter = \IPS\core\Profanity::hiddenByFilters( $this->content() );
$hiddenByFilter = \IPS\core\Profanity::hiddenByFilters( $content );
}
/* If the comment is hidden (probably requires moderator approval) and this is the first post in the item that requires a comment (i.e. a post in a topic), then we need to set the item as requiring approval instead. */
elseif ( $item instanceof \IPS\Content\Hideable AND !$member->group['g_bypass_badwords'] AND ( $item::$firstCommentRequired and $first ) )
{
$itemHiddenByFilter = \IPS\core\Profanity::hiddenByFilters( $this->content() );
$itemHiddenByFilter = \IPS\core\Profanity::hiddenByFilters( $content );
if ( isset( $item::$databaseColumnMap['approved'] ) and $itemHiddenByFilter )
{
/* 'approved' is easy, clear and concise */
$column = $item::$databaseColumnMap['approved'];
$item->$column = 0;
$item->save();
$recount = TRUE;
}
elseif ( isset( $item::$databaseColumnMap['hidden'] ) and $itemHiddenByFilter )
{
@@ -291,8 +285,9 @@ abstract class _Comment extends \IPS\Content
$column = $item::$databaseColumnMap['hidden'];
$item->$column = 1;
$item->save();
$recount = TRUE;
}
if( $edit === TRUE )
{
$sendNotifications = TRUE;
@@ -303,6 +298,7 @@ abstract class _Comment extends \IPS\Content
if ( $hiddenByFilter === TRUE )
{
$recount = TRUE;
if ( isset( static::$databaseColumnMap['approved'] ) )
{
/* 'approved' is easy, clear and concise */
@@ -318,6 +314,20 @@ abstract class _Comment extends \IPS\Content
$sendNotifications = TRUE;
}
if ( $recount === TRUE )
{
$item->resyncCommentCounts();
$item->resyncLastComment();
$item->save();
if ( $container = $item->containerWrapper() )
{
$container->resetCommentCounts();
$container->save();
}
}
return $sendNotifications;
}
@@ -422,7 +432,7 @@ abstract class _Comment extends \IPS\Content
{
$item->save();
if ( $item->containerWrapper() AND !$item->approvedButHidden() AND $item->container()->_unapprovedComments !== NULL )
if ( $item->containerWrapper() AND !$item->approvedButHidden() AND $this->hidden() == 1 AND $item->container()->_unapprovedComments !== NULL )
{
$item->container()->_unapprovedComments = $item->container()->_unapprovedComments + 1;
$item->container()->save();
@@ -564,14 +574,7 @@ abstract class _Comment extends \IPS\Content
{
$member = $member ?: \IPS\Member::loggedIn();
$container = NULL;
try
{
$container = $this->item()->container();
}
catch ( \BadMethodCallException $e ) { }
return ( !$this->isFirst() and ( static::modPermission( 'hide', $member, $container ) or ( $member->member_id and $member->member_id == $this->author()->member_id and ( $member->group['g_hide_own_posts'] == '1' or in_array( get_class( $this->item() ), explode( ',', $member->group['g_hide_own_posts'] ) ) ) ) ) );
return ( !$this->isFirst() and ( static::modPermission( 'hide', $member, $this->item()->containerWrapper() ) or ( $member->member_id and $member->member_id == $this->author()->member_id and ( $member->group['g_hide_own_posts'] == '1' or in_array( get_class( $this->item() ), explode( ',', $member->group['g_hide_own_posts'] ) ) ) ) ) );
}
/**
@@ -584,13 +587,6 @@ abstract class _Comment extends \IPS\Content
{
$member = $member ?: \IPS\Member::loggedIn();
$container = NULL;
try
{
$container = $this->item()->container();
}
catch ( \BadMethodCallException $e ) { }
$hiddenByItem = FALSE;
if ( isset( static::$databaseColumnMap['hidden'] ) )
{
@@ -598,7 +594,7 @@ abstract class _Comment extends \IPS\Content
$hiddenByItem = (boolean) ( $this->$column === 2 );
}
return ( !$this->isFirst() and ! $hiddenByItem and static::modPermission( 'unhide', $member, $container ) );
return ( !$this->isFirst() and ! $hiddenByItem and static::modPermission( 'unhide', $member, $this->item()->containerWrapper() ) );
}
/**
@@ -611,14 +607,7 @@ abstract class _Comment extends \IPS\Content
{
$member = $member ?: \IPS\Member::loggedIn();
$container = NULL;
try
{
$container = $this->item()->container();
}
catch ( \BadMethodCallException $e ) { }
return ( !$this->isFirst() and ( static::modPermission( 'delete', $member, $container ) or ( $member->member_id and $member->member_id == $this->author()->member_id and ( $member->group['g_delete_own_posts'] == '1' or in_array( get_class( $this->item() ), explode( ',', $member->group['g_delete_own_posts'] ) ) ) ) ) );
return ( !$this->isFirst() and ( static::modPermission( 'delete', $member, $this->item()->containerWrapper() ) or ( $member->member_id and $member->member_id == $this->author()->member_id and ( $member->group['g_delete_own_posts'] == '1' or in_array( get_class( $this->item() ), explode( ',', $member->group['g_delete_own_posts'] ) ) ) ) ) );
}
/**
@@ -633,17 +622,10 @@ abstract class _Comment extends \IPS\Content
if ( $itemClass::$firstCommentRequired )
{
$container = NULL;
try
{
$container = $this->item()->container();
}
catch ( \BadMethodCallException $e ) { }
if ( !$this->isFirst() )
{
$member = $member ?: \IPS\Member::loggedIn();
return $itemClass::modPermission( 'split_merge', $member, $container );
return $itemClass::modPermission( 'split_merge', $member, $this->item()->containerWrapper() );
}
}
return FALSE;
@@ -715,35 +697,17 @@ abstract class _Comment extends \IPS\Content
*/
public function editContents( $newContent )
{
/* Check if profanity filters should mod-queue this comment */
$sendNotifications = $this->checkProfanityFilters( $this->isFirst(), TRUE, $newContent );
/* Do it */
$valueField = static::$databaseColumnMap['content'];
$oldValue = $this->$valueField;
$this->$valueField = $newContent;
$isHiddenNow = $this->hidden();
/* Check if profanity filters should mod-queue this comment */
$sendNotifications = $this->checkProfanityFilters( $this->isFirst(), TRUE );
$this->save();
/* Send any new mention/quote notifications */
$this->sendAfterEditNotifications( $oldValue );
/* Resync the item - necessary as profanity filters may have just hidden an otherwise approved post */
$item = $this->item();
if( $isHiddenNow != $this->hidden() )
{
$item->resyncCommentCounts();
$item->resyncLastComment();
$item->save();
}
if ( $container = $item->containerWrapper() )
{
$container->resetCommentCounts();
$container->save();
}
/* Reindex */
if ( $this instanceof \IPS\Content\Searchable )
@@ -1158,7 +1122,9 @@ abstract class _Comment extends \IPS\Content
{
$oldAuthor = $this->author();
if( $oldAuthor->member_id == $newAuthor->member_id )
/* If we delete a member, then change author, the old author returns 0 as does the new author as the
member row is deleted before the task is run */
if( $newAuthor->member_id and ( $oldAuthor->member_id == $newAuthor->member_id ) )
{
return;
}
+44 -7
View File
@@ -700,6 +700,7 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
{
$this->container()->resetCommentCounts();
$this->container()->setLastComment();
$this->container()->setLastReview();
$this->container()->save();
}
}
@@ -756,13 +757,15 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
/* Save the changes */
$this->save();
/* Reindex the now hidden content */
if ( $this instanceof \IPS\Content\Item and static::$firstCommentRequired )
/* Reindex the now hidden content - if this is a content item with comments or reviews, then make sure to do those too. */
if ( $this instanceof \IPS\Content\Item AND ( isset( static::$commentClass ) OR isset( static::$reviewClass ) ) )
{
\IPS\Content\Search\Index::i()->index( $this->firstComment() );
\IPS\Content\Search\Index::i()->index( ( static::$firstCommentRequired ) ? $this->firstComment() : $this );
\IPS\Content\Search\Index::i()->indexSingleItem( $this );
}
else
{
/* Either this is a comment / review, or the item doesn't support comments or reviews, so we can just reindex it now. */
\IPS\Content\Search\Index::i()->index( $this );
}
@@ -774,6 +777,7 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
{
$this->container()->resetCommentCounts();
$this->container()->setLastComment();
$this->container()->setLastReview();
$this->container()->save();
}
}
@@ -980,11 +984,11 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
if ( $log['sdl_obj_member_id'] )
{
$this->hiddenBlurb = \IPS\Member::loggedIn()->language()->addToStack('hidden_blurb', FALSE, array( 'sprintf' => array( \IPS\Member::load( $log['sdl_obj_member_id'] )->name, \IPS\DateTime::ts( $log['sdl_obj_date'] )->relative(), \IPS\Member::loggedIn()->language()->addToStack( $log['sdl_obj_reason'] ) ?: \IPS\Member::loggedIn()->language()->addToStack('hidden_no_reason') ) ) );
$this->hiddenBlurb = \IPS\Member::loggedIn()->language()->addToStack('hidden_blurb', FALSE, array( 'sprintf' => array( \IPS\Member::load( $log['sdl_obj_member_id'] )->name, \IPS\DateTime::ts( $log['sdl_obj_date'] )->relative(), $log['sdl_obj_reason'] ?: \IPS\Member::loggedIn()->language()->addToStack('hidden_no_reason') ) ) );
}
else
{
$this->hiddenBlurb = \IPS\Member::loggedIn()->language()->addToStack('hidden_blurb_no_member', FALSE, array( 'sprintf' => array( \IPS\DateTime::ts( $log['sdl_obj_date'] )->relative(), \IPS\Member::loggedIn()->language()->addToStack( $log['sdl_obj_reason'] ) ?: \IPS\Member::loggedIn()->language()->addToStack('hidden_no_reason') ) ) );
$this->hiddenBlurb = \IPS\Member::loggedIn()->language()->addToStack('hidden_blurb_no_member', FALSE, array( 'sprintf' => array( \IPS\DateTime::ts( $log['sdl_obj_date'] )->relative(), $log['sdl_obj_reason'] ?: \IPS\Member::loggedIn()->language()->addToStack('hidden_no_reason') ) ) );
}
}
@@ -1219,6 +1223,22 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
return $index;
}
/**
* Change IP Address
* @param string $ip The new IP address
*
* @return void
*/
public function changeIpAddress( $ip )
{
if ( isset( static::$databaseColumnMap['ip_address'] ) )
{
$col = static::$databaseColumnMap['ip_address'];
$this->$col = (string) $ip;
$this->save();
}
}
/**
* Change Author
*
@@ -1229,7 +1249,9 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
{
$oldAuthor = $this->author();
if( $oldAuthor->member_id == $newAuthor->member_id )
/* If we delete a member, then change author, the old author returns 0 as does the new author as the
member row is deleted before the task is run */
if( $newAuthor->member_id and ( $oldAuthor->member_id == $newAuthor->member_id ) )
{
return;
}
@@ -1503,6 +1525,21 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
}
/**
* Prune IP addresses from content
*
* @param int $days Remove from content posted older than DAYS ago
* @return void
*/
public static function pruneIpAddresses( $days=0 )
{
if ( $days and isset( static::$databaseColumnMap['ip_address'] ) and isset( static::$databaseColumnMap['date'] ) )
{
$time = time() - ( 86400 * $days );
\IPS\Db::i()->update( static::$databaseTable, array( static::$databasePrefix . static::$databaseColumnMap['ip_address'] => '' ), array( static::$databasePrefix . static::$databaseColumnMap['date'] . ' <= ' . $time ) );
}
}
/* !Follow */
const FOLLOW_PUBLIC = 1;
@@ -1813,7 +1850,7 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
}
/* We have to send a limit even though we want all records because otherwise the Database store does not return all columns */
foreach( \IPS\Session\Store::i()->getOnlineUsers( 0, 'desc', array( 0, 5000 ), NULL, TRUE, TRUE ) as $session )
foreach( \IPS\Session\Store::i()->getOnlineUsers( 0, 'desc', array( 0, 5000 ), NULL, TRUE ) as $session )
{
if( mb_strpos( $session['location_url'], (string) $item->url() ) === 0 )
{
+38 -5
View File
@@ -423,9 +423,21 @@ class _Controller extends \IPS\Helpers\CoverPhoto\Controller
{
$class = static::$contentModel;
$item = $class::loadAndCheckPerms( \IPS\Request::i()->id );
if ( !$item->canEdit() and !\IPS\Request::i()->form_submitted ) // We check if the form has been submitted to prevent the user loosing their content
// We check if the form has been submitted to prevent the user loosing their content
if ( isset( \IPS\Request::i()->form_submitted ) )
{
throw new \OutOfRangeException;
if ( ! $item->couldEdit() )
{
throw new \OutOfRangeException;
}
}
else
{
if ( ! $item->canEdit() )
{
throw new \OutOfRangeException;
}
}
$container = NULL;
@@ -639,12 +651,23 @@ class _Controller extends \IPS\Helpers\CoverPhoto\Controller
$item = $class::loadAndCheckPerms( \IPS\Request::i()->id );
if ( !$item->canEditTitle() )
{
throw new \DomainException;
throw new \RuntimeException;
}
$oldTitle = $item->mapped( 'title' );
$titleField = $item::$databaseColumnMap['title'];
$maxLength = \IPS\Settings::i()->max_title_length ?: 255;
$titleField = $item::$databaseColumnMap['title'];
if( \strlen( \IPS\Request::i()->newTitle ) > $maxLength )
{
throw new \LengthException( \IPS\Member::loggedIn()->language()->addToStack( 'form_maxlength', FALSE, array( 'pluralize' => array( $maxLength ) ) ) );
}
elseif( !trim( \IPS\Request::i()->newTitle ) )
{
throw new \InvalidArgumentException('form_required');
}
$item->$titleField = \IPS\Request::i()->newTitle;
$idField = $item::$databaseColumnId;
$item->save();
@@ -680,6 +703,10 @@ class _Controller extends \IPS\Helpers\CoverPhoto\Controller
\IPS\Output::i()->json( 'OK' );
}
catch( \LogicException $e )
{
\IPS\Output::i()->error( $e->getMessage(), '2S136/1M', 403, '' );
}
catch ( \Exception $e )
{
\IPS\Output::i()->error( 'node_error', '2S136/11', 404, '' );
@@ -2020,6 +2047,11 @@ class _Controller extends \IPS\Helpers\CoverPhoto\Controller
$url = $url->setQueryString( 'showDeleted', 1 );
}
if ( isset( \IPS\Request::i()->_report ) )
{
$url = $url->setQueryString( '_report', \IPS\Request::i()->_report );
}
/* And redirect */
\IPS\Output::i()->redirect( $url->setFragment( $fragment . '-' . $comment->$idColumn ) );
}
@@ -2333,6 +2365,7 @@ class _Controller extends \IPS\Helpers\CoverPhoto\Controller
if ( $item->container() )
{
$item->container()->setLastComment();
$this->container()->setLastReview();
$item->container()->save();
}
}
+138 -131
View File
@@ -933,6 +933,18 @@ abstract class _Item extends \IPS\Content
$field = $commentClass::$databaseColumnMap['edit_show'];
$commentObj->$field = 0;
}
/* Check if profanity filters should mod-queue this comment */
$sendNotifications = $commentObj->checkProfanityFilters( TRUE, TRUE );
/* Send notifications */
if ( $sendNotifications AND !in_array( 'IPS\Content\Review', class_parents( get_called_class() ) ) )
{
if( $commentObj->hidden() === 1 )
{
$commentObj->sendUnapprovedNotification();
}
}
}
$oldValue = $commentObj->$column;
@@ -1231,20 +1243,47 @@ abstract class _Item extends \IPS\Content
public function contentImages( $limit = NULL )
{
$idColumn = static::$databaseColumnId;
$internal = NULL;
$internal = array();
$attachments = array();
$loadedExtensions = array();
/* Get attachments from the content, or all comments */
if ( isset( static::$databaseColumnMap['content'] ) or isset( static::$commentClass ) )
{
$internal = \IPS\Db::i()->select( 'attachment_id', 'core_attachments_map', array( 'location_key=? and id1=?', static::$application . '_' . mb_ucfirst( static::$module ), $this->$idColumn ) );
$internal = iterator_to_array( \IPS\Db::i()->select( '*', 'core_attachments_map', array( 'location_key=? and id1=?', static::$application . '_' . mb_ucfirst( static::$module ), $this->$idColumn ) )->setKeyField('attachment_id') );
}
if ( $internal )
{
foreach( \IPS\Db::i()->select( '*', 'core_attachments', array( array( 'attach_id IN(?)', $internal ), array( 'attach_is_image=1' ) ), 'attach_id ASC', $limit ) as $row )
{
$attachments[] = array( 'core_Attachment' => $row['attach_location'] );
foreach( \IPS\Db::i()->select( '*', 'core_attachments', array( array( \IPS\Db::i()->in( 'attach_id', array_keys( $internal ) ) ), array( 'attach_is_image=1' ) ), 'attach_id ASC', $limit ) as $row )
{
$map = $internal[ $row['attach_id'] ];
if ( !isset( $loadedExtensions[ $map['location_key'] ] ) )
{
$exploded = explode( '_', $map['location_key'] );
try
{
$extensions = \IPS\Application::load( $exploded[0] )->extensions( 'core', 'EditorLocations' );
if ( isset( $extensions[ $exploded[1] ] ) )
{
$loadedExtensions[ $map['location_key'] ] = $extensions[ $exploded[1] ];
}
}
catch ( \OutOfRangeException $e ) { }
}
if ( isset( $loadedExtensions[ $map['location_key'] ] ) )
{
try
{
if ( $loadedExtensions[ $map['location_key'] ]->attachmentPermissionCheck( \IPS\Member::loggedIn(), $map['id1'], $map['id2'], $map['id3'], $row, TRUE ) )
{
$attachments[] = array( 'core_Attachment' => $row['attach_location'] );
}
}
catch ( \Exception $e ) { }
}
}
}
@@ -1285,9 +1324,9 @@ abstract class _Item extends \IPS\Content
{
$return = parent::content();
}
elseif( static::$firstCommentRequired )
elseif( static::$firstCommentRequired AND $comment = $this->firstComment() )
{
$return = $this->firstComment()->content();
$return = $comment->content();
}
else
{
@@ -1505,29 +1544,18 @@ abstract class _Item extends \IPS\Content
/* Add to search index */
if ( $this instanceof \IPS\Content\Searchable )
{
if ( $this instanceof \IPS\Content\Item and static::$firstCommentRequired )
if ( $this instanceof \IPS\Content\Item AND ( isset( static::$commentClass ) OR isset( static::$reviewClass ) ) )
{
\IPS\Content\Search\Index::i()->index( $this->firstComment() );
\IPS\Content\Search\Index::i()->index( ( static::$firstCommentRequired ) ? $this->firstComment() : $this );
\IPS\Content\Search\Index::i()->indexSingleItem( $this );
}
else
{
/* Either this is a comment / review, or the item doesn't support comments or reviews, so we can just reindex it now. */
\IPS\Content\Search\Index::i()->index( $this );
}
}
$idColumn = static::$databaseColumnId;
foreach ( array( 'commentClass', 'reviewClass' ) as $class )
{
if ( isset( static::$$class ) )
{
$className = static::$$class;
if ( in_array( 'IPS\Content\Searchable', class_implements( $className ) ) )
{
\IPS\Content\Search\Index::i()->massUpdate( $className, NULL, $this->$idColumn, $this->searchIndexPermissions(), NULL, $container->_id );
}
}
}
/* Update caches */
$this->expireWidgetCaches();
$this->adjustSessions();
@@ -1782,11 +1810,11 @@ abstract class _Item extends \IPS\Content
$existingFlag = static::$useWriteServer;
static::$useWriteServer = TRUE;
$firstComment = $this->comments( 1, 0, 'date', 'asc', NULL, static::$firstCommentRequired ?: FALSE, NULL, NULL, TRUE );
$idColumn = static::$databaseColumnId;
if ( isset( static::$commentClass ) )
{
$firstComment = $this->comments( 1, 0, 'date', 'asc', NULL, static::$firstCommentRequired ?: FALSE, NULL, NULL, TRUE );
$idColumn = static::$databaseColumnId;
$commentClass = static::$commentClass;
$commentIdColumn = $commentClass::$databaseColumnId;
@@ -1897,6 +1925,7 @@ abstract class _Item extends \IPS\Content
/* Update container */
$this->container()->resetCommentCounts();
$this->container()->setLastComment();
$this->container()->setLastReview();
$this->container()->save();
}
@@ -1922,31 +1951,26 @@ abstract class _Item extends \IPS\Content
\IPS\Db::i()->delete( 'core_notifications', array( 'item_class=? AND item_id=?', (string) get_class( $this ), (int) $this->$idColumn ) );
foreach ( array( 'commentClass', 'reviewClass' ) as $class )
if ( $this instanceof \IPS\Content\Searchable )
{
if ( isset( static::$$class ) )
if ( $this instanceof \IPS\Content\Item AND ( isset( static::$commentClass ) OR isset( static::$reviewClass ) ) )
{
$className = static::$$class;
if ( in_array( 'IPS\Content\Hideable', class_implements( $className ) ) AND isset( $className::$databaseColumnMap['hidden'] ) )
$firstComment = NULL;
if ( static::$firstCommentRequired AND isset( static::$commentClass ) )
{
\IPS\Db::i()->update( $className::$databaseTable, array( $className::$databasePrefix . $className::$databaseColumnMap['hidden'] => 2 ), array( $className::$databasePrefix . $className::$databaseColumnMap['item'] . '=? AND ' . $className::$databasePrefix . $className::$databaseColumnMap['hidden'] . '=?', $this->$idColumn, 0 ) );
}
if ( in_array( 'IPS\Content\Searchable', class_implements( $className ) ) )
{
\IPS\Content\Search\Index::i()->massUpdate( $className, NULL, $this->$idColumn, NULL, 2 );
if ( static::$firstCommentRequired )
{
$firstComment = $this->comments( 1, NULL, 'date', 'asc' );
if( $firstComment )
{
$column = $className::$databaseColumnMap['hidden'];
$firstComment->$column = 2;
\IPS\Content\Search\Index::i()->index( $firstComment );
}
}
$className = static::$commentClass;
$firstComment = $this->firstComment();
$column = $className::$databaseColumnMap['hidden'];
$firstComment->$column = 2;
}
\IPS\Content\Search\Index::i()->index( ( static::$firstCommentRequired ) ? $firstComment : $this );
\IPS\Content\Search\Index::i()->indexSingleItem( $this );
}
else
{
/* Either this is a comment / review, or the item doesn't support comments or reviews, so we can just reindex it now. */
\IPS\Content\Search\Index::i()->index( $this );
}
}
@@ -2008,27 +2032,26 @@ abstract class _Item extends \IPS\Content
parent::unhide( $member );
/* And then update the search index */
if ( isset( static::$commentClass ) and static::$firstCommentRequired AND $this->mapped('first_comment_id') )
if ( $this instanceof \IPS\Content\Searchable )
{
$commentClass = static::$commentClass;
/* We have to do it this way because of R/W Separation */
$firstComment = $this->firstComment();
$column = $commentClass::$databaseColumnMap['hidden'];
$firstComment->$column = 0;
\IPS\Content\Search\Index::i()->index( $firstComment );
}
foreach ( array( 'commentClass', 'reviewClass' ) as $class )
{
if ( isset( static::$$class ) )
if ( $this instanceof \IPS\Content\Item AND ( isset( static::$commentClass ) OR isset( static::$reviewClass ) ) )
{
$className = static::$$class;
if ( in_array( 'IPS\Content\Searchable', class_implements( $className ) ) )
$firstComment = NULL;
if ( static::$firstCommentRequired AND isset( static::$commentClass ) )
{
\IPS\Content\Search\Index::i()->massUpdate( $className, NULL, $this->$idColumn, NULL, 0 );
$className = static::$commentClass;
$firstComment = $this->firstComment();
$column = $className::$databaseColumnMap['hidden'];
$firstComment->$column = 0;
}
\IPS\Content\Search\Index::i()->index( ( static::$firstCommentRequired ) ? $firstComment : $this );
\IPS\Content\Search\Index::i()->indexSingleItem( $this );
}
else
{
/* Either this is a comment / review, or the item doesn't support comments or reviews, so we can just reindex it now. */
\IPS\Content\Search\Index::i()->index( $this );
}
}
@@ -2247,6 +2270,28 @@ abstract class _Item extends \IPS\Content
}
}
/**
* Change IP Address
* @param string $ip The new IP address
*
* @return void
*/
public function changeIpAddress( $ip )
{
parent::changeIpAddress( $ip );
/* How about a required comment? */
if ( isset( static::$commentClass ) and static::$firstCommentRequired )
{
$commentClass = static::$commentClass;
if ( isset( static::$databaseColumnMap['first_comment_id'] ) AND $comment = $this->firstComment() )
{
$comment->changeIpAddress( $ip );
}
}
}
/**
* Change Author
*
@@ -2257,7 +2302,9 @@ abstract class _Item extends \IPS\Content
{
$oldAuthor = $this->author();
if( $oldAuthor->member_id == $newAuthor->member_id )
/* If we delete a member, then change author, the old author returns 0 as does the new author as the
member row is deleted before the task is run */
if( $newAuthor->member_id and ( $oldAuthor->member_id == $newAuthor->member_id ) )
{
return;
}
@@ -3297,7 +3344,7 @@ abstract class _Item extends \IPS\Content
$order = ( isset( $class::$databaseColumnMap[ $order ] ) ? ( $class::$databasePrefix . $class::$databaseColumnMap[ $order ] ) : $order ) . ' ' . $orderDirection;
}
$this->cachedReviews[ $cacheKey ] = $this->_comments( $class, $limit ?: static::$reviewsPerPage, $offset, $order, $member, $includeHiddenReviews, $cutoff, NULL, $extraWhereClause, $includeDeleted, $canViewWarn );
$this->cachedReviews[ $cacheKey ] = $this->_comments( $class, $limit ?: static::$reviewsPerPage, $offset, $order, $member, $includeHiddenReviews, $cutoff, $canViewWarn, $extraWhereClause, $includeDeleted );
return $this->cachedReviews[ $cacheKey ];
}
@@ -4233,7 +4280,7 @@ abstract class _Item extends \IPS\Content
public function canReview( $member=NULL )
{
$member = $member ?: \IPS\Member::loggedIn();
if ( $member->member_group_id == \IPS\Settings::i()->guest_group or $member->restrict_post or ( $this instanceof \IPS\Content\Lockable and $this->locked() and ( !$member->member_id or !static::modPermission( 'reply_to_locked', $member, $this->container() ) ) ) )
if ( $member->member_group_id == \IPS\Settings::i()->guest_group or $member->restrict_post or ( $this instanceof \IPS\Content\Lockable and $this->locked() and ( !$member->member_id or !static::modPermission( 'reply_to_locked', $member, $this->containerWrapper() ) ) ) )
{
return FALSE;
}
@@ -4303,17 +4350,7 @@ abstract class _Item extends \IPS\Content
return FALSE;
}
$container = NULL;
if ( method_exists( $this, 'container' ) )
{
try
{
$container = $this->container();
}
catch ( \BadMethodCallException $e ) { }
}
return ( static::modPermission( 'reply_to_locked', $member, $container ) and $this->can( $type, $member ) );
return ( static::modPermission( 'reply_to_locked', $member, $this->containerWrapper() ) and $this->can( $type, $member ) );
}
/* Check permissions as normal */
@@ -4694,6 +4731,9 @@ abstract class _Item extends \IPS\Content
if ( $resync )
{
$existingFlag = static::$useWriteServer;
static::$useWriteServer = TRUE;
try
{
$comment = $this->comments( 1, 0, 'date', 'desc', NULL, FALSE, NULL, NULL, TRUE );
@@ -4770,6 +4810,8 @@ abstract class _Item extends \IPS\Content
}
}
}
static::$useWriteServer = $existingFlag;
}
}
@@ -4797,6 +4839,9 @@ abstract class _Item extends \IPS\Content
if ( $resync )
{
$existingFlag = static::$useWriteServer;
static::$useWriteServer = TRUE;
try
{
$review = $this->reviews( 1, 0, 'date', 'desc', NULL, FALSE );
@@ -4856,6 +4901,8 @@ abstract class _Item extends \IPS\Content
}
}
}
static::$useWriteServer = $existingFlag;
}
}
@@ -5408,7 +5455,7 @@ abstract class _Item extends \IPS\Content
}
$member = $member ?: \IPS\Member::loggedIn();
return ( $member->member_id and static::modPermission( 'pin', $member, $this->container() ) );
return ( $member->member_id and static::modPermission( 'pin', $member, $this->containerWrapper() ) );
}
/**
@@ -5425,7 +5472,7 @@ abstract class _Item extends \IPS\Content
}
$member = $member ?: \IPS\Member::loggedIn();
return ( $member->member_id and static::modPermission( 'unpin', $member, $this->container() ) );
return ( $member->member_id and static::modPermission( 'unpin', $member, $this->containerWrapper() ) );
}
/**
@@ -5442,7 +5489,7 @@ abstract class _Item extends \IPS\Content
}
$member = $member ?: \IPS\Member::loggedIn();
return ( $member->member_id and static::modPermission( 'feature', $member, $this->container() ) );
return ( $member->member_id and static::modPermission( 'feature', $member, $this->containerWrapper() ) );
}
/**
@@ -5459,7 +5506,7 @@ abstract class _Item extends \IPS\Content
}
$member = $member ?: \IPS\Member::loggedIn();
return ( $member->member_id and static::modPermission( 'unfeature', $member, $this->container() ) );
return ( $member->member_id and static::modPermission( 'unfeature', $member, $this->containerWrapper() ) );
}
/**
@@ -5499,20 +5546,10 @@ abstract class _Item extends \IPS\Content
{
return FALSE;
}
$container = NULL;
if ( method_exists( $this, 'container' ) )
{
try
{
$container = $this->container();
}
catch ( \BadMethodCallException $e ) { }
}
$member = $member ?: \IPS\Member::loggedIn();
if( $member->member_id and static::modPermission( 'lock', $member, $container ) )
if( $member->member_id and static::modPermission( 'lock', $member, $this->containerWrapper() ) )
{
return TRUE;
}
@@ -5537,20 +5574,10 @@ abstract class _Item extends \IPS\Content
{
return FALSE;
}
$container = NULL;
if ( method_exists( $this, 'container' ) )
{
try
{
$container = $this->container();
}
catch ( \BadMethodCallException $e ) { }
}
$member = $member ?: \IPS\Member::loggedIn();
if( $member->member_id and static::modPermission( 'unlock', $member, $container ) )
if( $member->member_id and static::modPermission( 'unlock', $member, $this->containerWrapper() ) )
{
return TRUE;
}
@@ -5576,18 +5603,8 @@ abstract class _Item extends \IPS\Content
return FALSE;
}
$container = NULL;
if ( method_exists( $this, 'container' ) )
{
try
{
$container = $this->container();
}
catch ( \BadMethodCallException $e ) { }
}
$member = $member ?: \IPS\Member::loggedIn();
return ( $member->member_id and ( static::modPermission( 'hide', $member, $container ) or ( $member->member_id == $this->author()->member_id and ( $member->group['g_hide_own_posts'] == '1' or in_array( get_class( $this ), explode( ',', $member->group['g_hide_own_posts'] ) ) ) ) ) );
return ( $member->member_id and ( static::modPermission( 'hide', $member, $this->containerWrapper() ) or ( $member->member_id == $this->author()->member_id and ( $member->group['g_hide_own_posts'] == '1' or in_array( get_class( $this ), explode( ',', $member->group['g_hide_own_posts'] ) ) ) ) ) );
}
/**
@@ -5609,18 +5626,8 @@ abstract class _Item extends \IPS\Content
return FALSE;
}
$container = NULL;
if ( method_exists( $this, 'container' ) )
{
try
{
$container = $this->container();
}
catch ( \BadMethodCallException $e ) { }
}
$member = $member ?: \IPS\Member::loggedIn();
return ( $member->member_id and ( static::modPermission( 'unhide', $member, $container ) ) );
return ( $member->member_id and ( static::modPermission( 'unhide', $member, $this->containerWrapper() ) ) );
}
/**
@@ -5719,7 +5726,7 @@ abstract class _Item extends \IPS\Content
try
{
return ( $member->member_id and $this->container() and ( static::modPermission( 'move', $member, $this->container() ) ) );
return ( $member->member_id and $this->container() and ( static::modPermission( 'move', $member, $this->containerWrapper() ) ) );
}
catch( \BadMethodCallException $e )
{
@@ -5738,7 +5745,7 @@ abstract class _Item extends \IPS\Content
if ( static::$firstCommentRequired )
{
$member = $member ?: \IPS\Member::loggedIn();
return ( $member->member_id and ( static::modPermission( 'split_merge', $member, $this->container() ) ) );
return ( $member->member_id and ( static::modPermission( 'split_merge', $member, $this->containerWrapper() ) ) );
}
return FALSE;
}
@@ -5768,7 +5775,7 @@ abstract class _Item extends \IPS\Content
/* What about this? */
try
{
return static::modPermission( 'delete', $member, $this->container() );
return static::modPermission( 'delete', $member, $this->containerWrapper() );
}
catch ( \BadMethodCallException $e )
{
+56 -18
View File
@@ -40,19 +40,10 @@ class _Facebook extends PromoteAbstract
'tags' => NULL,
'image' => NULL,
'last_sync' => 0,
'shareable' => array()
'shareable' => array(),
'member_token' => array(),
);
/**
* Is connected?
*
* @return bool
*/
public function connected()
{
return ( $this->member->fb_uid and $this->member->fb_token );
}
/**
* Get image
*
@@ -103,7 +94,7 @@ class _Facebook extends PromoteAbstract
*/
public function canPostToPage()
{
$facebook = \IPS\Login\Handler::findMethod('IPS\Login\Handler\Oauth2\Facebook');
$facebook = static::getLoginHandler();
$authorizedScopes = $facebook->authorizedScopes( $this->member );
@@ -112,7 +103,7 @@ class _Facebook extends PromoteAbstract
return FALSE;
}
foreach( array( 'user_managed_groups', 'manage_pages', 'publish_pages', 'publish_actions' ) as $perm )
foreach( array( 'publish_to_groups', 'manage_pages', 'publish_pages' ) as $perm )
{
if ( ! in_array( $perm, $authorizedScopes ) )
{
@@ -131,7 +122,7 @@ class _Facebook extends PromoteAbstract
*/
protected static function appSecretProof( $token )
{
$facebook = \IPS\Login\Handler::findMethod('IPS\Login\Handler\Oauth2\Facebook');
$facebook = static::getLoginHandler();
return hash_hmac( 'sha256', $token, $facebook->settings['client_secret'] );
}
@@ -218,7 +209,7 @@ class _Facebook extends PromoteAbstract
$settings = $promote->form_data['facebook'];
$shareable = $this->settings['shareable'];
$responses = array();
foreach( $settings['shareable'] as $id )
{
$feedId = mb_substr( $id, 2 );
@@ -229,7 +220,7 @@ class _Facebook extends PromoteAbstract
}
else
{
$responses[ $id ][] = $this->_post( $promote, $photos, $feedId, $this->settings['token'] );
$responses[ $id ][] = $this->_post( $promote, $photos, $feedId, isset( $this->settings['member_token']['access_token'] ) ? $this->settings['member_token']['access_token'] : $this->settings['token'] );
}
}
@@ -292,14 +283,14 @@ class _Facebook extends PromoteAbstract
$response = \IPS\Http\Url::external( "https://graph.facebook.com/" . $feedId . "/photos" )->request()->post( $this->_addToken( array(
'message' => $promote->text['facebook'],
'url' => (string) $this->returnUrlWithProtocol( $thePhoto->url ),
'link' => $promote->short_link
'link' => (string) $promote->short_link
), $token ) )->decodeJson();
}
else
{
$response = \IPS\Http\Url::external( "https://graph.facebook.com/" . $feedId . "/feed" )->request()->post( $this->_addToken( array(
'message' => $promote->text['facebook'],
'link' => $promote->short_link
'link' => (string) $promote->short_link
), $token ) )->decodeJson();
}
}
@@ -315,6 +306,11 @@ class _Facebook extends PromoteAbstract
}
else
{
if ( isset( $response['error'] ) )
{
\IPS\Log::log( $response['error']['message'], 'facebook' );
}
throw new \InvalidArgumentException( \IPS\Member::loggedIn()->language()->addToStack('facebook_publish_exception') );
}
}
@@ -400,6 +396,30 @@ class _Facebook extends PromoteAbstract
throw new \InvalidArgumentException();
}
/**
* Is this member connected to the Facebook app?
*
* @param \IPS\Member $member A member object
* @return boolean
*/
public function isConnected( $member )
{
if ( \is_numeric( \IPS\Settings::i()->promote_facebook_auth ) )
{
/* Standard handler */
$facebook = \IPS\Login\Handler::findMethod('IPS\Login\Handler\Oauth2\Facebook');
return $facebook->canProcess( $member );
}
/* Custom */
if ( isset( $this->settings['member_token']['access_token'] ) )
{
return TRUE;
}
return FALSE;
}
/**
* Ensure the URL is not protocol relative
*
@@ -414,4 +434,22 @@ class _Facebook extends PromoteAbstract
return $url;
}
/**
* Wrapper to Facebook log in handler with the correct settings
*
* @return \IPS\Login object
*/
public static function getLoginHandler()
{
$facebook = \IPS\Login\Handler::findMethod('IPS\Login\Handler\Oauth2\Facebook');
/* We are using the default app here */
if ( ! \is_numeric( \IPS\Settings::i()->promote_facebook_auth ) )
{
$facebook->settings = json_decode( \IPS\Settings::i()->promote_facebook_auth, TRUE );
}
return $facebook;
}
}
+2 -1
View File
@@ -139,7 +139,8 @@ abstract class _PromoteAbstract extends \IPS\Patterns\ActiveRecord
*/
public function get_settings()
{
return array_merge( $this->defaultSettings, json_decode( $this->_data['settings'], TRUE ) );
$settings = json_decode( $this->_data['settings'], TRUE );
return ( $settings ? array_merge( $this->defaultSettings, $settings ) : $this->defaultSettings );
}
/**
+4 -4
View File
@@ -191,16 +191,16 @@ trait Reactable
/* Remove Notifications */
$memberIds = array();
foreach( \IPS\Db::i()->select( 'member', 'core_notifications', array( 'notification_key=? AND item_class=? AND item_id=?', 'new_likes', (string) get_class( $this ), (int) $this->$idColumn ) ) as $member )
foreach( \IPS\Db::i()->select( 'member', 'core_notifications', array( 'notification_key=? AND item_class=? AND item_id=?', 'new_likes', (string) get_class( $this ), (int) $this->$idColumn ) ) as $memberToRecount )
{
$memberIds[ $member ] = $member;
$memberIds[ $memberToRecount ] = $memberToRecount;
}
\IPS\Db::i()->delete( 'core_notifications', array( 'notification_key=? AND item_class=? AND item_id=?', 'new_likes', (string) get_class( $this ), (int) $this->$idColumn ) );
foreach( $memberIds as $member )
foreach( $memberIds as $memberToRecount )
{
\IPS\Member::load( $member )->recountNotifications();
\IPS\Member::load( $memberToRecount )->recountNotifications();
}
/* Reset some cached values */
+15
View File
@@ -125,6 +125,21 @@ class _ContentFilter
return $obj;
}
/**
* Constructor
* init() adds in review classes, comment classes, etc. But if we want to filter based on just the comment class, we need this method
*
* @param string $class The class
* @return \IPS\Content\Search\ContentFilter
*/
public static function initWithSpecificClass( $class )
{
$obj = new static;
$obj->classes = array( $class );
return $obj;
}
/**
* Only include results in containers
*
+234 -103
View File
@@ -47,7 +47,10 @@ class _Index extends \IPS\Content\Search\Index
{
$this->url->request()->delete();
$this->url->request()->setHeaders( array( 'Content-Type' => 'application/json' ) )->put( json_encode( array(
'mappings' => array(
'settings' => array(
'max_result_window' => \IPS\Settings::i()->search_index_maxresults
),
'mappings' => array(
'content' => array(
'properties' => array(
'index_class' => array( 'type' => 'keyword' ),
@@ -145,17 +148,87 @@ class _Index extends \IPS\Content\Search\Index
try
{
$class = get_class( $object );
/* Do not allow hidden comments to be flagged as latest */
if ( $object instanceof \IPS\Content\Comment and $indexData['index_is_last_comment'] and $indexData['index_item_id'] and $indexData['index_hidden'] !== 0 ) //
$existingData = NULL;
$existingIndexId = NULL;
$resetLastComment = FALSE;
try
{
$indexData['index_is_last_comment'] = false;
$r = $this->url->setPath( $this->url->data[ \IPS\Http\Url::COMPONENT_PATH ] . '/content/' . $this->getIndexId( $object ) )->request()->setHeaders( array( 'Content-Type' => 'application/json' ) )->get()->decodeJson();
if ( $r['found'] )
{
$existingData = $r['_source'];
$existingIndexId = $r['_id'];
}
}
catch( \Exception $e ) { }
if ( $object instanceof \IPS\Content\Comment and $existingIndexId and $existingData['index_is_last_comment'] and $indexData['index_is_last_comment'] and $indexData['index_item_id'] and $indexData['index_hidden'] !== 0 )
{
/* We do not allow hidden or needing approval comments to become flagged as the last comment as this means users without hidden view permission never see the item in an item only stream */
$indexData['index_is_last_comment'] = false;
$resetLastComment = TRUE;
}
else if ( $indexData['index_is_last_comment'] and $indexData['index_item_id'] )
{
$classes = array( $class );
/* If this is the latest comment, unflag what was set before on both item and comment */
if ( $object instanceof \IPS\Content\Comment )
{
$itemClass = $object::$itemClass;
if ( ! $itemClass::$firstCommentRequired )
{
$classes[] = $itemClass;
}
if ( isset( $itemClass::$reviewClass ) )
{
$classes[] = $itemClass::$reviewClass;
}
}
else if ( $object instanceof \IPS\Content\Item )
{
if ( isset( $class::$commentClass ) )
{
$classes[] = $class::$commentClass;
}
if ( isset( $class::$reviewClass ) )
{
$classes[] = $class::$reviewClass;
}
}
$this->url->setPath( $this->url->data[ \IPS\Http\Url::COMPONENT_PATH ] . '/content/_update_by_query' )->setQueryString( array( 'conflicts' => 'proceed', 'wait_for_completion' => 'false' ) )->request()->setHeaders( array( 'Content-Type' => 'application/json' ) )->post( json_encode( array(
'script' => array(
'inline' => "ctx._source.index_is_last_comment = false;",
'lang' => 'painless'
),
'query' => array(
'bool' => array(
'must' => array(
array(
'terms' => array(
'index_class' => $classes
)
),
array(
'term' => array(
'index_item_id' => $indexData['index_item_id']
)
),
)
)
)
) ) );
}
/* Insert into index */
$r = $this->url
->setPath( $this->url->data[ \IPS\Http\Url::COMPONENT_PATH ] . '/content/' . $this->getIndexId( $object ) )
->request()
->request( \IPS\LONG_REQUEST_TIMEOUT )
->setHeaders( array( 'Content-Type' => 'application/json' ) )
->put( json_encode( $indexData ) );
@@ -174,6 +247,11 @@ class _Index extends \IPS\Content\Search\Index
{
\IPS\Log::log( $e, 'elasticsearch' );
}
if ( $resetLastComment )
{
$this->resetLastComment( array( $indexData['index_class'] ), $indexData['index_item_id'] );
}
}
}
}
@@ -198,7 +276,7 @@ class _Index extends \IPS\Content\Search\Index
$classes[] = $class::$reviewClass;
}
$this->url->setPath( $this->url->data[ \IPS\Http\Url::COMPONENT_PATH ] . '/content/_update_by_query' )->setQueryString( 'conflicts', 'proceed' )->request()->setHeaders( array( 'Content-Type' => 'application/json' ) )->post( json_encode( array(
$this->url->setPath( $this->url->data[ \IPS\Http\Url::COMPONENT_PATH ] . '/content/_update_by_query' )->setQueryString( array( 'conflicts' => 'proceed', 'wait_for_completion' => 'false' ) )->request( \IPS\LONG_REQUEST_TIMEOUT )->setHeaders( array( 'Content-Type' => 'application/json' ) )->post( json_encode( array(
'script' => array(
'inline' => "ctx._source.index_views += ". intval( $count ) . ";",
'lang' => 'painless'
@@ -221,6 +299,44 @@ class _Index extends \IPS\Content\Search\Index
)
) ) );
}
/**
* Clear out any tasks associated with the search index method
*
* @return void
*/
public function clearTasks()
{
try
{
/* This request *intentionally* goes to _tasks and not (ourpath)/_tasks */
$response = $this->url->setPath( '/_tasks' )->request( \IPS\LONG_REQUEST_TIMEOUT )->setHeaders( array( 'Content-Type' => 'application/json' ) )->get()->decodeJson();
foreach( $response['nodes'] as $nodeId => $nodeData )
{
foreach( $nodeData['tasks'] as $taskId => $taskData )
{
/* We only need to worry about deleting parent tasks */
if( isset( $taskData['parent_task_id'] ) )
{
continue;
}
/* If the task is cancellable it isn't finished yet */
if( $taskData['cancellable'] === TRUE )
{
continue;
}
$this->url->setPath( '/.tasks/task' . $taskId )->request( \IPS\LONG_REQUEST_TIMEOUT )->setHeaders( array( 'Content-Type' => 'application/json' ) )->delete();
}
}
}
catch( \Exception $e )
{
\IPS\Log::log( $e, 'elasticsearch' );
}
}
/**
* Get the comment / review counts for an item
@@ -296,7 +412,7 @@ class _Index extends \IPS\Content\Search\Index
{
$updates[] = "ctx._source.index_is_last_comment = ( ctx._id == '{$lastCommentIndexId}' );";
}
$this->url->setPath( $this->url->data[ \IPS\Http\Url::COMPONENT_PATH ] . '/content/_update_by_query' )->setQueryString( 'conflicts', 'proceed' )->request()->setHeaders( array( 'Content-Type' => 'application/json' ) )->post( json_encode( array(
$this->url->setPath( $this->url->data[ \IPS\Http\Url::COMPONENT_PATH ] . '/content/_update_by_query' )->setQueryString( array( 'conflicts' => 'proceed', 'wait_for_completion' => 'false' ) )->request( \IPS\LONG_REQUEST_TIMEOUT )->setHeaders( array( 'Content-Type' => 'application/json' ) )->post( json_encode( array(
'script' => array(
'inline' => implode( ' ', $updates ),
'lang' => 'painless'
@@ -356,7 +472,7 @@ class _Index extends \IPS\Content\Search\Index
if ( isset( $class::$commentClass ) )
{
$commentClass = $class::$commentClass;
$response = $this->url->setPath( $this->url->data[ \IPS\Http\Url::COMPONENT_PATH ] . '/content/_delete_by_query' )->request()->setHeaders( array( 'Content-Type' => 'application/json' ) )->post( json_encode( array(
$response = $this->url->setPath( $this->url->data[ \IPS\Http\Url::COMPONENT_PATH ] . '/content/_delete_by_query' )->setQueryString( array( 'conflicts' => 'proceed', 'wait_for_completion' => 'false' ) )->request()->setHeaders( array( 'Content-Type' => 'application/json' ) )->post( json_encode( array(
'query' => array(
'bool' => array(
'must' => array(
@@ -379,7 +495,7 @@ class _Index extends \IPS\Content\Search\Index
if ( isset( $class::$reviewClass ) )
{
$reviewClass = $class::$reviewClass;
$this->url->setPath( $this->url->data[ \IPS\Http\Url::COMPONENT_PATH ] . '/content/_delete_by_query' )->request()->setHeaders( array( 'Content-Type' => 'application/json' ) )->post( json_encode( array(
$this->url->setPath( $this->url->data[ \IPS\Http\Url::COMPONENT_PATH ] . '/content/_delete_by_query' )->setQueryString( array( 'conflicts' => 'proceed', 'wait_for_completion' => 'false' ) )->request()->setHeaders( array( 'Content-Type' => 'application/json' ) )->post( json_encode( array(
'query' => array(
'bool' => array(
'must' => array(
@@ -455,13 +571,13 @@ class _Index extends \IPS\Content\Search\Index
);
}
$this->url->setPath( $this->url->data[ \IPS\Http\Url::COMPONENT_PATH ] . '/content/_delete_by_query' )->request()->setHeaders( array( 'Content-Type' => 'application/json' ) )->post( json_encode( array(
$this->url->setPath( $this->url->data[ \IPS\Http\Url::COMPONENT_PATH ] . '/content/_delete_by_query' )->setQueryString( array( 'conflicts' => 'proceed', 'wait_for_completion' => 'false' ) )->request()->setHeaders( array( 'Content-Type' => 'application/json' ) )->post( json_encode( array(
'query' => $query
) ) );
}
else
{
$this->url->setPath( $this->url->data[ \IPS\Http\Url::COMPONENT_PATH ] . '/content/_delete_by_query' )->request()->setHeaders( array( 'Content-Type' => 'application/json' ) )->post( json_encode( array(
$this->url->setPath( $this->url->data[ \IPS\Http\Url::COMPONENT_PATH ] . '/content/_delete_by_query' )->setQueryString( array( 'conflicts' => 'proceed', 'wait_for_completion' => 'false' ) )->request()->setHeaders( array( 'Content-Type' => 'application/json' ) )->post( json_encode( array(
'query' => array(
'term' => array(
'index_class' => $class
@@ -558,7 +674,7 @@ class _Index extends \IPS\Content\Search\Index
if ( count( $updates ) )
{
$this->url->setPath( $this->url->data[ \IPS\Http\Url::COMPONENT_PATH ] . '/content/_update_by_query' )->setQueryString( 'conflicts', 'proceed' )->request()->setHeaders( array( 'Content-Type' => 'application/json' ) )->post( json_encode( array(
$this->url->setPath( $this->url->data[ \IPS\Http\Url::COMPONENT_PATH ] . '/content/_update_by_query' )->setQueryString( array( 'conflicts' => 'proceed', 'wait_for_completion' => 'false' ) )->request()->setHeaders( array( 'Content-Type' => 'application/json' ) )->post( json_encode( array(
'script' => array(
'inline' => implode( ' ', $updates ),
'lang' => 'painless'
@@ -578,7 +694,7 @@ class _Index extends \IPS\Content\Search\Index
)
);
$this->url->setPath( $this->url->data[ \IPS\Http\Url::COMPONENT_PATH ] . '/content/_update_by_query' )->setQueryString( 'conflicts', 'proceed' )->request()->setHeaders( array( 'Content-Type' => 'application/json' ) )->post( json_encode( array(
$this->url->setPath( $this->url->data[ \IPS\Http\Url::COMPONENT_PATH ] . '/content/_update_by_query' )->setQueryString( array( 'conflicts' => 'proceed', 'wait_for_completion' => 'false' ) )->request()->setHeaders( array( 'Content-Type' => 'application/json' ) )->post( json_encode( array(
'script' => array(
'inline' => "ctx._source.index_permissions.add( 'm.' + ctx._source.index_author );",
'lang' => 'painless'
@@ -595,7 +711,7 @@ class _Index extends \IPS\Content\Search\Index
{
$conditions['must'][] = array(
'term' => array(
'index_item_author' => '0'
'index_hidden' => 0
)
);
}
@@ -603,14 +719,14 @@ class _Index extends \IPS\Content\Search\Index
{
$conditions['must'][] = array(
'term' => array(
'index_item_author' => '2'
'index_hidden' => 2
)
);
}
$this->url->setPath( $this->url->data[ \IPS\Http\Url::COMPONENT_PATH ] . '/content/_update_by_query' )->setQueryString( 'conflicts', 'proceed' )->request()->setHeaders( array( 'Content-Type' => 'application/json' ) )->post( json_encode( array(
$this->url->setPath( $this->url->data[ \IPS\Http\Url::COMPONENT_PATH ] . '/content/_update_by_query' )->setQueryString( array( 'conflicts' => 'proceed', 'wait_for_completion' => 'false' ) )->request()->setHeaders( array( 'Content-Type' => 'application/json' ) )->post( json_encode( array(
'script' => array(
'inline' => "ctx._source.index_permissions = " . intval( $newHiddenStatus ) . ";",
'inline' => "ctx._source.index_hidden = " . intval( $newHiddenStatus ) . ";",
'lang' => 'painless'
),
'query' => array(
@@ -706,7 +822,7 @@ class _Index extends \IPS\Content\Search\Index
{
try
{
$this->url->setPath( $this->url->data[ \IPS\Http\Url::COMPONENT_PATH ] . '/content/_delete_by_query' )->request()->setHeaders( array( 'Content-Type' => 'application/json' ) )->post( json_encode( array(
$this->url->setPath( $this->url->data[ \IPS\Http\Url::COMPONENT_PATH ] . '/content/_delete_by_query' )->setQueryString( array( 'conflicts' => 'proceed', 'wait_for_completion' => 'false' ) )->request()->setHeaders( array( 'Content-Type' => 'application/json' ) )->post( json_encode( array(
'query' => array(
'range' => array(
'index_date_updated' => array(
@@ -739,7 +855,7 @@ class _Index extends \IPS\Content\Search\Index
try
{
/* Remove the flag */
$this->url->setPath( $this->url->data[ \IPS\Http\Url::COMPONENT_PATH ] . '/content/_update_by_query' )->setQueryString( 'conflicts', 'proceed' )->request()->setHeaders( array( 'Content-Type' => 'application/json' ) )->post( json_encode( array(
$this->url->setPath( $this->url->data[ \IPS\Http\Url::COMPONENT_PATH ] . '/content/_update_by_query' )->setQueryString( array( 'conflicts' => 'proceed', 'wait_for_completion' => 'false' ) )->request()->setHeaders( array( 'Content-Type' => 'application/json' ) )->post( json_encode( array(
'script' => array(
'inline' => "ctx._source.index_is_last_comment = false;",
'lang' => 'painless'
@@ -763,76 +879,66 @@ class _Index extends \IPS\Content\Search\Index
) ) );
/* Get the latest comment */
$latest = $this->url->setPath( $this->url->data[ \IPS\Http\Url::COMPONENT_PATH ] . '/content/_search' )->request()->setHeaders( array( 'Content-Type' => 'application/json' ) )->get( json_encode( array(
'query' => array(
'bool' => array(
'must' => array(
array(
'terms' => array(
'index_class' => $classes
)
),
array(
'term' => array(
'index_item_id' => $indexItemId
)
),
array(
'term' => array(
'index_hidden' => 0
)
),
)
)
),
'sort' => array(
array(
'index_date_created' => 'desc'
)
),
'size' => 1
) ) )->decodeJson();
/* If we got it... */
if ( $latest['hits']['total'] )
$itemClass = NULL;
foreach ( $classes as $class )
{
$result = $latest['hits']['hits'][0];
/* Set that it is the latest comment */
$r = $this->url->setPath( $this->url->data[ \IPS\Http\Url::COMPONENT_PATH ] . '/' . $result['_type'] . '/' . $result['_id'] . '/_update' )->request()->setHeaders( array( 'Content-Type' => 'application/json' ) )->post( json_encode( array(
'doc' => array(
'index_is_last_comment' => true
)
) ) );
/* And set the updated time on the main item (done as _update_by_query because it might not exist if the first comment is required) */
$this->url->setPath( $this->url->data[ \IPS\Http\Url::COMPONENT_PATH ] . '/content/_update_by_query' )->setQueryString( 'conflicts', 'proceed' )->request()->setHeaders( array( 'Content-Type' => 'application/json' ) )->post( json_encode( array(
'script' => array(
'inline' => "ctx._source.index_date_updated = " . intval( $latest['_source']['index_date_updated'] ) ."; ctx._source.index_date_commented = " . intval( $latest['_source']['index_date_commented'] ) .";",
'lang' => 'painless'
),
'query' => array(
'bool' => array(
'must' => array(
array(
'terms' => array(
'index_class' => $classes
)
),
array(
'term' => array(
'index_item_id' => $indexItemId
)
),
array(
'term' => array(
'index_object_id' => $indexItemId
)
),
if ( in_array( 'IPS\Content\Item', class_parents( $class ) ) )
{
$itemClass = $class;
break;
}
elseif ( isset( $class::$itemClass ) )
{
$itemClass = $class::$itemClass;
}
}
if ( $itemClass )
{
try
{
$item = $itemClass::load( $indexItemId );
if ( $lastComment = $item->comments( 1, 0, 'date', 'desc', NULL, FALSE ) )
{
/* Set that it is the latest comment */
$r = $this->url->setPath( $this->url->data[ \IPS\Http\Url::COMPONENT_PATH ] . '/content/' . $this->getIndexId( $lastComment ) . '/_update' )->request()->setHeaders( array( 'Content-Type' => 'application/json' ) )->post( json_encode( array(
'doc' => array(
'index_is_last_comment' => true
)
)
)
) ) );
) ) );
/* And set the updated time on the main item (done as _update_by_query because it might not exist if the first comment is required) */
$indexDataForLastComment = $this->indexData( $lastComment );
$this->url->setPath( $this->url->data[ \IPS\Http\Url::COMPONENT_PATH ] . '/content/_update_by_query' )->setQueryString( array( 'conflicts' => 'proceed', 'wait_for_completion' => 'false' ) )->request()->setHeaders( array( 'Content-Type' => 'application/json' ) )->post( json_encode( array(
'script' => array(
'inline' => "ctx._source.index_date_updated = " . intval( $indexDataForLastComment['index_date_updated'] ) ."; ctx._source.index_date_commented = " . intval( $indexDataForLastComment['index_date_commented'] ) .";",
'lang' => 'painless'
),
'query' => array(
'bool' => array(
'must' => array(
array(
'terms' => array(
'index_class' => $classes
)
),
array(
'term' => array(
'index_item_id' => $indexItemId
)
),
array(
'term' => array(
'index_object_id' => $indexItemId
)
),
)
)
)
) ) );
}
}
catch ( \OutOfRangeException $e ) { }
}
}
catch ( \Exception $e )
@@ -852,23 +958,45 @@ class _Index extends \IPS\Content\Search\Index
{
try
{
$results = $this->url->setPath( $this->url->data[ \IPS\Http\Url::COMPONENT_PATH ] . '/content/_search' )->request()->setHeaders( array( 'Content-Type' => 'application/json' ) )->get( json_encode( array(
'query' => array(
'bool' => array(
'filter' => array(
array(
'terms' => array(
'index_item_index_id' => $itemIndexIds
),
/* Set the query */
$query = array(
'bool' => array(
'filter' => array(
array(
'terms' => array(
'index_item_index_id' => $itemIndexIds
),
array(
'term' => array(
'index_author' => $member->member_id
)
),
array(
'term' => array(
'index_author' => $member->member_id
)
)
)
)
);
/* Get the count */
$count = $this->url->setPath( $this->url->data[ \IPS\Http\Url::COMPONENT_PATH ] . '/content/_search' )->request()->setHeaders( array( 'Content-Type' => 'application/json' ) )->get( json_encode( array(
'size' => 0,
'query' => $query
) ) )->decodeJson();
if ( !$count['hits']['total'] )
{
return array();
}
/* Now get the unique item ids */
$results = $this->url->setPath( $this->url->data[ \IPS\Http\Url::COMPONENT_PATH ] . '/content/_search' )->request()->setHeaders( array( 'Content-Type' => 'application/json' ) )->get( json_encode( array(
'aggs' => array(
'itemIds' => array(
'terms' => array(
'field' => 'index_item_index_id',
'size' => $count['hits']['total']
)
)
),
'query' => $query
) ) )->decodeJson();
}
catch ( \Exception $e )
@@ -878,9 +1006,12 @@ class _Index extends \IPS\Content\Search\Index
}
$iPostedIn = array();
foreach ( $results['hits']['hits'] as $result )
foreach ( $results['aggregations']['itemIds']['buckets'] as $result )
{
$iPostedIn[] = $result['_source']['index_item_index_id'];
if ( $result['doc_count'] )
{
$iPostedIn[] = $result['key'];
}
}
return $iPostedIn;
+29 -8
View File
@@ -906,6 +906,12 @@ class _Query extends \IPS\Content\Search\Query
*/
public function search( $term = NULL, $tags = NULL, $method = 1, $operator = NULL )
{
/* If we're looking for more results than we can fetch, we don't need to ask Elastic Search */
if( ( $this->offset + $this->resultsToGet ) >= \IPS\Settings::i()->search_index_maxresults )
{
return new \IPS\Content\Search\Results( array(), 0 );
}
$operator = $operator ?: \IPS\Settings::i()->search_default_operator;
$must = array();
$filters = $this->filters;
@@ -924,11 +930,11 @@ class _Query extends \IPS\Content\Search\Query
$term = trim( $term, '"' );
if ( $method & static::TERM_TITLES_ONLY )
{
$searchConditions[] = array( 'match_phrase' => array( 'index_title' => array( 'query' => $term ), 'analyzer' => 'standard' ) );
$searchConditions[] = array( 'match_phrase' => array( 'index_title' => array( 'query' => $term ) ) );
}
else
{
$searchConditions[] = array( 'multi_match' => array( 'query' => $term, 'fields' => array( 'index_content', $titleField ), 'type' => 'phrase', 'analyzer' => 'standard' ) );
$searchConditions[] = array( 'multi_match' => array( 'query' => $term, 'fields' => array( 'index_content', $titleField ), 'type' => 'phrase' ) );
}
}
else
@@ -995,6 +1001,7 @@ class _Query extends \IPS\Content\Search\Query
/* Peform the search */
try
{
/* Initial query */
$query = array(
'bool' => array(
'must' => $must,
@@ -1003,6 +1010,7 @@ class _Query extends \IPS\Content\Search\Query
)
);
/* Add the time decay */
if ( \IPS\Settings::i()->search_decay_factor and !$this->sort )
{
$query = array(
@@ -1017,8 +1025,9 @@ class _Query extends \IPS\Content\Search\Query
)
);
}
if ( \IPS\Settings::i()->search_elastic_self_boost and $this->member->member_id )
/* Add the self boost */
if ( \IPS\Settings::i()->search_elastic_self_boost and $this->member->member_id and !$this->sort )
{
$query = array(
'function_score' => array(
@@ -1033,25 +1042,37 @@ class _Query extends \IPS\Content\Search\Query
);
}
$return = $this->url->setPath( $this->url->data[ \IPS\Http\Url::COMPONENT_PATH ] . '/_search' )->request()->setHeaders( array( 'Content-Type' => 'application/json' ) )->get( json_encode( array(
/* Build the JSON and validate it. Use JSON_PARTIAL_OUTPUT_ON_ERROR in case someone has used an unencodable value as
the term, and check json_encode() didn't return FALSE (which it may do on error) as a sanity check against
sending a blank query, which would return everything in the index */
$array = array(
'query' => $query,
'sort' => $this->sort ?: array(),
'from' => $this->offset,
'size' => $this->resultsToGet,
) ) )->decodeJson();
);
$json = json_encode( $array, JSON_PARTIAL_OUTPUT_ON_ERROR );
if ( $json === FALSE )
{
return new \IPS\Content\Search\Results( array(), 0 );
}
/* Make the call! */
$return = $this->url->setPath( $this->url->data[ \IPS\Http\Url::COMPONENT_PATH ] . '/_search' )->request()->setHeaders( array( 'Content-Type' => 'application/json' ) )->get( $json )->decodeJson();
if ( isset( $return['error'] ) )
{
\IPS\Log::log( print_r( $return['error'], TRUE ), 'elasticsearch' );
return new \IPS\Content\Search\Results( array(), 0 );
}
/* Set results */
return new \IPS\Content\Search\Results( array_map( function( $hit ) {
$indexData = $hit['_source'];
$indexData['index_permissions'] = implode( ',', $indexData['index_permissions'] );
$indexData['index_tags'] = $indexData['index_tags'] ? implode( ',', $indexData['index_tags'] ) : NULL;
return $indexData;
}, $return['hits']['hits'] ), $return['hits']['total'] );
}, $return['hits']['hits'] ), $return['hits']['total'] <= \IPS\Settings::i()->search_index_maxresults ? $return['hits']['total'] : (int) \IPS\Settings::i()->search_index_maxresults );
}
catch ( \Exception $e )
{
+49 -5
View File
@@ -39,7 +39,7 @@ abstract class _Index extends \IPS\Patterns\Singleton
{
if ( \IPS\Settings::i()->search_method == 'elastic' )
{
static::$instance = new \IPS\Content\Search\Elastic\Index( \IPS\Http\Url::external( \IPS\Settings::i()->search_elastic_server . '/' . \IPS\Settings::i()->search_elastic_index ) );
static::$instance = new \IPS\Content\Search\Elastic\Index( \IPS\Http\Url::external( rtrim( \IPS\Settings::i()->search_elastic_server, '/' ) . '/' . \IPS\Settings::i()->search_elastic_index ) );
}
else
{
@@ -59,7 +59,7 @@ abstract class _Index extends \IPS\Patterns\Singleton
{
if ( \IPS\Settings::i()->search_method == 'elastic' )
{
return new \IPS\Content\Search\Elastic\MassIndexer( \IPS\Http\Url::external( \IPS\Settings::i()->search_elastic_server . '/' . \IPS\Settings::i()->search_elastic_index ) );
return new \IPS\Content\Search\Elastic\MassIndexer( \IPS\Http\Url::external( rtrim( \IPS\Settings::i()->search_elastic_server, '/' ) . '/' . \IPS\Settings::i()->search_elastic_index ) );
}
else
{
@@ -104,6 +104,30 @@ abstract class _Index extends \IPS\Patterns\Singleton
}
}
/**
* Index a single items comments and reviews if applicable.
*
* @param \IPS\Content\Searchable $object The item to index
* @return void
*/
public function indexSingleItem( \IPS\Content\Searchable $object )
{
try
{
$idColumn = $object::$databaseColumnId;
if ( isset( $object::$commentClass ) AND is_subclass_of( $object::$commentClass, 'IPS\Content\Searchable' ) )
{
\IPS\Task::queue( 'core', 'IndexSingleItem', array( 'class' => $object::$commentClass, 'id' => $object->$idColumn, 'title' => $object->mapped('title'), 'url' => (string) $object->url() ), 5, TRUE );
}
if ( isset( $object::$reviewClass ) AND is_subclass_of( $object::$reviewClass, 'IPS\Content\Searchable' ) )
{
\IPS\Task::queue( 'core', 'IndexSingleItem', array( 'class' => $object::$reviewClass, 'id' => $object->$idColumn, 'title' => $object->mapped('title'), 'url' => (string) $object->url() ), 5, TRUE );
}
}
catch( \OutOfRangeException $e ) {}
}
/**
* Get index data
*
@@ -138,9 +162,19 @@ abstract class _Index extends \IPS\Patterns\Singleton
$itemClass = $class::$itemClass;
if ( $itemClass::$firstCommentRequired and $object->isFirst() )
{
$title = $object->item()->searchIndexTitle();
$tags = ( $object->item() instanceof \IPS\Content\Tags and \IPS\Settings::i()->tags_enabled ) ? implode( ',', array_filter( $object->item()->tags() ) ) : NULL;
$prefix = ( $object->item() instanceof \IPS\Content\Tags and \IPS\Settings::i()->tags_enabled ) ? $object->item()->prefix() : NULL;
try
{
$item = $object->item();
}
catch( \OutOfRangeException $ex )
{
/* Comment has no working item, return */
return NULL;
}
$title = $item->searchIndexTitle();
$tags = ( $item instanceof \IPS\Content\Tags and \IPS\Settings::i()->tags_enabled ) ? implode( ',', array_filter( $item->tags() ) ) : NULL;
$prefix = ( $item instanceof \IPS\Content\Tags and \IPS\Settings::i()->tags_enabled ) ? $item->prefix() : NULL;
$isForItem = TRUE;
}
}
@@ -375,6 +409,16 @@ abstract class _Index extends \IPS\Patterns\Singleton
{
// Do nothing by default
}
/**
* Clear out any tasks associated with the search index method
*
* @return void
*/
public function clearTasks()
{
// Do nothing by default
}
/**
* Retrieve the search ID for an item
+4 -2
View File
@@ -484,8 +484,10 @@ class _Index extends \IPS\Content\Search\Index
{
if ( $cutoff )
{
\IPS\Db::i()->delete( 'core_search_index_item_map', array( 'index_item_id IN( ? )', \IPS\Db::i()->select( 'index_item_id', 'core_search_index', array( 'index_date_updated < ?', $cutoff->getTimestamp() ) ) ) );
\IPS\Db::i()->delete( 'core_search_index_tags', array( 'index_id IN( ? )', \IPS\Db::i()->select( 'index_id', 'core_search_index', array( 'index_date_updated < ?', $cutoff->getTimestamp() ) ) ) );
$prefix = \IPS\Db::i()->prefix;
\IPS\Db::i()->query( "DELETE {$prefix}core_search_index_item_map FROM {$prefix}core_search_index_item_map, {$prefix}core_search_index WHERE {$prefix}core_search_index_item_map.index_item_id={$prefix}core_search_index.index_item_id AND index_date_updated < " . $cutoff->getTimestamp() );
\IPS\Db::i()->query( "DELETE {$prefix}core_search_index_tags FROM {$prefix}core_search_index_tags, {$prefix}core_search_index WHERE {$prefix}core_search_index_tags.index_id={$prefix}core_search_index.index_id AND index_date_updated < " . $cutoff->getTimestamp() );
\IPS\Db::i()->delete( 'core_search_index', array( 'index_date_updated < ?', $cutoff->getTimestamp() ) );
}
else
+12 -9
View File
@@ -94,7 +94,7 @@ class _Query extends \IPS\Content\Search\Query
foreach ( $contentFilters as $filter )
{
$clause = array();
if ( $type )
if ( $type and $filter->itemClass )
{
$this->itemClasses[] = $filter->itemClass;
}
@@ -116,16 +116,19 @@ class _Query extends \IPS\Content\Search\Query
$clause[] = \IPS\Db::i()->in( 'index_container_id', $filter->containerIds, $filter->containerIdFilter === FALSE );
}
$itemClass = $filter->itemClass;
if ( isset( $itemClass::$containerNodeClass ) )
if ( $filter->itemClass )
{
$containerClass = $itemClass::$containerNodeClass;
$unsearchableIds = $containerClass::unsearchableNodeIds();
if ( $unsearchableIds != NULL )
$itemClass = $filter->itemClass;
if ( isset( $itemClass::$containerNodeClass ) )
{
$clause[] = \IPS\Db::i()->in( 'index_container_id', $unsearchableIds, TRUE );
}
$containerClass = $itemClass::$containerNodeClass;
$unsearchableIds = $containerClass::unsearchableNodeIds();
if ( $unsearchableIds != NULL )
{
$clause[] = \IPS\Db::i()->in( 'index_container_id', $unsearchableIds, TRUE );
}
}
}
/* Are we excluding certain container classes? */
+1 -1
View File
@@ -54,7 +54,7 @@ abstract class _Query
{
if ( \IPS\Settings::i()->search_method == 'elastic' )
{
return new \IPS\Content\Search\Elastic\Query( $member ?: \IPS\Member::loggedIn(), \IPS\Http\Url::external( \IPS\Settings::i()->search_elastic_server . '/' . \IPS\Settings::i()->search_elastic_index ) );
return new \IPS\Content\Search\Elastic\Query( $member ?: \IPS\Member::loggedIn(), \IPS\Http\Url::external( rtrim( \IPS\Settings::i()->search_elastic_server, '/' ) . '/' . \IPS\Settings::i()->search_elastic_index ) );
}
else
{
+8 -3
View File
@@ -119,8 +119,13 @@ class _Content extends \IPS\Content\Search\Result
*/
public function addToRssFeed( \IPS\Xml\Rss $document )
{
$class = $this->indexData['index_class'];
$object = $class::load( $this->indexData['index_object_id'] );
$document->addItem( $object instanceof \IPS\Content\Comment ? $object->item()->mapped('title') : $object->mapped('title'), $object->url(), $this->indexData['index_content'], \IPS\DateTime::ts( $this->indexData['index_date_created'] ) );
try
{
$class = $this->indexData['index_class'];
$object = $class::load( $this->indexData['index_object_id'] );
$document->addItem( $object instanceof \IPS\Content\Comment ? $object->item()->searchIndexTitle() : $object->searchIndexTitle(), $object->url(), $this->indexData['index_content'], \IPS\DateTime::ts( $this->indexData['index_date_created'] ) );
}
/* If the search result was orphaned, let us continue */
catch( \OutOfRangeException $e ){}
}
}
+1 -1
View File
@@ -546,7 +546,7 @@ class _Results extends \ArrayIterator
{
$extraItem = array_pop( $extraItems );
$mergeList = array_merge( array( $urlToThing ), $extraItem->getMergeData() );
$mergeList = array_filter( array_merge( array( $urlToThing ), $extraItem->getMergeData() ) );
$listToDisplay = $mergeList;
if( count( $mergeList ) > 3 )
+2 -34
View File
@@ -53,15 +53,12 @@ class _Facebook
*/
public static function canAutoshare()
{
if ( $method = \IPS\Login\Handler::findMethod( 'IPS\Login\Handler\OAuth2\Facebook' ) and $authorizedScopes = $method->authorizedScopes( \IPS\Member::loggedIn() ) )
{
return in_array( 'publish_actions', $authorizedScopes );
}
return FALSE;
}
/**
* Publish text or a URL to this service
* @deprecated 4.3.3: Facebook have removed access to permission this method needs. Retaining method for 3rd party compatibility
*
* @param string $content Text to publish
* @param string $url [URL to publish]
@@ -69,26 +66,7 @@ class _Facebook
*/
public static function publish( $content, $url=null )
{
if ( static::canAutoshare() and $method = \IPS\Login\Handler::findMethod( 'IPS\Login\Handler\OAuth2\Facebook' ) )
{
try
{
if ( !$method->postToFacebook( \IPS\Member::loggedIn(), $content, $url ) )
{
throw new \Exception;
}
}
catch( \Exception $e )
{
\IPS\Log::log( \IPS\Member::loggedIn()->member_id . ': '. $e->getMessage(), 'facebook' );
throw new \InvalidArgumentException( \IPS\Member::loggedIn()->language()->addToStack('facebook_publish_exception') );
}
}
else
{
throw new \InvalidArgumentException( \IPS\Member::loggedIn()->language()->addToStack('facebook_publish_no_user') );
}
return FALSE;
}
/**
@@ -101,16 +79,6 @@ class _Facebook
public static function modifyForm( \IPS\Helpers\Form &$form, $service )
{
$form->add( new \IPS\Helpers\Form\Select( 'fbc_bot_group', \IPS\Settings::i()->fbc_bot_group, TRUE, array( 'options' => array_combine( array_keys( \IPS\Member\Group::groups() ), array_map( function( $_group ) { return (string) $_group; }, \IPS\Member\Group::groups() ) ) ) ) );
if ( \IPS\Login\Handler::findMethod( 'IPS\Login\Handler\OAuth2\Facebook' ) )
{
$form->add( new \IPS\Helpers\Form\YesNo( 'share_autoshare_Facebook', $service->autoshare, false ) );
}
else
{
$form->add( new \IPS\Helpers\Form\YesNo( 'share_autoshare_Facebook', FALSE, false, array( 'disabled' => TRUE ) ) );
\IPS\Member::loggedIn()->language()->words['share_autoshare_Facebook_desc'] = \IPS\Member::loggedIn()->language()->addToStack('share_autoshare_Facebook_disabled');
}
}
/**
+2 -2
View File
@@ -44,7 +44,7 @@ abstract class _Widget extends \IPS\Widget\PermissionCache
}
/* Block title */
$form->add( new \IPS\Helpers\Form\Translatable( 'widget_feed_title', isset( $this->configuration['language_key'] ) ? NULL : \IPS\Member::loggedIn()->language()->addToStack( $class::$title . '_pl' ), FALSE, array( 'app' => 'cms', 'key' => ( isset( $this->configuration['language_key'] ) ? $this->configuration['language_key'] : NULL ) ) ) );
$form->add( new \IPS\Helpers\Form\Translatable( 'widget_feed_title', isset( $this->configuration['language_key'] ) ? NULL : \IPS\Member::loggedIn()->language()->addToStack( $class::$title . '_pl' ), FALSE, array( 'app' => 'core', 'key' => ( isset( $this->configuration['language_key'] ) ? $this->configuration['language_key'] : NULL ) ) ) );
/* Container */
if ( isset( $class::$containerNodeClass ) )
@@ -357,7 +357,7 @@ abstract class _Widget extends \IPS\Widget\PermissionCache
$items = iterator_to_array( $class::getItemsWithPermission(
$where, /* Where clause */
( isset( $this->configuration['widget_feed_sort_on'] ) and isset( $this->configuration['widget_feed_sort_dir'] ) ) ? ( ( $this->configuration['widget_feed_sort_on'] == '_rand' ) ? $this->configuration['widget_feed_sort_on'] : ( $class::$databasePrefix . $this->configuration['widget_feed_sort_on'] . ' ' . $this->configuration['widget_feed_sort_dir'] ) ) : ( $class::$databasePrefix . $class::$databaseColumnMap['updated'] . ' DESC' ), /* Order */
isset( $this->configuration['widget_feed_show'] ) ? $this->configuration['widget_feed_show'] : 5, /* Limit */
( isset( $this->configuration['widget_feed_show'] ) AND $this->configuration['widget_feed_show'] ) ? $this->configuration['widget_feed_show'] : 5, /* Limit */
$skipPerms ? NULL : 'read', /* Permission key to check against */
$hidden, /* Whether or not to include hidden items */
0, NULL, FALSE, FALSE, FALSE, FALSE, NULL, $skipPerms, TRUE, FALSE, FALSE
+1 -1
View File
@@ -371,7 +371,7 @@ abstract class _WidgetComment extends \IPS\Widget\PermissionCache
$items = $class::getItemsWithPermission(
$this->buildWhere(),
( isset( $this->configuration['widget_feed_sort_on'] ) and isset( $this->configuration['widget_feed_sort_dir'] ) ) ? ( $class::$databasePrefix . $class::$databaseColumnMap['date'] . ' ' . $this->configuration['widget_feed_sort_dir'] ) : NULL,
isset( $this->configuration['widget_feed_show'] ) ? $this->configuration['widget_feed_show'] : 5,
( isset( $this->configuration['widget_feed_show'] ) AND $this->configuration['widget_feed_show'] ) ? $this->configuration['widget_feed_show'] : 5,
( !isset( $this->configuration['widget_feed_use_perms'] ) or $this->configuration['widget_feed_use_perms'] ) ? 'read' : NULL,
$hidden
);
+16 -1
View File
@@ -31,7 +31,12 @@ class _Redis extends \IPS\Data\Cache
{
try
{
\IPS\Redis::i();
$connection = \IPS\Redis::i()->connection('write');
if( !$connection )
{
throw new \RedisException;
}
}
catch( \RedisException $e )
{
@@ -48,6 +53,16 @@ class _Redis extends \IPS\Data\Cache
{
return class_exists('Redis');
}
/**
* Needs cache key check with this storage engine to maintain integrity
*
* @return boolean
*/
public function checkKeys()
{
return false;
}
/**
* Configuration
+10 -2
View File
@@ -168,7 +168,15 @@ class _Redis extends \IPS\Data\Store
*/
public function clearAll()
{
$this->_redisKey = md5( mt_rand() );
\IPS\Redis::i()->setex( 'redisKey_store', 604800, $this->_redisKey );
try
{
$this->_redisKey = md5( mt_rand() );
\IPS\Redis::i()->setex( 'redisKey_store', 604800, $this->_redisKey );
}
catch( \RedisException $e )
{
\IPS\Redis::i()->resetConnection( $e );
return FALSE;
}
}
}
+3 -1
View File
@@ -2182,16 +2182,18 @@ class _Db extends \mysqli
* @param array $definition Bitwise keys as defined by the class
* @param string $key The key to check for
* @param bool $value Value to check for
* @param string $prefix Column prefix (optional)
* @return string
* @throws \InvalidArgumentException
*/
public function bitwiseWhere( $definition, $key, $value=TRUE )
public function bitwiseWhere( $definition, $key, $value=TRUE, $prefix=NULL )
{
$operator = $value ? '& ' : '& ~';
foreach ( $definition as $column => $keys )
{
if ( isset( $keys[ $key ] ) )
{
$column = $prefix ? $prefix . $column : $column;
return "(`{$column}` {$operator}{$keys[ $key ]} ) != 0";
}
}
+43 -21
View File
@@ -128,20 +128,20 @@ abstract class _Controller
$commentClass = $class::$commentClass;
if ( isset( $class::$archiveClass ) )
{
$item = $class::loadAndCheckPerms( \IPS\Request::i()->id );
$item = $class::load( \IPS\Request::i()->id );
if ( $item->isArchived() )
{
$commentClass = $class::$archiveClass;
}
}
$content = $commentClass::loadAndCheckPerms( \IPS\Request::i()->embedComment );
$content = $commentClass::load( \IPS\Request::i()->embedComment );
$title = $content->item()->mapped('title');
}
elseif( \IPS\Request::i()->embedReview )
{
$reviewClass = $class::$reviewClass;
$content = $reviewClass::loadAndCheckPerms( \IPS\Request::i()->embedReview );
$content = $reviewClass::load( \IPS\Request::i()->embedReview );
$title = $content->item()->mapped('title');
}
else
@@ -154,25 +154,21 @@ abstract class _Controller
{
$params['do'] = \IPS\Request::i()->embedDo;
}
$content = $class::loadAndCheckPerms( \IPS\Request::i()->id );
$title = $content instanceof \IPS\Node\Model ? $content->_title : $content->mapped('title');
$content = $class::load( \IPS\Request::i()->id );
$title = $content instanceof \IPS\Node\Model ? $content->_title : $content->mapped( 'title' );
}
if ( !( $content instanceof \IPS\Content\Embeddable ) )
{
$output = \IPS\Theme::i()->getTemplate( 'embed', 'core', 'global' )->embedUnavailable();
}
else
{
$output = $content->embedContent( $params );
}
}
catch( \Exception $e )
{
$output = \IPS\Theme::i()->getTemplate( 'embed', 'core', 'global' )->embedNoPermission();
}
$output = $this->getEmbedOutput( $content, $params );
}
catch ( \OutOfRangeException $e )
{
$output = \IPS\Theme::i()->getTemplate( 'embed', 'core', 'global' )->embedUnavailable();
}
catch( \Exception $e )
{
\IPS\Log::log( $e, 'embed_error' );
$output = \IPS\Theme::i()->getTemplate( 'embed', 'core', 'global' )->embedNoPermission();
}
}
/* Make sure our iframe contents get the necessary elements and JS */
@@ -213,4 +209,30 @@ abstract class _Controller
\IPS\Output::i()->sendOutput( \IPS\Theme::i()->getTemplate( 'global', 'core', 'front' )->embedInternal( $output, $js ), 200, 'text/html', \IPS\Output::i()->httpHeaders );
}
/**
* Returns the content item or the proper error message
*
* @param \IPS\Content $content
* @param array $params
*
* @return string
*/
protected function getEmbedOutput( $content, array $params = array() )
{
if ( !$content->canView() )
{
$output = \IPS\Theme::i()->getTemplate( 'embed', 'core', 'global' )->embedNoPermission();
}
else if ( !( $content instanceof \IPS\Content\Embeddable ) )
{
$output = \IPS\Theme::i()->getTemplate( 'embed', 'core', 'global' )->embedUnavailable();
}
else
{
$output = $content->embedContent( $params );
}
return $output;
}
}
+4 -1
View File
@@ -39,11 +39,14 @@ class _External extends \IPS\Dispatcher\Standard
/* Base JS */
static::baseJs();
/* Run global init */
try
{
parent::init();
/* Don't update sessions for this hit as it will wipe location data */
\IPS\Session::i()->noUpdate();
}
catch ( \DomainException $e )
{
+13 -8
View File
@@ -172,15 +172,20 @@ class _Front extends \IPS\Dispatcher\Standard
}
/* Do we need more info from the member or do they need to validate? */
if( \IPS\Member::loggedIn()->member_id and $this->controller !== 'language' and $this->controller !== 'theme' and $this->controller !== 'ajax' )
/* This controllers should always be accessible, no matter if the member is awaiting validation or needs to set up the email or name */
$legalControllers = array( 'privacy', 'contact', 'terms' );
/* Do we need more info from the member or do they need to validate? */
if( \IPS\Member::loggedIn()->member_id and $this->controller !== 'language' and $this->controller !== 'theme' and $this->controller !== 'ajax' and !\in_array( $this->controller, $legalControllers ) )
{
/* Need their name or email... */
if( ( \IPS\Member::loggedIn()->real_name === '' or !\IPS\Member::loggedIn()->email ) and $this->controller !== 'register' )
{
{
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=core&module=system&controller=register&do=complete' )->setQueryString( 'ref', base64_encode( \IPS\Request::i()->url() ) ) );
}
/* Need them to validate... */
elseif( \IPS\Member::loggedIn()->members_bitoptions['validating'] and $this->controller !== 'register' and $this->controller !== 'login' and $this->controller != 'redirect' and $this->controller !== 'contact' and $this->controller !== 'privacy' )
elseif( \IPS\Member::loggedIn()->members_bitoptions['validating'] and $this->controller !== 'register' and $this->controller !== 'login' and $this->controller != 'redirect' )
{
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=core&module=system&controller=register&do=validating', 'front', 'register' ) );
}
@@ -190,7 +195,7 @@ class _Front extends \IPS\Dispatcher\Standard
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=core&module=system&controller=register&do=reconfirm', 'front', 'register' )->setQueryString( 'ref', base64_encode( \IPS\Request::i()->url() ) ) );
}
/* Have required profile actions that need completing */
else if ( \IPS\Settings::i()->quick_register AND !\IPS\Member::loggedIn()->members_bitoptions['profile_completed'] AND !in_array( $this->controller, array( 'register', 'login', 'redirect', 'contact', 'privacy', 'ajax', 'settings' ) ) AND $completion = \IPS\Member::loggedIn()->profileCompletion() AND count( $completion['required'] ) )
else if ( \IPS\Settings::i()->quick_register AND !\IPS\Member::loggedIn()->members_bitoptions['profile_completed'] AND !in_array( $this->controller, array( 'register', 'login', 'redirect', 'ajax', 'settings' ) ) AND $completion = \IPS\Member::loggedIn()->profileCompletion() AND count( $completion['required'] ) )
{
foreach( $completion['required'] AS $id => $completed )
{
@@ -201,7 +206,7 @@ class _Front extends \IPS\Dispatcher\Standard
}
}
/* Need to set up MFA... */
elseif ( !in_array( $this->controller, array( 'register', 'login', 'redirect', 'contact', 'privacy', 'ajax', 'settings' ) ) )
elseif ( !in_array( $this->controller, array( 'register', 'login', 'redirect', 'ajax', 'settings' ) ) )
{
$haveAcceptableHandlers = FALSE;
$haveConfiguredHandler = FALSE;
@@ -416,7 +421,7 @@ class _Front extends \IPS\Dispatcher\Standard
}
/* Which theme? */
$theme = isset( \IPS\Request::i()->cookie['theme'] ) ? \IPS\Request::i()->cookie['theme'] : \IPS\Theme::defaultTheme();
$theme = isset( \IPS\Request::i()->cookie['theme'] ) ? \IPS\Request::i()->cookie['theme'] : '';
/* Get cache */
try
@@ -581,7 +586,7 @@ class _Front extends \IPS\Dispatcher\Standard
/* MFA Login? */
if ( isset( \IPS\Request::i()->_mfaLogin ) and isset( $_SESSION['processing2FA'] ) and $member = \IPS\Member::load( $_SESSION['processing2FA']['memberId'] ) and $member->member_id )
{
$device = \IPS\Member\Device::loadOrCreate( $member );
$device = \IPS\Member\Device::loadOrCreate( $member, FALSE );
if ( $output = \IPS\MFA\MFAHandler::accessToArea( 'core', $device->known ? 'AuthenticateFrontKnown' : 'AuthenticateFront', \IPS\Request::i()->url(), $member ) )
{
if ( $return )
@@ -675,4 +680,4 @@ class _Front extends \IPS\Dispatcher\Standard
}
}
}
}
}
+2 -2
View File
@@ -130,8 +130,8 @@ class _Setup extends \IPS\Dispatcher
require \IPS\ROOT_PATH . '/conf_global.php';
if ( isset( $INFO ) and isset( $INFO['installed'] ) )
{
echo \IPS\Member::loggedIn()->language()->addToStack( 'installer_locked' );
exit;
$upgradeUrl = new \IPS\Http\Url( \IPS\Settings::i()->base_url . \IPS\CP_DIRECTORY . '/upgrade/' );
\IPS\Output::i()->redirect( $upgradeUrl );
}
}
/* Upgrader Checks */
+13 -2
View File
@@ -36,6 +36,11 @@ abstract class _Standard extends \IPS\Dispatcher
* Controller
*/
public $controller;
/**
* @brief Check access permissions
*/
public $checkGenericPermissions = TRUE;
/**
* Base CSS
@@ -81,10 +86,16 @@ abstract class _Standard extends \IPS\Dispatcher
\IPS\Output::i()->jsVars['ipb_url_filter_option'] = \IPS\Settings::i()->ipb_url_filter_option;
\IPS\Output::i()->jsVars['url_filter_any_action'] = \IPS\Settings::i()->url_filter_any_action;
\IPS\Output::i()->jsVars['bypass_profanity'] = intval( \IPS\Member::loggedIn()->group['g_bypass_badwords'] );
\IPS\Output::i()->jsVars['emoji_style'] = \IPS\Settings::i()->getFromConfGlobal('sql_utf8mb4') ? \IPS\Settings::i()->emoji_style : 'disabled';
\IPS\Output::i()->jsVars['emoji_style'] = ( \IPS\Settings::i()->getFromConfGlobal('sql_utf8mb4') === TRUE ) ? \IPS\Settings::i()->emoji_style : 'disabled';
\IPS\Output::i()->jsVars['emoji_shortcodes'] = \IPS\Settings::i()->emoji_shortcodes;
\IPS\Output::i()->jsVars['emoji_ascii'] = \IPS\Settings::i()->emoji_ascii;
\IPS\Output::i()->jsVars['emoji_cache'] = \IPS\Settings::i()->emoji_cache;
/* Load the howler JS package if notifications are enabled */
if( !\IPS\Member::loggedIn()->members_bitoptions['disable_notification_sounds'] )
{
array_unshift( \IPS\Output::i()->jsFiles, \IPS\Output::i()->js( 'howler/howler.core.min.js', 'core', 'interface' )[0] );
}
}
}
@@ -186,7 +197,7 @@ abstract class _Standard extends \IPS\Dispatcher
}
/* Init Application */
if( !$this->application->canAccess( \IPS\Member::loggedIn() ) AND $this->controllerLocation != 'admin' )
if( $this->checkGenericPermissions === TRUE AND !$this->application->canAccess( \IPS\Member::loggedIn() ) AND $this->controllerLocation != 'admin' )
{
\IPS\Output::i()->jsFiles = array_merge( \IPS\Output::i()->jsFiles, \IPS\Output::i()->js( 'app.js' ) );
$message = $this->application->disabled_message ?: 'generic_offline_message';
+15 -6
View File
@@ -278,7 +278,7 @@ abstract class _Email
/* Get our picks */
$ourPicks = NULL;
if( \IPS\Settings::i()->our_picks_in_email and $this->type !== 'transactional' )
if( \IPS\Settings::i()->promote_community_enabled and \IPS\Settings::i()->our_picks_in_email and $this->type !== 'transactional' )
{
$ourPicks = \IPS\core\Promote::internalStream( 4 );
}
@@ -291,7 +291,7 @@ abstract class _Email
$language->parseEmail( $return );
/* Parse URLs */
$this->parseFileObjectUrls( $return );
static::parseFileObjectUrls( $return );
/* Return */
return $return;
@@ -324,7 +324,7 @@ abstract class _Email
/* Using a template */
elseif ( $this->templateApp )
{
{
$return = trim( static::devProcessTemplate( "email__{$this->templateApp}_{$this->templateKey}_subject_" . str_replace( array( ' ', '.', '-', '@' ), '_', $language->short ), $language->get( "mailsub__{$this->templateApp}_{$this->templateKey}" ), array_merge( $this->templateParams, array( $this ) ) ) );
}
@@ -543,7 +543,7 @@ abstract class _Email
'mlog_subject' => $subject,
'mlog_content' => $html ?: $plaintext,
'mlog_resend_data' => json_encode( array( 'type' => $this->type, 'headers' => $this->_compileHeaders( $subject, $to, $cc, $bcc, $fromEmail, $fromName, $additionalHeaders, $boundary ), 'body' => array( 'html' => $html, 'plain' => $plaintext ), 'boundary' => $boundary ) ),
'mlog_msg' => $e->getMessage(),
'mlog_msg' => json_encode( array( 'message' => $e->getMessage(), 'details' => $e->extraDetails ) ),
'mlog_smtp_log' => $this->getLog(),
) );
@@ -597,6 +597,10 @@ abstract class _Email
public function mergeAndSend( $recipients, $fromEmail = NULL, $fromName = NULL, $additionalHeaders = array(), \IPS\Lang $language )
{
$return = 0;
/* Get the current locale, and then set the language's locale so datetime formatting in templates is correct for this language */
$currentLocale = setlocale( LC_ALL, '0' );
$language->setLocale();
foreach ( $recipients as $address => $vars )
{
@@ -625,6 +629,9 @@ abstract class _Email
$return++;
}
}
/* Now restore the locale we started with */
\IPS\Lang::restoreLocale( $currentLocale );
return $return;
}
@@ -1211,14 +1218,16 @@ abstract class _Email
* Makes HTML acceptable for use in emails: Image
*
* @param \DOMElement $node The element
* @param \DOMElement $parent The element's parent node
* @param \DOMNode $parent The element's parent node
* @param \IPS\Lang $language Language
* @return string
*/
protected static function _parseElementForEmailImage( \DOMElement &$node, \DOMNode $parent, \IPS\Lang $language )
{
$maxImageDims = \IPS\Settings::i()->attachment_image_size ? explode( 'x', \IPS\Settings::i()->attachment_image_size ) : array( 1000, 750 );
$node->setAttribute( 'style', "max-width:{$maxImageDims[0]}px;max-height:{$maxImageDims[1]}px;" );
/* Set the max image height and width */
$node->setAttribute( 'style', "max-width:{$maxImageDims[0]}px;max-height:{$maxImageDims[1]}px;" . $node->getAttribute('style') ) ;
}
/**
+23
View File
@@ -290,6 +290,13 @@ class _Email
{
$this->to = array( $headers['to'] );
}
/* We need to account for formats like "Doe, John (Contractor)" <a@b.com>, "Doe, Jane" <z@b.com> */
elseif( mb_strpos( $headers['to'], '"' ) !== FALSE )
{
/* The regex here is looking for opening quote, anything except <, comma, anything, closing quote and then stripping the comma so that
commas are only used to separate the actual addresses */
$this->to = explode( ',', preg_replace( "/\"([^<]*?),(.*?)\"/", "\"$1 $2\"", $headers['to'] ) );
}
else
{
$this->to = explode( ',', $headers['to'] );
@@ -310,6 +317,13 @@ class _Email
{
$this->cc = $headers['cc'];
}
/* We need to account for formats like "Doe, John (Contractor)" <a@b.com>, "Doe, Jane" <z@b.com> */
elseif( mb_strpos( $headers['cc'], '"' ) !== FALSE )
{
/* The regex here is looking for opening quote, anything except <, comma, anything, closing quote and then stripping the comma so that
commas are only used to separate the actual addresses */
$this->cc = explode( ',', preg_replace( "/\"([^<]*?),(.*?)\"/", "\"$1 $2\"", $headers['cc'] ) );
}
else
{
$this->cc = explode( ",", $headers['cc'] );
@@ -540,6 +554,15 @@ class _Email
{
$exploded = explode( '<br>', $body );
}
/* It is possible the email is formatted with <p></p> and not <br> which can confuse the quote selection */
if ( mb_stristr( $body, '</p>' ) or mb_stristr( $body, '</div>' ) )
{
$body = preg_replace( '#<(p|div)[^>]*?>#', '', $body );
$body = preg_replace( '#</(p|div)>#', '<br>', $body );
$exploded = explode( '<br>', $body );
}
foreach ( $exploded as $k => $line )
{
$line = trim( $line );
+2 -2
View File
@@ -62,7 +62,7 @@ class _Debug extends \IPS\Email
{
if( !is_dir( $this->debugPath ) )
{
throw new \IPS\Email\Outgoing\Exception( sprintf( \IPS\Member::loggedIn()->language()->get('no_path_email_debug'), $this->debugPath ) );
throw new \IPS\Email\Outgoing\Exception( 'no_path_email_debug', 0, NULL, array( $this->debugPath ) );
}
$fullEmailContents = $this->compileFullEmail( $to, $cc, $bcc, $fromEmail, $fromName, $additionalHeaders );
@@ -70,7 +70,7 @@ class _Debug extends \IPS\Email
$filename = date("M-j-Y") . '-' . microtime() . '-' . urlencode( mb_substr( $this->_parseRecipients( $to, TRUE ), 0, 200 ) ) . ".eml";
if( !@\file_put_contents( rtrim( $this->debugPath, '/' ) . '/' . $filename, $fullEmailContents ) )
{
throw new \IPS\Email\Outgoing\Exception( \IPS\Member::loggedIn()->language()->addToStack( 'no_write_email_debug', FALSE, array( 'sprintf' => array( rtrim( $this->debugPath, '/' ) . '/' . $filename ) ) ) );
throw new \IPS\Email\Outgoing\Exception( 'no_write_email_debug', 0, NULL, array( rtrim( $this->debugPath, '/' ) . '/' . $filename ) );
}
}
}
+21 -1
View File
@@ -22,5 +22,25 @@ if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
*/
class _Exception extends \RuntimeException
{
/**
* @brief Extra details for log
*/
public $extraDetails = array();
/**
* Constructor
*
* @param string $message Error message
* @param int $code Error Code
* @param \Exception|NULL $previous Previous Exception
* @param array|NULL $extra Extra details for log
* @return void
*/
public function __construct( $message = null, $code = 0, $previous = null, $extra=NULL )
{
/* Store these for the extraLogData() method */
$this->extraDetails = $extra;
return parent::__construct( $message, $code, $previous );
}
}
+1 -1
View File
@@ -61,7 +61,7 @@ class _PHP extends \IPS\Email
else
{
/* If $error is null, mail() is probably disabled */
throw new \IPS\Email\Outgoing\Exception( \IPS\Member::loggedIn()->language()->addToStack( 'email_test_mailfunction_disabled', FALSE ) );
throw new \IPS\Email\Outgoing\Exception( 'email_test_mailfunction_disabled' );
}
}
}
+2 -2
View File
@@ -192,7 +192,7 @@ class _SendGrid extends \IPS\Email
'mlog_subject' => $subject,
'mlog_content' => $htmlContent,
'mlog_resend_data' => NULL,
'mlog_msg' => json_encode( $e->getMessage() ),
'mlog_msg' => json_encode( array( 'message' => $e->getMessage() ) ),
'mlog_smtp_log' => NULL
) );
@@ -210,7 +210,7 @@ class _SendGrid extends \IPS\Email
'mlog_subject' => $subject,
'mlog_content' => $htmlContent,
'mlog_resend_data' => NULL,
'mlog_msg' => json_encode( $response['errors'] ),
'mlog_msg' => json_encode( array( 'message' => $response['errors'] ) ),
'mlog_smtp_log' => NULL
) );
}
+2 -2
View File
@@ -109,7 +109,7 @@ class _SMTP extends \IPS\Email
$responseCode = mb_substr( $announce, 0, 3 );
if ( $responseCode != 220 )
{
throw new \IPS\Email\Outgoing\Exception( \IPS\Member::loggedIn()->language()->addToStack('smtpmail_fsock_error_initial', FALSE, array( 'sprintf' => array( $responseCode ) ) ) );
throw new \IPS\Email\Outgoing\Exception( 'smtpmail_fsock_error_initial', 0, NULL, array( $responseCode ) );
}
/* HELO/EHLO */
@@ -143,7 +143,7 @@ class _SMTP extends \IPS\Email
else
{
/* If it still failed on the second connection attempt, throw the exception */
throw new \IPS\Email\Outgoing\Exception( \IPS\Member::loggedIn()->language()->addToStack('smtpmail_tls_failed') );
throw new \IPS\Email\Outgoing\Exception( 'smtpmail_tls_failed' );
}
}
+14 -4
View File
@@ -322,6 +322,10 @@ class _Amazon extends \IPS\File
{
throw new \IPS\File\Exception( $this->container ? "{$this->container}/{$this->filename}" : "{$this->filename}", \IPS\File\Exception::DOES_NOT_EXIST );
}
elseif( $response->httpResponseCode == 403 )
{
throw new \IPS\File\Exception( $this->container ? "{$this->container}/{$this->filename}" : "{$this->filename}", \IPS\File\Exception::CANNOT_COPY );
}
else
{
$this->contents = (string) $response;
@@ -339,13 +343,19 @@ class _Amazon extends \IPS\File
{
$this->container = trim( $this->container, '/' );
$path = $this->container ? "{$this->container}/{$this->filename}" : "{$this->filename}";
$debug = array_map( function( $row ) {
return array_filter( $row, function( $key ) {
return in_array( $key, array( 'class', 'function', 'line' ) );
}, ARRAY_FILTER_USE_KEY );
}, debug_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS ) );
try
{
$response = static::makeRequest( $path, 'DELETE', $this->configuration, $this->configurationId );
/* Log deletion request */
$this->log( "file_deletion", 'delete', NULL, 'log' );
$this->log( "file_deletion", 'delete', $debug, 'log' );
if ( $response->httpResponseCode == 204 )
{
@@ -361,13 +371,13 @@ class _Amazon extends \IPS\File
if ( $response->httpResponseCode != 200 )
{
$this->log( 'COULD_NOT_DELETE_FILE', 'delete', array( $response->httpResponseCode, $response->httpResponseText ), 'error' );
$this->log( 'COULD_NOT_DELETE_FILE', 'delete', array( $response->httpResponseCode, $response->httpResponseText, $debug ), 'error' );
}
}
catch( \IPS\Http\Request\Exception $e )
{
/* If there was a problem deleting the file, don't stop code execution just because of that */
$this->log( 'HTTP_ERROR_DELETE_FILE', 'delete', array( $e->getCode(), $e->getMessage() ), 'error' );
$this->log( 'HTTP_ERROR_DELETE_FILE', 'delete', array( $e->getCode(), $e->getMessage(), $debug ), 'error' );
}
}
+7 -1
View File
@@ -196,7 +196,13 @@ class _Database extends \IPS\File
\IPS\Db::i()->delete( 'core_files', array( 'filename=? AND container=?', $this->filename, $this->container ) );
/* Log deletion request */
$this->log( "file_deletion", 'delete', NULL, 'log' );
$immediateCaller = debug_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS, 1 );
$debug = array_map( function( $row ) {
return array_filter( $row, function( $key ) {
return in_array( $key, array( 'class', 'function', 'line' ) );
}, ARRAY_FILTER_USE_KEY );
}, debug_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS ) );
$this->log( "file_deletion", 'delete', $debug, 'log' );
}
/**
+6 -7
View File
@@ -60,12 +60,12 @@ abstract class _File
/**
* Get class
*
* @param string|int $storageExtension Storage extension or configuration ID
* @param string|\IPS\Http\Url|NULL $url URL to validate storage configs with
* @param string|int $storageExtension Storage extension or configuration ID
* @param bool $tryOldFirst Whether to try the old file storage config first or not
* @throws \RuntimeException
* @return \IPS\File
*/
public static function getClass( $storageExtension, $url=NULL )
public static function getClass( $storageExtension, $tryOldFirst=FALSE )
{
static::getConfigurations();
@@ -114,8 +114,8 @@ abstract class _File
/* We have an array of IDs when a move is in progress, the first ID is the new storage method, the second ID is the old */
if ( is_array( $settings[ "filestorage__{$storageExtension}" ] ) )
{
/* Do we have a URL to check? */
if ( $url )
/* Do we want to use the old storage config if available? */
if ( $tryOldFirst === TRUE )
{
/* We want the old storage method as we know the file is on the old server, but may not have moved to the new yet */
$copyOfSettings = $settings["filestorage__{$storageExtension}"];
@@ -522,13 +522,12 @@ abstract class _File
*/
public static function get( $storageExtension, $url )
{
/* Don't allow something/../../../../etc/passwd */
if( mb_strpos( rawurldecode( $url ), '../' ) !== FALSE )
{
throw new \OutOfRangeException( 'INVALID_PATH' );
}
$class = static::getClass( $storageExtension, $url );
$class = static::getClass( $storageExtension, TRUE );
$class->storageExtension = $storageExtension;
$class->url = $url;
$class->load();
+8 -2
View File
@@ -362,9 +362,15 @@ class _FileSystem extends \IPS\File
* @return bool
*/
public function delete()
{
{
/* Log deletion request */
$this->log( "file_deletion", 'delete', NULL, 'log' );
$immediateCaller = debug_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS, 1 );
$debug = array_map( function( $row ) {
return array_filter( $row, function( $key ) {
return in_array( $key, array( 'class', 'function', 'line' ) );
}, ARRAY_FILTER_USE_KEY );
}, debug_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS ) );
$this->log( "file_deletion", 'delete', $debug, 'log' );
if( file_exists( "{$this->configuration['dir']}/{$this->container}/{$this->filename}" ) )
{
+7 -1
View File
@@ -198,7 +198,13 @@ class _Ftp extends \IPS\File
}
/* Log deletion request */
$this->log( "file_deletion", 'delete', NULL, 'log' );
$immediateCaller = debug_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS, 1 );
$debug = array_map( function( $row ) {
return array_filter( $row, function( $key ) {
return in_array( $key, array( 'class', 'function', 'line' ) );
}, ARRAY_FILTER_USE_KEY );
}, debug_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS ) );
$this->log( "file_deletion", 'delete', $debug, 'log' );
}
/**
+45 -1
View File
@@ -100,10 +100,11 @@ class _Google extends \IPS\GeoLocation\GeoCoder
* Get the latitude and longitude for the current object. Address must be set.
*
* @param \IPS\GeoLocation $geoLocation Geolocation object
* @param bool $setAddress Whether or not to update the address information from the GeoCoder service
* @return void
* @throws \BadMethodCallException
*/
public function setLatLong( \IPS\GeoLocation &$geoLocation )
public function setLatLong( \IPS\GeoLocation &$geoLocation, $setAddress = FALSE )
{
if ( \IPS\Settings::i()->googlemaps AND \IPS\Settings::i()->google_maps_api_key AND $geoLocation->toString() )
{
@@ -129,6 +130,49 @@ class _Google extends \IPS\GeoLocation\GeoCoder
$geoLocation->lat = $data['results'][0]['geometry']['location']['lat'];
$geoLocation->long = $data['results'][0]['geometry']['location']['lng'];
if( $setAddress === TRUE )
{
$_address = '';
/* Set the address data */
foreach( $data['results'][0]['address_components'] as $component )
{
if( $component['types'][0] == 'street_number' )
{
$_address = $component['long_name'];
}
elseif( $component['types'][0] == 'route' )
{
$_address .= " " . $component['long_name'];
}
if( $component['types'][0] == 'postal_code' )
{
$geoLocation->postalCode = $component['long_name'];
}
if( $component['types'][0] == 'country' )
{
$geoLocation->country = $component['short_name'];
}
if( $component['types'][0] == 'administrative_area_level_1' )
{
$geoLocation->region = $component['long_name'];
}
if( $component['types'][0] == 'locality' )
{
$geoLocation->city = $component['long_name'];
}
}
if( $_address )
{
$geoLocation->addressLines = array( $_address );
}
}
}
else
{
+45 -1
View File
@@ -99,10 +99,11 @@ class _Mapbox extends \IPS\GeoLocation\GeoCoder
* Get the latitude and longitude for the current object. Address must be set.
*
* @param \IPS\GeoLocation $geoLocation Geolocation object
* @param bool $setAddress Whether or not to update the address information from the GeoCoder service
* @return void
* @throws \BadMethodCallException
*/
public function setLatLong( \IPS\GeoLocation &$geoLocation )
public function setLatLong( \IPS\GeoLocation &$geoLocation, $setAddress = FALSE )
{
if ( \IPS\Settings::i()->mapbox AND \IPS\Settings::i()->mapbox_api_key AND $geoLocation->toString() )
{
@@ -126,6 +127,49 @@ class _Mapbox extends \IPS\GeoLocation\GeoCoder
$geoLocation->long = $data['features'][0]['center'][0];
$geoLocation->lat = $data['features'][0]['center'][1];
if( $setAddress === TRUE )
{
if( isset( $data['results'] ) AND is_array( $data['results'] ) AND count( $data['results'] ) )
{
foreach( $data['results'][0]['address_components'] as $component )
{
if( $component['types'][0] == 'street_number' )
{
$_address = $component['long_name'];
}
elseif( $component['types'][0] == 'route' )
{
$_address .= " " . $component['long_name'];
}
if( $component['types'][0] == 'postal_code' )
{
$geoLocation->postalCode = $component['long_name'];
}
if( $component['types'][0] == 'country' )
{
$geoLocation->country = $component['short_name'];
}
if( $component['types'][0] == 'administrative_area_level_1' )
{
$geoLocation->region = $component['long_name'];
}
if( $component['types'][0] == 'locality' )
{
$geoLocation->city = $component['long_name'];
}
}
}
if( $_address )
{
$geoLocation->addressLines = array( $_address );
}
}
}
else
{
+32 -3
View File
@@ -543,6 +543,11 @@ class _GeoLocation
return static::buildFromJson( $data );
}
/**
* @brief Cached lookups to prevent duplicate lookups
*/
static protected $lookupCache = array( 'latlong' => array(), 'address' => array() );
/**
* Get by latitude and longitude
*
@@ -554,18 +559,42 @@ class _GeoLocation
*/
public static function getByLatLong( $lat, $long )
{
return \IPS\GeoLocation\GeoCoder::i()->decodeLatLong( $lat, $long );
if( isset( static::$lookupCache['latlong'][ $lat . 'x' . $long ] ) )
{
return static::$lookupCache['latlong'][ $lat . 'x' . $long ];
}
static::$lookupCache['latlong'][ $lat . 'x' . $long ] = \IPS\GeoLocation\GeoCoder::i()->decodeLatLong( $lat, $long );
return static::$lookupCache['latlong'][ $lat . 'x' . $long ];
}
/**
* Get the latitude and longitude for the current object. Address must be set.
*
* @param bool $setAddress Whether or not to update the address information from the GeoCoder service
* @return void
* @throws \BadMethodCallException
*/
public function getLatLong()
public function getLatLong( $setAddress = FALSE )
{
\IPS\GeoLocation\GeoCoder::i()->setLatLong( $this );
$lookupKey = md5( $this->toString() );
if( isset( static::$lookupCache['address'][ $lookupKey ] ) )
{
foreach( json_decode( static::$lookupCache['address'][ $lookupKey ], true ) as $property => $value )
{
if( $property == 'lat' OR $property == 'long' OR $setAddress === TRUE )
{
$this->$property = $value;
}
}
return;
}
\IPS\GeoLocation\GeoCoder::i()->setLatLong( $this, $setAddress );
static::$lookupCache['address'][ $lookupKey ] = json_encode( $this );
}
/**
+1 -1
View File
@@ -53,7 +53,7 @@ class _Mapbox
return "";
}
$location = $this->geolocation->long . ',' . $this->geolocation->lat;
$location = str_replace( ',', '.', $this->geolocation->long ) . ',' . str_replace( ',', '.', $this->geolocation->lat );
$imageUrl = \IPS\Http\Url::external( "https://api.mapbox.com/styles/v1/mapbox/streets-v10/static/pin-l-marker+f00({$location})/{$location},14,0,60/{$width}x{$height}@2x" )->setQueryString( array(
'access_token' => \IPS\Settings::i()->mapbox_api_key,
+9 -1
View File
@@ -216,7 +216,15 @@ class _Database extends \IPS\Helpers\Chart\Dynamic
}
$table = new \IPS\Helpers\Table\Db( $this->table, $this->url, $where );
$table->joins = $this->joins;
/* Reformat chart join for the table helper */
if( \is_array( $this->joins ) )
{
array_walk( $this->joins, function ( $item ) use ( $table )
{
$table->joins[] = array('from' => $item[0], 'where' => $item[1], 'type' => isset( $item[2] ) ? $item[2] : 'LEFT');
} );
}
if( count( $this->tableInclude ) )
{
+6 -1
View File
@@ -92,7 +92,12 @@ class _Color extends FormAbstract
public function validate()
{
parent::validate();
if( !$this->required AND ( !$this->value OR $this->value == '#' ) )
{
return TRUE;
}
if ( !preg_match( '/^(?:#)?(([a-f0-9]{3})|([a-f0-9]{6}))$/i', $this->value ) )
{
throw new \InvalidArgumentException('form_color_bad');
+5 -1
View File
@@ -177,7 +177,7 @@ class _Date extends FormAbstract
{
if ( is_int( $v ) or ( $v and is_string( $v ) and preg_match( '/^[0-9]+$/', $v ) ) )
{
return \IPS\DateTime::ts( $v );
return \IPS\DateTime::ts( $v, $timezone );
}
else if( is_array( $v ) )
{
@@ -207,6 +207,10 @@ class _Date extends FormAbstract
}
else if( $v instanceof \IPS\DateTime )
{
if ( $timezone )
{
$v->setTimeZone( $timezone );
}
return $v;
}
else
+13 -3
View File
@@ -45,6 +45,7 @@ class _Editor extends FormAbstract
'maxLength' => FALSE, // The maximum length. Note that the content is HTML, so this isn't the maximum number of visible characters, so should only be used for database limits. The database's max allowed packet will override if smaller
'editorId' => NULL, // Passed to editorAttachments. Only necessary if the name may be changed
'allowAttachments => TRUE, // Should the editor show upload options?
'embedLocalOnly' => FALSE, // Allow local embeds only
);
* @endcode
*/
@@ -64,6 +65,7 @@ class _Editor extends FormAbstract
'maxLength' => 16777215, // The default for MEDIUMTEXT */
'editorId' => NULL,
'allowAttachments' => TRUE,
'embedLocalOnly' => FALSE
);
/**
@@ -113,6 +115,11 @@ class _Editor extends FormAbstract
}
$this->extension = $extensions[ $args[3]['key'] ];
if ( method_exists( $this->extension, 'embedLocalOnly' ) )
{
$args[3]['embedLocalOnly'] = $this->extension->embedLocalOnly();
}
}
/* Don't minimize if we have a value */
@@ -251,8 +258,8 @@ class _Editor extends FormAbstract
$value = \IPS\Text\Parser::mungeResources( $value );
}
/* Fix Emoji */
\IPS\Output::i()->replaceEmojiWithImages( $value );
/* Fix Emoji */
$value = \IPS\Output::i()->replaceEmojiWithImages( $value );
/* CKEditor will replace <p></p> (which doesn't display anything in a normal post) with <p><br></p> (which does)
which creates a discrepency between wheat displays in a post and what displays in the editor */
@@ -345,7 +352,7 @@ class _Editor extends FormAbstract
$parser = $this->_getParser();
$value = $parser->parse( $value );
}
/* Add any attachments that weren't inserted in the content */
if ( $this->uploader )
{
@@ -618,6 +625,9 @@ class _Editor extends FormAbstract
/* Handle delete calls */
if ( isset( \IPS\Request::i()->postKey ) and \IPS\Request::i()->postKey == $this->postKey and isset( \IPS\Request::i()->deleteFile ) )
{
/* CSRF check */
\IPS\Session::i()->csrfCheck();
/* Get the attachment */
try
{
+1 -1
View File
@@ -156,7 +156,7 @@ class _Item extends FormAbstract
{
if ( isset( $itemClass::$databaseColumnMap[ $this->options['orderResults'][0] ] ) )
{
$order = $itemClass::$databaseTable . '.' . $itemClass::$databaseColumnMap[ $this->options['orderResults'][0] ] . ' ' . $this->options['orderResults'][1];
$order = $itemClass::$databaseTable . '.' . $itemClass::$databasePrefix . $itemClass::$databaseColumnMap[ $this->options['orderResults'][0] ] . ' ' . $this->options['orderResults'][1];
}
}
+8 -1
View File
@@ -248,7 +248,14 @@ class _Matrix extends \IPS\Helpers\Form
{
if( is_array( $inputValue ) AND isset( $inputValue[ $newId ] ) )
{
$data = array_merge( $data, $inputValue[ $newId ] );
/* @note This previously used an array_merge() however this was causing elements to be overwritten with a blank value in some cases */
foreach( $inputValue[ $newId ] AS $key => $value )
{
if ( $value )
{
$data[ $key ] = $value;
}
}
}
}
}
+28 -2
View File
@@ -83,6 +83,7 @@ class _Stack extends FormAbstract
foreach( $this->value as $k => $v )
{
$class = ( new $classType( $this->name . '[' . count($fields) . ']', $v, FALSE, $this->options, $this->customValidationCode, $this->prefix, $this->suffix, $this->htmlId . '_' . count($fields) ) );
$class->setValue( TRUE );
$html = $class->html();
if( !\IPS\Login::compareHashes( $remove, md5( $html ) ) )
@@ -94,6 +95,7 @@ class _Stack extends FormAbstract
else
{
$class = ( new $classType( $this->name . '[0]', NULL, FALSE, $this->options, $this->customValidationCode, $this->prefix, $this->suffix, $this->htmlId . '_' . count($fields) ) );
$class->setValue( TRUE );
$html = $class->html();
if( !\IPS\Login::compareHashes( $remove, md5( $html ) ) )
@@ -106,6 +108,7 @@ class _Stack extends FormAbstract
if ( $this->reloadForm === TRUE AND $remove === NULL )
{
$class = ( new $classType( $this->name . '[]', NULL, FALSE, $this->options, $this->customValidationCode, $this->prefix, $this->suffix, $this->htmlId . '_' . count($fields) ) );
$class->setValue( TRUE );
$html = $class->html();
$fields[] = $html;
}
@@ -113,6 +116,27 @@ class _Stack extends FormAbstract
return \IPS\Theme::i()->getTemplate( 'forms', 'core', 'global' )->stack( $this->name, $fields, $this->options );
}
/**
* @brief Temporarily store if this is the intitial setValue call
*/
protected $_setValueInitial = NULL;
/**
* Set the value of the element
*
* @param bool $initial Whether this is the initial call or not. Do not reset default values on subsequent calls.
* @param bool $force Set the value even if one was not submitted (done on the final validation when getting values)?
* @return void
*/
public function setValue( $initial=FALSE, $force=FALSE )
{
$this->_setValueInitial = $initial;
parent::setValue( $initial, $force );
$this->_setValueInitial = NULL;
}
/**
* Format Value
*
@@ -130,7 +154,8 @@ class _Stack extends FormAbstract
{
foreach( \IPS\Request::i()->$name as $k => $v )
{
$class = ( new $classType( $this->name . '[]', $v, FALSE, $this->options, $this->customValidationCode, $this->prefix, $this->suffix, $this->htmlId ) );
$class = ( new $classType( $this->name . '[' . $k . ']', $v, FALSE, $this->options, $this->customValidationCode, $this->prefix, $this->suffix, $this->htmlId ) );
$class->setValue( $this->_setValueInitial, $this->_setValueInitial ? FALSE : TRUE );
$values[] = $class->value;
}
}
@@ -141,7 +166,8 @@ class _Stack extends FormAbstract
{
foreach( $this->value as $k => $v )
{
$class = ( new $classType( $this->name . '[]', $v, FALSE, $this->options, $this->customValidationCode, $this->prefix, $this->suffix, $this->htmlId ) );
$class = ( new $classType( $this->name . '[' . $k . ']', $v, FALSE, $this->options, $this->customValidationCode, $this->prefix, $this->suffix, $this->htmlId ) );
$class->setValue( $this->_setValueInitial, $this->_setValueInitial ? FALSE : TRUE );
$values[] = $class->value;
}
+6 -4
View File
@@ -190,13 +190,15 @@ class _Text extends TextArea
{
foreach( $return as $k => $v )
{
$return = preg_replace( '/\p{C}+/u', '', $return );
$return[ $k ] = preg_replace( '/\p{C}+/u', '', $v );
}
}
}
if ( isset( $this->options['autocomplete']['prefix'] ) and $this->options['autocomplete']['prefix'] )
{
$return = is_array( $return ) ? $return : ( $return ? array( $return ) : array() );
$firstAsPrefix = $this->name . '_first_as_prefix';
$freechoicePrefixCheckbox = $this->name . '_freechoice_prefix';
$freechoicePrefix = $this->name . '_prefix';
@@ -437,7 +439,7 @@ class _Text extends TextArea
}
/* Split on profanity */
if( $this->options['bypassProfanity'] === FALSE and is_string( $this->value ) and \IPS\Dispatcher::i() instanceof \IPS\Dispatcher\Front and !\IPS\Member::loggedIn()->group['g_bypass_badwords'] )
if( $this->options['bypassProfanity'] === FALSE and is_string( $this->value ) and \IPS\Dispatcher::i() instanceof \IPS\Dispatcher\Front and !\IPS\Member::loggedIn()->group['g_bypass_badwords'] AND !$this->options['accountUsername'] )
{
$newVal = NULL;
if ( count( $exactProfanityBreakpoints ) )
@@ -457,7 +459,7 @@ class _Text extends TextArea
}
else
{
throw new \DomainException( 'Profanity Filter' );
throw new \DomainException( \IPS\Member::loggedIn()->language()->addToStack( 'form_tags_not_allowed', FALSE, array( 'sprintf' => array( $section ) ) ) );
}
}
else
@@ -483,7 +485,7 @@ class _Text extends TextArea
{
if ( mb_stristr( $value, $type ) )
{
throw new \DomainException( 'Profanity Filter' );
throw new \DomainException( \IPS\Member::loggedIn()->language()->addToStack( 'form_tags_not_allowed', FALSE, array( 'sprintf' => array( $row->type ) ) ) );
}
}
}
+2
View File
@@ -282,6 +282,8 @@ class _Upload extends FormAbstract
}
catch ( \Exception $e )
{
\IPS\Log::log( $e, 'upload_failure' );
$message = $e->getMessage();
if ( \IPS\Member::loggedIn()->isAdmin() and \IPS\Member::loggedIn()->language()->checkKeyExists("uploaderr_{$e->getMessage()}_admin") )
{
+6 -5
View File
@@ -205,11 +205,12 @@ class _Db extends Table
if ( isset( $options['multiple'] ) AND $options['multiple'] === TRUE )
{
$where[] = array( \IPS\Db::i()->in( $k, $v ) );
break;
}
else
{
$where[] = array( "{$k}=?", $v );
}
// No break so we fall through to radio
case SEARCH_RADIO:
$where[] = array( "{$k}=?", $v );
break;
case SEARCH_MEMBER:
@@ -308,7 +309,7 @@ class _Db extends Table
$count = $count->first();
/* Now get column headers */
$query = \IPS\Db::i()->select( '*', $this->table, NULL, NULL, array( 0, 1 ) );
$query = \IPS\Db::i()->select( ( count( $selects ) ) ? $this->table . '.*, ' . implode( ', ', $selects ) : '*', $this->table, NULL, NULL, array( 0, 1 ) );
if ( count( $this->joins ) )
{
+1 -1
View File
@@ -986,7 +986,7 @@ class _Url
}
/* The path is terminated by the first question mark ("?") or number sign ("#") character, or by the end of the URI. */
preg_match( '/^(.+?)(\?|\#|$)/', $url, $matches );
preg_match( '/^(.*?)(\?|\#|$)/', $url, $matches );
if ( !static::validateComponent( static::COMPONENT_PATH, $matches[1] ) )
{
if ( $autoEncode )
+11
View File
@@ -230,6 +230,17 @@ class _Internal extends \IPS\Http\Url
return static::convertQueryAsArrayToString( $queryString );
}
/**
* Check if the URL is an open redirect.
* Useful if you are checking if a given URL is internal to ensure it also doesn't redirect elsewhere
*
* @return bool
*/
public function openRedirect()
{
return isset( $this->queryString['app'] ) and $this->queryString['app'] === 'core' and isset( $this->queryString['module'] ) and $this->queryString['module'] === 'system' and isset( $this->queryString['controller'] ) and $this->queryString['controller'] === 'redirect';
}
/* !Deprecated */
/**
+6 -1
View File
@@ -142,7 +142,7 @@ class _Useragent
'alexa' => "^ia_archive",
'altavista' => "Scooter[ /\-]*[a-z]*([0-9.]{1,10})",
'ask' => "Ask[ \-]?Jeeves",
'baidu' => "^baiduspider\-",
'baidu' => array( "^baiduspider\-", "baiduspider[ /]([0-9.]{1,10})" ),
'bing' => array( "bingbot[ /]([0-9.]{1,10})", "msnbot(?:-media)?[ /]([0-9.]{1,10})" ),
'brandwatch' => "magpie-crawler",
'excite' => "Architext[ \-]?Spider",
@@ -161,6 +161,11 @@ class _Useragent
'websense' => "(?:Sqworm|websense|Konqueror/3\.(?:0|1)(?:\-rc[1-6])?; i686 Linux; 2002[0-9]{4})",
'yahoo' => "Yahoo(?:.*?)(?:Slurp|FeedSeeker)",
'yandex' => "Yandex(?:[^\/]+?)\/([0-9.]{1,10})",
'seznam' => array( "SeznamBot[ /]([0-9.]{1,10})", "Seznam screenshot-generator ([0-9.]{1,10})" ),
'dotbot' => "DotBot[ /]([0-9.]{1,10})",
'sogou' => "Sogou web spider[ /]([0-9.]{1,10})",
'isetallabot' => "istellabot[ /][a-z]([0-9.]{1,10})",
'blexbot' => "BLEXBot[ /]([0-9.]{1,10})"
);
/**
+26
View File
@@ -90,6 +90,32 @@ class _Imagemagick extends \IPS\Image
*/
public function __toString()
{
/* If possible, retain the color profiles when stripping EXIF data */
if( \IPS\Settings::i()->imagick_strip_exif )
{
$imageColorProfiles = array();
try
{
$imageColorProfiles = $this->imagick->getImageProfiles( 'icc', true );
}
catch( \ImagickException $e ){}
$this->imagick->stripImage();
if( count( $imageColorProfiles ) )
{
foreach( $imageColorProfiles as $type => $profile )
{
try
{
$this->imagick->profileImage( $type, $profile );
}
catch( \ImagickException $e ){}
}
}
}
return (string) $this->imagick->getImagesBlob();
}
@@ -56,7 +56,7 @@ class _ExternalDatabase extends \IPS\Login\Handler
'login_external_conn',
'sql_host' => new \IPS\Helpers\Form\Text( 'login_external_host', ( isset( $this->settings['sql_host'] ) and $this->settings['sql_host'] ) ? $this->settings['sql_host'] : 'localhost', TRUE ),
'sql_user' => new \IPS\Helpers\Form\Text( 'login_external_user', isset( $this->settings['sql_user'] ) ? $this->settings['sql_user'] : NULL, TRUE ),
'sql_pass' => new \IPS\Helpers\Form\Text( 'login_external_pass', isset( $this->settings['sql_pass'] ) ? $this->settings['sql_pass'] : NULL, TRUE ),
'sql_pass' => new \IPS\Helpers\Form\Text( 'login_external_pass', isset( $this->settings['sql_pass'] ) ? $this->settings['sql_pass'] : NULL, FALSE ),
'sql_database' => new \IPS\Helpers\Form\Text( 'login_external_database', isset( $this->settings['sql_database'] ) ? $this->settings['sql_database'] : NULL, TRUE ),
'sql_port' => new \IPS\Helpers\Form\Number( 'login_external_port', isset( $this->settings['sql_port'] ) ? $this->settings['sql_port'] : 3306, FALSE ),
'sql_socket' => new \IPS\Helpers\Form\Text( 'login_external_socket', isset( $this->settings['sql_socket'] ) ? $this->settings['sql_socket'] : NULL, FALSE ),
@@ -222,6 +222,8 @@ class _ExternalDatabase extends \IPS\Login\Handler
$settings['update_name_changes'] = $_values['login_update_name_changes_inc_optional'];
$settings['update_email_changes'] = $_values['login_update_email_changes_inc_optional'];
}
$settings['forgot_password_url'] = (string) $settings['forgot_password_url'];
unset( $settings['update_name_changes_inc_optional'] );
unset( $settings['update_name_changes_no_optional'] );
@@ -935,7 +937,7 @@ class _ExternalDatabase extends \IPS\Login\Handler
return sha1( $providedPassword );
case 'password_hash':
return password_hash( $providedPassword );
return password_hash( $providedPassword, PASSWORD_DEFAULT );
case 'other':
try
+14 -2
View File
@@ -150,7 +150,7 @@ abstract class _Handler extends \IPS\Node\Model
{
try
{
$this->_cachedLinks[ $member->member_id ] = \IPS\Db::i()->select( '*', 'core_login_links', array( 'token_login_method=? AND token_member=? AND token_linked=1', $this->id, $member->member_id ) )->first();
$this->_cachedLinks[ $member->member_id ] = \IPS\Db::i()->select( '*', 'core_login_links', array( 'token_login_method=? AND token_member=? AND token_linked=1', $this->id, $member->member_id ), NULL, NULL, NULL, NULL, \IPS\Db::SELECT_FROM_WRITE_SERVER )->first();
}
catch ( \UnderflowException $e )
{
@@ -340,6 +340,7 @@ abstract class _Handler extends \IPS\Node\Model
public function completeLink( \IPS\Member $member, $details )
{
\IPS\Db::i()->update( 'core_login_links', array( 'token_linked' => 1 ), array( 'token_login_method=? AND token_member=?', $this->id, $member->member_id ) );
unset( $this->_cachedLinks[ $member->member_id ] );
$member->logHistory( 'core', 'social_account', array(
'service' => static::getTitle(),
@@ -809,6 +810,17 @@ abstract class _Handler extends \IPS\Node\Model
return parent::formatFormValues( $values );
}
/**
* Test Compatibility
*
* @return bool
* @throws \LogicException
*/
public static function testCompatibility()
{
return TRUE;
}
/**
* Test Settings
@@ -818,7 +830,7 @@ abstract class _Handler extends \IPS\Node\Model
*/
public function testSettings()
{
return TRUE;
return static::testCompatibility();
}
/**
+12 -6
View File
@@ -37,7 +37,7 @@ abstract class _OAuth1 extends \IPS\Login\Handler
public function acpForm()
{
$return = array(
array( 'login_handler_oauth_settings', static::getTitle() . '_info' ),
array( 'login_handler_oauth_settings', \IPS\Member::loggedIn()->language()->addToStack( static::getTitle() . '_info', FALSE, array( 'sprintf' => array( (string) $this->redirectionEndpoint() ) ) ) ),
'consumer_key' => new \IPS\Helpers\Form\Text( 'oauth_consumer_key', ( isset( $this->settings['consumer_key'] ) ) ? $this->settings['consumer_key'] : '', TRUE ),
'consumer_secret' => new \IPS\Helpers\Form\Text( 'oauth_consumer_secret', ( isset( $this->settings['consumer_secret'] ) ) ? $this->settings['consumer_secret'] : '', TRUE ),
);
@@ -125,11 +125,7 @@ abstract class _OAuth1 extends \IPS\Login\Handler
*/
protected function _redirectToAuthorizationEndpoint( \IPS\Login $login )
{
$callback = $login->url->setQueryString( '_processLogin', $this->id )->csrf();
if ( isset( \IPS\Request::i()->ref ) )
{
$callback = $callback->setQueryString( 'ref', \IPS\Request::i()->ref );
}
$callback = $this->redirectionEndpoint()->setQueryString( 'state' , $this->id . '-' . base64_encode( $login->url ) . '-' . \IPS\Session::i()->csrfKey . '-' . \IPS\Request::i()->ref );
try
{
@@ -297,6 +293,16 @@ abstract class _OAuth1 extends \IPS\Login\Handler
* @return \IPS\Http\Url
*/
abstract protected function accessTokenEndpoint();
/**
* Redirection Endpoint
*
* @return \IPS\Http\Url
*/
public function redirectionEndpoint()
{
return \IPS\Http\Url::internal( 'oauth/callback/', 'none' );
}
/**
* Send Request
+5
View File
@@ -454,6 +454,11 @@ class _Twitter extends \IPS\Login\Handler\OAuth1
{
$response = $this->_sendRequest( 'get', \IPS\Http\Url::external('https://api.twitter.com/1.1/account/verify_credentials.json'), array(), $accessToken, $accessTokenSecret );
if ( $response->httpResponseCode == 401 )
{
return FALSE;
}
if ( $response->httpHeaders['x-access-level'] == 'read' )
{
return FALSE;
+10 -3
View File
@@ -102,9 +102,9 @@ class _Custom extends \IPS\Login\Handler\OAuth2
}
}
};
$return['authorization_endpoint'] = new \IPS\Helpers\Form\Url( 'oauth_authorization_endpoint', isset( $this->settings['authorization_endpoint'] ) ? $this->settings['authorization_endpoint'] : NULL, NULL, array( 'placeholder' => 'https://example.com/oauth/authorize' ), $authorizationEndpointValidation, NULL, NULL, 'authorization_endpoint' );
$return['authorization_endpoint_secure'] = new \IPS\Helpers\Form\Url( 'oauth_authorization_endpoint_secure', isset( $this->settings['authorization_endpoint_secure'] ) ? $this->settings['authorization_endpoint_secure'] : NULL, NULL, array( 'nullLang' => 'oauth_authorization_endpoint_same', 'placeholder' => 'https://example.com/oauth/authorize/?prompt=login' ), $authorizationEndpointValidation, NULL, NULL, 'authorization_endpoint_secure' );
$return['authorization_endpoint_secure'] = new \IPS\Helpers\Form\Url( 'oauth_authorization_endpoint_secure', ( isset( $this->settings['authorization_endpoint_secure'] ) AND $this->settings['authorization_endpoint_secure'] ) ? $this->settings['authorization_endpoint_secure'] : NULL, NULL, array( 'nullLang' => 'oauth_authorization_endpoint_same', 'placeholder' => 'https://example.com/oauth/authorize/?prompt=login' ), $authorizationEndpointValidation, NULL, NULL, 'authorization_endpoint_secure' );
$return['token_endpoint'] = new \IPS\Helpers\Form\Url( 'oauth_token_endpoint', isset( $this->settings['token_endpoint'] ) ? $this->settings['token_endpoint'] : NULL, TRUE, array( 'placeholder' => 'https://www.example.com/oauth/token' ) );
$return['user_endpoint'] = new \IPS\Helpers\Form\Url( 'oauth_user_endpoint', isset( $this->settings['user_endpoint'] ) ? $this->settings['user_endpoint'] : NULL, TRUE, array( 'placeholder' => 'https://www.example.com/oauth/me' ) );
$return['uid_field'] = new \IPS\Helpers\Form\Text( 'oauth_custom_uid_field', isset( $this->settings['uid_field'] ) ? $this->settings['uid_field'] : NULL, TRUE, array() );
@@ -442,7 +442,14 @@ class _Custom extends \IPS\Login\Handler\OAuth2
->decodeJson();
}
}
/* Check for any errors */
if ( static::getValueFromArray( $response, $this->settings['uid_field'] ) === NULL )
{
\IPS\Log::log( print_r( $response, TRUE ), 'oauth_custom' );
throw new \IPS\Login\Exception( 'generic_error', \IPS\Login\Exception::INTERNAL_ERROR );
}
/* Set */
$this->_cachedUserData[ $accessToken ] = $response;
}
+80 -50
View File
@@ -22,11 +22,6 @@ if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
*/
class _Facebook extends \IPS\Login\Handler\OAuth2
{
/**
* @brief Share Service
*/
public static $shareService = 'Facebook';
/**
* Get title
*
@@ -71,6 +66,22 @@ class _Facebook extends \IPS\Login\Handler\OAuth2
return $return;
}
/**
* Test Compatibility
*
* @return bool
* @throws \LogicException
*/
public static function testCompatibility()
{
if ( mb_substr( \IPS\Settings::i()->base_url, 0, 8 ) !== 'https://' )
{
throw new \LogicException( \IPS\Member::loggedIn()->language()->addToStack( \IPS\CIC ? 'login_facebook_https_cic' : 'login_facebook_https' ) );
}
return TRUE;
}
/**
* Get the button color
@@ -143,25 +154,12 @@ class _Facebook extends \IPS\Login\Handler\OAuth2
{
$return = array('email');
if ( isset( static::$shareService ) )
{
try
{
$shareService = \IPS\core\ShareLinks\Service::load( static::$shareService, 'share_key' );
if ( $shareService->autoshare )
{
$return[] = 'publish_actions';
}
}
catch ( \OutOfRangeException $e ) { }
}
if ( \IPS\Settings::i()->profile_comments and isset( $this->settings['allow_status_import'] ) and $this->settings['allow_status_import'] )
{
$return[] = 'user_posts';
}
$additionalPermitted = array( 'manage_pages', 'publish_pages', 'user_managed_groups', 'publish_actions' );
$additionalPermitted = array( 'manage_pages', 'publish_pages', 'publish_to_groups' );
if ( $additional !== NULL )
{
foreach( $additional as $scope )
@@ -186,7 +184,9 @@ class _Facebook extends \IPS\Login\Handler\OAuth2
{
try
{
$response = $this->_authorizedRequest( 'me/permissions', $accessToken );
$response = $this->_authorizedRequest( 'me/permissions', $accessToken, array(
'appsecret_proof' => hash_hmac( 'sha256', $accessToken, $this->settings['client_secret'] )
), 'get' );
}
catch ( \Exception $e )
{
@@ -406,22 +406,6 @@ class _Facebook extends \IPS\Login\Handler\OAuth2
return $return;
}
/**
* Get link to user's remote profile
* May return NULL if server doesn't support this
*
* @param string $identifier The ID Nnumber/string from remote service
* @param string $username The username from remote service
* @return \IPS\Http\Url|NULL
* @throws \IPS\Login\Exception The token is invalid and the user needs to reauthenticate
* @throws \DomainException General error where it is safe to show a message to the user
* @throws \RuntimeException Unexpected error from service
*/
public function userLink( $identifier, $username )
{
return \IPS\Http\Url::external( "https://www.facebook.com/" . $identifier );
}
/**
* Syncing Options
*
@@ -432,8 +416,9 @@ class _Facebook extends \IPS\Login\Handler\OAuth2
public function syncOptions( \IPS\Member $member, $defaultOnly = FALSE )
{
$return = array();
if ( ( !isset( $this->settings['update_email_changes'] ) or $this->settings['update_email_changes'] === 'optional' ) and in_array( 'email', $this->authorizedScopes( $member ) ) )
$scopes = $this->authorizedScopes( $member );
if ( ( !isset( $this->settings['update_email_changes'] ) or $this->settings['update_email_changes'] === 'optional' ) and ( $scopes and in_array( 'email', $scopes ) ) )
{
$return[] = 'email';
}
@@ -446,7 +431,7 @@ class _Facebook extends \IPS\Login\Handler\OAuth2
$return[] = 'photo';
$return[] = 'cover';
if ( \IPS\Settings::i()->profile_comments and isset( $this->settings['allow_status_import'] ) and $this->settings['allow_status_import'] and in_array( 'user_posts', $this->authorizedScopes( $member ) ) )
if ( \IPS\Settings::i()->profile_comments and isset( $this->settings['allow_status_import'] ) and $this->settings['allow_status_import'] and ( $scopes and in_array( 'user_posts', $this->authorizedScopes( $member ) ) ) )
{
$return[] = 'status';
}
@@ -546,6 +531,22 @@ class _Facebook extends \IPS\Login\Handler\OAuth2
/* ! Social Promotion */
/**
* Exchange a short lived member token for a longer lived token
*
* @param string $shortLivedToken The short lived token to exchange for a long lived token
* @return array
*/
public function exchangeMemberToken( $code )
{
$accessToken = $this->_exchangeAuthorizationCodeForAccessToken( $code );
/* Get user ID */
$accessToken['identifier'] = $this->authenticatedUserId( $accessToken['access_token'] );
return $accessToken;
}
/**
* Exchange a short lived token for a longer lived token
*
@@ -579,13 +580,13 @@ class _Facebook extends \IPS\Login\Handler\OAuth2
*/
public function getPages( $member )
{
if ( !( $link = $this->_link( $member ) ) )
{
throw new \IPS\Login\Exception( $error['message'], \IPS\Login\Exception::INTERNAL_ERROR );
}
$pages = array();
$response = $this->_authorizedRequest( $link['token_identifier'] . '/accounts', $link['token_access_token'], array(
if ( !( $link = $this->_promoteLink( $member ) ) )
{
return $pages;
}
$response = $this->_authorizedRequest( $link['token_identifier'] . '/accounts?limit=100', $link['token_access_token'], array(
'appsecret_proof' => hash_hmac( 'sha256', $link['token_access_token'], $this->settings['client_secret'] )
), 'get' );
@@ -608,11 +609,11 @@ class _Facebook extends \IPS\Login\Handler\OAuth2
*/
public function getGroups( $member )
{
if ( !( $link = $this->_link( $member ) ) )
{
throw new \IPS\Login\Exception( $error['message'], \IPS\Login\Exception::INTERNAL_ERROR );
}
$groups = array();
if ( !( $link = $this->_promoteLink( $member ) ) )
{
return $groups;
}
$response = $this->_authorizedRequest( $link['token_identifier'] . '/groups', $link['token_access_token'], array(
'appsecret_proof' => hash_hmac( 'sha256', $link['token_access_token'], $this->settings['client_secret'] )
@@ -622,11 +623,40 @@ class _Facebook extends \IPS\Login\Handler\OAuth2
{
foreach( $response['data'] as $group )
{
$groups[ $group['id'] ] = array( $group['name'], $group['privacy'] );
$groups[ $group['id'] ] = array( $group['name'], ( isset( $group['privacy'] ) ? $group['privacy'] : NULL ) );
}
}
return $groups;
}
/**
* Get user link
*
* @param \IPS\Member $member Member requesting pages
* @return array
*/
protected function _promoteLink( $member )
{
if ( \is_numeric( \IPS\Settings::i()->promote_facebook_auth ) )
{
/* standard handler */
if ( !( $link = $this->_link( $member ) ) )
{
return $pages;
}
}
else
{
$account = \IPS\core\Promote::getPromoter('Facebook')->setMember( $member );
$this->settings = json_decode( \IPS\Settings::i()->promote_facebook_auth, TRUE );
$link = array(
'token_identifier' => $account->settings['member_token']['identifier'],
'token_access_token' => $account->settings['member_token']['access_token']
);
}
return $link;
}
}
+1 -1
View File
@@ -79,7 +79,7 @@ class _Invision extends \IPS\Login\Handler\OAuth2
}
if ( !is_string( $v ) and !is_array( $v ) )
{
$$active[ $k ] = $v;
${$active}[ $k ] = $v;
}
}
+21 -9
View File
@@ -56,9 +56,9 @@ abstract class _OAuth2 extends \IPS\Login\Handler
* @endcode
*/
public function acpForm()
{
{
$return = array(
array( 'login_handler_oauth_settings', static::getTitle() . '_info' ),
array( 'login_handler_oauth_settings', \IPS\Member::loggedIn()->language()->addToStack( static::getTitle() . '_info', FALSE, array( 'sprintf' => array( (string) $this->redirectionEndpoint() ) ) ) ),
'client_id' => new \IPS\Helpers\Form\Text( 'oauth_client_id', isset( $this->settings['client_id'] ) ? $this->settings['client_id'] : NULL, TRUE ),
'client_secret' => new \IPS\Helpers\Form\Text( 'oauth_client_client_secret', isset( $this->settings['client_secret'] ) ? $this->settings['client_secret'] : NULL, NULL, array(), NULL, NULL, NULL, 'client_secret' ),
);
@@ -92,6 +92,8 @@ abstract class _OAuth2 extends \IPS\Login\Handler
*/
public function testSettings()
{
parent::testSettings();
try
{
/* Authorization Code / Implicit */
@@ -159,7 +161,7 @@ abstract class _OAuth2 extends \IPS\Login\Handler
{
$target = $this->authorizationEndpoint( $login )->setQueryString( array(
'client_id' => $this->settings['client_id'],
'response_type' => $this->grantType() === 'authorization_code' ? 'code' : 'implicit',
'response_type' => $this->grantType() === 'authorization_code' ? 'code' : 'token',
'redirect_uri' => (string) $this->redirectionEndpoint(),
'state' => $this->id . '-' . base64_encode( $login->url ) . '-' . \IPS\Session::i()->csrfKey . '-' . \IPS\Request::i()->ref,
) );
@@ -501,11 +503,21 @@ abstract class _OAuth2 extends \IPS\Login\Handler
protected function _exchangeAuthorizationCodeForAccessToken( $code )
{
/* Make the request */
$response = $this->_authenticatedRequest( $this->tokenEndpoint(), array(
'grant_type' => 'authorization_code',
'code' => $code,
'redirect_uri' => (string) $this->redirectionEndpoint(),
) )->decodeJson();
$data = NULL;
$response = array();
try
{
$data = $this->_authenticatedRequest( $this->tokenEndpoint(), array(
'grant_type' => 'authorization_code',
'code' => $code,
'redirect_uri' => (string) $this->redirectionEndpoint(),
) );
$response = $data->decodeJson();
}
catch( \RuntimeException $e )
{
\IPS\Log::log( var_export( $data, true ), 'oauth' );
}
/* Check for any errors */
if ( isset( $response['error'] ) or !isset( $response['access_token'] ) or ( isset( $response['token_type'] ) and mb_strtolower( $response['token_type'] ) !== 'bearer' ) )
@@ -551,7 +563,7 @@ abstract class _OAuth2 extends \IPS\Login\Handler
}
if ( isset( $newAccessToken['expires_in'] ) )
{
$update['token_expires'] = $newAccessToken['expires_in'];
$update['token_expires'] = ( time() + $newAccessToken['expires_in'] );
}
if ( isset( $newAccessToken['refresh_token'] ) )
{
+27 -6
View File
@@ -236,7 +236,7 @@ class _Wordpress extends \IPS\Login\Handler\OAuth2
{
return $this->_userData( $accessToken )[ 'username' ];
}
throw new \Exception;
return NULL;
}
/**
@@ -252,7 +252,7 @@ class _Wordpress extends \IPS\Login\Handler\OAuth2
{
return $this->_userData( $accessToken )[ 'display_name' ];
}
throw new \Exception;
return NULL;
}
/**
@@ -268,7 +268,7 @@ class _Wordpress extends \IPS\Login\Handler\OAuth2
{
return $this->_userData( $accessToken )[ 'email' ];
}
throw new \Exception;
return NULL;
}
/**
@@ -287,10 +287,31 @@ class _Wordpress extends \IPS\Login\Handler\OAuth2
{
throw new \IPS\Login\Exception( NULL, \IPS\Login\Exception::INTERNAL_ERROR );
}
if ( isset( $this->_userData( $link['token_access_token'] )[ 'avatar' ] ) )
{
return $this->_userData( $link['token_access_token'] )[ 'avatar' ];
{
$url = preg_replace( '/^.*src=[\'"](.+?)[\'"].*$/i', '$1', $this->_userData( $link['token_access_token'] )[ 'avatar' ] );
if ( preg_match( '/^https?:\/\/(?:secure\.|www\.)?gravatar\.com\/avatar\/(.{32})/', $url, $matches ) )
{
$url = \IPS\Http\Url::external( "https://secure.gravatar.com/avatar/" . $matches[1] )->setQueryString( 'd', '404' );
try
{
$response = $url->request()->head();
if ( $response->httpResponseCode == 200 )
{
return $url;
}
}
catch ( \Exception $e ) { }
}
else
{
try
{
return \IPS\Http\Url::external( $url );
}
catch ( \Exception $e ) { }
}
}
return NULL;
}
+1 -1
View File
@@ -75,7 +75,7 @@ abstract class _LoginAbstract extends \IPS\Login\Handler
try
{
$url = \IPS\Http\Url::createFromString( base64_decode( \IPS\Request::i()->ref ) );
if ( $url instanceof \IPS\Http\Url\Internal )
if ( $url instanceof \IPS\Http\Url\Internal and !$url->openRedirect() )
{
$destination = $url;
}
+2 -2
View File
@@ -63,7 +63,7 @@ class _Success
$this->device = \IPS\Member\Device::loadOrCreate( $member );
$this->handler = $handler;
$this->rememberMe = $rememberMe;
$this->anonymous = $anonymous;
$this->anonymous = ( $anonymous or $member->group['g_hide_online_list'] );
}
/**
@@ -97,7 +97,7 @@ class _Success
else
{
/* If we're not anonymous, update our last visit time */
$this->member->last_visit = time();
$this->member->last_visit = $this->member->last_activity;
$this->member->save();
}
+15 -3
View File
@@ -359,7 +359,15 @@ class _Club extends \IPS\Patterns\ActiveRecord implements \IPS\Content\Embeddabl
}
\IPS\Member::loggedIn()->language()->words['club_fee_desc'] = $commissionBlurb;
$form->add( new \IPS\nexus\Form\Money( 'club_fee', $this->id ? json_decode( $this->fee, TRUE ) : array(), NULL, array(), NULL, NULL, NULL, 'club_fee' ) );
$form->add( new \IPS\nexus\Form\Money( 'club_fee', $this->id ? json_decode( $this->fee, TRUE ) : array(), NULL, array(), function( $value ){
foreach( $value as $currency => $fee )
{
if( !$fee->amount->isGreaterThanZero() )
{
throw new \DomainException( 'form_required' );
}
}
}, NULL, NULL, 'club_fee' ) );
$form->add( new \IPS\Helpers\Form\Radio( 'club_renewals', $this->id ? ( $this->renewal_term ? 1 : 0 ) : 0, TRUE, array(
'options' => array( 0 => 'club_renewals_off', 1 => 'club_renewals_on' ),
'toggles' => array( 1 => array( 'club_renewal_term' ) )
@@ -696,7 +704,7 @@ class _Club extends \IPS\Patterns\ActiveRecord implements \IPS\Content\Embeddabl
}
/**
* Joining fee
* Renewal fee
*
* @param string|NULL $currency Desired currency, or NULL to choose based on member's chosen currency
* @return \IPS\nexus\Money|NULL
@@ -740,7 +748,11 @@ class _Club extends \IPS\Patterns\ActiveRecord implements \IPS\Content\Embeddabl
try
{
$renewalTerm = $this->renewalTerm( $fee->currency );
if ( $renewalTerm->cost->amount == $fee->amount )
if ( !$renewalTerm )
{
return $fee;
}
else if ( $renewalTerm AND $renewalTerm->cost->amount == $fee->amount )
{
return $renewalTerm;
}
+20 -16
View File
@@ -150,16 +150,17 @@ class _Device extends \IPS\Patterns\ActiveRecord
$object->device_key = $deviceKey;
return $object;
}
/**
* Update after logging in / automatic authentication with current request's user agent / IP address, etc.
*
* @param bool|null $rememberMe Remember me? NULL can be provided if the login is being processed from somewhere that doesn't ask
* @param \IPS\Login\Handler $loginHandler The login handler which processed the login, or NULL if updating an existing login. Can also be empty string for logins that weren't processed by any handler (such as after registration or using the lost password feature)
* @param bool $refreshLoginKey If login key should be refreshed (FALSE for automatic logins which will need the same login key subsequently)
* @param book $setCookies Should the cookies be set ( FALSE for ACP login )
* @return void
*/
public function updateAfterAuthentication( $rememberMe, \IPS\Login\Handler $loginHandler=NULL, $refreshLoginKey=TRUE )
public function updateAfterAuthentication( $rememberMe, \IPS\Login\Handler $loginHandler=NULL, $refreshLoginKey=TRUE, $setCookies = TRUE )
{
$this->user_agent = isset( $_SERVER['HTTP_USER_AGENT'] ) ? $_SERVER['HTTP_USER_AGENT'] : NULL;
if ( $refreshLoginKey )
@@ -174,21 +175,24 @@ class _Device extends \IPS\Patterns\ActiveRecord
$this->save();
$this->logIpAddress( \IPS\Request::i()->ipAddress() );
$cookieExpiration = ( new \IPS\DateTime )->add( new \DateInterval( static::LOGIN_KEY_VALIDITY ) );
if ( $rememberMe === NULL )
if ( $setCookies )
{
\IPS\Request::i()->setCookie( 'member_id', $this->member_id, $cookieExpiration );
}
elseif ( $rememberMe === TRUE )
{
\IPS\Request::i()->setCookie( 'member_id', $this->member_id, $cookieExpiration );
\IPS\Request::i()->setCookie( 'login_key', $this->login_key, $cookieExpiration );
}
else
{
\IPS\Request::i()->setCookie( 'member_id', $this->member_id, NULL ); // Just tells the guest caching mechanism that we are logged in, so it can expire on the session end
\IPS\Request::i()->setCookie( 'login_key', NULL ); // Clear it in case they previously had chosen "Remember Me"
$cookieExpiration = ( new \IPS\DateTime )->add( new \DateInterval( static::LOGIN_KEY_VALIDITY ) );
if ( $rememberMe === NULL )
{
\IPS\Request::i()->setCookie( 'member_id', $this->member_id, $cookieExpiration );
}
elseif ( $rememberMe === TRUE )
{
\IPS\Request::i()->setCookie( 'member_id', $this->member_id, $cookieExpiration );
\IPS\Request::i()->setCookie( 'login_key', $this->login_key, $cookieExpiration );
}
else
{
\IPS\Request::i()->setCookie( 'member_id', $this->member_id, NULL ); // Just tells the guest caching mechanism that we are logged in, so it can expire on the session end
\IPS\Request::i()->setCookie( 'login_key', NULL ); // Clear it in case they previously had chosen "Remember Me"
}
}
}
+1 -1
View File
@@ -61,7 +61,7 @@ class _History extends \IPS\Helpers\Table\Db
}
/* This is here specifically so that log_type is always shown first, if required */
$this->include = array_merge( $this->include, array( 'log_id', 'log_date', 'log_data' ) );
$this->include = array_merge( $this->include, array( 'log_date', 'log_data' ) );
if ( $showIp )
{
+95 -14
View File
@@ -106,8 +106,8 @@ class _Member extends \IPS\Patterns\ActiveRecord
'security_questions_opt_out' => 1024,
'ignore_promotions' => 2048,// 1 means that the user will not be checked against group promotion rules. Set by Commerce to prevent "promoting" users incorrectly after a purchase.
'profile_completed' => 4096,
'profile_completion_dismissed' => 8192
'profile_completion_dismissed' => 8192,
'created_externally' => 16384
)
)
);
@@ -153,6 +153,12 @@ class _Member extends \IPS\Patterns\ActiveRecord
if ( isset( \IPS\Data\Store::i()->$key ) )
{
static::$loggedInMember = static::load( \IPS\Data\Store::i()->$key );
if ( !static::$loggedInMember->member_id )
{
unset( \IPS\Data\Store::i()->$key );
unset( $_SESSION['logged_in_as_key'] );
}
}
}
}
@@ -565,12 +571,14 @@ class _Member extends \IPS\Patterns\ActiveRecord
{
if ( isset( $class::$databaseColumnMap['author'] ) and ( $action == 'delete' or in_array( 'IPS\Content\Hideable', class_implements( $class ) ) ) )
{
\IPS\Task::queue( 'core', 'MemberContent', array_merge( array( 'initiated_by_member_id' => \IPS\Member::loggedIn()->member_id, 'member_id' => $this->member_id, 'name' => $this->name, 'class' => $class, 'action' => $action ), $extra ), 1 );
/* Comments run first so rebuilding topic doesn't fail with incorrect author ID */
$order = ( is_subclass_of( $class, '\IPS\Content\Comment' ) ) ? 1 : 2;
\IPS\Task::queue( 'core', 'MemberContent', array_merge( array( 'initiated_by_member_id' => \IPS\Member::loggedIn()->member_id, 'member_id' => $this->member_id, 'name' => $this->name, 'class' => $class, 'action' => $action ), $extra ), $order );
}
}
/* And private messages */
\IPS\Task::queue( 'core', 'MemberContent', array_merge( array( 'initiated_by_member_id' => \IPS\Member::loggedIn()->member_id, 'member_id' => $this->member_id, 'name' => $this->name, 'class' => 'IPS\\core\\Messenger\\Conversation', 'action' => $action ), $extra ), 1 );
\IPS\Task::queue( 'core', 'MemberContent', array_merge( array( 'initiated_by_member_id' => \IPS\Member::loggedIn()->member_id, 'member_id' => $this->member_id, 'name' => $this->name, 'class' => 'IPS\\core\\Messenger\\Conversation', 'action' => $action ), $extra ), 2 );
}
/**
@@ -1601,7 +1609,18 @@ class _Member extends \IPS\Patterns\ActiveRecord
if ( isset( $memberData['member_id'] ) and $memberData['member_id'] )
{
/* Is Gravatar disabled for them? */
$gravatarDisabled = isset( $memberData['members_bitoptions'] ) AND is_object( $memberData['members_bitoptions'] ) ? $memberData['members_bitoptions']['bw_disable_gravatar'] : $memberData['members_bitoptions'] & static::$bitOptions['members_bitoptions']['members_bitoptions']['bw_disable_gravatar'];
$gravatarDisabled = FALSE;
if ( isset( $memberData['members_bitoptions'] ) )
{
if ( is_object( $memberData['members_bitoptions'] ) )
{
$gravatarDisabled = $memberData['members_bitoptions']['bw_disable_gravatar'];
}
else
{
$gravatarDisabled = $memberData['members_bitoptions'] & static::$bitOptions['members_bitoptions']['members_bitoptions']['bw_disable_gravatar'];
}
}
/* Either uploaded or synced from social media */
if ( $memberData['pp_main_photo'] and ( mb_substr( $memberData['pp_photo_type'], 0, 5 ) === 'sync-' or $memberData['pp_photo_type'] === 'custom' or ( \IPS\Settings::i()->letter_photos == 'letters' AND $memberData['pp_photo_type'] == 'letter' and $useDefaultPhoto and ( $gravatarDisabled OR !\IPS\Settings::i()->allow_gravatars ) ) ) )
@@ -1830,6 +1849,31 @@ class _Member extends \IPS\Patterns\ActiveRecord
return TRUE;
}
/**
* Is Online Anonymously
*
* @return bool
*/
public function isOnlineAnonymously()
{
if ( !$this->member_id )
{
return FALSE;
}
if ( $this->sessionData === NULL )
{
$this->sessionData = \IPS\Session\Store::i()->getLatestMemberSession( $this->member_id );
}
if ( $this->sessionData === FALSE )
{
return FALSE;
}
return ( $this->sessionData['login_type'] == \IPS\Session\Front::LOGIN_TYPE_ANONYMOUS );
}
/**
* Is banned?
* If is banned until a certain time, returns an \IPS\DateTime object
@@ -2852,7 +2896,7 @@ class _Member extends \IPS\Patterns\ActiveRecord
$reportCount = \IPS\Db::i()->select(
'COUNT(*)',
'core_rc_index',
array( $where, \IPS\Db::i()->select( 'perm_id', 'core_permission_index', \IPS\Db::i()->findInSet( 'perm_view', array_merge( array( \IPS\Member::loggedIn()->member_group_id ), array_filter( explode( ',', \IPS\Member::loggedIn()->mgroup_others ) ) ) ) . " OR perm_view='*'" ) )
array( $where, \IPS\Db::i()->select( 'perm_id', 'core_permission_index', \IPS\Db::i()->findInSet( 'perm_view', $this->permissionArray() ) . " OR perm_view='*'" ) )
)->first();
if( $force === FALSE )
@@ -3821,10 +3865,12 @@ class _Member extends \IPS\Patterns\ActiveRecord
try
{
$group = \IPS\Member\Group::load( $action['primary_group'] );
$this->member_group_id = $action['primary_group'];
/* Need to store the history before we actually change the group, otherwise history shows "promoted from group X to X" */
$this->logHistory( 'core', 'group', array( 'type' => 'primary', 'by' => 'promotion', 'id' => $ruleToUse->id, 'old' => $this->member_group_id, 'new' => $action['primary_group'] ) );
$this->member_group_id = $action['primary_group'];
/* Reset profile completion flag to see if there are any items to complete now permissions are elevated */
$this->members_bitoptions['profile_completed'] = 0;
}
@@ -4106,7 +4152,7 @@ class _Member extends \IPS\Patterns\ActiveRecord
/**
* Email Validation Confirmed
*
* @param $record validating record
* @param array $record validating record
* @return void
*/
public function emailValidationConfirmed( $record )
@@ -4139,7 +4185,6 @@ class _Member extends \IPS\Patterns\ActiveRecord
* If email-only validation is enabled: this is called after the user has validated their email address or if the admin manually validates the account
* If admin (including email and admin) validation is enabled: this is called after the admin has validated the account
*
* @param array $record Row from core_validating
* @return void
*/
public function validationComplete()
@@ -4265,7 +4310,7 @@ class _Member extends \IPS\Patterns\ActiveRecord
{
case 'email':
$email = $method->userEmail( $this );
if ( $email != $this->email )
if ( $email and $email != $this->email )
{
if ( $error = \IPS\Login::emailIsInUse( $email, $this ) )
{
@@ -4347,7 +4392,7 @@ class _Member extends \IPS\Patterns\ActiveRecord
$contents = $photoUrl->request()->get();
$md5 = md5( $contents );
if ( !isset( $v['ref'] ) or $md5 != $v['ref'] )
if ( $contents AND ( !isset( $v['ref'] ) or $md5 != $v['ref'] ) )
{
$photoVars = explode( ':', $this->group['g_photo_max_vars'] );
@@ -4413,7 +4458,7 @@ class _Member extends \IPS\Patterns\ActiveRecord
throw new \DomainException('upload_too_big_unspecific');
}
$newFile = \IPS\File::create( 'core_Profile', basename( $coverPhotoUrl->data[ \IPS\Http\Url::COMPONENT_PATH ] ), (string) $image );
$newFile = \IPS\File::create( 'core_Profile', 'imported-cover-photo-' . $this->member_id . '.' . $image->type, (string) $image );
if ( $this->pp_cover_photo )
{
@@ -4424,7 +4469,7 @@ class _Member extends \IPS\Patterns\ActiveRecord
catch ( \Exception $e ) { }
}
$this->pp_cover_photo = $newFile;
$this->pp_cover_photo = (string) $newFile;
$this->logHistory( 'core', 'photo', array( 'action' => 'new', 'type' => 'profilesync', 'id' => $method->id, 'service' => $method::getTitle() ) );
$profileSync['cover']['ref'] = $md5;
@@ -4737,6 +4782,10 @@ class _Member extends \IPS\Patterns\ActiveRecord
{
return new \IPS\Math\Number( '100' );
}
elseif( !$total )
{
return new \IPS\Math\Number( '100' );
}
else
{
return new \IPS\Math\Number( (string) round( 100 / $total * $done ) );
@@ -4827,4 +4876,36 @@ class _Member extends \IPS\Patterns\ActiveRecord
{
return $this->staticCoverPhotoBackgroundColor( $this->name );
}
/**
* returns the recent profile visitors
*
* @return array
*/
public function get_profileVisitors()
{
$visitors = array();
$visitorData = array();
$visitorInfo = json_decode( $this->pp_last_visitors, TRUE );
if ( !is_array( $visitorInfo ) )
{
$visitorInfo = array();
}
foreach( new \IPS\Patterns\ActiveRecordIterator( \IPS\Db::i()->select( '*', 'core_members', array( \IPS\Db::i()->in( 'member_id', array_keys( array_reverse( $visitorInfo, TRUE ) ) ) ) ), 'IPS\Member' ) AS $row )
{
$visitorData[$row->member_id] = $row;
}
foreach( array_reverse( $visitorInfo, TRUE ) as $id => $time )
{
if ( isset( $visitorData[$id] ) )
{
$visitors[$id]['member'] = $visitorData[$id];
$visitors[$id]['visit_time'] = $time;
}
}
return $visitors;
}
}
+53 -3
View File
@@ -428,13 +428,16 @@ class _ProfileStep extends \IPS\Node\Model
{
if ( isset( $subActions[ $item ] ) )
{
$lang[] = \IPS\Member::loggedIn()->language()->get( $subActions[ $item ] );
$lang[] = \IPS\Member::loggedIn()->language()->addToStack( $subActions[ $item ] );
}
}
if ( count( $lang ) )
{
return \IPS\Member::loggedIn()->language()->get( 'complete_profile_' . $this->completion_act ) . ' (' . \IPS\Member::loggedIn()->language()->formatList( $lang ) . ')';
$result = \IPS\Member::loggedIn()->language()->get( 'complete_profile_' . $this->completion_act ) . ' (' . \IPS\Member::loggedIn()->language()->formatList( $lang ) . ')';
\IPS\Member::loggedIn()->language()->parseOutputForDisplay( $result );
return $result;
}
else
{
@@ -466,6 +469,46 @@ class _ProfileStep extends \IPS\Node\Model
return $this->extension->completed( $this, $member );
}
/**
* @brief Can add cache
*/
protected static $canAdd = NULL;
/**
* Can add new steps
*
* @return param
*/
public function canAdd()
{
if ( static::$canAdd === NULL )
{
$actions = static::actions();
$subActions = static::subActions();
$usedSubActions = $this->usedSubActions();
foreach( $actions as $key => $lang )
{
if ( isset( $subActions[ $key ] ) )
{
if ( isset( $usedSubActions[ $key ] ) )
{
if ( \count( $usedSubActions[ $key ] ) == \count( $subActions[ $key ] ) )
{
/* Nothing left to select, so skip this */
unset( $actions[ $key ] );
continue;
}
}
}
}
static::$canAdd = \count( $actions );
}
return static::$canAdd;
}
/**
* Form
*
@@ -491,6 +534,13 @@ class _ProfileStep extends \IPS\Node\Model
if ( isset( $usedSubActions[ $key ] ) )
{
if ( \count( $usedSubActions[ $key ] ) >= \count( $subActions[ $key ] ) )
{
/* Nothing left to select, so skip this */
unset( $actions[ $key ] );
continue;
}
$disabled = $usedSubActions[ $key ];
}
@@ -499,7 +549,7 @@ class _ProfileStep extends \IPS\Node\Model
$toggles[ $key ][] = 'profile_step_subaction_' . $key;
}
}
foreach( static::canBeRequired() AS $action )
{
$toggles[ $action ][] = 'step_required';
+28 -15
View File
@@ -201,6 +201,11 @@ class _Controller extends \IPS\Dispatcher\Controller
{
$buttons['edit']['link'] = $buttons['edit']['link']->setQueryString( 'searchResult', \IPS\Request::i()->searchResult );
}
if ( isset( $buttons['delete'] ) and isset( \IPS\Request::i()->root ) and \IPS\Request::i()->root == $id )
{
unset( $buttons['delete']['data']['delete'] );
$buttons['delete']['data']['confirm'] = '';
}
return \IPS\Theme::i()->getTemplate( 'trees', 'core' )->row(
$this->url,
@@ -545,22 +550,26 @@ class _Controller extends \IPS\Dispatcher\Controller
$key = \IPS\Request::i()->key;
$dummyForm = new \IPS\Helpers\Form;
$node->form( $dummyForm );
$value = isset( \IPS\Request::i()->value ) ? \IPS\Request::i()->value : NULL;
if ( $value === NULL )
if( isset( $_SESSION[ $key ] ) )
{
foreach ( $dummyForm->elements as $tab => $elements )
$value = $_SESSION[ $key ];
}
else
{
$value = isset( \IPS\Request::i()->value ) ? \IPS\Request::i()->value : NULL;
if ( $value === NULL )
{
if ( isset( $elements[ $key ] ) )
foreach ( $dummyForm->elements as $tab => $elements )
{
$value = $elements[ $key ]->value;
break;
if ( isset( $elements[ $key ] ) )
{
$value = $elements[ $key ]->value;
break;
}
}
}
}
if ( is_array( $value ) )
{
$value = implode( ',', $value );
$_SESSION[ $key ] = $value;
}
/* Build Form */
@@ -577,7 +586,7 @@ class _Controller extends \IPS\Dispatcher\Controller
if ( $values = $form->values() )
{
$url = $this->url;
$multiRedirectUrl = $this->url->setQueryString( array( 'do' => 'massChange', 'key' => \IPS\Request::i()->key, 'value' => $value, 'nodes' => \IPS\Request::i()->nodes ?: $values['nodes'], 'from' => \IPS\Request::i()->from, 'form_submitted' => 1, 'csrfKey' => \IPS\Request::i()->csrfKey ) );
$multiRedirectUrl = $this->url->setQueryString( array( 'do' => 'massChange', 'key' => \IPS\Request::i()->key, 'nodes' => \IPS\Request::i()->nodes ?: $values['nodes'], 'from' => \IPS\Request::i()->from, 'form_submitted' => 1, 'csrfKey' => \IPS\Request::i()->csrfKey ) );
if ( \IPS\Request::i()->subnode )
{
$multiRedirectUrl = $multiRedirectUrl->setQueryString( 'subnode', 1 );
@@ -598,9 +607,9 @@ class _Controller extends \IPS\Dispatcher\Controller
$did++;
$_node = $nodeClass::constructFromData( $row );
if ( $_node->canCopyValue( \IPS\Request::i()->key, \IPS\Request::i()->value ) )
if ( $_node->canCopyValue( \IPS\Request::i()->key, $_SESSION[ \IPS\Request::i()->key ] ) )
{
$values = $_node->formatFormValues( array( \IPS\Request::i()->key => \IPS\Request::i()->value ) );
$values = $_node->formatFormValues( array( \IPS\Request::i()->key => $_SESSION[ \IPS\Request::i()->key ] ) );
foreach( $values as $k => $v )
{
@@ -639,6 +648,10 @@ class _Controller extends \IPS\Dispatcher\Controller
/* Clear guest page caches */
\IPS\Data\Cache::i()->clearAll();
/* Clear the value in the session */
unset( $_SESSION[ \IPS\Request::i()->key ] );
/* And redirect */
$finishUrl = $url->setQueryString( array( 'do' => 'form', 'id' => \IPS\Request::i()->from ) );
if ( \IPS\Request::i()->subnode )
{
@@ -1223,7 +1236,7 @@ class _Controller extends \IPS\Dispatcher\Controller
}
/* Do we have any children or content? */
if ( $node->hasChildren( NULL, NULL, TRUE ) or $node->showDeleteOrMoveForm() )
if ( $node->hasChildren( NULL, NULL, TRUE ) or $node->showDeleteOrMoveForm() or isset( \IPS\Request::i()->ajaxValidate ) )
{
$form = $node->deleteOrMoveForm( FALSE, TRUE );
if ( $values = $form->values() )
+9 -4
View File
@@ -478,11 +478,16 @@ abstract class _Model extends \IPS\Patterns\ActiveRecord
}
}
/**
* @brief Cached URL
*/
protected $_url = NULL;
/**
* Get URL
*
* @return \IPS\Http\Url
* @throws BadMethodCallException
* @throws \BadMethodCallException
*/
public function url()
{
@@ -554,9 +559,9 @@ abstract class _Model extends \IPS\Patterns\ActiveRecord
* [Node] Fetches the only node if only one exists
* One Node to rule them all, One Node to find them, One Node to bring them all and in the darkness bind them
*
* @param $properties Array of property=>value i.e. array( 'redirect_url' => FALSE, 'password' => FALSE );
* @param $returnRoots Enable to check and return the root node
* @param $subNodes Enable to check subnodes
* @param array $properties Array of property=>value i.e. array( 'redirect_url' => FALSE, 'password' => FALSE );
* @param bool $returnRoots Enable to check and return the root node
* @param bool $subNodes Enable to check subnodes
* @return \IPS\Node\Model|NULL
*/
public static function theOnlyNode( $properties=array(), $returnRoots=TRUE, $subNodes=TRUE )
+12 -5
View File
@@ -206,6 +206,11 @@ class _Output
*/
public $allowDefaultWidgets = TRUE;
/**
* @brief Allow page caching. This can be set at any point during controller execution to override defaults
*/
public $pageCaching = TRUE;
/**
* Get a JS bundle
*
@@ -227,7 +232,7 @@ class _Output
if ( $location === 'interface' AND mb_substr( $file, -3 ) === '.js' )
{
return array( rtrim( \IPS\Http\Url::baseUrl( \IPS\Http\Url::PROTOCOL_RELATIVE ), '/' ) . "/applications/{$app}/interface/{$file}?v=" . ( defined( \IPS\CACHEBUST_KEY ) ? \IPS\CACHEBUST_KEY : time() ) );
return array( rtrim( \IPS\Http\Url::baseUrl( \IPS\Http\Url::PROTOCOL_RELATIVE ), '/' ) . "/applications/{$app}/interface/{$file}?v=" . ( defined( '\IPS\CACHEBUST_KEY' ) ? \IPS\CACHEBUST_KEY : time() ) );
}
elseif ( \IPS\IN_DEV )
{
@@ -711,7 +716,7 @@ class _Output
/* Full page caching for guests */
if (
\IPS\CACHE_PAGE_TIMEOUT and // Page caching is enabled
\IPS\CACHE_PAGE_TIMEOUT and $this->pageCaching and // Page caching is enabled
$cacheThisPage and // Some pages can specify not to be cached (for example, when displaying a cached page, you don't want it recached)
!isset( \IPS\Request::i()->cookie['noCache'] ) and // A noCache cookie might get set to not cache a particular guest (for example, if they have added items to their cart in the store)
mb_strtolower( $_SERVER['REQUEST_METHOD'] ) == 'get' and // Is a HTTP GET request (don't cache output for POSTs)
@@ -720,11 +725,13 @@ class _Output
\IPS\Dispatcher::hasInstance() and class_exists( 'IPS\Dispatcher', FALSE ) and \IPS\Dispatcher::i()->controllerLocation === 'front' and // Is a normal, front-end page (necessary to know if user is logged in)
!\IPS\Member::loggedIn()->member_id // User is not logged in
) {
\IPS\Data\Cache::i()->storeWithExpire( 'page_' . md5( (int) \IPS\Request::i()->isSecure() . ( !empty( $_SERVER['HTTP_HOST'] ) ? $_SERVER['HTTP_HOST'] : $_SERVER['SERVER_NAME'] ) . '/' . $_SERVER['REQUEST_URI'] ) . '_' . \IPS\Member::loggedIn()->language()->id . '_' . \IPS\Theme::i()->id, array(
$theme = isset( \IPS\Request::i()->cookie['theme'] ) ? \IPS\Request::i()->cookie['theme'] : '';
\IPS\Data\Cache::i()->storeWithExpire( 'page_' . md5( (int) \IPS\Request::i()->isSecure() . ( !empty( $_SERVER['HTTP_HOST'] ) ? $_SERVER['HTTP_HOST'] : $_SERVER['SERVER_NAME'] ) . '/' . $_SERVER['REQUEST_URI'] ) . '_' . \IPS\Member::loggedIn()->language()->id . '_' . $theme, array(
'output' => str_replace( \IPS\Session::i()->csrfKey, '{{csrfKey}}', $output ),
'code' => $httpStatusCode,
'contentType' => $contentType,
'httpHeaders' => $httpHeaders,
'httpHeaders' => array_merge( $httpHeaders, array( 'X-IPS-Cached-Response' => \IPS\DateTime::create()->rfc1123() ) ),
'lastUpdated' => time()
), \IPS\DateTime::create()->add( new\DateInterval( 'PT' . \IPS\CACHE_PAGE_TIMEOUT . 'S' ) ), TRUE );
}
@@ -934,7 +941,7 @@ class _Output
{
try
{
static::$fileObjectClasses[ $data[1] ] = \IPS\File::getClass( $data[1] );
static::$fileObjectClasses[ $data[1] ] = \IPS\File::getClass( $data[1], TRUE );
}
catch ( \RuntimeException $e )
{
+1 -1
View File
@@ -353,7 +353,7 @@ class _Hook extends \IPS\Patterns\ActiveRecord
}
$method->signature = json_encode( 'function ' . $method->name . '\(' );
$method->codeToInject = json_encode( "\n\t{$method->getDocComment()}\n\t" . ( $method->isFinal() ? 'final ' : '' ) . ( $method->isAbstract() ? 'abstract ' : '' ) . ( $method->isStatic() ? 'static ' : '' ) . ( $method->isPublic() ? 'public ' : 'protected ' ) . "function {$method->name}(" . ( empty( $parameters ) ? '' : ( ' ' . implode( ', ', $parameters ) . ' ' ) ) . ")\n\t{\n\t\treturn parent::{$method->name}(" . ( empty( $parameters ) ? '' : ( ' ' . implode( ', ', array_map( function( $param ){ return str_replace( '&', '', $param ); }, $parameters ) ) . ' ' ) ) . ");\n\t}\n" );
$method->codeToInject = json_encode( "\n\t{$method->getDocComment()}\n\t" . ( $method->isFinal() ? 'final ' : '' ) . ( $method->isAbstract() ? 'abstract ' : '' ) . ( $method->isStatic() ? 'static ' : '' ) . ( $method->isPublic() ? 'public ' : 'protected ' ) . "function {$method->name}(" . ( empty( $parameters ) ? '' : ( ' ' . implode( ', ', $parameters ) . ' ' ) ) . ")\n\t{\n\t\treturn parent::{$method->name}(" . ( empty( $parameters ) ? '' : ( ' ' . implode( ', ', array_map( function( $param ){ return '$' . $param->name; }, $method->getParameters() ) ) . ' ' ) ) . ");\n\t}\n" );
$data[ $method->getDeclaringClass()->getName() ]['methods'][ $method->getName() ] = $method;
}
+43 -14
View File
@@ -51,6 +51,11 @@ class _Redis
* @brief Unpack the config once
*/
protected static $config = NULL;
/**
* @brief Can we encrypt?
*/
protected $canEncrypt = FALSE;
/**
* Get instance
@@ -77,6 +82,16 @@ class _Redis
/* Return */
return static::$multitons[ $identifier ];
}
/**
* Constructor
*
* @return void
*/
public function __construct()
{
$this->canEncrypt = \IPS\REDIS_ENCRYPT === TRUE and function_exists( 'openssl_encrypt' ) and in_array( 'aes-256-ctr', openssl_get_cipher_methods() );
}
/**
* Connect to Redis
@@ -84,7 +99,7 @@ class _Redis
* @param string $identifier
* @return \Redis
*/
protected function connection( $identifier=NULL )
public function connection( $identifier=NULL )
{
if ( ! class_exists('Redis') )
{
@@ -173,6 +188,9 @@ class _Redis
{
$connection->close();
}
/* Reset stored connections so they can be re-connected correctly if tasks run after this shutdown proceses */
static::$connections = array();
}
catch( \RedisException $e ){}
}, $this );
@@ -294,7 +312,7 @@ class _Redis
*/
public function sort( $key, $options=array() )
{
$return = static::connection('write')->sort( $this->key( $key ), $options );
$return = $this->connection('write')->sort( $this->key( $key ), $options );
if ( isset( $options['store'] ) )
{
@@ -319,7 +337,7 @@ class _Redis
public function hGetAll( $key )
{
/* Make sure we read */
$return = static::connection('read')->hGetAll( $this->key( $key ) );
$return = $this->connection('read')->hGetAll( $this->key( $key ) );
if ( \IPS\REDIS_LOG )
{
@@ -370,6 +388,11 @@ class _Redis
return $key;
}
/**
* @brief Cached encryption key
*/
protected $encryptionKey = NULL;
/**
* Encryption key
@@ -378,12 +401,18 @@ class _Redis
*/
protected function _encryptionKey()
{
$password = \IPS\Settings::i()->sql_pass;
if( $this->encryptionKey !== NULL )
{
return $this->encryptionKey;
}
$this->encryptionKey = \IPS\Settings::i()->sql_pass;
if ( function_exists( 'openssl_digest' ) and in_array( 'sha256', openssl_get_md_methods() ) )
{
$password = openssl_digest( $password, 'sha256', TRUE );
$this->encryptionKey = openssl_digest( $this->encryptionKey, 'sha256', TRUE );
}
return $password;
return $this->encryptionKey;
}
/**
@@ -396,7 +425,7 @@ class _Redis
{
$value = json_encode( $value );
if ( function_exists( 'openssl_encrypt' ) and in_array( 'aes-256-ctr', openssl_get_cipher_methods() ) )
if ( $this->canEncrypt )
{
$iv = \IPS\Login::generateRandomString( 16 );
$value = $iv . openssl_encrypt( $value, 'aes-256-ctr', $this->_encryptionKey(), OPENSSL_RAW_DATA, $iv );
@@ -413,7 +442,7 @@ class _Redis
*/
public function decode( $value )
{
if ( function_exists( 'openssl_encrypt' ) and in_array( 'aes-256-ctr', openssl_get_cipher_methods() ) )
if ( $this->canEncrypt )
{
$value = @openssl_decrypt( \substr( $value, 16 ), 'aes-256-ctr', $this->_encryptionKey(), OPENSSL_RAW_DATA, \substr( $value, 0, 16 ) );
}
@@ -497,20 +526,20 @@ class _Redis
switch( $type )
{
case \Redis::REDIS_STRING:
$return[ $key . '(' . $ttl . ')' ] = \IPS\Redis::i()->decode( $this->connection('write')->get( $key ) );
$return[ $key . ' (TTL: ' . $ttl . ')' ] = \IPS\Redis::i()->decode( $this->connection('write')->get( $key ) );
break;
case \Redis::REDIS_ZSET:
$return[ $key . '(' . $ttl . ')' ] = $this->connection('write')->zRange( $key, 0, -1, TRUE );
$return[ $key . ' (TTL: ' . $ttl . ')' ] = $this->connection('write')->zRange( $key, 0, -1, TRUE );
break;
case \Redis::REDIS_HASH:
$return[ $key . '(' . $ttl . ')' ] = $this->connection('write')->hGetAll( $key );
if ( isset( $return[ $key . '(' . $ttl . ')' ]['data'] ) )
$return[ $key . ' (TTL: ' . $ttl . ')' ] = $this->connection('write')->hGetAll( $key );
if ( isset( $return[ $key . ' (TTL: ' . $ttl . ')' ]['data'] ) )
{
$return[ $key . '(' . $ttl . ')' ]['data'] = \IPS\Redis::i()->decode( $return[ $key . '(' . $ttl . ')' ]['data'] );
$return[ $key . ' (TTL: ' . $ttl . ')' ]['data'] = \IPS\Redis::i()->decode( $return[ $key . ' (TTL: ' . $ttl . ')' ]['data'] );
}
break;
case \Redis::REDIS_LIST:
$return[ $key . '(' . $ttl . ')' ] = $this->connection('write')->lRange( $key, 0, -1 );
$return[ $key . ' (TTL: ' . $ttl . ')' ] = $this->connection('write')->lRange( $key, 0, -1 );
break;
}
}
+17 -7
View File
@@ -87,6 +87,12 @@ class _Front extends \IPS\Session
$session = NULL;
}
/* We match bots by browser and IP address, so reset our session ID if we found a matching bot row */
if( $session AND $session['uagent_type'] == 'search' AND $session['id'] != $this->sessionId )
{
$this->sessionId = $session['id'];
}
/* Store this so plugins can access */
$this->sessionData = $session;
@@ -163,7 +169,7 @@ class _Front extends \IPS\Session
/* Work out the type */
if ( $this->member->member_id )
{
if ( ( $session and $session['login_type'] === static::LOGIN_TYPE_ANONYMOUS ) or $device and $device->anonymous )
if ( ( $session and $session['login_type'] === static::LOGIN_TYPE_ANONYMOUS ) or ( $device and $device->anonymous ) OR $this->member->group['g_hide_online_list'] )
{
$type = static::LOGIN_TYPE_ANONYMOUS;
}
@@ -267,12 +273,6 @@ class _Front extends \IPS\Session
$this->save = FALSE;
}
/* Don't update if we're an interface file showing database filesystem images, or custom field images, etc as this wipes location data */
if ( isset( $_SERVER['SCRIPT_FILENAME'] ) and preg_match( '#/applications/(\w+?)/interface/#', $_SERVER['SCRIPT_FILENAME'] ) )
{
$this->save = FALSE;
}
$this->data['member_name'] = $this->member->member_id ? $this->member->name : '';
$this->data['member_id'] = $this->member->member_id ?: NULL;
$this->data['data'] = $data;
@@ -286,6 +286,16 @@ class _Front extends \IPS\Session
return TRUE;
}
/**
* Do not update sessions
*
* @return void
*/
public function noUpdate()
{
$this->save = FALSE;
}
/**
* @brief Stored engine
*/
+12 -2
View File
@@ -186,10 +186,10 @@ abstract class _Session
/* Set the last activity */
if ( isset( $this->data ) and $this->data['login_type'] != static::LOGIN_TYPE_ANONYMOUS and ! \IPS\Request::i()->isAjax() )
{
if ( time() - $this->member->last_visit > 3600 )
if ( time() - $this->member->last_activity > 3600 )
{
$save = TRUE;
$this->member->last_visit = time();
$this->member->last_visit = $this->member->last_activity;
}
if ( time() - $this->member->last_activity > 180 )
{
@@ -212,6 +212,16 @@ abstract class _Session
}
}
}
/**
* Do not update sessions
*
* @return void
*/
public function noUpdate()
{
/* Overridden methods do something (or not) */
}
/**
* CSRF Check
+10 -6
View File
@@ -56,9 +56,14 @@ abstract class _Store
try
{
/* Try and use Redis */
$object = \IPS\Redis::i();
$connection = \IPS\Redis::i()->connection('write');
/* No exceptions that it worked */
if( !$connection )
{
throw new \RuntimeException;
}
/* No exceptions means it worked */
static::$instance = new \IPS\Session\Store\Redis;
}
catch( \Exception $e )
@@ -72,7 +77,7 @@ abstract class _Store
static::$instance = new \IPS\Session\Store\Database;
}
}
return static::$instance;
}
@@ -122,14 +127,13 @@ abstract class _Store
* Fetch all online users (but not spiders)
*
* @param int $flags Bitwise flags
* @param array $skipGroups Ignore members in these groups
* @param string $sort Sort direction
* @param array|NULL $limit Limit [ offset, limit ]
* @param int $memberGroup Limit by a specific member group ID
* @param boolean $showAnonymous Show anonymously logged in peoples?
* @param boolean $includeAllGroups Include all groups? Used for statistics primarily.
* @return array
*/
abstract public function getOnlineUsers( $flags=0, $sort='desc', $limit=NULL, $memberGroup=NULL, $showAnonymous=FALSE, $includeAllGroups=FALSE );
abstract public function getOnlineUsers( $flags=0, $sort='desc', $limit=NULL, $memberGroup=NULL, $showAnonymous=FALSE );
/**
* Fetch all members active at a specific location
+5 -45
View File
@@ -74,7 +74,7 @@ class _Database extends \IPS\Session\Store
*/
public function updateSession( $data )
{
\IPS\Db::i()->replace( 'core_sessions', $data, TRUE );
\IPS\Db::i()->insert( 'core_sessions', $data, TRUE );
}
/**
@@ -155,15 +155,14 @@ class _Database extends \IPS\Session\Store
/**
* Fetch all online users (but not spiders)
*
* @param int $flags Bitwise flags
* @param array $skipGroups Ignore members in these groups
* @param int $flags Bitwise flags
* @param string $sort Sort direction
* @param array|NULL $limit Limit [ offset, limit ]
* @param int $memberGroup Limit by a specific member group ID
* @param boolean $showAnonymous Show anonymously logged in peoples?
* @param boolean $includeAllGroups Include all groups? Used for statistics primarily.
* @return array
*/
public function getOnlineUsers( $flags=0, $sort='desc', $limit=NULL, $memberGroup=NULL, $showAnonymous=FALSE, $includeAllGroups=FALSE )
public function getOnlineUsers( $flags=0, $sort='desc', $limit=NULL, $memberGroup=NULL, $showAnonymous=FALSE )
{
/* Query */
$where = array(
@@ -175,23 +174,6 @@ class _Database extends \IPS\Session\Store
{
$where[] = array( "s.login_type!=?", \IPS\Session\Front::LOGIN_TYPE_ANONYMOUS );
}
if( ! $includeAllGroups )
{
$notInMyName = array();
foreach( \IPS\Member\Group::groups() as $group )
{
if ( $group->g_hide_online_list )
{
$notInMyName[] = $group->g_id;
}
}
if ( count( $notInMyName ) )
{
$where[] = array( \IPS\Db::i()->in( 's.member_group', $notInMyName, TRUE ) );
}
}
if ( ! $flags and ! $limit )
{
@@ -205,14 +187,6 @@ class _Database extends \IPS\Session\Store
/* Ok, this looks odd, but the ONLY_FULL_GROUP_BY bites us here, so selecting max(id) allows us to return a session ID even though we're grouping on member_id */
$where = array( array( "( core_sessions.id IN(?) OR ( core_sessions.member_id IS NULL AND core_sessions.login_type != ? AND core_sessions.running_time > ?) )", \IPS\Db::i()->select( 'MAX(id)', array( 'core_sessions', 's' ), $subWhere, NULL, NULL, 'member_id' ), \IPS\Session\Front::LOGIN_TYPE_SPIDER, \IPS\DateTime::create()->sub( new \DateInterval( 'PT30M' ) )->getTimeStamp() ) );
if( ! $includeAllGroups )
{
if ( count( $notInMyName ) )
{
$where[] = array( \IPS\Db::i()->in( 'core_sessions.member_group', $notInMyName, TRUE ) );
}
}
/* Limiting to a user group? */
if ( $memberGroup )
@@ -269,21 +243,7 @@ class _Database extends \IPS\Session\Store
{
$where[] = array( 'core_sessions.current_id = ?', \IPS\Request::i()->id );
}
$notInMyName = array();
foreach( \IPS\Member\Group::groups() as $group )
{
if ( $group->g_hide_online_list )
{
$notInMyName[] = $group->g_id;
}
}
if ( count( $notInMyName ) )
{
$where[] = array( \IPS\Db::i()->in( 'core_sessions.member_group', $notInMyName, TRUE ) );
}
foreach( \IPS\Db::i()->select( 'core_sessions.member_id,core_sessions.member_name,core_sessions.seo_name,core_sessions.member_group,core_sessions.login_type,core_sessions.in_editor', 'core_sessions', $where, 'core_sessions.running_time DESC' ) as $row )
{
if( $row['login_type'] == \IPS\Session\Front::LOGIN_TYPE_MEMBER and $row['member_name'] )
+13 -11
View File
@@ -67,10 +67,7 @@ class _Redis extends \IPS\Session\Store
), static::$ttl );
/* Update the list of sessions for the online list [ microtime => sessionID ] */
if ( ! $group->g_hide_online_list )
{
\IPS\Redis::i()->zAdd( $this->_key( 'session_map' ), time(), 'session_id_' . md5( $data['id'] . \IPS\Settings::i()->sql_pass ), static::$ttl );
}
\IPS\Redis::i()->zAdd( $this->_key( 'session_map' ), time(), 'session_id_' . md5( $data['id'] . \IPS\Settings::i()->sql_pass ), static::$ttl );
/* Update users list */
if ( $data['uagent_type'] == 'search' )
@@ -78,7 +75,7 @@ class _Redis extends \IPS\Session\Store
/* Make a unique row based on IP and user-agent to prevent multiple rows for each spider */
\IPS\Redis::i()->zAdd( $this->_key( 'session_online_spiders' ), time(), md5( $data['ip_address'] . $data['browser'] ), static::$ttl );
}
else if ( $data['member_id'] and ! $group->g_hide_online_list )
else if ( $data['member_id'] )
{
\IPS\Redis::i()->zAdd( $this->_key( 'session_online_users' ), time(), $data['member_id'] . '__' . 'session_id_' . md5( $data['id'] . \IPS\Settings::i()->sql_pass ), static::$ttl );
}
@@ -237,15 +234,14 @@ class _Redis extends \IPS\Session\Store
/**
* Fetch all online users (but not spiders)
*
* @param int $flags Bitwise flags
* @param array $skipGroups Ignore members in these groups
* @param int $flags Bitwise flags
* @param string $sort Sort direction
* @param array|NULL $limit Limit [ offset, limit ]
* @param int $memberGroup Limit by a specific member group ID
* @param boolean $showAnonymous Show anonymously logged in peoples?
* @param boolean $includeAllGroups Include all groups? Used for statistics primarily.
* @return array
*/
public function getOnlineUsers( $flags=0, $sort='desc', $limit=NULL, $memberGroup=NULL, $showAnonymous=FALSE, $includeAllGroups=FALSE )
public function getOnlineUsers( $flags=0, $sort='desc', $limit=NULL, $memberGroup=NULL, $showAnonymous=FALSE )
{
$results = \IPS\Redis::i()->lRange('session_onlinelist', 0, -1 );
@@ -256,7 +252,7 @@ class _Redis extends \IPS\Session\Store
'store' => \IPS\Redis::i()->prefix . 'session_onlinelist',
'alpha' => true,
'by' => 'nosort ' . $sort === 'asc' ? 'asc' : 'desc',
'ttl' => 60,
'ttl' => 30, /* This ensures the stored file session_online only lasts for 30 seconds */
'get' => array(
$this->_key( \IPS\Redis::i()->prefix ) . '*->member_id',
$this->_key( \IPS\Redis::i()->prefix ) . '*->member_name',
@@ -300,6 +296,12 @@ class _Redis extends \IPS\Session\Store
continue;
}
/* Cut off is 30 mins */
if ( $data['running_time'] < ( time() - ( 30 * 60 ) ) )
{
continue;
}
$return[ $fields['member_id'] ? $fields['member_id'] : $data['ip_address'] ] = array_merge( $data, $fields );
}
}
@@ -372,7 +374,7 @@ class _Redis extends \IPS\Session\Store
foreach( $this->getOnlineUsers( 0, 'desc' ) as $member )
{
if ( ! \IPS\Member\Group::load( $member['member_group'] )->g_hide_online_list and $member['current_appcomponent'] == $app and $member['current_module'] == $module and $member['current_controller'] = $controller and $member['current_id'] = $id )
if ( $member['current_appcomponent'] == $app and $member['current_module'] == $module and $member['current_controller'] = $controller and $member['current_id'] = $id )
{
if ( $url and mb_stristr( $member['location_url'], $url ) )
{
+8 -2
View File
@@ -25,8 +25,8 @@ class _Exception extends \RuntimeException
/**
* Constructor
*
* @param \IPS\Task $task The task with the issue
* @param string $message Error Message
* @param \IPS\Task $task The task with the issue
* @param string|array $message Error Message
* @return void
*/
public function __construct( \IPS\Task $task, $message )
@@ -34,6 +34,12 @@ class _Exception extends \RuntimeException
$task->running = FALSE;
$task->next_run = \IPS\DateTime::create()->add( new \DateInterval( $task->frequency ) )->getTimestamp();
$task->save();
/* Exception message must be a string */
if( is_array( $message ) )
{
$message = json_encode( $message );
}
return parent::__construct( $message );
}
+1 -1
View File
@@ -71,7 +71,7 @@ class _Task extends \IPS\Patterns\ActiveRecord
$newData = json_encode( $json );
/* Did it change?? */
if ( $newData !== $json )
if ( $newData !== $queueData['data'] )
{
$queueData['data'] = $newData;
\IPS\Db::i()->update( 'core_queue', array( 'data' => $newData ), array( 'id=?', $queueData['id'] ) );
+7 -8
View File
@@ -546,7 +546,7 @@ class _LegacyParser
try
{
$album = \IPS\gallery\Album::constructFromData( \IPS\Db::i()->select( '*', 'gallery_albums', array( 'album_id=?', (int) $matches[3] ) )->first() );
return \IPS\Text\Parser::embeddableMedia( \IPS\Http\Url::createFromString( $album->url(), FALSE, TRUE ) );
return \IPS\Text\Parser::embeddableMedia( \IPS\Http\Url::createFromString( $album->url(), TRUE, TRUE ) );
}
catch( \Exception $e )
{
@@ -743,7 +743,7 @@ class _LegacyParser
/* Parse standard URLs */
preg_match_all( '#(?:^|\s|\)|\(|\{|\}|/>|>|\]|\[|;|href=\S)((http|https|news|ftp)://(?:[^<>\)\[\"\s]+|[a-zA-Z0-9/\._\-!&\#;,%\+\?:=]+))#is', $value, $matches );
foreach( $matches[1] as $m )
foreach( $matches[1] as $k => $m )
{
$me = null;
@@ -775,13 +775,12 @@ class _LegacyParser
if( $me )
{
$embedCount++;
}
$value = preg_replace( '/' . preg_quote( $m, '/' ) . '/i', $me, $value, 1 );
}
else
{
$value = preg_replace( '/' . preg_quote( $m, '/' ) . '/i', "<a href='{$m}'>{$m}</a>", $value, 1 );
}
$c = count( $urls );
$urls[ $c ] = $me ?: "<a href='{$m}'>{$m}</a>";
$value = str_replace( $matches[1][ $k ], '<!--url{' . $c . '}-->', $value );
}
foreach( $urls as $k => $v )
+135 -7
View File
@@ -22,7 +22,11 @@ if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
*/
class _Parser
{
const EMOJI_REGEX = '[\\x{fe00}-\\x{fe0f}\\x{2712}\\x{2714}\\x{2716}\\x{271d}\\x{2721}\\x{2728}\\x{2733}\\x{2734}\\x{2744}\\x{2747}\\x{274c}\\x{274e}\\x{2753}-\\x{2755}\\x{2757}\\x{2763}\\x{2764}\\x{2795}-\\x{2797}\\x{27a1}\\x{27b0}\\x{27bf}\\x{2934}\\x{2935}\\x{2b05}-\\x{2b07}\\x{2b1b}\\x{2b1c}\\x{2b50}\\x{2b55}\\x{3030}\\x{303d}\\x{1f004}\\x{1f0cf}\\x{1f170}\\x{1f171}\\x{1f17e}\\x{1f17f}\\x{1f18e}\\x{1f191}-\\x{1f19a}\\x{1f201}\\x{1f202}\\x{1f21a}\\x{1f22f}\\x{1f232}-\\x{1f23a}\\x{1f250}\\x{1f251}\\x{1f300}-\\x{1f321}\\x{1f324}-\\x{1f393}\\x{1f396}\\x{1f397}\\x{1f399}-\\x{1f39b}\\x{1f39e}-\\x{1f3f0}\\x{1f3f3}-\\x{1f3f5}\\x{1f3f7}-\\x{1f4fd}\\x{1f4ff}-\\x{1f53d}\\x{1f549}-\\x{1f54e}\\x{1f550}-\\x{1f567}\\x{1f56f}\\x{1f570}\\x{1f573}-\\x{1f579}\\x{1f587}\\x{1f58a}-\\x{1f58d}\\x{1f590}\\x{1f595}\\x{1f596}\\x{1f5a5}\\x{1f5a8}\\x{1f5b1}\\x{1f5b2}\\x{1f5bc}\\x{1f5c2}-\\x{1f5c4}\\x{1f5d1}-\\x{1f5d3}\\x{1f5dc}-\\x{1f5de}\\x{1f5e1}\\x{1f5e3}\\x{1f5ef}\\x{1f5f3}\\x{1f5fa}-\\x{1f64f}\\x{1f680}-\\x{1f6c5}\\x{1f6cb}-\\x{1f6d0}\\x{1f6e0}-\\x{1f6e5}\\x{1f6e9}\\x{1f6eb}\\x{1f6ec}\\x{1f6f0}\\x{1f6f3}\\x{1f910}-\\x{1f918}\\x{1f980}-\\x{1f984}\\x{1f9c0}\\x{3297}\\x{3299}\\x{a9}\\x{ae}\\x{203c}\\x{2049}\\x{2122}\\x{2139}\\x{2194}-\\x{2199}\\x{21a9}\\x{21aa}\\x{231a}\\x{231b}\\x{2328}\\x{2388}\\x{23cf}\\x{23e9}-\\x{23f3}\\x{23f8}-\\x{23fa}\\x{24c2}\\x{25aa}\\x{25ab}\\x{25b6}\\x{25c0}\\x{25fb}-\\x{25fe}\\x{2600}-\\x{2604}\\x{260e}\\x{2611}\\x{2614}\\x{2615}\\x{2618}\\x{261d}\\x{2620}\\x{2622}\\x{2623}\\x{2626}\\x{262a}\\x{262e}\\x{262f}\\x{2638}-\\x{263a}\\x{2648}-\\x{2653}\\x{2660}\\x{2663}\\x{2665}\\x{2666}\\x{2668}\\x{267b}\\x{267f}\\x{2692}-\\x{2694}\\x{2696}\\x{2697}\\x{2699}\\x{269b}\\x{269c}\\x{26a0}\\x{26a1}\\x{26aa}\\x{26ab}\\x{26b0}\\x{26b1}\\x{26bd}\\x{26be}\\x{26c4}\\x{26c5}\\x{26c8}\\x{26ce}\\x{26cf}\\x{26d1}\\x{26d3}\\x{26d4}\\x{26e9}\\x{26ea}\\x{26f0}-\\x{26f5}\\x{26f7}-\\x{26fa}\\x{26fd}\\x{2702}\\x{2705}\\x{2708}-\\x{270d}\\x{270f}]|\\x{23}\\x{20e3}|\\x{2a}\\x{20e3}|\\x{30}\\x{20e3}|\\x{31}\\x{20e3}|\\x{32}\\x{20e3}|\\x{33}\\x{20e3}|\\x{34}\\x{20e3}|\\x{35}\\x{20e3}|\\x{36}\\x{20e3}|\\x{37}\\x{20e3}|\\x{38}\\x{20e3}|\\x{39}\\x{20e3}|\\x{1f1e6}[\\x{1f1e8}-\\x{1f1ec}\\x{1f1ee}\\x{1f1f1}\\x{1f1f2}\\x{1f1f4}\\x{1f1f6}-\\x{1f1fa}\\x{1f1fc}\\x{1f1fd}\\x{1f1ff}]|\\x{1f1e7}[\\x{1f1e6}\\x{1f1e7}\\x{1f1e9}-\\x{1f1ef}\\x{1f1f1}-\\x{1f1f4}\\x{1f1f6}-\\x{1f1f9}\\x{1f1fb}\\x{1f1fc}\\x{1f1fe}\\x{1f1ff}]|\\x{1f1e8}[\\x{1f1e6}\\x{1f1e8}\\x{1f1e9}\\x{1f1eb}-\\x{1f1ee}\\x{1f1f0}-\\x{1f1f5}\\x{1f1f7}\\x{1f1fa}-\\x{1f1ff}]|\\x{1f1e9}[\\x{1f1ea}\\x{1f1ec}\\x{1f1ef}\\x{1f1f0}\\x{1f1f2}\\x{1f1f4}\\x{1f1ff}]|\\x{1f1ea}[\\x{1f1e6}\\x{1f1e8}\\x{1f1ea}\\x{1f1ec}\\x{1f1ed}\\x{1f1f7}-\\x{1f1fa}]|\\x{1f1eb}[\\x{1f1ee}-\\x{1f1f0}\\x{1f1f2}\\x{1f1f4}\\x{1f1f7}]|\\x{1f1ec}[\\x{1f1e6}\\x{1f1e7}\\x{1f1e9}-\\x{1f1ee}\\x{1f1f1}-\\x{1f1f3}\\x{1f1f5}-\\x{1f1fa}\\x{1f1fc}\\x{1f1fe}]|\\x{1f1ed}[\\x{1f1f0}\\x{1f1f2}\\x{1f1f3}\\x{1f1f7}\\x{1f1f9}\\x{1f1fa}]|\\x{1f1ee}[\\x{1f1e8}-\\x{1f1ea}\\x{1f1f1}-\\x{1f1f4}\\x{1f1f6}-\\x{1f1f9}]|\\x{1f1ef}[\\x{1f1ea}\\x{1f1f2}\\x{1f1f4}\\x{1f1f5}]|\\x{1f1f0}[\\x{1f1ea}\\x{1f1ec}-\\x{1f1ee}\\x{1f1f2}\\x{1f1f3}\\x{1f1f5}\\x{1f1f7}\\x{1f1fc}\\x{1f1fe}\\x{1f1ff}]|\\x{1f1f1}[\\x{1f1e6}-\\x{1f1e8}\\x{1f1ee}\\x{1f1f0}\\x{1f1f7}-\\x{1f1fb}\\x{1f1fe}]|\\x{1f1f2}[\\x{1f1e6}\\x{1f1e8}-\\x{1f1ed}\\x{1f1f0}-\\x{1f1ff}]|\\x{1f1f3}[\\x{1f1e6}\\x{1f1e8}\\x{1f1ea}-\\x{1f1ec}\\x{1f1ee}\\x{1f1f1}\\x{1f1f4}\\x{1f1f5}\\x{1f1f7}\\x{1f1fa}\\x{1f1ff}]|\\x{1f1f4}\\x{1f1f2}|\\x{1f1f5}[\\x{1f1e6}\\x{1f1ea}-\\x{1f1ed}\\x{1f1f0}-\\x{1f1f3}\\x{1f1f7}-\\x{1f1f9}\\x{1f1fc}\\x{1f1fe}]|\\x{1f1f6}\\x{1f1e6}|\\x{1f1f7}[\\x{1f1ea}\\x{1f1f4}\\x{1f1f8}\\x{1f1fa}\\x{1f1fc}]|\\x{1f1f8}[\\x{1f1e6}-\\x{1f1ea}\\x{1f1ec}-\\x{1f1f4}\\x{1f1f7}-\\x{1f1f9}\\x{1f1fb}\\x{1f1fd}-\\x{1f1ff}]|\\x{1f1f9}[\\x{1f1e6}\\x{1f1e8}\\x{1f1e9}\\x{1f1eb}-\\x{1f1ed}\\x{1f1ef}-\\x{1f1f4}\\x{1f1f7}\\x{1f1f9}\\x{1f1fb}\\x{1f1fc}\\x{1f1ff}]|\\x{1f1fa}[\\x{1f1e6}\\x{1f1ec}\\x{1f1f2}\\x{1f1f8}\\x{1f1fe}\\x{1f1ff}]|\\x{1f1fb}[\\x{1f1e6}\\x{1f1e8}\\x{1f1ea}\\x{1f1ec}\\x{1f1ee}\\x{1f1f3}\\x{1f1fa}]|\\x{1f1fc}[\\x{1f1eb}\\x{1f1f8}]|\\x{1f1fd}\\x{1f1f0}|\\x{1f1fe}[\\x{1f1ea}\\x{1f1f9}]|\\x{1f1ff}[\\x{1f1e6}\\x{1f1f2}\\x{1f1fc}]';
/**
* @brief Regex for detecting an Emoji character
* @note This string is automatically generated by the Emoji Data Builder. Do not modify it manually.
*/
const EMOJI_REGEX = '\x{1F469}\x{200D}\x{2764}\x{FE0F}\x{200D}\x{1F48B}\x{200D}\x{1F468}|\x{1F468}\x{200D}\x{2764}\x{FE0F}\x{200D}\x{1F48B}\x{200D}\x{1F468}|\x{1F469}\x{200D}\x{2764}\x{FE0F}\x{200D}\x{1F48B}\x{200D}\x{1F469}|\x{1F3F4}\x{E0067}\x{E0062}\x{E0073}\x{E0063}\x{E0074}\x{E007F}|\x{1F3F4}\x{E0067}\x{E0062}\x{E0077}\x{E006C}\x{E0073}\x{E007F}|\x{1F3F4}\x{E0067}\x{E0062}\x{E0065}\x{E006E}\x{E0067}\x{E007F}|\x{1F469}\x{200D}\x{1F469}\x{200D}\x{1F467}\x{200D}\x{1F467}|\x{1F468}\x{200D}\x{1F468}\x{200D}\x{1F467}\x{200D}\x{1F467}|\x{1F469}\x{200D}\x{1F469}\x{200D}\x{1F467}\x{200D}\x{1F466}|\x{1F468}\x{200D}\x{1F468}\x{200D}\x{1F466}\x{200D}\x{1F466}|\x{1F469}\x{200D}\x{1F469}\x{200D}\x{1F466}\x{200D}\x{1F466}|\x{1F468}\x{200D}\x{1F469}\x{200D}\x{1F467}\x{200D}\x{1F467}|\x{1F468}\x{200D}\x{1F468}\x{200D}\x{1F467}\x{200D}\x{1F466}|\x{1F468}\x{200D}\x{1F469}\x{200D}\x{1F466}\x{200D}\x{1F466}|\x{1F468}\x{200D}\x{1F469}\x{200D}\x{1F467}\x{200D}\x{1F466}|\x{1F3CA}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2640}\x{FE0F}|\x{1F3CA}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2642}\x{FE0F}|\x{1F93D}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2642}\x{FE0F}|\x{1F471}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2642}\x{FE0F}|\x{1F93E}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2640}\x{FE0F}|\x{1F93E}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2642}\x{FE0F}|\x{1F473}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2640}\x{FE0F}|\x{1F3CC}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2640}\x{FE0F}|\x{1F3CB}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2642}\x{FE0F}|\x{1F3CB}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2640}\x{FE0F}|\x{1F3CC}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2642}\x{FE0F}|\x{1F93D}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2640}\x{FE0F}|\x{1F477}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2642}\x{FE0F}|\x{1F473}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2642}\x{FE0F}|\x{1F477}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2640}\x{FE0F}|\x{1F939}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2640}\x{FE0F}|\x{1F937}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2640}\x{FE0F}|\x{1F937}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2642}\x{FE0F}|\x{1F938}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2640}\x{FE0F}|\x{1F926}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2642}\x{FE0F}|\x{1F926}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2640}\x{FE0F}|\x{1F468}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2695}\x{FE0F}|\x{1F468}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2696}\x{FE0F}|\x{1F468}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2708}\x{FE0F}|\x{1F938}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2642}\x{FE0F}|\x{1F469}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2695}\x{FE0F}|\x{1F6B6}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2642}\x{FE0F}|\x{1F6B6}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2640}\x{FE0F}|\x{1F46E}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2640}\x{FE0F}|\x{1F939}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2642}\x{FE0F}|\x{1F6B5}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2642}\x{FE0F}|\x{1F469}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2696}\x{FE0F}|\x{1F469}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2708}\x{FE0F}|\x{1F46E}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2642}\x{FE0F}|\x{1F471}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2640}\x{FE0F}|\x{1F9DB}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2640}\x{FE0F}|\x{1F9DA}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2642}\x{FE0F}|\x{1F64B}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2642}\x{FE0F}|\x{1F64B}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2640}\x{FE0F}|\x{1F9DB}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2642}\x{FE0F}|\x{1F6B5}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2640}\x{FE0F}|\x{1F64D}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2642}\x{FE0F}|\x{1F9D9}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2642}\x{FE0F}|\x{1F64E}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2642}\x{FE0F}|\x{1F64E}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2640}\x{FE0F}|\x{1F9DA}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2640}\x{FE0F}|\x{1F647}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2642}\x{FE0F}|\x{1F647}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2640}\x{FE0F}|\x{1F9DD}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2640}\x{FE0F}|\x{1F9DD}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2642}\x{FE0F}|\x{1F575}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2642}\x{FE0F}|\x{1F575}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2640}\x{FE0F}|\x{1F645}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2640}\x{FE0F}|\x{1F645}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2642}\x{FE0F}|\x{1F9DC}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2640}\x{FE0F}|\x{1F646}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2642}\x{FE0F}|\x{1F646}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2640}\x{FE0F}|\x{1F9DC}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2642}\x{FE0F}|\x{1F9D9}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2640}\x{FE0F}|\x{1F64D}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2640}\x{FE0F}|\x{1F6B4}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2642}\x{FE0F}|\x{1F6B4}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2640}\x{FE0F}|\x{1F9D6}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2640}\x{FE0F}|\x{1F9D6}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2642}\x{FE0F}|\x{1F486}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2642}\x{FE0F}|\x{1F3C4}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2640}\x{FE0F}|\x{1F9D8}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2642}\x{FE0F}|\x{1F481}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2640}\x{FE0F}|\x{1F3C3}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2642}\x{FE0F}|\x{1F481}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2642}\x{FE0F}|\x{1F482}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2640}\x{FE0F}|\x{1F482}[\x{1F3FB}-\x{1F3FF}]?\x{200D}\x{2642}\x{FE0F}|Line truncated
/* !Parser: Bootstrap */
@@ -420,6 +424,9 @@ class _Parser
'src' => 'URI',
'type' => 'Text',
) );
/* Image proxy */
$def->addAttribute( 'img','data-imageproxy-source', 'Text' );
}
/**
@@ -503,6 +510,7 @@ class _Parser
'www.hulu.com/embed.html',
'www.collegehumor.com/e/',
'embed-ssl.ted.com/',
'embed.ted.com',
'vine.co/v/',
'gfycat.com/ifr/',
'embed.spotify.com/',
@@ -686,6 +694,41 @@ class _Parser
}
}
}
/* The editor button just drops in a <hr>, so we need to make sure that the structure is correct, as follows:
<div data-controller="core.front.core.articlePages">
<div data-role="contentPage">
<hr data-role="contentPageBreak" />
<p>
Page one
</p>
</div>
<div data-role="contentPage">
<hr data-role="contentPageBreak" />
<p>
Page two
</p>
</div>
</div>
The editor will just have
<p>
Page one
</p>
<hr data-role="contentPageBreak">
<p>
Page two
</p>
*/
if ( static::canUse( $this->member, 'Page', $this->area ) )
{
if ( ! preg_match( '#<div\s+?data-controller=["\']core.front.core.articlePages["\']#', $value ) and preg_match( '#<hr\s+?data-role=["\']contentPageBreak["\']\s{0,}/?>#', $value ) )
{
$value = preg_replace( '#<hr\s+?data-role=["\']contentPageBreak["\']\s{0,}/?>#', '</div>' . "\n" . '<div data-role="contentPage">' . "\n" . '<hr data-role="contentPageBreak">', $value );
$value = '<div data-controller="core.front.core.articlePages"><div data-role="contentPage">' . $value . '</div></div>';
}
}
/* Parse */
$parser = new DOMParser( array( $this, '_parseDomElement' ), array( $this, '_parseDomText' ) );
@@ -848,7 +891,7 @@ class _Parser
/* Init */
$text = $textNode->wholeText;
$breakPoints = array( '([\x{00A0}-\x{1F9FF}])' );
$breakPoints = array( '(' . static::EMOJI_REGEX . ')' );
/* Contains [page] tags? */
if ( mb_strpos( $text, '[page]' ) !== FALSE )
@@ -1343,7 +1386,7 @@ class _Parser
{
$element->removeAttribute( 'href' );
$element->setAttribute( 'class', 'ipsType_noLinkStyling' );
return;
return $element;
}
/* Attachment? */
@@ -1916,7 +1959,7 @@ class _Parser
{
unset( $this->existingAttachments[ $attachment['attach_id'] ] );
}
elseif ( $attachment['attach_member_id'] === $this->member->member_id and !in_array( $attachment['attach_id'], $this->mappedAttachments ) )
elseif ( $attachment['attach_member_id'] === (int) $this->member->member_id and !in_array( $attachment['attach_id'], $this->mappedAttachments ) )
{
\IPS\Db::i()->replace( 'core_attachments_map', array(
'attachment_id' => $attachment['attach_id'],
@@ -3336,6 +3379,12 @@ class _Parser
{
return (string) $url;
}
/* If we're not allowing remote embeds, just return it. */
if ( !\IPS\Settings::i()->allow_remote_images AND !( $url instanceof \IPS\Http\Url\Internal ) )
{
return (string) $url;
}
$maxImageDims = \IPS\Settings::i()->attachment_image_size ? explode( 'x', \IPS\Settings::i()->attachment_image_size ) : array( 1000, 750 );
$widthToUse = $width;
@@ -3468,7 +3517,12 @@ class _Parser
if ( $element->tagName === 'img' OR $element->tagName === 'iframe' )
{
$localDomain = parse_url( \IPS\Settings::i()->base_url, PHP_URL_HOST );
$currentSrc = $element->getAttribute('src');
$src = $element->getAttribute('src');
\IPS\Output::i()->parseFileObjectUrls( $src );
/* If for some reason we're processing an existing imageproxy or munged URL, set the full URL. */
$currentSrc = str_replace( '<___base_url___>', rtrim( \IPS\Settings::i()->base_url, '/' ), $src );
$srcDomain = parse_url( $currentSrc, PHP_URL_HOST );
if( $localDomain != $srcDomain )
{
@@ -3478,13 +3532,24 @@ class _Parser
$element->setAttribute( 'src', (string) \IPS\Http\Url::internal( 'app=core&module=system&controller=redirect', 'front' )->setQueryString( array( 'url' => $currentSrc, 'key' => $key, 'resource' => '1' ) ) );
$element->setAttribute( 'data-munge-src', $currentSrc );
}
else if ( mb_strpos( $src, '<___base_url___>' ) !== FALSE )
{
$element->setAttribute( 'src', str_replace( '<___base_url___>', rtrim( \IPS\Settings::i()->base_url, '/' ), $src ) );
}
else
{
$element->setAttribute( 'src', $src );
}
}
if ( $makeLinksAcpSafe and $element->tagName == 'a' )
{
$href = $element->getAttribute('href');
\IPS\Output::i()->parseFileObjectUrls( $href );
$element->setAttribute( 'href', (string) \IPS\Http\Url::internal( "app=core&module=system&controller=redirect", 'front' )->setQueryString( array(
'url' => $element->getAttribute('href'),
'key' => hash_hmac( "sha256", $element->getAttribute('href'), \IPS\Settings::i()->site_secret_key ),
'url' => $href,
'key' => hash_hmac( "sha256", $href, \IPS\Settings::i()->site_secret_key ),
) ) );
}
@@ -3648,4 +3713,67 @@ class _Parser
return $value;
}
/**
* Does this post contain a quote by member?
*
* @param int $memberId I dunno, take a wild guess. We don't pass a member object as they may have been deleted.
* @param string $content Content, that's "content", not "content". I'm data, not relaxed.
* @return boolean
*/
public static function containsQuoteBy( $memberId, $content )
{
if ( $memberId and $content )
{
return preg_match( '#data-ipsquote-userid=[\'"]' . $memberId . '[\'"]#i', $content );
}
return FALSE;
}
/**
* Remove quote attribution (sets it to guest )
*
* @param int $memberId Member ID. We don't pass a member object as they may have been deleted.
* @param string $content Still Content
* @return boolean
*/
public static function removeQuoteAttributionBy( $memberId, $content )
{
/* Do it */
return DOMParser::parse( $content, function( \DOMElement $element, \DOMNode $parent, \IPS\Text\DOMParser $parser ) use( $memberId )
{
/* If the element tag name matches the first bit... */
if( $element->tagName == 'blockquote' )
{
if ( $element->getAttribute('data-ipsquote-userid') == $memberId )
{
$element->setAttribute('data-ipsquote-userid', 0 );
if ( $element->hasAttribute('data-ipsquote-username') )
{
$element->setAttribute('data-ipsquote-username', '' );
}
foreach( $element->childNodes AS $child )
{
if ( $child instanceof \DOMElement and mb_strpos( $child->getAttribute('class'), 'ipsQuote_citation' ) !== FALSE )
{
$replace = new \DOMElement( 'div' );
$element->replaceChild( $replace, $child );
$replace->setAttribute('class', 'ipsQuote_citation' );
}
}
}
}
/* If we're still here, it's fine and we can import it */
$ownerDocument = $parent->ownerDocument ?: $parent;
$newElement = $ownerDocument->importNode( $element );
$parent->appendChild( $newElement );
/* And continue to children */
$parser->_parseDomNodeList( $element->childNodes, $newElement );
} );
}
}
+2 -1
View File
@@ -366,7 +366,7 @@ class _Theme extends \IPS\Theme\Dev\Theme
unlink( $dirToWrite . '/lang.php' );
}
$languageStrings = \IPS\Db::i()->select( 'word_key, word_default', 'core_sys_lang_words', array( 'word_theme=?', static::$currentThemeId ) )->setKeyField('word_key')->setValueField('word_default');
$languageStrings = \IPS\Db::i()->select( 'word_key, word_default', 'core_sys_lang_words', array( 'word_theme=? and lang_id=?', static::$currentThemeId, \IPS\Lang::defaultLanguage() ) )->setKeyField('word_key')->setValueField('word_default');
if( count( $languageStrings ) )
{
@@ -385,6 +385,7 @@ class _Theme extends \IPS\Theme\Dev\Theme
}
@\file_put_contents( $dirToWrite . '/lang.php', '<?' . "php\n\n{$langToWrite}\n" );
@chmod( $dirToWrite . '/lang.php', \IPS\IPS_FILE_PERMISSION );
return $dirToWrite;
}
+2 -2
View File
@@ -794,7 +794,7 @@ class _Theme extends \IPS\Theme
}
else
{
@chmod( $dirToWrite, IPS_FOLDER_PERMISSION );
@chmod( $dirToWrite, \IPS\IPS_FOLDER_PERMISSION );
}
}
@@ -830,7 +830,7 @@ class _Theme extends \IPS\Theme
}
else
{
@chmod( $dirToWrite, IPS_FOLDER_PERMISSION );
@chmod( $dirToWrite, \IPS\IPS_FOLDER_PERMISSION );
}
}
+20 -13
View File
@@ -45,22 +45,29 @@ class _SandboxedTemplate
*/
public function __call( $name, $args )
{
if ( !method_exists( $this->template, $name ) )
try
{
return "<span style='background:black;color:white;padding:6px;'>[[Template {$this->template->app}/{$this->template->templateLocation}/{$this->template->templateName}/{$name} does not exist. This theme may be out of date. Run the support tool in the AdminCP to restore the default theme.]]</span>";
if ( !method_exists( $this->template, $name ) )
{
return "<span style='background:black;color:white;padding:6px;'>[[Template {$this->template->app}/{$this->template->templateLocation}/{$this->template->templateName}/{$name} does not exist. This theme may be out of date. Run the support tool in the AdminCP to restore the default theme.]]</span>";
}
else
{
try
{
return $this->template->$name( ...$args );
}
catch ( \ErrorException $e )
{
throw new \Error( $e );
}
}
}
else
catch( \Throwable $e )
{
try
{
return $this->template->$name( ...$args );
}
catch ( \ErrorException $e )
{
\IPS\Log::log( $e, 'template_error' );
return "<span style='background:black;color:white;padding:6px;'>[[Template {$this->template->app}/{$this->template->templateLocation}/{$this->template->templateName}/{$name} is throwing an error. This theme may be out of date. Run the support tool in the AdminCP to restore the default theme.]]</span>";
}
\IPS\Log::log( $e, 'template_error' );
return "<span style='background:black;color:white;padding:6px;'>[[Template {$this->template->app}/{$this->template->templateLocation}/{$this->template->templateName}/{$name} is throwing an error. This theme may be out of date. Run the support tool in the AdminCP to restore the default theme.]]</span>";
}
}
}
+34 -8
View File
@@ -541,7 +541,7 @@ class _Theme extends \IPS\Node\Model
*/
public function css( $file, $app=NULL, $location=NULL )
{
$app = $app ?: \IPS\Request::i()->app;
$app = $app ?: \IPS\Dispatcher::i()->application->directory;
$location = $location ?: \IPS\Dispatcher::i()->controllerLocation;
$paths = explode( '/', $file );
$name = array_pop( $paths );
@@ -620,7 +620,7 @@ class _Theme extends \IPS\Node\Model
*/
public function resource( $path, $app=NULL, $location=NULL, $noProtocol=FALSE )
{
$app = $app ?: \IPS\Request::i()->app;
$app = $app ?: \IPS\Dispatcher::i()->application->directory;
$location = $location ?: \IPS\Dispatcher::i()->controllerLocation;
$paths = explode( '/', $path );
$name = array_pop( $paths );
@@ -672,7 +672,7 @@ class _Theme extends \IPS\Node\Model
$resource = \IPS\Db::i()->select( '*', 'core_theme_resources', array( 'resource_set_id=? AND resource_app=? AND resource_location=? AND resource_path=? AND resource_name=?', $this->id, $app, $location, $path, $name ) )->first();
$resourceMap[ $key ] = (string) \IPS\File::create( 'core_Theme', $key, $resource['resource_data'], 'set_resources_' . $this->id, FALSE, NULL, FALSE );
$resourceMap[ $key ] = (string) \IPS\File::create( 'core_Theme', $key, $resource['resource_data'], 'set_resources_' . $this->id, TRUE, NULL, FALSE );
\IPS\Db::i()->update( 'core_theme_resources', array( 'resource_added' => time(), 'resource_filename' => $resourceMap[ $key ] ), array( 'resource_id=?', $resource['resource_id'] ) );
@@ -775,7 +775,7 @@ class _Theme extends \IPS\Node\Model
if ( $attempts === 5 )
{
/* Rebuild in progress */
\IPS\Log::log( "Template store key: {$key} rebuilding and requested again ({$app}, {$location}, {$group})", "template_store_building" );
\IPS\Log::debug( "Template store key: {$key} rebuilding and requested again ({$app}, {$location}, {$group})", "template_store_building" );
/* Since we can't do anything else, this ends up just being an uncaught exception - show the error page right away to avoid the unnecessary logging */
\IPS\IPS::genericExceptionPage();
@@ -1287,6 +1287,14 @@ class _Theme extends \IPS\Node\Model
) );
}
/* Copy any language keys */
foreach( \IPS\Db::i()->select( '*', 'core_sys_lang_words', array( 'word_theme=?', $originalId ) ) AS $word )
{
$word['word_theme'] = $this->id;
unset( $word['word_id'] );
\IPS\Db::i()->insert( 'core_sys_lang_words', $word );
}
/* Copy across resources */
$this->copyResourcesFromSet( $originalId );
@@ -4879,11 +4887,15 @@ PHP;
* @param string|null $group Group
* @param int|null $plugin Plugin ID
* @param bool $doAll Delete all - by default only the master set is cleared
* @param string|null $template Template name if you just want to do one template. If specified, deleteCompiledTemplate is NOT called and must be done manually
* @return void
*/
public static function removeTemplates( $app, $location=NULL, $group=NULL, $plugin=NULL, $doAll=FALSE )
public static function removeTemplates( $app, $location=NULL, $group=NULL, $plugin=NULL, $doAll=FALSE, $template=NULL )
{
static::deleteCompiledTemplate( $app, $location, $group );
if ( !$template )
{
static::deleteCompiledTemplate( $app, $location, $group );
}
$where = array( array( 'template_app=?', $app ) );
@@ -4897,6 +4909,11 @@ PHP;
$where[] = array( 'template_group=?', $group );
}
if ( $template !== NULL )
{
$where[] = array( 'template_name=?', $template );
}
if ( $plugin !== NULL )
{
$where[] = array( 'template_plugin=?', $plugin );
@@ -4931,11 +4948,15 @@ PHP;
* @param string|null $path Group
* @param int|null $plugin Plugin ID
* @param bool $doAll Delete all - by default only the master set is cleared
* @param string|null $name CSS file name if you just want to do one file. If specified, deleteCompiledCss is NOT called and must be done manually
* @return void
*/
public static function removeCss( $app, $location=NULL, $path=NULL, $plugin=NULL, $doAll=FALSE )
public static function removeCss( $app, $location=NULL, $path=NULL, $plugin=NULL, $doAll=FALSE, $name=NULL )
{
static::deleteCompiledCss( $app, $location, $path );
if ( !$name )
{
static::deleteCompiledCss( $app, $location, $path );
}
$where = array( array( 'css_app=?', $app ) );
@@ -4949,6 +4970,11 @@ PHP;
$where[] = array( 'css_path=?', $path );
}
if ( $name !== NULL )
{
$where[] = array( 'css_name=?', $name );
}
if ( $plugin !== NULL )
{
$where[] = array( 'css_plugin=?', $plugin );
+1 -1
View File
@@ -60,7 +60,7 @@ class _PermissionCache extends \IPS\Widget
sort( $permissions );
/* For permissions based cache we need to store once per language and permission config */
$this->cacheKey = "widget_{$this->key}_" . $this->uniqueKey . '_' . md5( json_encode( $this->configuration ) . "_" . \IPS\Member::loggedIn()->language()->id . "_" . \IPS\Member::loggedIn()->skin . "_" . json_encode( $permissions ) . "_" . $this->orientation );
$this->cacheKey = "widget_{$this->key}_" . $this->uniqueKey . '_' . md5( json_encode( $this->configuration ) . "_" . $member->language()->id . "_" . $member->skin . "_" . json_encode( $permissions ) . "_" . $this->orientation );
}
}
}
Loaded 100 of 609 files, more files were not shown because too many files have changed in this diff. Show more