Version 5.0.13

This commit is contained in:
Neo committed 2025-12-19 19:04:57 -08:00
1 parent b89858b2ff
commit 5efe744c5f
1407 files changed
+69690 -30177

No files matched your search

+156 -28
View File
@@ -12,6 +12,7 @@ namespace IPS\core;
/* To prevent PHP errors (extending class does not exist) revealing path */
use Exception;
use InvalidArgumentException;
use IPS\core\modules\admin\settings\dataLayer as DataLayerController;
use IPS\Data\Store;
@@ -23,6 +24,7 @@ use IPS\Member;
use IPS\Member\Group;
use IPS\Output;
use IPS\Patterns\Singleton;
use IPS\Platform\Bridge;
use IPS\Request;
use IPS\Session;
use IPS\Settings;
@@ -284,6 +286,16 @@ HTML,
'type' => 'string',
'page_level' => true,
],
'content_is_followed' => [
'event_keys' => ['content_*', 'filter_*', 'sort', 'file_download'],
'pii' => false,
'formatted_name' => 'content_is_followed',
'description' => 'Whether the user if following the content item.',
'short' => 'Content item followed',
'enabled' => true,
'type' => 'boolean',
'page_level' => true,
],
'comment_type' => [
'event_keys' => ['content_comment', 'content_react', 'content_quote'],
'pii' => false,
@@ -294,6 +306,17 @@ HTML,
'type' => 'string',
'page_level' => false,
],
'content_anonymous' => [
'event_keys' => ['content_*', 'filter_*', 'sort', 'file_download'],
'pii' => false,
'formatted_name' => 'content_anonymous',
'description' => 'Whether the relevant content (item/comment/review) was created anonymously. This attribute is only possible for certain content types, and when true the author ID and Name will be obfuscated',
'short' => 'Content is anonymous',
'enabled' => true,
'type' => 'boolean',
'page_level' => true,
'default' => false,
],
'content_container_id' => [
'event_keys' => ['content_*', 'filter_*', 'sort', 'file_download'],
'pii' => false,
@@ -334,6 +357,17 @@ HTML,
'type' => 'string',
'page_level' => true,
],
'message_recipient_count' => [
'event_keys' => ['content_*', 'filter_*', 'sort', 'file_download'],
'pii' => false,
'formatted_name' => 'message_recipient_count',
'description' => 'The number of recipients a message was sent to. If the content event or page context is not relevant to a message, this will be <code>null</code>',
'short' => 'PM Recipient Count',
'enabled' => true,
'type' => 'number | null',
'default' => null,
'page_level' => true,
],
'ips_key' => [
'event_keys' => ['*'],
'pii' => false,
@@ -700,7 +734,7 @@ HTML,
$piiProperties = array_merge( $piiProperties, $this->relatedPiiProperties( $propertyKey ) );
continue;
}
$value = $this->getSsoId( $value );
$value = $value ? $this->getSsoId( $value ) : $value;
}
$_values[$propertyKey] = $value;
@@ -728,31 +762,28 @@ HTML,
{
$output = array();
$piiExcludes = array();
$pii = Settings::i()->core_datalayer_include_pii;
foreach ( $this->propertiesConfiguration as $key => $property )
{
if ( $key === 'content_anonymous' and @$properties[$key] )
{
$piiExcludes = array_merge( $piiExcludes, $this->relatedPiiProperties( 'author_id' ) );
}
if ( !$property['enabled'] OR !isset( $properties[$key] ) )
{
continue;
}
if ( !$pii AND ( $property['pii'] ?? 0 ) )
if ( !Settings::i()->core_datalayer_include_pii AND ( $property['pii'] ?? 0 ) )
{
continue;
}
/* Check the member's pii preference. If we can verify the member's PII should NOT be exposed, don't allow it to be added */
if ( Settings::i()->core_datalayer_member_pii_choice AND
@$property['replace_with_sso'] AND
( $originalID = array_search( $properties[$key], $this->ssoIds ) )
)
if ( @$property['replace_with_sso'] and ( $originalID = array_search( $properties[$key], $this->ssoIds ) ) and !$this->includeSSOForMember( $originalID ) )
{
$member = Member::load( $originalID );
if ( $member->member_id AND $member->members_bitoptions['datalayer_pii_optout'] )
{
$piiExcludes = array_merge( $piiExcludes, $this->relatedPiiProperties( $key ) );
continue;
}
$piiExcludes = array_merge( $piiExcludes, $this->relatedPiiProperties( $key ) );
continue;
}
$output[$key] = $properties[$key];
@@ -832,13 +863,13 @@ HTML,
}
/* If this has PII, is PII allowed? */
if ( ( $properties[$key]['pii'] ?? 0 ) AND ! Settings::i()->core_datalayer_include_pii )
if ( @$properties[$key]['pii'] AND !Settings::i()->core_datalayer_include_pii )
{
return;
}
/* Replace with sso id if needed */
if ( $properties[$key]['replace_with_sso'] ?? 0 )
if ( @$properties[$key]['replace_with_sso'] )
{
$value = $this->getSsoId( $value );
}
@@ -942,7 +973,7 @@ HTML,
}
/* Logged In Member PII */
if ( Settings::i()->core_datalayer_include_pii AND ( !Settings::i()->core_datalayer_member_pii_choice OR !Member::loggedIn()->members_bitoptions['datalayer_pii_optout'] ) )
if ( $this->includeSSOForMember() )
{
if ( $properties['member_id']['enabled'] )
{
@@ -1002,6 +1033,11 @@ HTML,
$piiExcludes = array();
foreach ( $properties as $key => $property )
{
if ( $key === 'content_anonymous' and @$_context[$key] )
{
$piiExcludes = array_merge( $piiExcludes, $this->relatedPiiProperties( 'author_id' ) );
}
/* Make sure all are page level, enabled, and valid */
if ( !$property['enabled'] or !$property['page_level'] )
{
@@ -1010,20 +1046,11 @@ HTML,
$fkey = $property['formatted_name'];
/* If we can determine a member based on this property's PII, only include the related PII if we know that member hasn't opted out? */
if (
( $property['replace_with_sso'] ?? 0 ) AND
Settings::i()->core_datalayer_include_pii AND
Settings::i()->core_datalayer_member_pii_choice AND
isset( $_context[ $key ] ) AND
( $originalId = array_search( $_context[ $key ], $this->ssoIds ) )
if ( @$property['replace_with_sso'] AND isset( $_context[ $key ] ) AND ( $originalId = array_search( $_context[ $key ], $this->ssoIds ) AND !( $this->includeSSOForMember( $originalId ) ) )
)
{
$member = Member::load( $originalId );
if ( !( $member->member_id AND !$member->members_bitoptions['datalayer_pii_optout'] ) )
{
$piiExcludes = array_merge( $piiExcludes, $this->relatedPiiProperties( $key ) );
continue;
}
$piiExcludes = array_merge( $piiExcludes, $this->relatedPiiProperties( $key ) );
continue;
}
/* Add context properties set by the system */
@@ -1063,6 +1090,52 @@ HTML,
return str_replace( ["\u{2028}", "\u{2029}"], ['', ''], $out );
}
/**
* @param Member|int|null $member
* @return bool
*/
public function includeSSOForMember( Member|int|null $member=null ) :bool
{
/* Can we determine using settings? */
if ( !Settings::i()->core_datalayer_include_pii )
{
return false;
}
if ( Settings::i()->core_datalayer_include_pii and !Settings::i()->core_datalayer_member_pii_choice )
{
return true;
}
if ( is_null( $member ) )
{
$member = Member::loggedIn();
}
static $res = [];
$memberId = ( is_int( $member ) ? $member : $member->member_id ) ?: 0;
if ( array_key_exists( 'member_' . $memberId, $res ) )
{
return $res['member_'.$memberId];
}
if ( is_int( $member ) )
{
try
{
$member = Member::load( $memberId );
}
catch ( OutOfRangeException )
{
$res['member_'.$memberId] = false;
return false; // edge case, but if the member doesn't exist, let's not expose PII on the grounds that there technically shouldn't really be PII to expose
}
}
$res['member_' . $memberId] = !$member->members_bitoptions['datalayer_pii_optout'];
return $res['member_' . $memberId];
}
public static string $configCacheKey = 'dataLayerConfig';
@@ -1731,4 +1804,59 @@ HTML,
return array_merge( $properties, $additionalProperties );
}
/**
* Method to determine whether
*
* @param string[]|string $features Features that must be available for this site's package for the data layer to be enabled.
*
* @return bool
*/
public static function enabled( array|string $features=[] ) : bool
{
if ( !Settings::i()->core_datalayer_enabled or Output::i()->bypassDataLayer )
{
return false;
}
else if ( empty( $features ) )
{
return true;
}
/* Now let's determine if the data layer is enabled for the provided features. We'll cache the result */
static $results = [];
if ( is_string( $features ) )
{
$cacheKey = $features;
$features = [ $features];
}
else
{
$cacheKey = implode( ',', sort( $features ) );
}
if ( !array_key_exists( $cacheKey, $results ) )
{
/* assume it's enabled till we find one that's not */
$results[$cacheKey] = true;
foreach ( $features as $feature )
{
try
{
if ( !Bridge::i()->featureIsEnabled( $feature ) )
{
$results[$cacheKey] = false;
break;
}
}
catch ( Exception )
{
$results[$cacheKey] = false;
break;
}
}
}
return $results[$cacheKey];
}
}