Version 5.0.13
This commit is contained in:
1 parent
b89858b2ff
commit
5efe744c5f
1407 files changed
+69690
-30177
No files matched your search
@@ -13,6 +13,7 @@ namespace IPS\core\modules\front\clubs;
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
|
||||
use DateInterval;
|
||||
use Exception;
|
||||
use IPS\Application;
|
||||
use IPS\Content\Search\Query;
|
||||
use IPS\core\Stream;
|
||||
@@ -21,6 +22,7 @@ use IPS\Db;
|
||||
use IPS\Dispatcher\Controller;
|
||||
use IPS\GeoLocation;
|
||||
use IPS\Helpers\Form;
|
||||
use IPS\Helpers\Form\Address;
|
||||
use IPS\Helpers\Form\CheckboxSet;
|
||||
use IPS\Helpers\Form\Radio;
|
||||
use IPS\Helpers\Form\Text;
|
||||
@@ -118,14 +120,19 @@ class directory extends Controller
|
||||
|
||||
/* All Clubs: Sort */
|
||||
$sortOption = Settings::i()->clubs_default_sort;
|
||||
if ( isset( Request::i()->sort ) and in_array( Request::i()->sort, array( 'last_activity', 'members', 'content', 'created', 'name' ) ) )
|
||||
$allSortOptions = array( 'last_activity', 'members', 'content', 'created', 'name' );
|
||||
if( Settings::i()->clubs_locations and GeoLocation::enabled() )
|
||||
{
|
||||
$allSortOptions[] = 'distance';
|
||||
}
|
||||
if ( isset( Request::i()->sort ) and in_array( Request::i()->sort, $allSortOptions ) )
|
||||
{
|
||||
$sortOption = Request::i()->sort;
|
||||
|
||||
$baseUrl = $baseUrl->setQueryString( 'sort', $sortOption );
|
||||
}
|
||||
|
||||
/* All Clubs: Filters */
|
||||
|
||||
/* All Clubs: Filters */
|
||||
$filters = array();
|
||||
$mineOnly = FALSE;
|
||||
$extraWhere = [];
|
||||
@@ -139,6 +146,36 @@ class directory extends Controller
|
||||
$mineOnly = TRUE;
|
||||
$baseUrl = $baseUrl->setQueryString( 'filter', 'mine' );
|
||||
}
|
||||
|
||||
/* Are we sorting by location? */
|
||||
$location = null;
|
||||
if( ( $sortOption == 'distance' or ( isset( Request::i()->location ) and Request::i()->location ) ) and Settings::i()->clubs_locations )
|
||||
{
|
||||
if( isset( Request::i()->location ) )
|
||||
{
|
||||
$baseUrl = $baseUrl->setQueryString( 'location', Request::i()->location );
|
||||
$geo = explode( ",", Request::i()->location );
|
||||
$location = array( 'lat' => $geo[0], 'lon' => $geo[1] );
|
||||
|
||||
/* If we are filtering by location, we can leave out clubs with no location */
|
||||
$extraWhere[] = [ 'location_lat is not null AND location_long is not null' ];
|
||||
}
|
||||
elseif( GeoLocation::enabled() )
|
||||
{
|
||||
/* Do an IP lookup */
|
||||
try
|
||||
{
|
||||
$geo = GeoLocation::getRequesterLocation();
|
||||
$location = array( 'lat' => $geo->lat, 'lon' => $geo->long );
|
||||
}
|
||||
catch ( Exception $e )
|
||||
{
|
||||
$location = array( 'lat' => Settings::i()->map_center_lat, 'lon' => Settings::i()->map_center_lon );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if ( isset( Request::i()->type ) and in_array( Request::i()->type, array( 'free', 'paid' ) ) )
|
||||
{
|
||||
$baseUrl = $baseUrl->setQueryString( 'type', Request::i()->type );
|
||||
@@ -207,8 +244,8 @@ class directory extends Controller
|
||||
$page = 1;
|
||||
}
|
||||
|
||||
$clubsCount = Club::clubs( Member::loggedIn(), array( ( $page - 1 ) * $perPage, $perPage ), $sortOption, $mineOnly, $filters, $extraWhere, TRUE );
|
||||
$allClubs = Club::clubs( Member::loggedIn(), array( ( $page - 1 ) * $perPage, $perPage ), $sortOption, $mineOnly, $filters, $extraWhere );
|
||||
$clubsCount = Club::clubs( Member::loggedIn(), array( ( $page - 1 ) * $perPage, $perPage ), $sortOption, $mineOnly, $filters, $extraWhere, TRUE, $location );
|
||||
$allClubs = iterator_to_array( Club::clubs( Member::loggedIn(), array( ( $page - 1 ) * $perPage, $perPage ), $sortOption, $mineOnly, $filters, $extraWhere, false, $location ) );
|
||||
$pagination = Theme::i()->getTemplate( 'global', 'core', 'global' )->pagination( $baseUrl, ceil( $clubsCount / $perPage ), $page, $perPage );
|
||||
|
||||
/* Get my clubs and invites */
|
||||
@@ -262,8 +299,6 @@ class directory extends Controller
|
||||
Output::i()->sidebar['contextual'] = Theme::i()->getTemplate('clubs')->myClubsSidebar( Club::clubs( Member::loggedIn(), 10, 'last_activity', TRUE ), $myClubsActivity, $myClubsInvites );
|
||||
|
||||
/* Prime the cache */
|
||||
$allClubs = iterator_to_array( $allClubs );
|
||||
|
||||
if( count( $allClubs ) )
|
||||
{
|
||||
foreach( $allClubs as $clubId => $clubData )
|
||||
@@ -282,7 +317,7 @@ class directory extends Controller
|
||||
}
|
||||
|
||||
Output::i()->title = Member::loggedIn()->language()->addToStack('module__core_clubs');
|
||||
Output::i()->output = Theme::i()->getTemplate('clubs')->directory( $featuredClubs, $allClubs, $pagination, $baseUrl, $sortOption, $myClubsActivity, $mapMarkers, $view, $mineOnly );
|
||||
Output::i()->output = Theme::i()->getTemplate('clubs')->directory( $featuredClubs, $allClubs, $pagination, $baseUrl, $sortOption, $myClubsActivity, $mapMarkers, $view, $mineOnly, $allSortOptions );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -292,7 +327,7 @@ class directory extends Controller
|
||||
*/
|
||||
protected function filters() : void
|
||||
{
|
||||
$fields = CustomField::roots();
|
||||
$fields = CustomField::fields();
|
||||
|
||||
$form = new Form('filter', 'filter');
|
||||
|
||||
@@ -305,6 +340,14 @@ class directory extends Controller
|
||||
'mine' => 'my_clubs'
|
||||
) ) ) );
|
||||
}
|
||||
|
||||
if( Settings::i()->clubs_locations )
|
||||
{
|
||||
$form->add( new Address( 'club_filter_location', null, false, [
|
||||
'requireFullAddress' => false,
|
||||
'minimize' => true
|
||||
] ) );
|
||||
}
|
||||
|
||||
if ( Application::appIsEnabled( 'nexus' ) and Settings::i()->clubs_paid_on )
|
||||
{
|
||||
@@ -353,6 +396,12 @@ class directory extends Controller
|
||||
$url = $url->setQueryString( 'name', $values['club_filter_name'] );
|
||||
}
|
||||
|
||||
if( !empty( $values['club_filter_location'] ) and $values['club_filter_location'] instanceof GeoLocation)
|
||||
{
|
||||
$values['club_filter_location']->getLatLong();
|
||||
$url = $url->setQueryString( 'location', $values['club_filter_location']->lat . ',' . $values['club_filter_location']->long );
|
||||
}
|
||||
|
||||
if ( Member::loggedIn()->member_id and $values['club_filter_type'] === 'mine' )
|
||||
{
|
||||
$url = $url->setQueryString( 'filter', 'mine' );
|
||||
|
||||
@@ -15,6 +15,7 @@ namespace IPS\core\modules\front\contact;
|
||||
use IPS\Application;
|
||||
use IPS\Db;
|
||||
use IPS\Dispatcher\Controller;
|
||||
use IPS\File;
|
||||
use IPS\Helpers\Form;
|
||||
use IPS\Helpers\Form\Captcha;
|
||||
use IPS\Helpers\Form\Editor;
|
||||
@@ -103,6 +104,9 @@ class contact extends Controller
|
||||
|
||||
if ( $values = $form->values() )
|
||||
{
|
||||
/* Clear the autosave by claiming attachments */
|
||||
File::claimAttachments( 'contact-' . Member::loggedIn()->member_id );
|
||||
|
||||
if ( ! Member::loggedIn()->member_id AND Settings::i()->contact_email_verify )
|
||||
{
|
||||
$key = Login::generateRandomString( 32 );
|
||||
@@ -158,11 +162,14 @@ class contact extends Controller
|
||||
/* Show interstitial page to prevent email clients from auto-verifying. */
|
||||
Output::i()->sidebar['enabled'] = FALSE;
|
||||
Output::i()->bodyClasses[] = 'ipsLayout_minimal';
|
||||
|
||||
$form = new Form( 'form', NULL );
|
||||
|
||||
/* Strip out the key and email address values from the URL which will be urlcoded (%40) and will be encoded again when used in the form action (%2540) */
|
||||
$form = new Form( 'form', NULL, Request::i()->url()->stripQueryString( [ 'key', 'email' ] ) );
|
||||
$form->class = 'ipsForm--vertical ipsForm--contact-confirm';
|
||||
$form->actionButtons[] = Theme::i()->getTemplate( 'forms', 'core', 'global' )->button( 'contact_click_to_verify', 'submit', null, 'ipsButton ipsButton_primary ipsButton_fullWidth', array( 'tabindex' => '2', 'accesskey' => 's' ) );
|
||||
$form->hiddenValues['email'] = Request::i()->email;
|
||||
$form->actionButtons[] = Theme::i()->getTemplate( 'forms', 'core', 'global' )->button( 'contact_click_to_verify', 'submit', null, 'ipsButton ipsButton--primary ipsButton--wide', array( 'tabindex' => '2', 'accesskey' => 's' ) );
|
||||
|
||||
/* The email address will still be encoded (%40) */
|
||||
$form->hiddenValues['email'] = urldecode( Request::i()->email );
|
||||
$form->hiddenValues['key'] = Request::i()->key;
|
||||
|
||||
if ( $values = $form->values() )
|
||||
|
||||
@@ -96,7 +96,7 @@ class popular extends Controller
|
||||
$content = $this->$activeTab();
|
||||
|
||||
/* Data Layer Context Property */
|
||||
if ( Settings::i()->core_datalayer_enabled AND ! Request::i()->isAjax() )
|
||||
if ( DataLayer::enabled() AND ! Request::i()->isAjax() )
|
||||
{
|
||||
DataLayer::i()->addContextProperty( 'community_area', 'leaderboard' );
|
||||
}
|
||||
|
||||
@@ -402,15 +402,17 @@ class streams extends Controller
|
||||
unset( $tmp );
|
||||
}
|
||||
|
||||
if ( isset( Request::i()->latest ) )
|
||||
if ( isset( Request::i()->latest ) and is_numeric( Request::i()->latest ) )
|
||||
{
|
||||
$latest = (int) Request::i()->latest;
|
||||
|
||||
if ( $stream->id and !$stream->include_comments )
|
||||
{
|
||||
$query->filterByLastUpdatedDate( DateTime::ts( Request::i()->latest ) );
|
||||
$query->filterByLastUpdatedDate( DateTime::ts( $latest ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
$query->filterByCreateDate( DateTime::ts( Request::i()->latest ) );
|
||||
$query->filterByCreateDate( DateTime::ts( $latest) );
|
||||
}
|
||||
|
||||
$query->setLimit(350);
|
||||
@@ -687,7 +689,14 @@ class streams extends Controller
|
||||
{
|
||||
$url = $stream ? $stream->baseUrl->setQueryString( 'className', $class ) : Request::i()->url()->setQueryString( 'className', $class );
|
||||
$containerClass = $class::$containerNodeClass;
|
||||
$field = new Node( 'stream_containers_' . $class::$title, $currentContainers[$class] ?? array(), NULL, array( 'url' => $url, 'class' => $class::$containerNodeClass, 'multiple' => TRUE, 'permissionCheck' => $containerClass::searchableNodesPermission(), 'clubs' => ( Settings::i()->clubs AND IPS::classUsesTrait( $containerClass, 'IPS\Content\ClubContainer' ) ) ), NULL, NULL, NULL, 'stream_containers_' . $class::$title );
|
||||
$field = new Node( 'stream_containers_' . $class::$title, $currentContainers[$class] ?? array(), NULL, array(
|
||||
'url' => $url,
|
||||
'class' => $class::$containerNodeClass,
|
||||
'multiple' => TRUE,
|
||||
'permissionCheck' => $containerClass::searchableNodesPermission(),
|
||||
'clubs' => ( Settings::i()->clubs AND IPS::classUsesTrait( $containerClass, 'IPS\Content\ClubContainer' ) ),
|
||||
'nodeGroups' => TRUE,
|
||||
), NULL, NULL, NULL, 'stream_containers_' . $class::$title );
|
||||
$field->label = Member::loggedIn()->language()->addToStack( 'stream_narrow_by_container_label', FALSE, array( 'sprintf' => array( Member::loggedIn()->language()->addToStack( $containerClass::$nodeTitle ) ) ) );
|
||||
|
||||
return Output::i()->json( array( 'node' => Theme::i()->getTemplate('streams')->filterFormContentTypeContent( $field, $class, Request::i()->key ) ) );
|
||||
|
||||
@@ -33,7 +33,6 @@ use IPS\Theme;
|
||||
use OutOfRangeException;
|
||||
use function count;
|
||||
use function defined;
|
||||
use function sprintf;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
@@ -71,6 +70,9 @@ class tag extends Controller
|
||||
}
|
||||
|
||||
parent::execute();
|
||||
|
||||
/* Breadcrumb */
|
||||
Output::i()->breadcrumb = array( array( $this->tag->url(), $this->tag->text ) );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -119,7 +121,7 @@ class tag extends Controller
|
||||
|
||||
if( $image = $this->tag->coverPhotoFile() )
|
||||
{
|
||||
Output::i()->metaTags['og:image'] = (string) $image;
|
||||
Output::i()->metaTags['og:image'] = (string) $image->url;
|
||||
}
|
||||
|
||||
/* Do not set description tags for page 2+ as you end up with duplicate tags */
|
||||
|
||||
@@ -41,9 +41,11 @@ use IPS\Helpers\Form\Text;
|
||||
use IPS\Helpers\Form\Upload;
|
||||
use IPS\Helpers\Form\YesNo;
|
||||
use IPS\Helpers\Table\Content;
|
||||
use IPS\Helpers\Table\Db as TableDb;
|
||||
use IPS\Http\Url;
|
||||
use IPS\Image;
|
||||
use IPS\IPS;
|
||||
use IPS\Lang;
|
||||
use IPS\Log;
|
||||
use IPS\Member;
|
||||
use IPS\Member\Club;
|
||||
@@ -51,6 +53,7 @@ use IPS\Member\Group;
|
||||
use IPS\Member\ProfileStep;
|
||||
use IPS\Output;
|
||||
use IPS\Patterns\ActiveRecordIterator;
|
||||
use IPS\Platform\Bridge;
|
||||
use IPS\Request;
|
||||
use IPS\Session;
|
||||
use IPS\Settings;
|
||||
@@ -64,6 +67,9 @@ use function defined;
|
||||
use function in_array;
|
||||
use function intval;
|
||||
use function is_array;
|
||||
use function is_numeric;
|
||||
use function is_object;
|
||||
use function is_string;
|
||||
use const IPS\PHOTO_THUMBNAIL_SIZE;
|
||||
|
||||
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
@@ -362,7 +368,7 @@ class profile extends Controller
|
||||
);
|
||||
|
||||
/* Data Layer Stuff */
|
||||
if ( Settings::i()->core_datalayer_enabled )
|
||||
if ( DataLayer::enabled() )
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -620,7 +626,7 @@ class profile extends Controller
|
||||
$baseUrl = $baseUrl->setQueryString( 'type', Request::i()->type );
|
||||
}
|
||||
|
||||
if ( isset( Request::i()->page ) and Request::i()->page > 1 )
|
||||
if ( isset( Request::i()->page ) and is_numeric( Request::i()->page ) and Request::i()->page > 1 )
|
||||
{
|
||||
$baseUrl = $baseUrl->setPage( 'page', Request::i()->page );
|
||||
}
|
||||
@@ -909,10 +915,11 @@ class profile extends Controller
|
||||
/* Handle the submission */
|
||||
if ( $values = $form->values() )
|
||||
{
|
||||
$this->_saveMember( $form, $values );
|
||||
$dataLayerUpdates = [];
|
||||
$this->_saveMember( $form, $values, $dataLayerUpdates );
|
||||
|
||||
/* Data Layer Stuff */
|
||||
if ( Settings::i()->core_datalayer_enabled )
|
||||
if ( DataLayer::enabled() )
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -927,6 +934,10 @@ class profile extends Controller
|
||||
'profile_group_id' => $this->member->member_group_id ?: null,
|
||||
'profile_id' => intval( $this->member->member_id ) ?: null,
|
||||
'profile_name' => $this->member->name ?: null,
|
||||
'updated_custom_fields' => $dataLayerUpdates['profile_fields'] ?? false,
|
||||
'updated_profile_name' => false,
|
||||
'updated_profile_photo' => false,
|
||||
'updated_profile_coverphoto' => false,
|
||||
);
|
||||
DataLayer::i()->addEvent( 'social_update', $properties );
|
||||
}
|
||||
@@ -1067,9 +1078,10 @@ class profile extends Controller
|
||||
*
|
||||
* @param Form $form
|
||||
* @param array $values
|
||||
* @param array &$dataLayerUpdates=[] Optional: a reference to an array which will be populated with datalayer updates
|
||||
* @return void
|
||||
*/
|
||||
protected function _saveMember( Form $form, array $values ) : void
|
||||
protected function _saveMember( Form $form, array $values, array &$dataLayerUpdates=[] ) : void
|
||||
{
|
||||
if( isset( $values['bday'] ) )
|
||||
{
|
||||
@@ -1116,6 +1128,7 @@ class profile extends Controller
|
||||
{
|
||||
foreach ( $fields as $id => $field )
|
||||
{
|
||||
$originalValue = $profileFields[ 'field_' . $id ] ?? null;
|
||||
if ( $field instanceof Upload )
|
||||
{
|
||||
$profileFields[ "field_{$id}" ] = (string) $values[ $field->name ];
|
||||
@@ -1129,6 +1142,21 @@ class profile extends Controller
|
||||
{
|
||||
Field::load( $id )->claimAttachments( $this->member->member_id );
|
||||
}
|
||||
|
||||
if ( $originalValue !== $profileFields[ "field_{$id}" ] )
|
||||
{
|
||||
$dataLayerUpdates['profile_fields'] = $dataLayerUpdates['profile_fields'] ?? [];
|
||||
/* Note the char length on old and new is limited to prevent the datalayer JS object from getting too large. That should only really feasibly happen for editor fields anyway. */
|
||||
$update = [
|
||||
'id' => $id,
|
||||
'name' => Lang::load( Lang::defaultLanguage() )->addToStack( Field::$titleLangPrefix . $id ),
|
||||
'old' => DataLayer::i()->includeSSOForMember( $this->member ) ? ( is_string( $originalValue ) ? mb_substr( $originalValue, 0, 2048 ) : $originalValue ) : null,
|
||||
'new' => DataLayer::i()->includeSSOForMember( $this->member ) ? ( is_string( $profileFields[ "field_{$id}" ] ) ? mb_substr( $profileFields[ "field_{$id}" ], 0, 2048 ) : $profileFields[ "field_{$id}" ] ) : null,
|
||||
];
|
||||
Lang::load( Lang::defaultLanguage() )->parseOutputForDisplay( $update['name'] );
|
||||
|
||||
$dataLayerUpdates['profile_fields'][] = $update;
|
||||
}
|
||||
}
|
||||
|
||||
$this->member->changedCustomFields = $profileFields;
|
||||
@@ -1193,6 +1221,17 @@ class profile extends Controller
|
||||
{
|
||||
$options['custom'] = 'member_photo_upload';
|
||||
}
|
||||
|
||||
/* Do we have a custom gallery? */
|
||||
if( Bridge::i()->featureIsEnabled( 'profile_gallery' ) and Settings::i()->cloud_profile_gallery )
|
||||
{
|
||||
$options['profile_gallery'] = 'member_profile_gallery';
|
||||
$toggles['profile_gallery'] = array( 'profile_gallery_id' );
|
||||
if( $this->member->pp_photo_type == 'cloud_ProfilePhotos' )
|
||||
{
|
||||
$defaultType = 'profile_gallery';
|
||||
}
|
||||
}
|
||||
|
||||
/* Can we use gallery images? */
|
||||
if ( Application::appIsEnabled('gallery') AND $this->member->pp_photo_type == 'gallery_Images' )
|
||||
@@ -1262,9 +1301,29 @@ class profile extends Controller
|
||||
}, NULL, NULL, 'member_photo_upload' ) );
|
||||
}
|
||||
|
||||
/* And generate the profile gallery if we have it */
|
||||
if( Bridge::i()->featureIsEnabled( 'profile_gallery' ) and Settings::i()->cloud_profile_gallery )
|
||||
{
|
||||
$form->add( new Custom( 'profile_gallery_id', null, null, [
|
||||
'getHtml' => function( $element )
|
||||
{
|
||||
return \IPS\cloud\Profiles\Gallery::gallerySelectHtml( $this->member );
|
||||
},
|
||||
'validate' => function( $element )
|
||||
{
|
||||
if( Request::i()->pp_photo_type == 'profile_gallery' and empty( $element->value ))
|
||||
{
|
||||
throw new DomainException( 'form_required' );
|
||||
}
|
||||
}
|
||||
], id: 'profile_gallery_id' ) );
|
||||
}
|
||||
|
||||
/* Handle submissions */
|
||||
if ( $values = $form->values() )
|
||||
{
|
||||
$oldPhotoURL = $this->member->get_photo( false );
|
||||
|
||||
/* Disable syncing */
|
||||
$profileSync = $this->member->profilesync;
|
||||
if ( isset( $profileSync['photo'] ) )
|
||||
@@ -1292,6 +1351,10 @@ class profile extends Controller
|
||||
}
|
||||
break;
|
||||
|
||||
case 'profile_gallery':
|
||||
\IPS\cloud\Profiles\Gallery::setGalleryPhoto( $this->member, Request::i()->profile_gallery_id );
|
||||
break;
|
||||
|
||||
case 'none':
|
||||
$this->member->pp_photo_type = NULL;
|
||||
$this->member->pp_main_photo = NULL;
|
||||
@@ -1303,6 +1366,30 @@ class profile extends Controller
|
||||
$this->member->members_bitoptions['profile_completed'] = FALSE;
|
||||
|
||||
$this->member->save();
|
||||
|
||||
/* Data Layer Stuff */
|
||||
if ( DataLayer::enabled() and ( $newPhotoURL = $this->member->get_photo( false ) ) and $newPhotoURL !== $oldPhotoURL )
|
||||
{
|
||||
try
|
||||
{
|
||||
$groupName = Group::load( $this->member->member_group_id )->formattedName;
|
||||
}
|
||||
catch ( UnderflowException $e )
|
||||
{
|
||||
$groupName = null;
|
||||
}
|
||||
$properties = array(
|
||||
'profile_group' => $groupName,
|
||||
'profile_group_id' => $this->member->member_group_id ?: null,
|
||||
'profile_id' => intval( $this->member->member_id ) ?: null,
|
||||
'profile_name' => $this->member->name ?: null,
|
||||
'updated_custom_fields' => false,
|
||||
'updated_profile_name' => false,
|
||||
'updated_profile_photo' => DataLayer::i()->includeSSOForMember( $this->member ) ? [ 'old' => $oldPhotoURL, 'new' => $newPhotoURL ] : true,
|
||||
'updated_profile_coverphoto' => false,
|
||||
);
|
||||
DataLayer::i()->addEvent( 'social_update', $properties );
|
||||
}
|
||||
|
||||
if ( $this->member->pp_photo_type )
|
||||
{
|
||||
@@ -1462,7 +1549,16 @@ class profile extends Controller
|
||||
}
|
||||
|
||||
$limit = array( ( $page - 1 ) * 50, 50 );
|
||||
$followers = iterator_to_array( $this->member->followers( ( Member::loggedIn()->isAdmin() OR Member::loggedIn()->member_id === $this->member->member_id ) ? \IPS\Content::FOLLOW_PUBLIC + \IPS\Content::FOLLOW_ANONYMOUS : \IPS\Content::FOLLOW_PUBLIC, array( 'immediate', 'daily', 'weekly', 'none' ), NULL, $limit, 'name' ) );
|
||||
$followerIterator = $this->member->followers( ( Member::loggedIn()->isAdmin() or Member::loggedIn()->member_id === $this->member->member_id ) ? \IPS\Content::FOLLOW_PUBLIC + \IPS\Content::FOLLOW_ANONYMOUS : \IPS\Content::FOLLOW_PUBLIC, ['immediate', 'daily', 'weekly', 'none'], NULL, $limit, 'name' );
|
||||
if( is_iterable( $followerIterator ) === FALSE )
|
||||
{
|
||||
$followers = [];
|
||||
}
|
||||
else
|
||||
{
|
||||
$followers = iterator_to_array( $followerIterator );
|
||||
}
|
||||
|
||||
$followersCount = $this->member->followersCount( ( Member::loggedIn()->isAdmin() OR Member::loggedIn()->member_id === $this->member->member_id ) ? \IPS\Content::FOLLOW_PUBLIC + \IPS\Content::FOLLOW_ANONYMOUS : \IPS\Content::FOLLOW_PUBLIC, array( 'immediate', 'daily', 'weekly', 'none' ) );
|
||||
|
||||
if ( Request::i()->isAjax() )
|
||||
@@ -1494,6 +1590,9 @@ class profile extends Controller
|
||||
*/
|
||||
protected function _coverPhotoSet( CoverPhoto $photo, ?string $type=NULL ) : void
|
||||
{
|
||||
$originalPhotoUrl = $this->member->coverPhoto()?->file?->url ?: null;
|
||||
$originalPhotoUrl = ( is_object( $originalPhotoUrl ) and $originalPhotoUrl instanceof Url ) ? (string) $originalPhotoUrl : $originalPhotoUrl;
|
||||
|
||||
/* Disable syncing */
|
||||
$profileSync = $this->member->profilesync;
|
||||
if ( isset( $profileSync['cover'] ) )
|
||||
@@ -1513,6 +1612,32 @@ class profile extends Controller
|
||||
{
|
||||
$this->member->logHistory( 'core', 'coverphoto', array( 'action' => $type ) );
|
||||
}
|
||||
|
||||
/* Data Layer Stuff */
|
||||
$newPhotoUrl = $photo->file?->url ?: null;
|
||||
$newPhotoUrl = ( is_object( $newPhotoUrl ) and $newPhotoUrl instanceof Url ) ? (string) $newPhotoUrl : $newPhotoUrl;
|
||||
if ( DataLayer::enabled() and ( $type === 'reposition' or $newPhotoUrl !== $originalPhotoUrl ) )
|
||||
{
|
||||
try
|
||||
{
|
||||
$groupName = Group::load( $this->member->member_group_id )->formattedName;
|
||||
}
|
||||
catch ( UnderflowException $e )
|
||||
{
|
||||
$groupName = null;
|
||||
}
|
||||
$properties = array(
|
||||
'profile_group' => $groupName,
|
||||
'profile_group_id' => $this->member->member_group_id ?: null,
|
||||
'profile_id' => intval( $this->member->member_id ) ?: null,
|
||||
'profile_name' => $this->member->name ?: null,
|
||||
'updated_custom_fields' => false,
|
||||
'updated_profile_name' => false,
|
||||
'updated_profile_photo' => false,
|
||||
'updated_profile_coverphoto' => DataLayer::i()->includeSSOForMember( $this->member ) ? [ 'repositioned' => $type === 'reposition', 'old' => $originalPhotoUrl, 'new' => $newPhotoUrl ] : true,
|
||||
);
|
||||
DataLayer::i()->addEvent( 'social_update', $properties );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -19,6 +19,7 @@ use IPS\Application;
|
||||
use IPS\Content\Controller;
|
||||
use IPS\Content\Search\Mysql\Query;
|
||||
use IPS\core\Alerts\Alert;
|
||||
use IPS\core\DataLayer;
|
||||
use IPS\core\Messenger\Conversation;
|
||||
use IPS\DateTime;
|
||||
use IPS\Db;
|
||||
@@ -31,6 +32,7 @@ use IPS\Notification;
|
||||
use IPS\Output;
|
||||
use IPS\Request;
|
||||
use IPS\Session;
|
||||
use IPS\Settings;
|
||||
use IPS\Theme;
|
||||
use LogicException;
|
||||
use OutOfRangeException;
|
||||
@@ -506,6 +508,13 @@ class messenger extends Controller
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( DataLayer::enabled() and $conversation )
|
||||
{
|
||||
foreach ( $conversation->getDataLayerProperties() as $key => $value )
|
||||
{
|
||||
DataLayer::i()->addContextProperty( $key, $value );
|
||||
}
|
||||
}
|
||||
Output::i()->output = Theme::i()->getTemplate('messaging')->template( $folder, $folders, $folderCounts, $conversations, $pagination, $conversation, $baseUrl, ( $conversation ? 'messenger_convo' : 'messenger' ), ( Request::i()->sortBy ? htmlspecialchars( Request::i()->sortBy, ENT_QUOTES | ENT_DISALLOWED, 'UTF-8', FALSE ): 'mt_last_post_time' ), ( Request::i()->filter ? htmlspecialchars( Request::i()->filter, ENT_QUOTES | ENT_DISALLOWED, 'UTF-8', FALSE ) : '' ), $alert );
|
||||
}
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ class modcp extends Controller
|
||||
*/
|
||||
public function __call( string $method, array $args ) : void
|
||||
{
|
||||
if ( Settings::i()->core_datalayer_enabled )
|
||||
if ( DataLayer::enabled() )
|
||||
{
|
||||
DataLayer::i()->addContextProperty( 'community_area', Lang::load( Lang::defaultLanguage() )->addToStack('modcp') );
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ namespace IPS\core\modules\front\search;
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
|
||||
use DateInterval;
|
||||
use DateMalformedStringException;
|
||||
use DateTimeZone;
|
||||
use IPS\Application;
|
||||
use IPS\Application\Module;
|
||||
@@ -92,11 +93,15 @@ class search extends Controller
|
||||
protected function _checkCached() : void
|
||||
{
|
||||
/* Check whether a 304 Not modified response is appropriate */
|
||||
if( !empty( $_SERVER['HTTP_IF_MODIFIED_SINCE'] ) AND ( new DateTime( $_SERVER['HTTP_IF_MODIFIED_SINCE'] ) )->getTimestamp() > ( time() - $this->_cacheTimeout ) )
|
||||
try
|
||||
{
|
||||
header('HTTP/1.1 304 Not Modified' );
|
||||
exit;
|
||||
if( !empty( $_SERVER['HTTP_IF_MODIFIED_SINCE'] ) AND ( new DateTime( $_SERVER['HTTP_IF_MODIFIED_SINCE'] ) )->getTimestamp() > ( time() - $this->_cacheTimeout ) )
|
||||
{
|
||||
header('HTTP/1.1 304 Not Modified' );
|
||||
exit;
|
||||
}
|
||||
}
|
||||
catch( DateMalformedStringException ){}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -160,7 +165,7 @@ class search extends Controller
|
||||
}
|
||||
|
||||
/* Data Layer Event */
|
||||
if ( Settings::i()->core_datalayer_enabled AND !Request::i()->isAjax() AND Request::i()->q )
|
||||
if ( DataLayer::enabled() AND !Request::i()->isAjax() AND Request::i()->q )
|
||||
{
|
||||
DataLayer::i()->addEvent( 'search', array( 'query' => Request::i()->q, ) );
|
||||
}
|
||||
@@ -1010,8 +1015,10 @@ class search extends Controller
|
||||
'source' => Content\Tag::allTags(),
|
||||
'minimized' => FALSE,
|
||||
'forceLower' => (bool)Settings::i()->tags_force_lower,
|
||||
'freeChoice' => false
|
||||
'freeChoice' => false,
|
||||
'separator' => ','
|
||||
) ), NULL, NULL, NULL, 'tags' ), NULL, 'search_tab_content' );
|
||||
Member::loggedIn()->language()->words['tags_desc'] = Member::loggedIn()->language()->get( 'tags_search_desc' );
|
||||
$form->add( new Radio( 'eitherTermsOrTags', Request::i()->eitherTermsOrTags, FALSE, array( 'options' => array( 'or' => 'termsortags_or_desc', 'and' => 'termsortags_and_desc' ) ), NULL, NULL, NULL, 'eitherTermsOrTags' ), NULL, 'search_tab_content' );
|
||||
|
||||
/* Author */
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace IPS\core\modules\front\sitemap;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
|
||||
use IPS\Content\Controller;
|
||||
use IPS\Dispatcher\Controller;
|
||||
use IPS\DateTime;
|
||||
use IPS\Db;
|
||||
use IPS\Http\Url;
|
||||
@@ -22,6 +22,7 @@ use IPS\Settings;
|
||||
use IPS\Xml\SimpleXML;
|
||||
use UnderflowException;
|
||||
use function defined;
|
||||
use const IPS\CIC;
|
||||
|
||||
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
@@ -58,9 +59,16 @@ class sitemap extends Controller
|
||||
}
|
||||
else
|
||||
{
|
||||
$sitemapUrl = Url::external( Settings::i()->sitemap_url ? rtrim( Settings::i()->sitemap_url, '/' ) : rtrim( Settings::i()->base_url, '/' ) . '/sitemap.php' );
|
||||
|
||||
$content = SimpleXML::create( 'sitemapindex', 'https://www.sitemaps.org/schemas/sitemap/0.9' );
|
||||
if( CIC )
|
||||
{
|
||||
$sitemapUrl = Url::external( rtrim( Settings::i()->base_url, '/' ).'/sitemap.php' );
|
||||
}
|
||||
else
|
||||
{
|
||||
$sitemapUrl = Url::external( Settings::i()->sitemap_url ? rtrim( Settings::i()->sitemap_url, '/' ) : rtrim( Settings::i()->base_url, '/' ) . '/sitemap.php' );
|
||||
}
|
||||
|
||||
$content = SimpleXML::create( 'sitemapindex', 'http://www.sitemaps.org/schemas/sitemap/0.9' );
|
||||
foreach ( Db::i()->select( array( 'sitemap', 'updated' ), 'core_sitemap', array( 'data IS NOT NULL' ) ) as $sitemap )
|
||||
{
|
||||
$content->addChild( 'sitemap', array( 'loc' => $sitemapUrl->setQueryString( 'file', $sitemap['sitemap'] ), 'lastmod' => DateTime::ts( $sitemap['updated'] )->format('c') ) );
|
||||
|
||||
@@ -17,6 +17,7 @@ use Exception;
|
||||
use IPS\Application;
|
||||
use IPS\Application\Module;
|
||||
use IPS\cms\Pages\Page;
|
||||
use IPS\cms\Records;
|
||||
use IPS\Content\Item;
|
||||
use IPS\core\AdminNotification;
|
||||
use IPS\core\DataLayer;
|
||||
@@ -445,7 +446,7 @@ class ajax extends Controller
|
||||
}
|
||||
|
||||
/* Render */
|
||||
$output = Theme::i()->getTemplate( 'widgets' )->topContributorRows( $topContributors, $timeframe, Request::i()->orientation );
|
||||
$output = Theme::i()->getTemplate( 'widgets' )->topContributorRows( $topContributors, $timeframe, Request::i()->layout, Request::i()->isCarousel );
|
||||
if ( Request::i()->isAjax() )
|
||||
{
|
||||
Output::i()->sendOutput( $output );
|
||||
@@ -511,7 +512,7 @@ class ajax extends Controller
|
||||
}
|
||||
|
||||
/* Render */
|
||||
$output = Theme::i()->getTemplate( 'widgets' )->mostSolvedRows( $topSolved, $timeframe, Request::i()->orientation );
|
||||
$output = Theme::i()->getTemplate( 'widgets' )->mostSolvedRows( $topSolved, $timeframe, Request::i()->layout, Request::i()->isCarousel );
|
||||
if ( Request::i()->isAjax() )
|
||||
{
|
||||
Output::i()->sendOutput( $output );
|
||||
@@ -827,13 +828,14 @@ class ajax extends Controller
|
||||
public function getDataLayerEvents() : void
|
||||
{
|
||||
$payload = '{}';
|
||||
if ( Settings::i()->core_datalayer_enabled AND Member::loggedIn()->member_id )
|
||||
if ( DataLayer::enabled() AND Member::loggedIn()->member_id )
|
||||
{
|
||||
$payload = DataLayer::i()->jsonEvents;
|
||||
DataLayer::i()->clearCache();
|
||||
}
|
||||
|
||||
/* Using this instead of \IPS\Output::i()->json because it's already JSON encoded */
|
||||
/* We have to set this before invoking sendOutput to prevent the data layer events from being cached all over again in the session */
|
||||
Output::i()->bypassDataLayer = true;
|
||||
Output::i()->sendOutput( Member::loggedIn()->language()->stripVLETags( $payload ), 200, 'application/json', Output::i()->httpHeaders );
|
||||
}
|
||||
|
||||
@@ -867,17 +869,19 @@ class ajax extends Controller
|
||||
Output::i()->redirect( Url::internal( '' ) );
|
||||
}
|
||||
|
||||
/* Is there a widget in the request? */
|
||||
if ( !is_string( Request::i()->blockID ) or empty( Request::i()->blockID ) )
|
||||
{
|
||||
Output::i()->json( [ "No block id present in the request" ], 400 );
|
||||
}
|
||||
|
||||
/* In this case, let's see if the block exists at all */
|
||||
foreach ( ['pageApp', 'pageModule', 'pageController', 'pageID', 'pageArea'] as $pageParam )
|
||||
/* Note - pageID is the ID of the CMS page (if there is one) while pageElse is the ID of the content in the page (e.g. records, topics, blogs, etc) */
|
||||
foreach ( ['pageApp', 'pageModule', 'pageController', 'pageID', 'pageArea', 'pageElse', 'pageContentClass'] as $pageParam )
|
||||
{
|
||||
$$pageParam = Request::i()->$pageParam;
|
||||
}
|
||||
|
||||
$item = null;
|
||||
try
|
||||
{
|
||||
// If they're not a member, they definitely don't have permission
|
||||
@@ -892,18 +896,28 @@ class ajax extends Controller
|
||||
}
|
||||
|
||||
$canEdit = false;
|
||||
$itemId = $pageID ?: Request::i()->pageElse;
|
||||
if ( $pageApp and $pageModule and $pageController and is_numeric( $itemId ) )
|
||||
$itemId = $pageID ?: $pageElse;
|
||||
if ( is_numeric( $itemId ) )
|
||||
{
|
||||
$controllerClass = "\\IPS\\{$pageApp}\\modules\\front\\{$pageModule}\\{$pageController}";
|
||||
if ( class_exists( $controllerClass ) && is_subclass_of( $controllerClass, ContentController::class ) )
|
||||
// first, check the content class directly
|
||||
if ( is_numeric( $pageElse ) and isset( $pageContentClass ) and class_exists( $pageContentClass ) and is_subclass_of( $pageContentClass, Item::class ) )
|
||||
{
|
||||
$item = $controllerClass::loadItem( (int) $itemId );
|
||||
if ( $item instanceof Item and $item->author()?->member_id === Member::loggedIn()->member_id ) /* todo we may want to add a mod permission as well; for now we just do an author check */
|
||||
$item = $pageContentClass::load( $pageElse );
|
||||
}
|
||||
else if ( $pageApp and $pageModule and $pageController ) // Still haven't verified permission? Check permissions to edit the CMS page itself
|
||||
{
|
||||
$controllerClass = "\\IPS\\{$pageApp}\\modules\\front\\{$pageModule}\\{$pageController}";
|
||||
if ( class_exists( $controllerClass ) && is_subclass_of( $controllerClass, ContentController::class ) )
|
||||
{
|
||||
$canEdit = true;
|
||||
$item = $controllerClass::loadItem( (int)$itemId );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if ( $item instanceof Item and $item->author()?->member_id === Member::loggedIn()->member_id )
|
||||
{
|
||||
$canEdit = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ( !$canEdit && $app->canManageWidgets() )
|
||||
@@ -959,15 +973,17 @@ class ajax extends Controller
|
||||
$items = json_decode( Request::i()->items, true ) ?: [];
|
||||
if ( is_array( $items ) )
|
||||
{
|
||||
Db::i()->replace( 'core_table_of_contents', [
|
||||
$contentId = strval( $pageElse ?: $pageID ) ?: null;
|
||||
$insert = [
|
||||
'contents' => json_encode( $items ),
|
||||
'app' => $pageApp,
|
||||
'controller' => $pageController,
|
||||
'module' => $pageModule,
|
||||
'key' => strval( $pageID ?: Request::i()->pageElse ) ?: null
|
||||
]);
|
||||
'key' => $contentId ? ( $item instanceof Records ? $item::class . "_" : '' ) . $contentId : null
|
||||
];
|
||||
Db::i()->insert( 'core_table_of_contents', $insert, true );
|
||||
|
||||
Widget::deleteCaches( Request::i()->blockID );
|
||||
Widget::deleteCaches( 'tableofcontents' );
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1006,7 +1022,7 @@ class ajax extends Controller
|
||||
$timestamps = array_map( "intval", explode( ",", Request::i()->timestamps ) );
|
||||
if ( count( $timestamps ) )
|
||||
{
|
||||
$lang = Lang::defaultLanguage() ? Lang::load( Lang::defaultLanguage() ) : Member::loggedIn()->language();
|
||||
$lang = Member::loggedIn()->language() ?: Lang::load( Lang::defaultLanguage() );
|
||||
foreach ( $timestamps as $timestamp )
|
||||
{
|
||||
// we cache based on how different the time is compared to the current time
|
||||
|
||||
@@ -78,7 +78,6 @@ class announcement extends Controller
|
||||
if ( ( !Settings::i()->site_online and !Member::loggedIn()->group['g_access_offline'] ) OR ( !Member::loggedIn()->member_id AND !Member::loggedIn()->group['g_view_board'] ) )
|
||||
{
|
||||
Output::i()->bodyClasses[] = 'ipsLayout_minimal';
|
||||
Output::i()->allowDefaultWidgets = FALSE;
|
||||
Output::i()->sidebar['enabled'] = FALSE;
|
||||
}
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ use IPS\Settings;
|
||||
use IPS\Text\Parser;
|
||||
use IPS\Theme;
|
||||
use OutOfRangeException;
|
||||
use UnderflowException;
|
||||
use UnexpectedValueException;
|
||||
use function class_exists;
|
||||
use function count;
|
||||
@@ -338,7 +339,7 @@ class editor extends Controller
|
||||
{
|
||||
$categoryName = $categoryNames[ $row['emo_set'] ] ?? Member::loggedIn()->language()->get( 'core_emoticon_group_' . $row['emo_set'] );
|
||||
}
|
||||
catch ( OutOfRangeException )
|
||||
catch ( UnderflowException )
|
||||
{
|
||||
$categoryName = '_default';
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace IPS\core\modules\front\system;
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
|
||||
use DateInterval;
|
||||
use IPS\Content\Controller;
|
||||
use IPS\Dispatcher\Controller;
|
||||
use IPS\Data\Cache;
|
||||
use IPS\DateTime;
|
||||
use IPS\Http\Url;
|
||||
@@ -55,6 +55,10 @@ class embed extends Controller
|
||||
{
|
||||
try
|
||||
{
|
||||
if( empty( Request::i()->url ) )
|
||||
{
|
||||
throw new UnexpectedValueException;
|
||||
}
|
||||
$return = Parser::embeddableMedia( Url::createFromString( Request::i()->url, FALSE, TRUE ), TRUE );
|
||||
}
|
||||
catch( UnexpectedValueException $e )
|
||||
|
||||
@@ -19,6 +19,7 @@ use IPS\IPS;
|
||||
use IPS\Member;
|
||||
use IPS\Output;
|
||||
use IPS\Request;
|
||||
use IPS\Settings as SettingsClass;
|
||||
use IPS\Theme;
|
||||
use function defined;
|
||||
|
||||
@@ -71,7 +72,12 @@ class followed extends Controller
|
||||
}
|
||||
|
||||
/* Don't forget Members - add this on to the end so it never defaults UNLESS we only have apps that do not have followable content */
|
||||
$types['core'] = array( 'core_member' => "\IPS\Member", 'core_tag' => "IPS\Content\Tag" );
|
||||
$types['core'] = array( 'core_member' => "\IPS\Member" );
|
||||
|
||||
if( SettingsClass::i()->tags_enabled )
|
||||
{
|
||||
$types['core']['core_tag'] = "IPS\Content\Tag";
|
||||
}
|
||||
|
||||
/* What type are we looking at? */
|
||||
$currentAppModule = NULL;
|
||||
|
||||
@@ -12,10 +12,13 @@ namespace IPS\core\modules\front\system;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
|
||||
use DateInterval;
|
||||
use IPS\Application;
|
||||
use IPS\Data\Store;
|
||||
use IPS\DateTime;
|
||||
use IPS\Db;
|
||||
use IPS\Dispatcher\Controller;
|
||||
use IPS\Email;
|
||||
use IPS\Extensions\SSOAbstract;
|
||||
use IPS\Http\Url;
|
||||
use IPS\Http\Url\Internal;
|
||||
@@ -29,6 +32,7 @@ use IPS\MFA\MFAHandler;
|
||||
use IPS\Output;
|
||||
use IPS\Request;
|
||||
use IPS\Session;
|
||||
use IPS\Text\Encrypt;
|
||||
use IPS\Theme;
|
||||
use OutOfRangeException;
|
||||
use UnderflowException;
|
||||
@@ -157,7 +161,6 @@ class login extends Controller
|
||||
}
|
||||
|
||||
/* Display Login Form */
|
||||
Output::i()->allowDefaultWidgets = FALSE;
|
||||
Output::i()->bodyClasses[] = 'ipsLayout_minimal';
|
||||
Output::i()->sidebar['enabled'] = FALSE;
|
||||
Output::i()->title = Member::loggedIn()->language()->addToStack('login');
|
||||
@@ -254,8 +257,58 @@ class login extends Controller
|
||||
$login->reauthenticateAs = $member;
|
||||
$error = NULL;
|
||||
|
||||
/* Show error if linking cannot be completed */
|
||||
if( !count( $login->usernamePasswordMethods() ) AND !count( $login->buttonMethods() ) )
|
||||
{
|
||||
/**
|
||||
* Send validation email and show page explaining the next step.
|
||||
*/
|
||||
$vid = LoginClass::generateRandomString();
|
||||
$plainSecurityKey = LoginClass::generateRandomString();
|
||||
|
||||
/* Invalidating any existing validating links */
|
||||
try
|
||||
{
|
||||
/* Anything sent in the last 6 minutes? -- keep in mind the email link is only valid for 10 minutes */
|
||||
Db::i()->select( '*', 'core_validating', [
|
||||
'member_id=? AND login_link=1 AND email_sent>?',
|
||||
$member->member_id,
|
||||
( new DateTime )->sub( new DateInterval( 'PT6M' ) )->getTimestamp()
|
||||
] )->first();
|
||||
}
|
||||
catch( \UnderflowException $e )
|
||||
{
|
||||
Db::i()->delete( 'core_validating', [ 'member_id=? AND login_link=1', $member->member_id ] );
|
||||
Db::i()->insert( 'core_validating', [
|
||||
'vid' => $vid,
|
||||
'member_id' => $member->member_id,
|
||||
'entry_date' => time(),
|
||||
'email_chg' => false,
|
||||
'ip_address' => Request::i()->ipAddress(),
|
||||
'new_email' => '',
|
||||
'email_sent' => time(),
|
||||
'security_key' => Encrypt::fromPlaintext( $plainSecurityKey )->tag(),
|
||||
'login_link' => true,
|
||||
'extra' => $_SESSION['linkAccounts']
|
||||
] );
|
||||
|
||||
/* send email */
|
||||
Email::buildFromTemplate( 'core', 'loginLinkValidate', [ $member, $vid, $plainSecurityKey ], Email::TYPE_TRANSACTIONAL )->send( $member );
|
||||
}
|
||||
|
||||
unset( $_SESSION['linkAccounts'] );
|
||||
|
||||
/* Otherwise show the reauthenticate form */
|
||||
Output::i()->bodyClasses[] = 'ipsLayout_minimal';
|
||||
Output::i()->sidebar['enabled'] = FALSE;
|
||||
Output::setCacheTime();
|
||||
Output::i()->title = Member::loggedIn()->language()->addToStack('login');
|
||||
Output::i()->output = Theme::i()->getTemplate( 'system' )->mergeSocialAccountEmailValidation( $handler );
|
||||
return;
|
||||
}
|
||||
|
||||
/* Did we submit the merge form? */
|
||||
if( isset( Request::i()->mergeAccount ) and Request::i()->mergeAccount )
|
||||
else if( isset( Request::i()->mergeAccount ) and Request::i()->mergeAccount )
|
||||
{
|
||||
Session::i()->csrfCheck();
|
||||
|
||||
|
||||
@@ -207,7 +207,8 @@ class metatags extends Controller
|
||||
|
||||
$output = array(
|
||||
'scope' => rtrim( Settings::i()->base_url, '/' ) . '/',
|
||||
'name' => Settings::i()->board_name
|
||||
'name' => Settings::i()->board_name,
|
||||
'display' => 'standalone',
|
||||
);
|
||||
|
||||
foreach( $manifest as $k => $v )
|
||||
@@ -273,63 +274,4 @@ class metatags extends Controller
|
||||
Output::i()->sendOutput( json_encode( $output, JSON_PRETTY_PRINT ), 200, 'application/manifest+json', $cacheHeaders );
|
||||
}
|
||||
|
||||
/**
|
||||
* Output the IE browserconfig.xml file
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function iebrowserconfig() : void
|
||||
{
|
||||
if( \IPS\IN_DEV === FALSE AND isset( Store::i()->iebrowserconfig ) )
|
||||
{
|
||||
$output = Store::i()->iebrowserconfig;
|
||||
}
|
||||
else
|
||||
{
|
||||
$manifest = json_decode( Settings::i()->manifest_details, TRUE );
|
||||
|
||||
/* Init */
|
||||
$xml = new XMLWriter;
|
||||
$xml->openMemory();
|
||||
$xml->setIndent( TRUE );
|
||||
$xml->startDocument( '1.0', 'UTF-8' );
|
||||
$xml->startElement( 'browserconfig' );
|
||||
$xml->startElement( 'msapplication' );
|
||||
$xml->startElement( 'tile' );
|
||||
|
||||
if( isset( $manifest['theme_color'] ) AND $manifest['theme_color'] )
|
||||
{
|
||||
$xml->writeElement( 'TileColor', $manifest['theme_color'] );
|
||||
}
|
||||
|
||||
$homeScreen = json_decode( Settings::i()->icons_homescreen, TRUE ) ?? array();
|
||||
|
||||
foreach( $homeScreen as $k => $v )
|
||||
{
|
||||
if( mb_strpos( $k, 'msapplication' ) !== FALSE AND $k !== 'msapplication-TileImage' )
|
||||
{
|
||||
$file = File::get( 'core_Icons', $v['url'] );
|
||||
|
||||
$xml->startElement( str_replace( 'msapplication-', '', $k ) );
|
||||
$xml->startAttribute( 'src' );
|
||||
$xml->text( (string) $file->url );
|
||||
$xml->endAttribute();
|
||||
$xml->endElement();
|
||||
}
|
||||
}
|
||||
|
||||
$xml->endElement();
|
||||
$xml->endElement();
|
||||
$xml->endElement();
|
||||
$xml->endDocument();
|
||||
|
||||
$output = $xml->outputMemory();
|
||||
|
||||
Store::i()->iebrowserconfig = $output;
|
||||
}
|
||||
|
||||
$cacheHeaders = ( !\IPS\IN_DEV ) ? Output::getCacheHeaders( time(), 86400 ) : array();
|
||||
|
||||
Output::i()->sendOutput( $output, 200, 'application/xml', $cacheHeaders );
|
||||
}
|
||||
}
|
||||
@@ -13,9 +13,12 @@ namespace IPS\core\modules\front\system;
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
|
||||
use Exception;
|
||||
use IPS\Api\Webhook;
|
||||
use IPS\Application;
|
||||
use IPS\Content;
|
||||
use IPS\Content\Item;
|
||||
use IPS\core\DataLayer;
|
||||
use IPS\core\Followed\Follow;
|
||||
use IPS\Db;
|
||||
use IPS\Dispatcher\Controller;
|
||||
use IPS\Events\Event;
|
||||
@@ -31,14 +34,17 @@ use IPS\Node\Model;
|
||||
use IPS\Notification;
|
||||
use IPS\Notification\Table;
|
||||
use IPS\Output;
|
||||
use IPS\Platform\Bridge;
|
||||
use IPS\Request;
|
||||
use IPS\Session;
|
||||
use IPS\Settings;
|
||||
use IPS\Theme;
|
||||
use OutOfRangeException;
|
||||
use UnderflowException;
|
||||
use function count;
|
||||
use function defined;
|
||||
use function in_array;
|
||||
use function md5;
|
||||
use const IPS\REBUILD_QUICK;
|
||||
|
||||
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
@@ -132,6 +138,37 @@ class notifications extends Controller
|
||||
'encoding' => Request::i()->encoding,
|
||||
'device' => $device->device_key
|
||||
) );
|
||||
|
||||
/* The expectation is to receive some push notifications, so let's add them to follower_content, new_content and new_comment and they can be removed manually if the user doesn't want them */
|
||||
$notificationTypes = array( 'follower_content', 'new_content', 'new_comment' );
|
||||
|
||||
foreach ( $notificationTypes as $notificationKey )
|
||||
{
|
||||
try
|
||||
{
|
||||
/* See if we have a row in preferences already */
|
||||
$currentRow = Db::i()->select( '*', 'core_notification_preferences', array( 'member_id=? AND notification_key=?', Member::loggedIn()->member_id, $notificationKey ) )->first();
|
||||
|
||||
/* Does this row have a preference of push? */
|
||||
$prefs = explode( ',', $currentRow['preference'] );
|
||||
|
||||
if ( !in_array( 'push', $prefs ) )
|
||||
{
|
||||
$prefs[] = 'push';
|
||||
}
|
||||
Db::i()->update( 'core_notification_preferences', [
|
||||
'preference' => implode( ',', $prefs ),
|
||||
], array( 'member_id=? AND notification_key=?', Member::loggedIn()->member_id, $notificationKey ) );
|
||||
}
|
||||
catch( UnderflowException $e )
|
||||
{
|
||||
Db::i()->insert( 'core_notification_preferences', [
|
||||
'member_id' => Member::loggedIn()->member_id,
|
||||
'notification_key' => $notificationKey,
|
||||
'preference' => implode( ',', [ 'inline', 'push' ] )
|
||||
], true );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Output::i()->json( 'OK' );
|
||||
@@ -239,13 +276,14 @@ class notifications extends Controller
|
||||
$form->class = 'ipsForm--vertical ipsForm--notification-options';
|
||||
}
|
||||
|
||||
if ( Request::i()->isAjax() )
|
||||
if ( Request::i()->isAjax() and ! isset( Request::i()->fromFollowButton ) )
|
||||
{
|
||||
$form->actionButtons = array();
|
||||
Output::i()->sendOutput( Theme::i()->getTemplate('system')->notificationSettingsType( Member::loggedIn()->language()->addToStack("notifications__{$extensionKey}"), $form, TRUE ), 200, 'text/html', Output::i()->httpHeaders );
|
||||
}
|
||||
else
|
||||
{
|
||||
/* If it's not ajax, or come via the follow button, then we want to show the form with the save button */
|
||||
Output::i()->output = Theme::i()->getTemplate('system')->notificationSettingsType( Member::loggedIn()->language()->addToStack("notifications__{$extensionKey}"), $form, FALSE );
|
||||
}
|
||||
}
|
||||
@@ -327,34 +365,7 @@ class notifications extends Controller
|
||||
}
|
||||
|
||||
/* Get class */
|
||||
$class = NULL;
|
||||
foreach ( $application->extensions( 'core', 'ContentRouter' ) as $ext )
|
||||
{
|
||||
foreach ( $ext->classes as $classname )
|
||||
{
|
||||
if ( $classname == 'IPS\\' . Request::i()->follow_app . '\\' . IPS::mb_ucfirst( Request::i()->follow_area ) )
|
||||
{
|
||||
$class = $classname;
|
||||
break;
|
||||
}
|
||||
if ( isset( $classname::$containerNodeClass ) and $classname::$containerNodeClass == 'IPS\\' . Request::i()->follow_app . '\\' . IPS::mb_ucfirst( Request::i()->follow_area ) )
|
||||
{
|
||||
$class = $classname::$containerNodeClass;
|
||||
break;
|
||||
}
|
||||
if( isset( $classname::$containerFollowClasses ) )
|
||||
{
|
||||
foreach( $classname::$containerFollowClasses as $followClass )
|
||||
{
|
||||
if( $followClass == 'IPS\\' . Request::i()->follow_app . '\\' . IPS::mb_ucfirst( Request::i()->follow_area ) )
|
||||
{
|
||||
$class = $followClass;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$class = Follow::getClassToFollow( Request::i()->follow_app, Request::i()->follow_area );
|
||||
|
||||
if( Request::i()->follow_app == 'core' and Request::i()->follow_area == 'member' )
|
||||
{
|
||||
@@ -376,18 +387,6 @@ class notifications extends Controller
|
||||
{
|
||||
Output::i()->error( 'cant_follow_member', '3C154/8', 403, '' );
|
||||
}
|
||||
|
||||
$class = 'IPS\\Member';
|
||||
}
|
||||
|
||||
if( Request::i()->follow_app == 'core' and Request::i()->follow_area == 'club' )
|
||||
{
|
||||
$class = 'IPS\Member\Club';
|
||||
}
|
||||
|
||||
if( Request::i()->follow_app == 'core' and Request::i()->follow_area == 'tag' )
|
||||
{
|
||||
$class = 'IPS\Content\Tag';
|
||||
}
|
||||
|
||||
if ( !$class )
|
||||
@@ -401,7 +400,7 @@ class notifications extends Controller
|
||||
{
|
||||
if ( in_array( 'IPS\Node\Model', class_parents( $class ) ) )
|
||||
{
|
||||
/* @var Model $thing */
|
||||
/* @var Model $class */
|
||||
$thing = $class::loadAndCheckPerms( (int) Request::i()->follow_id );
|
||||
Output::i()->title = Member::loggedIn()->language()->addToStack( 'follow_thing', FALSE, array( 'sprintf' => array( $thing->_title ) ) );
|
||||
|
||||
@@ -418,6 +417,7 @@ class notifications extends Controller
|
||||
}
|
||||
elseif ( $class == 'IPS\Member\Club' )
|
||||
{
|
||||
/* @var Club $class */
|
||||
$thing = $class::loadAndCheckPerms( (int) Request::i()->follow_id );
|
||||
Output::i()->title = Member::loggedIn()->language()->addToStack( 'follow_thing', FALSE, array( 'sprintf' => array( $thing->_title ) ) );
|
||||
Output::i()->breadcrumb = array(
|
||||
@@ -432,7 +432,7 @@ class notifications extends Controller
|
||||
throw new OutOfRangeException;
|
||||
}
|
||||
|
||||
/* @var Item $thing */
|
||||
/* @var Item $class */
|
||||
$thing = $class::loadAndCheckPerms( (int) Request::i()->follow_id );
|
||||
Output::i()->title = Member::loggedIn()->language()->addToStack( 'follow_thing', FALSE, array( 'sprintf' => array( $thing->mapped('title') ) ) );
|
||||
|
||||
@@ -553,7 +553,7 @@ class notifications extends Controller
|
||||
{
|
||||
foreach ( $options as $k => $v )
|
||||
{
|
||||
$form->hiddenValues[ $k ] = $v;
|
||||
$form->hiddenValues[ 'follow_type' ] = $k;
|
||||
if ( empty( $notificationConfiguration ) )
|
||||
{
|
||||
$type = $type == 'follower_content' ? 'core_Content' : $type;
|
||||
@@ -581,91 +581,26 @@ class notifications extends Controller
|
||||
/* Handle submissions */
|
||||
if ( $values = $form->values() )
|
||||
{
|
||||
/* Insert */
|
||||
$save = array(
|
||||
'follow_id' => md5( Request::i()->follow_app . ';' . Request::i()->follow_area . ';' . Request::i()->follow_id . ';' . Member::loggedIn()->member_id ),
|
||||
'follow_app' => Request::i()->follow_app,
|
||||
'follow_area' => Request::i()->follow_area,
|
||||
'follow_rel_id' => Request::i()->follow_id,
|
||||
'follow_member_id' => Member::loggedIn()->member_id,
|
||||
'follow_is_anon' => !$values['follow_public'],
|
||||
'follow_added' => time(),
|
||||
'follow_notify_do' => ( isset( $values['follow_type'] ) AND $values['follow_type'] == 'none' ) ? 0 : 1,
|
||||
'follow_notify_meta' => '',
|
||||
'follow_notify_freq' => ( $class == "IPS\Member" ) ? 'immediate' : $values['follow_type'],
|
||||
'follow_notify_sent' => 0,
|
||||
'follow_visible' => 1,
|
||||
);
|
||||
if ( $current )
|
||||
{
|
||||
Db::i()->update( 'core_follow', $save, array( 'follow_id=?', $current['follow_id'] ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
Db::i()->insert( 'core_follow', $save );
|
||||
}
|
||||
$class = Follow::getClassToFollow( Request::i()->follow_app, Request::i()->follow_area );
|
||||
try
|
||||
{
|
||||
$useDataLayer = Member::loggedIn()->member_id and DataLayer::enabled( 'analytics_full' );
|
||||
$alreadyFollowing = $useDataLayer ? Db::i()->select( 'count(*)', 'core_follow', [
|
||||
'follow_app=? AND follow_area=? AND follow_rel_id=? AND follow_member_id=?', Request::i()->follow_app, Request::i()->follow_area, Request::i()->follow_id, Member::loggedIn()->member_id
|
||||
] )->first() : false;
|
||||
$thing = $class::load( Request::i()->follow_id );
|
||||
$thing->follow( $values['follow_type'], $values['follow_public'] );
|
||||
|
||||
/* Fire event */
|
||||
Event::fire( 'onFollow', Member::loggedIn(), [ $thing, !$values['follow_public'] ] );
|
||||
|
||||
/* Remove cached */
|
||||
Db::i()->delete( 'core_follow_count_cache', array( 'id=? AND class=?', Request::i()->follow_id, 'IPS\\' . Request::i()->follow_app . '\\' . IPS::mb_ucfirst( Request::i()->follow_area ) ) );
|
||||
|
||||
/* Also follow all nodes if following club */
|
||||
if( $class == "IPS\Member\Club" )
|
||||
{
|
||||
foreach ( $thing->nodes() as $node )
|
||||
if ( $useDataLayer and !$alreadyFollowing )
|
||||
{
|
||||
$itemClass = $node['node_class']::$contentItemClass;
|
||||
$followApp = $itemClass::$application;
|
||||
$followArea = mb_strtolower( mb_substr( $node['node_class'], mb_strrpos( $node['node_class'], '\\' ) + 1 ) );
|
||||
|
||||
$save = array(
|
||||
'follow_id' => md5( $followApp . ';' . $followArea . ';' . $node['node_id'] . ';' . Member::loggedIn()->member_id ),
|
||||
'follow_app' => $followApp,
|
||||
'follow_area' => $followArea,
|
||||
'follow_rel_id' => $node['node_id'],
|
||||
'follow_member_id' => Member::loggedIn()->member_id,
|
||||
'follow_is_anon' => !$values['follow_public'],
|
||||
'follow_added' => time(),
|
||||
'follow_notify_do' => ( isset( $values['follow_type'] ) AND $values['follow_type'] == 'none' ) ? 0 : 1,
|
||||
'follow_notify_meta' => '',
|
||||
'follow_notify_freq' => $values['follow_type'],
|
||||
'follow_notify_sent' => 0,
|
||||
'follow_visible' => 1,
|
||||
);
|
||||
Db::i()->insert( 'core_follow', $save, TRUE );
|
||||
DataLayer::i()->addEvent( 'follow', [
|
||||
'follow_app' => Request::i()->follow_app,
|
||||
'follow_area' => Request::i()->follow_area,
|
||||
'follow_id' => Request::i()->follow_id
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/* Send notification if following member */
|
||||
if( $class == "IPS\Member" )
|
||||
{
|
||||
if( $values['follow_public'] )
|
||||
{
|
||||
/* Give points */
|
||||
$receiver = Member::load( Request::i()->follow_id );
|
||||
$receiver->achievementAction( 'core', 'FollowMember', [
|
||||
'giver' => Member::loggedIn()
|
||||
] );
|
||||
|
||||
$notification = new Notification( Application::load( 'core' ), 'member_follow', Member::loggedIn(), array( Member::loggedIn() ) );
|
||||
$notification->recipients->attach( $thing );
|
||||
$notification->send();
|
||||
}
|
||||
}
|
||||
else if ( in_array( 'IPS\Node\Model', class_parents( $class ) ) )
|
||||
{
|
||||
Member::loggedIn()->achievementAction( 'core', 'FollowNode', $class::load( Request::i()->follow_id ) );
|
||||
}
|
||||
else if ( in_array( 'IPS\Content\Item', class_parents( $class ) ) )
|
||||
{
|
||||
$item = $class::load( Request::i()->follow_id );
|
||||
Member::loggedIn()->achievementAction( 'core', 'FollowContentItem', [
|
||||
'item' => $item,
|
||||
'author' => $item->author()
|
||||
] );
|
||||
}
|
||||
}
|
||||
catch( OutOfRangeException ){}
|
||||
|
||||
/* Boink */
|
||||
if ( Request::i()->isAjax() )
|
||||
@@ -704,63 +639,33 @@ class notifications extends Controller
|
||||
|
||||
try
|
||||
{
|
||||
$follow = Db::i()->select( '*', 'core_follow', array( 'follow_id=? AND follow_member_id=?', Request::i()->id, Member::loggedIn()->member_id ) )->first();
|
||||
$follow = Follow::load( Request::i()->id );
|
||||
if( $follow->member_id != Member::loggedIn()->member_id )
|
||||
{
|
||||
throw new OutOfRangeException;
|
||||
}
|
||||
}
|
||||
catch ( UnderflowException $e )
|
||||
catch ( OutOfRangeException $e )
|
||||
{
|
||||
Output::i()->error( 'cant_find_unfollow', '2C154/D', 404, '' );
|
||||
}
|
||||
|
||||
/* Fire event */
|
||||
if( $follow['follow_app'] == 'core' AND $follow['follow_area'] == 'member' )
|
||||
{
|
||||
$class = 'IPS\\Member';
|
||||
}
|
||||
elseif( $follow['follow_app'] == 'core' AND $follow['follow_area'] == 'club' )
|
||||
{
|
||||
$class = 'IPS\\Member\\Club';
|
||||
}
|
||||
elseif( $follow['follow_app'] == 'core' and $follow['follow_area'] == 'tag' )
|
||||
{
|
||||
$class = 'IPS\Content\Tag';
|
||||
}
|
||||
else
|
||||
{
|
||||
$class = 'IPS\\' . $follow['follow_app'] . '\\' . IPS::mb_ucfirst( $follow['follow_area'] );
|
||||
}
|
||||
try
|
||||
{
|
||||
$thing = $class::load( $follow['follow_rel_id'] );
|
||||
Event::fire( 'onUnfollow', Member::loggedIn(), [ $thing ] );
|
||||
}
|
||||
catch( OutOfRangeException ){}
|
||||
|
||||
Db::i()->delete( 'core_follow', array( 'follow_id=? AND follow_member_id=?', Request::i()->id, Member::loggedIn()->member_id ) );
|
||||
Db::i()->delete( 'core_follow_count_cache', array( 'id=? AND class=?', $follow['follow_rel_id'], 'IPS\\' . Request::i()->follow_app . '\\' . IPS::mb_ucfirst( Request::i()->follow_area ) ) );
|
||||
|
||||
/* If we are unfollowing a club, unfollow all of its nodes */
|
||||
if( Request::i()->follow_app == 'core' and Request::i()->follow_area == 'club' )
|
||||
{
|
||||
$class = 'IPS\\Member\Club';
|
||||
/* We call the unfollow on the object, not on the follow record.
|
||||
This is so that we can handle object-specific actions (e.g. unfollow a club unfollow club nodes)
|
||||
It's a bit circular, but we'll live. */
|
||||
if( $item = $follow->_item )
|
||||
{
|
||||
$item->unfollow( Member::loggedIn(), $follow->id );
|
||||
|
||||
/* Get thing */
|
||||
$thing = NULL;
|
||||
|
||||
try
|
||||
{
|
||||
$thing = $class::loadAndCheckPerms( (int) $follow['follow_rel_id'] );
|
||||
|
||||
foreach ( $thing->nodes() as $node )
|
||||
{
|
||||
$itemClass = $node['node_class']::$contentItemClass;
|
||||
$followApp = $itemClass::$application;
|
||||
$followArea = mb_strtolower( mb_substr( $node['node_class'], mb_strrpos( $node['node_class'], '\\' ) + 1 ) );
|
||||
|
||||
Db::i()->delete( 'core_follow', array( 'follow_id=? AND follow_member_id=?', md5( $followApp . ';' . $followArea . ';' . $node['node_id'] . ';' . Member::loggedIn()->member_id ), Member::loggedIn()->member_id ) );
|
||||
}
|
||||
}
|
||||
catch ( OutOfRangeException $e ) {}
|
||||
}
|
||||
if ( DataLayer::enabled( 'analytics_full' ) )
|
||||
{
|
||||
DataLayer::i()->addEvent( 'unfollow', [
|
||||
'follow_app' => $follow->app,
|
||||
'follow_area' => $follow->area,
|
||||
'follow_id' => $follow->rel_id,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
if ( Request::i()->isAjax() )
|
||||
{
|
||||
@@ -898,7 +803,7 @@ class notifications extends Controller
|
||||
protected function unfollowFromEmail() : void
|
||||
{
|
||||
/* Logged in? */
|
||||
if ( Member::loggedIn()->member_id )
|
||||
if ( Member::loggedIn()->member_id and ! isset( Request::i()->listunsubscribe ) )
|
||||
{
|
||||
/* Go to the normal page */
|
||||
Output::i()->redirect( Url::internal( "app=core&module=system&controller=notifications&do=follow&follow_app=". Request::i()->follow_app ."&follow_area=". Request::i()->follow_area ."&follow_id=" . Request::i()->follow_id ) );
|
||||
@@ -906,12 +811,12 @@ class notifications extends Controller
|
||||
|
||||
if ( ! empty( Request::i()->gkey ) )
|
||||
{
|
||||
list( $followKey, $memberKey ) = explode( '-', Request::i()->gkey );
|
||||
[ $followKey, $memberKey ] = explode( '-', Request::i()->gkey );
|
||||
/* Do we follow it? */
|
||||
try
|
||||
{
|
||||
$current = Db::i()->select( '*', 'core_follow', array( 'MD5( CONCAT_WS( \';\', follow_app, follow_area, follow_rel_id, follow_member_id, follow_added ) )=?', $followKey ) )->first();
|
||||
|
||||
|
||||
/* Already no subs? */
|
||||
if ( $current['follow_notify_freq'] === 'none' )
|
||||
{
|
||||
@@ -929,29 +834,8 @@ class notifications extends Controller
|
||||
{
|
||||
throw new Exception;
|
||||
}
|
||||
|
||||
/* Get class */
|
||||
if( Request::i()->follow_app == 'core' and Request::i()->follow_area == 'member' )
|
||||
{
|
||||
$class = 'IPS\\Member';
|
||||
}
|
||||
elseif( Request::i()->follow_app == 'core' and Request::i()->follow_area == 'club' )
|
||||
{
|
||||
$class = 'IPS\\Member\Club';
|
||||
}
|
||||
elseif( Request::i()->follow_app == 'core' and Request::i()->follow_area == 'tag' )
|
||||
{
|
||||
$class = 'IPS\Content\Tag';
|
||||
}
|
||||
else
|
||||
{
|
||||
$class = 'IPS\\' . Request::i()->follow_app . '\\' . IPS::mb_ucfirst( Request::i()->follow_area );
|
||||
|
||||
if( !IPS::classUsesTrait( $class, 'IPS\Content\Followable' ) )
|
||||
{
|
||||
throw new Exception;
|
||||
}
|
||||
}
|
||||
|
||||
$class = Follow::getClassToFollow( Request::i()->follow_app, Request::i()->follow_area );
|
||||
if ( !class_exists( $class ) )
|
||||
{
|
||||
throw new Exception;
|
||||
@@ -987,7 +871,18 @@ class notifications extends Controller
|
||||
|
||||
/* Grab a count */
|
||||
$count = Db::i()->select( 'COUNT(*)', 'core_follow', array( 'follow_member_id=? and follow_notify_freq != ?', $member->member_id, 'none' ) )->first();
|
||||
|
||||
|
||||
if ( isset( Request::i()->listunsubscribe ) and Request::i()->requestMethod() == 'POST' )
|
||||
{
|
||||
/* Unsubscribe them now as it's come from clicking the list-unsubscribe header in the email which
|
||||
contains List-Unsubscribe-Post:List-Unsubscribe=One-Click which sends via POST to prevent links being crawled and activated */
|
||||
$thing->follow( 'none', !$current['follow_is_anon'], $member );
|
||||
|
||||
/* Done */
|
||||
Output::i()->output = Member::loggedIn()->language()->addToStack('unsubscribed');
|
||||
return;
|
||||
}
|
||||
|
||||
$form = new Form( 'unfollowFromEmail', 'update_follow' );
|
||||
$form->class = 'ipsForm--vertical ipsForm--notifications-unfollow-email';
|
||||
|
||||
@@ -1014,20 +909,7 @@ class notifications extends Controller
|
||||
{
|
||||
if ( $values['guest_unfollow_choice'] == 'single' or isset( $values['guest_unfollow_single'] ) )
|
||||
{
|
||||
Db::i()->update( 'core_follow', array( 'follow_notify_freq' => 'none' ), array( 'follow_id=? AND follow_member_id=?', $current['follow_id'], $member->member_id ) );
|
||||
|
||||
/* Unfollow club areas */
|
||||
if ( $class == "IPS\Member\Club" )
|
||||
{
|
||||
foreach ( $thing->nodes() as $node )
|
||||
{
|
||||
$itemClass = $node['node_class']::$contentItemClass;
|
||||
$followApp = $itemClass::$application;
|
||||
$followArea = mb_strtolower( mb_substr( $node['node_class'], mb_strrpos( $node['node_class'], '\\' ) + 1 ) );
|
||||
|
||||
Db::i()->update( 'core_follow', array( 'follow_notify_freq' => 'none' ), array( 'follow_id=? AND follow_member_id=?', md5( $followApp . ';' . $followArea . ';' . $node['node_id'] . ';' . $member->member_id ), $member->member_id ) );
|
||||
}
|
||||
}
|
||||
$thing->follow( 'none', !$current['follow_is_anon'], $member );
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1196,9 +1078,15 @@ class notifications extends Controller
|
||||
Output::i()->json( array( 'error' => 'not_found' ), 404 );
|
||||
}
|
||||
|
||||
/* Is this our notification? */
|
||||
$data = json_decode( $notification['notification_data'], true );
|
||||
|
||||
if ( ! is_array( $data ) )
|
||||
{
|
||||
Output::i()->json( array( 'error' => 'invalid_data' ), 400 );
|
||||
}
|
||||
|
||||
$data['unreadCount'] = Member::loggedIn()->notification_cnt;
|
||||
|
||||
if( !isset( $data['member'] ) OR $data['member'] != Member::loggedIn()->member_id )
|
||||
{
|
||||
Output::i()->json( array( 'error' => 'no_permission' ), 403 );
|
||||
|
||||
@@ -141,7 +141,6 @@ class register extends Controller
|
||||
|
||||
/* Set Session Location */
|
||||
Session::i()->setLocation( Url::internal( 'app=core&module=system&controller=register', NULL, 'register' ), array(), 'loc_registering' );
|
||||
Output::i()->allowDefaultWidgets = FALSE;
|
||||
|
||||
/* What's the "log in" link? */
|
||||
$loginUrl = Url::internal( 'app=core&module=system&controller=login', NULL, 'login' );
|
||||
@@ -561,7 +560,7 @@ class register extends Controller
|
||||
$captcha = new Captcha;
|
||||
|
||||
/* If PBR request is from the last 5 minutes, don't ask for a captcha again */
|
||||
if( $postBeforeRegister !== NULL AND DateTime::ts( $postBeforeRegister['timestamp'] )->add( new \DateInterval('PT5M' ) )->getTimestamp() > time() )
|
||||
if( $postBeforeRegister !== NULL AND DateTime::ts( $postBeforeRegister['timestamp'] )->add( new DateInterval('PT5M' ) )->getTimestamp() > time() )
|
||||
{
|
||||
$captcha = '';
|
||||
}
|
||||
@@ -901,13 +900,19 @@ class register extends Controller
|
||||
/* Load record */
|
||||
try
|
||||
{
|
||||
$record = Db::i()->select( '*', 'core_validating', array( 'vid=? AND member_id=? AND ( new_reg=? or email_chg=? )', Request::i()->vid, Request::i()->mid, 1, 1 ) )->first();
|
||||
$record = Db::i()->select( '*', 'core_validating', array( 'vid=? AND member_id=? AND ( new_reg=? or email_chg=? or login_link=? )', Request::i()->vid, Request::i()->mid, 1, 1, 1 ) )->first();
|
||||
}
|
||||
catch ( UnderflowException $e )
|
||||
{
|
||||
$this->_performRedirect( NULL, FALSE, 'validate_no_record' );
|
||||
}
|
||||
|
||||
/* If this is a login link, make sure it's recent */
|
||||
if( $record['login_link'] AND $record['email_sent'] < ( new DateTime )->sub( new DateInterval( 'PT10M' ) )->getTimestamp() )
|
||||
{
|
||||
$this->_performRedirect( NULL, FALSE, 'validate_no_record' );
|
||||
}
|
||||
|
||||
/* Check security key is valid. */
|
||||
if( !Login::compareHashes( Encrypt::fromTag( $record['security_key'] )->decrypt(), Request::i()->security_key ) )
|
||||
{
|
||||
@@ -948,7 +953,16 @@ class register extends Controller
|
||||
{
|
||||
$member->emailValidationConfirmed( $record );
|
||||
}
|
||||
else
|
||||
elseif( $record['login_link'] )
|
||||
{
|
||||
$details = json_decode( $record['extra'], TRUE );
|
||||
$member = Member::load( $details['member'] );
|
||||
/* And then handler to link with */
|
||||
$handler = Handler::load( $details['handler'] );
|
||||
$handler->completeLink( $member, $details['details'] ?? null );
|
||||
Db::i()->delete( 'core_validating', array( 'member_id=? AND login_link=?', $member->member_id, 1 ) );
|
||||
}
|
||||
elseif( $record['email_chg'] )
|
||||
{
|
||||
$oldEmail = $member->email;
|
||||
$member->changeEmail( $record['new_email'] );
|
||||
@@ -957,6 +971,9 @@ class register extends Controller
|
||||
|
||||
Db::i()->delete( 'core_validating', array( 'member_id=?', $member->member_id ) );
|
||||
|
||||
/* Invalidate sessions except this one */
|
||||
$member->invalidateSessionsAndLogins( Session::i()->id );
|
||||
|
||||
/* Send a confirmation email */
|
||||
Email::buildFromTemplate( 'core', 'email_address_changed', array( $member, $oldEmail ), Email::TYPE_TRANSACTIONAL )->send( $oldEmail, array(), array(), NULL, NULL, array( 'Reply-To' => Settings::i()->email_in ) );
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ use DOMXpath;
|
||||
use Exception;
|
||||
use IPS\Api\OAuthClient;
|
||||
use IPS\Application;
|
||||
use IPS\core\DataLayer;
|
||||
use IPS\core\ShareLinks\Service;
|
||||
use IPS\DateTime;
|
||||
use IPS\Db;
|
||||
@@ -37,12 +38,14 @@ use IPS\Helpers\Form\Text;
|
||||
use IPS\Helpers\Form\YesNo;
|
||||
use IPS\Helpers\Wizard;
|
||||
use IPS\Http\Url;
|
||||
use IPS\Log;
|
||||
use IPS\Login;
|
||||
use IPS\Login\Exception as LoginException;
|
||||
use IPS\Login\Handler;
|
||||
use IPS\Login\Success;
|
||||
use IPS\Member;
|
||||
use IPS\Member\Device;
|
||||
use IPS\Member\Group;
|
||||
use IPS\Member\PrivacyAction;
|
||||
use IPS\Member\ProfileStep;
|
||||
use IPS\MFA\MFAHandler;
|
||||
@@ -61,6 +64,7 @@ use UnderflowException;
|
||||
use function count;
|
||||
use function defined;
|
||||
use function in_array;
|
||||
use function intval;
|
||||
use function is_array;
|
||||
use function is_numeric;
|
||||
use function is_string;
|
||||
@@ -194,10 +198,6 @@ class settings extends Controller
|
||||
'title' => 'email_address',
|
||||
'url' => Url::internal( "app=core&module=system&controller=settings&area=email", "front", "settings_email" )
|
||||
];
|
||||
if( Member::loggedIn()->members_bitoptions['email_messages_bounce'] )
|
||||
{
|
||||
$tabs['email']['warning'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
/* Can change password? */
|
||||
@@ -424,7 +424,6 @@ class settings extends Controller
|
||||
$form->class = 'ipsForm_collapseTablet';
|
||||
|
||||
$currentEmail = htmlspecialchars( Member::loggedIn()->email, ENT_DISALLOWED, 'UTF-8', FALSE );
|
||||
$form->addDummy( 'current_email', Member::loggedIn()->members_bitoptions["email_messages_bounce"] ? Theme::i()->getTemplate( 'global', 'core' )->memberEmailBlockedMessage( $currentEmail ) : $currentEmail );
|
||||
$form->add( new FormEmail(
|
||||
'new_email',
|
||||
'',
|
||||
@@ -989,6 +988,10 @@ class settings extends Controller
|
||||
$row = Db::i()->select( '*', PrivacyAction::$databaseTable, $where )->first();
|
||||
PrivacyAction::constructFromData( $row )->delete();
|
||||
Member::loggedIn()->logHistory( 'core', 'privacy', [ 'type' => 'account_deletion_cancelled' ] );
|
||||
if ( DataLayer::enabled() )
|
||||
{
|
||||
DataLayer::i()->addEvent( 'account_deletion_canceled', [] );
|
||||
}
|
||||
Output::i()->redirect( Url::internal( 'app=core&module=system&controller=settings&area=mfa', 'front'), 'account_deletion_cancelled' );
|
||||
}
|
||||
catch( UnderflowException $e )
|
||||
@@ -1010,6 +1013,12 @@ class settings extends Controller
|
||||
$request = PrivacyAction::getDeletionRequestByMemberAndKey( Member::loggedIn(), $key );
|
||||
|
||||
$request->confirmAccountDeletion();
|
||||
|
||||
/* Add data layer event */
|
||||
if ( DataLayer::enabled() )
|
||||
{
|
||||
DataLayer::i()->addEvent( 'account_deletion_requested', [] );
|
||||
}
|
||||
Output::i()->redirect( $this->url->setQueryString( 'area','mfa'), 'account_deletion_confirmed' );
|
||||
}
|
||||
catch ( OutOfRangeException $e )
|
||||
@@ -1350,6 +1359,30 @@ class settings extends Controller
|
||||
}
|
||||
catch( BadMethodCallException $e ){}
|
||||
}
|
||||
|
||||
/* Data Layer Event */
|
||||
if ( $oldName !== $values['new_username'] and DataLayer::enabled() )
|
||||
{
|
||||
try
|
||||
{
|
||||
$groupName = Group::load( Member::loggedIn()->member_group_id )->formattedName;
|
||||
}
|
||||
catch ( UnderflowException $e )
|
||||
{
|
||||
$groupName = null;
|
||||
}
|
||||
$properties = array(
|
||||
'profile_group' => $groupName,
|
||||
'profile_group_id' => Member::loggedIn()->member_group_id ?: null,
|
||||
'profile_id' => intval( Member::loggedIn()->member_id ) ?: null,
|
||||
'profile_name' => Member::loggedIn()->name ?: null,
|
||||
'updated_custom_fields' => false,
|
||||
'updated_profile_name' => DataLayer::i()->includeSSOForMember() ? [ 'old' => $oldName, 'new' => $values['new_username'] ] : true,
|
||||
'updated_profile_photo' => false,
|
||||
'updated_profile_coverphoto' => false,
|
||||
);
|
||||
DataLayer::i()->addEvent( 'social_update', $properties );
|
||||
}
|
||||
|
||||
/* Redirect */
|
||||
Output::i()->redirect( Url::internal( 'app=core&module=system&controller=settings&area=username', 'front', 'settings_username' ), 'username_changed' );
|
||||
|
||||
@@ -22,8 +22,10 @@ use IPS\Image;
|
||||
use IPS\Member;
|
||||
use IPS\Output;
|
||||
use IPS\Request;
|
||||
use IPS\Session;
|
||||
use IPS\Theme;
|
||||
use IPS\Settings;
|
||||
use IPS\Theme\Editor\Setting;
|
||||
use RuntimeException;
|
||||
use function defined;
|
||||
use function file_get_contents;
|
||||
@@ -81,7 +83,7 @@ class themeeditor extends Controller
|
||||
Output::i()->jsFiles = array_merge( Output::i()->jsFiles, Output::i()->js( 'codemirror/diff_match_patch.js', 'core', 'interface' ) );
|
||||
Output::i()->jsFiles = array_merge( Output::i()->jsFiles, Output::i()->js( 'codemirror/codemirror.js', 'core', 'interface' ) );
|
||||
Color::loadJS();
|
||||
Output::i()->jsFiles = array_merge( Output::i()->jsFiles, Output::i()->js( 'themeeditor/themeEditor.js', 'core', 'interface' ) );
|
||||
Output::i()->jsFiles = array_merge( Output::i()->jsFiles, Output::i()->js( 'themeEditor.js', 'global', 'components' ) );
|
||||
|
||||
/* Flag our theme as being edited */
|
||||
Theme::i()->editingStart();
|
||||
@@ -98,22 +100,37 @@ class themeeditor extends Controller
|
||||
*/
|
||||
public function save(): void
|
||||
{
|
||||
Session::i()->csrfCheck();
|
||||
|
||||
/* Has someone ended this session for us? */
|
||||
if ( ! Member::loggedIn()->isEditingTheme() )
|
||||
{
|
||||
Output::i()->error( 'theme_editor_session_expired', '2S164/A', 403, '' );
|
||||
}
|
||||
|
||||
/* Can we use the theme editor? */
|
||||
if( ! Member::loggedIn()->modPermission( 'can_use_theme_editor' ) )
|
||||
{
|
||||
Output::i()->error( 'no_permission', '2S164/D', 403, '' );
|
||||
}
|
||||
|
||||
/* Useful for debugging */
|
||||
$changed = Theme::i()->getCssVariables( Theme::CUSTOM_ONLY );
|
||||
$data = [];
|
||||
$unchanged = [];
|
||||
$imageKeys = iterator_to_array( Db::i()->select( 'setting_key', 'core_theme_editor_settings', [ 'setting_type=?', Setting::SETTING_IMAGE ] ) );
|
||||
|
||||
$rootCssVariables = Theme::master()->getCssVariables( Theme::FORCE_DEFAULT );
|
||||
$cssVariables = [];
|
||||
|
||||
foreach( $rootCssVariables as $key => $value )
|
||||
{
|
||||
/* Skip images, we will handle those later */
|
||||
if( in_array( $key, $imageKeys ) )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
$cssVariables[ $key ] = $value;
|
||||
if ( isset( Request::i()->$key ) )
|
||||
{
|
||||
@@ -139,11 +156,10 @@ class themeeditor extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
/* Handle image settings (except logos) */
|
||||
foreach( Db::i()->select( 'setting_key', 'core_theme_editor_settings', [
|
||||
[ 'setting_type=?', Theme\Editor\Setting::SETTING_IMAGE ],
|
||||
//[ Db::i()->in( 'setting_key', [ 'set__logo-light', 'set__logo-dark', 'set__mobile-logo-light', 'set__mobile-logo-dark' ], true ) ]
|
||||
] ) as $key )
|
||||
$imageFiles = [];
|
||||
|
||||
/* Handle image settings */
|
||||
foreach( $imageKeys as $key )
|
||||
{
|
||||
/* Did we delete the file? */
|
||||
if( isset( Request::i()->{ 'delete__' . $key } ) and empty( Request::i()->$key ) )
|
||||
@@ -152,7 +168,8 @@ class themeeditor extends Controller
|
||||
}
|
||||
elseif ( isset( $_FILES[ $key ] ) and $_FILES[ $key ]['tmp_name'] )
|
||||
{
|
||||
$imageData = $this->_handleImageUpload( $key, false );
|
||||
$imageData = $this->_handleImageUpload( $key, in_array( $key, [ 'set__logo-light', 'set__logo-dark', 'set__mobile-logo-light', 'set__mobile-logo-dark' ] ) );
|
||||
$imageFiles[ $key ] = $imageData;
|
||||
|
||||
/* If we specifically set the value to null, then we will use the default value */
|
||||
$newValue = isset( $imageData['fullUrl'] ) ? (string) $imageData['fullUrl'] : null;
|
||||
@@ -229,7 +246,16 @@ class themeeditor extends Controller
|
||||
}
|
||||
elseif ( isset( $_FILES[ 'set__' . $type ] ) and $_FILES[ 'set__' . $type ]['tmp_name'] )
|
||||
{
|
||||
$data['logo'][ $logoKey ] = $this->_handleImageUpload( 'set__' . $type );
|
||||
/* We have most likely already processed the logos with all the other images */
|
||||
if( isset( $imageFiles['set__' . $type ] ) )
|
||||
{
|
||||
$data['logo'][ $logoKey ] = $imageFiles['set__' . $type ];
|
||||
unset( $data['logo'][ $logoKey ]['fullUrl'] );
|
||||
}
|
||||
else
|
||||
{
|
||||
$data['logo'][ $logoKey ] = $this->_handleImageUpload( 'set__' . $type );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -277,6 +303,8 @@ class themeeditor extends Controller
|
||||
] )
|
||||
] );
|
||||
|
||||
Theme::i()->clearEditingSession();
|
||||
|
||||
Output::i()->redirect( Url::internal( "" ), ( \IPS\IN_DEV and ! Settings::i()->theme_designer_mode ) ? 'theme_editor_saved_indev' : 'theme_editor_saved' );
|
||||
}
|
||||
|
||||
@@ -330,11 +358,25 @@ class themeeditor extends Controller
|
||||
catch ( Exception $e ) { }
|
||||
}
|
||||
|
||||
$file = File::create( 'core_Theme', $uniqueFilename, $image, null, true, null, false );
|
||||
$file = File::create( 'core_Theme', $uniqueFilename, $image, 'set_resources_' . Theme::i()->_id, true, null, false );
|
||||
|
||||
$save = array(
|
||||
'resource_app' => 'core',
|
||||
'resource_location' => 'front',
|
||||
'resource_path' => '/custom/',
|
||||
'resource_set_id' => Theme::i()->_id,
|
||||
'resource_user_edited' => 1,
|
||||
'resource_name' => $file->originalFilename,
|
||||
'resource_added' => time(),
|
||||
'resource_filename' => (string) $file,
|
||||
'resource_data' => $file->contents()
|
||||
);
|
||||
|
||||
Db::i()->insert( 'core_theme_resources', $save );
|
||||
|
||||
return [
|
||||
'url' => (string) $file,
|
||||
'fullUrl' => (string) $file->url,
|
||||
'filename' => $file->originalFilename,
|
||||
'fullUrl' => '{resource="custom/' . $file->originalFilename . '" app="core" location="front"}',
|
||||
'setting_height' => ( isset( Request::i()->$logoHeightSetting ) and $isLogo ) ? Request::i()->$logoHeightSetting : 100,
|
||||
'img_width' => $width,
|
||||
'img_height' => $height,
|
||||
@@ -355,6 +397,14 @@ class themeeditor extends Controller
|
||||
*/
|
||||
public function close(): void
|
||||
{
|
||||
Session::i()->csrfCheck();
|
||||
|
||||
/* Can we use the theme editor? */
|
||||
if( ! Member::loggedIn()->modPermission( 'can_use_theme_editor' ) )
|
||||
{
|
||||
Output::i()->error( 'no_permission', '2S164/E', 403, '' );
|
||||
}
|
||||
|
||||
/* Flag our theme as no longer being edited */
|
||||
Theme::i()->editingFinish();
|
||||
|
||||
@@ -363,6 +413,33 @@ class themeeditor extends Controller
|
||||
Output::i()->redirect( $redirect );
|
||||
}
|
||||
|
||||
/**
|
||||
* Store modified theme settings
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function store() : void
|
||||
{
|
||||
Session::i()->csrfCheck();
|
||||
|
||||
/* Has someone ended this session for us? */
|
||||
if ( ! Member::loggedIn()->isEditingTheme() )
|
||||
{
|
||||
Output::i()->error( 'theme_editor_session_expired', '2S164/F', 403, '' );
|
||||
}
|
||||
|
||||
/* Can we use the theme editor? */
|
||||
if( ! Member::loggedIn()->modPermission( 'can_use_theme_editor' ) )
|
||||
{
|
||||
Output::i()->error( 'no_permission', '2S164/G', 403, '' );
|
||||
}
|
||||
|
||||
/* Update the session */
|
||||
Theme::i()->updateSessionVars( Request::i()->data );
|
||||
|
||||
Output::i()->sendOutput( '' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse Custom CSS so that we can properly handle any
|
||||
* resource tags, etc
|
||||
@@ -371,7 +448,25 @@ class themeeditor extends Controller
|
||||
*/
|
||||
protected function customCss() : void
|
||||
{
|
||||
Session::i()->csrfCheck();
|
||||
|
||||
/* Has someone ended this session for us? */
|
||||
if ( ! Member::loggedIn()->isEditingTheme() )
|
||||
{
|
||||
Output::i()->error( 'theme_editor_session_expired', '2S164/B', 403, '' );
|
||||
}
|
||||
|
||||
/* Can we use the theme editor? */
|
||||
if( ! Member::loggedIn()->modPermission( 'can_use_theme_editor' ) )
|
||||
{
|
||||
Output::i()->error( 'no_permission', '2S164/C', 403, '' );
|
||||
}
|
||||
|
||||
/* Store the latest CSS */
|
||||
Theme::i()->updateSessionCss( Request::i()->content );
|
||||
|
||||
$content = Theme::compileCustomCss( Request::i()->content );
|
||||
|
||||
Output::i()->json( [ 'content' => $content ] );
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@ namespace IPS\core\modules\front\system;
|
||||
|
||||
use DateInterval;
|
||||
use IPS\Content\Controller;
|
||||
use IPS\core\DataLayer;
|
||||
use IPS\core\Warnings\Reason;
|
||||
use IPS\core\Warnings\Warning;
|
||||
use IPS\DateTime;
|
||||
@@ -22,6 +23,7 @@ use IPS\Helpers\Table\Content;
|
||||
use IPS\Http\Url;
|
||||
use IPS\Member;
|
||||
use IPS\Output;
|
||||
use IPS\Platform\Bridge;
|
||||
use IPS\Request;
|
||||
use IPS\Session;
|
||||
use IPS\Settings;
|
||||
@@ -121,6 +123,13 @@ class warnings extends Controller
|
||||
catch ( OutOfRangeException $e )
|
||||
{
|
||||
Output::i()->error( 'node_error', '2C184/3', 404, '' );
|
||||
return;
|
||||
}
|
||||
|
||||
/* If the member viewing this is the member who was warned, log a data layer event */
|
||||
if ( Member::loggedIn()->member_id === $warning->member and DataLayer::enabled( "analytics_full" ) )
|
||||
{
|
||||
DataLayer::i()->addEvent( 'warning_viewed', $warning->getDataLayerProperties() );
|
||||
}
|
||||
|
||||
/* Show it */
|
||||
@@ -211,10 +220,17 @@ class warnings extends Controller
|
||||
}
|
||||
|
||||
/* Acknowledge it */
|
||||
$alreadyAcknowledged = $warning->acknowledged;
|
||||
$warning->acknowledged = TRUE;
|
||||
$warning->save();
|
||||
$member->members_bitoptions['unacknowledged_warnings'] = (bool) Db::i()->select( 'COUNT(*)', 'core_members_warn_logs', array( "wl_member=? AND wl_acknowledged=0", $member->member_id ), NULL, NULL, NULL, NULL, Db::SELECT_FROM_WRITE_SERVER )->first();
|
||||
$member->save();
|
||||
|
||||
/* Data Layer */
|
||||
if ( !$alreadyAcknowledged and DataLayer::enabled( "analytics_full" ) )
|
||||
{
|
||||
DataLayer::i()->addEvent( 'warning_acknowledged', $warning->getDataLayerProperties() );
|
||||
}
|
||||
|
||||
/* Redirect */
|
||||
if ( $redirectTo = Request::i()->referrer() )
|
||||
|
||||
@@ -18,6 +18,7 @@ use IPS\cms\widgets\Blocks;
|
||||
use IPS\Db;
|
||||
use IPS\Dispatcher\Controller;
|
||||
use IPS\Helpers\Form;
|
||||
use IPS\Log;
|
||||
use IPS\Member;
|
||||
use IPS\Output;
|
||||
use IPS\Patterns\ActiveRecordIterator;
|
||||
@@ -33,6 +34,7 @@ use function count;
|
||||
use function defined;
|
||||
use function explode;
|
||||
use function in_array;
|
||||
use function json_encode;
|
||||
|
||||
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
@@ -122,6 +124,11 @@ class widgets extends Controller
|
||||
continue;
|
||||
}
|
||||
|
||||
if( !$block->isExecutableByPage( $this->pageApp, $this->pageModule, $this->pageController ) )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Is this widget hidden from the block list? */
|
||||
if( !$block::$showInBlockList )
|
||||
{
|
||||
@@ -527,6 +534,14 @@ class widgets extends Controller
|
||||
$pageController = 'global';
|
||||
}
|
||||
|
||||
/* Stop Pages widgets from being stored here */
|
||||
if ( $pageApp === 'cms' )
|
||||
{
|
||||
/* Log it */
|
||||
Log::log( 'Pages widget wants be stored in core_widget_areas: ' . json_encode( $area->toArray( true, false ) ), 'page_builder' );
|
||||
return;
|
||||
}
|
||||
|
||||
/* If we have no content, clear this out entirely */
|
||||
if( !$area->hasWidgets() )
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user