Version 4.5.0

This commit is contained in:
Neo committed 2025-12-19 05:51:58 -08:00
1 parent 1a0c7fd3c2
commit f79dcf067a
3791 files changed
+248032 -76372

No files matched your search

+203 -62
View File
@@ -11,7 +11,7 @@
namespace IPS\core\ProfileFields;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
@@ -35,6 +35,8 @@ class _Field extends \IPS\CustomField
const STAFF = 2;
const CONTENT = 3;
const SEARCH = 4;
const EDIT = 5;
const PROFILE_COMPLETION = 6;
/**
* @brief [ActiveRecord] Database Table
@@ -100,6 +102,100 @@ class _Field extends \IPS\CustomField
*/
public static $uploadStorageExtension = 'core_ProfileField';
/**
* Display Value
*
* @param mixed $value The value
* @param bool $showSensitiveInformation If TRUE, potentially sensitive data (like passwords) will be displayed - otherwise will be blanked out
* @param int $location \IPS\core\ProfileFields\Field::PROFILE for profile, \IPS\core\ProfileFields\Field::REG for registration screen, \IPS\core\ProfileFields\Field::STAFF for ModCP/ACP or \IPS\core\ProfileFields\Field::CONTENT for content areas (post bit)
* @param \IPS\Member|NULL $member Member who completed the profile field
* @return string
*/
public function displayValue( $value=NULL, $showSensitiveInformation=FALSE, $location=0, \IPS\Member $member = null )
{
$formattedValue = parent::displayValue( $value, $showSensitiveInformation );
$member = $member ?: new \IPS\Member;
switch( $location )
{
case ( static::CONTENT ):
/* If there's no value, return nothing */
if( empty( $formattedValue ) )
{
return '';
}
/* If we are using the "default" formatting, set that up now */
$format = $this->format ?: "<strong>{\$title}:</strong> {\$processedContent}";
return $this->parseHtmlLogic( $format, $value, $formattedValue, $member );
break;
case ( static::PROFILE ):
case ( static::PROFILE_COMPLETION ):
if( $this->profile_format )
{
return $this->parseHtmlLogic( $this->profile_format, $value, $formattedValue, $member );
}
else
{
return $formattedValue;
}
break;
case ( static::STAFF ) :
default:
return ( $this->type == 'Editor' ) ? \IPS\Theme::i()->getTemplate( 'global', 'core', 'global' )->richText( $formattedValue ) : $formattedValue;
break;
}
}
/**
* Parse the HTML logic to format the field
*
* @param string $template Format (which may include HTML logic)
* @param string $rawValue Raw value
* @param string $value Formatted value
* @param \IPS\Member $member Member who completed the profile field
* @return string
*/
protected function parseHtmlLogic( $template, $rawValue, $value, \IPS\Member $member )
{
try
{
$functionName = 'profilefields_t_' . md5( $template );
if ( ! isset( \IPS\Data\Store::i()->$functionName ) )
{
/* We need the "raw" content because HTML is typically used to format the value */
$template = str_replace( '{$processedContent}', '{$processedContent|raw}', $template );
/* If this an editor, we can safely use the |raw value as it is clean, otherwise the escaped value will display which is not what the user expects */
if ( $this->type == 'Editor' )
{
$template = str_replace( '{$content}', '{$content|raw}', $template );
$template = \IPS\Theme::i()->getTemplate('global', 'core', 'global')->richText( $template );
}
/* $content is the raw content from the database, $processedContent is the parsed content ready for display */
\IPS\Data\Store::i()->$functionName = \IPS\Theme::compileTemplate( $template, $functionName, '$title, $content, $processedContent, $member, $member_id', true );
}
\IPS\Theme::runProcessFunction( \IPS\Data\Store::i()->$functionName, $functionName );
$themeFunction = 'IPS\\Theme\\'. $functionName;
$html = $themeFunction( \IPS\Member::loggedIn()->language()->addToStack( static::$langKey . '_' . $this->id ), $rawValue, $value, $member, $member->member_id );
return $html;
}
catch ( \ParseError $e )
{
@ob_end_clean();
\IPS\Log::log( $e, 'pfield_error' );
return $value;
}
}
/**
* Get field data
*
@@ -120,7 +216,8 @@ class _Field extends \IPS\CustomField
foreach ( \IPS\Db::i()->select( '*', 'core_pfields_data', NULL, 'pf_position' ) as $row )
{
$fields[ $row['pf_group_id'] ][ $row['pf_id'] ] = $row;
if ( $row['pf_format'] )
if ( $row['pf_topic_hide'] != 'hide' )
{
$display = TRUE;
}
@@ -149,11 +246,12 @@ class _Field extends \IPS\CustomField
/**
* Get Fields
*
* @param array $values Current values
* @param int $location \IPS\core\ProfileFields\Field::PROFILE for profile, \IPS\core\ProfileFields\Field::REG for registration screen or \IPS\core\ProfileFields\Field::STAFF for ModCP/ACP
* @param array $values Current values
* @param int $location \IPS\core\ProfileFields\Field::PROFILE for profile, \IPS\core\ProfileFields\Field::REG for registration screen, \IPS\core\ProfileFields\Field::STAFF for ModCP/ACP, \IPS\core\ProfileFields\Field::EDIT for member editing
* @param \IPS\Member|NULL $member IPS Member Object
* @return array
*/
public static function fields( $values=array(), $location=0 )
public static function fields( $values=array(), $location=0, ?\IPS\Member $member = null ): array
{
if( !$values )
{
@@ -167,10 +265,10 @@ class _Field extends \IPS\CustomField
foreach ( $fields as $row )
{
if (
( $row['pf_admin_only'] and $location !== static::STAFF ) or
( $location === static::PROFILE and !$row['pf_member_edit'] ) or
( $location === static::SEARCH and ( !$row['pf_search_type'] or $row['pf_member_hide'] ) ) or
( $location === static::REG and !$row['pf_show_on_reg'] )
( $location === static::PROFILE and $row['pf_member_hide'] == 'hide' ) or
( $location === static::SEARCH and ( !$row['pf_search_type'] or $row['pf_member_hide'] == 'hide' ) ) or
( $location === static::REG and !$row['pf_show_on_reg'] ) or
( ( $location === static::EDIT OR $location === static::PROFILE_COMPLETION ) and !$row['pf_member_edit'] )
)
{
continue;
@@ -180,13 +278,12 @@ class _Field extends \IPS\CustomField
{
$values['field_' . $row['pf_id'] ] = NULL;
}
/* @todo Remove reliance on \IPS\Request here and instead pass an \IPS\Member object to the method */
static::$editorOptions['autoSaveKey'] = md5( get_called_class() . '-' . $row['pf_id'] ) . ( isset( \IPS\Request::i()->id ) ? '-' . \IPS\Request::i()->id : '' );
if( $row['pf_type'] == 'Editor' )
static::$editorOptions['autoSaveKey'] = md5( \get_called_class() . '-' . $row['pf_id'] ) . ( $member ? '-' . $member->member_id : '' );
if( $row['pf_type'] == 'Editor' AND $member )
{
static::$editorOptions['attachIds'] = array( ( isset( \IPS\Request::i()->id ) ? \IPS\Request::i()->id : 0 ), $row['pf_id'] );
static::$editorOptions['attachIds'] = array( $member->member_id, $row['pf_id'] );
}
if ( $location === static::STAFF )
@@ -202,31 +299,31 @@ class _Field extends \IPS\CustomField
}
/**
* Load Record
* Load Record with Member
*
* @see \IPS\Db::build
* @param int|string $id ID
* @param string $idField The database column that the $id parameter pertains to (NULL will use static::$databaseColumnId)
* @param mixed $extraWhereClause Additional where clause(s) (see \IPS\Db::build for details)
* @param int|string $id ID
* @param string $idField The database column that the $id parameter pertains to (NULL will use static::$databaseColumnId)
* @param mixed $extraWhereClause Additional where clause(s) (see \IPS\Db::build for details)
* @param \IPS\Member|NULL $member IPS Member Object
* @return static
* @throws \InvalidArgumentException
* @throws \OutOfRangeException
*/
public static function load( $id, $idField=NULL, $extraWhereClause=NULL )
public static function loadWithMember( $id, $idField=NULL, $extraWhereClause=NULL, \IPS\Member $member = NULL ): self
{
$result = parent::load( $id, $idField, $extraWhereClause );
static::$editorOptions['autoSaveKey'] = md5( get_called_class() . '-' . $result->id ) . ( isset( \IPS\Request::i()->id ) ? '-' . \IPS\Request::i()->id : '' );
static::$editorOptions['autoSaveKey'] = md5( \get_called_class() . '-' . $result->id ) . ( $member ? '-' . $member->member_id : '' );
if( $result->type == 'Editor' )
if( $result->type == 'Editor' AND $member )
{
static::$editorOptions['attachIds'] = array( ( isset( \IPS\Request::i()->id ) ? \IPS\Request::i()->id : 0 ), $result->id );
static::$editorOptions['attachIds'] = array( $member->member_id, $result->id );
}
return $result;
}
/**
* Get Values
*
@@ -241,30 +338,41 @@ class _Field extends \IPS\CustomField
{
foreach ( $fields as $row )
{
if ( $row['pf_admin_only'] and $location !== static::STAFF )
/* Make sure we have permission to see the field */
switch( $location )
{
case ( static::CONTENT ):
if( $row['pf_topic_hide'] == 'hide' OR ( $row['pf_topic_hide'] == 'staff' AND !\IPS\Member::loggedIn()->isAdmin() AND !\IPS\Member::loggedIn()->modPermissions() ) )
{
continue 2;
}
break;
case ( static::PROFILE ):
if( $row['pf_member_hide'] == 'hide' OR ( $row['pf_member_hide'] == 'staff' AND !\IPS\Member::loggedIn()->isAdmin() AND !\IPS\Member::loggedIn()->modPermissions() ) OR ( $row['pf_member_hide'] == 'owner' AND !\IPS\Member::loggedIn()->isAdmin() AND !\IPS\Member::loggedIn()->modPermissions() AND \IPS\Member::loggedIn()->member_id != $values['member_id'] ) )
{
continue 2;
}
break;
}
/* Next to user content we perform a couple of extra checks */
if ( $location == static::CONTENT and ( !isset( $values[ 'field_' . $row['pf_id'] ] ) OR $row['pf_type'] == 'Poll' ) )
{
continue;
}
if ( $location == static::CONTENT and $row['pf_format'] != '' and isset( $values[ 'field_' . $row['pf_id'] ] ) and $row['pf_type'] != 'Poll' )
{
$row = static::constructFromData( $row );
if( $row->displayValue( $values[ 'field_' . $row->id ] ) !== "" )
{
$value = $row->type == 'Url' ? (string) $values[ 'field_' . $row->id ] : $row->displayValue( $values[ 'field_' . $row->id ] );
$return[ $row->group_id ][ $row->id ] = str_replace( array( '{title}', '{content}' ), array( \IPS\Member::loggedIn()->language()->addToStack( static::$langKey . '_' . $row->id ), $value ), $row->format );
}
}
else if ( $location !== static::CONTENT )
{
$return[ $groupId ][ static::$langKey . '_' . $row['pf_id'] ] = static::constructFromData( $row )->displayValue( $values[ 'field_' . $row['pf_id'] ] );
}
$return[ $groupId ][ static::$langKey . '_' . $row['pf_id'] ] = static::constructFromData( $row )->displayValue( $values[ 'field_' . $row['pf_id'] ], FALSE, $location, \IPS\Member::load( $values['member_id'] ) );
}
}
return $return;
}
/**
* @brief Field ID controlling formatting that we should show/hide depending upon field type selection
*/
protected $fieldFormattingId = 'pf_topic_hide';
/**
* [Node] Add/Edit Form
@@ -275,11 +383,13 @@ class _Field extends \IPS\CustomField
public function form( &$form )
{
parent::form( $form );
$form->add( new \IPS\Helpers\Form\TextArea( 'pf_format', $this->id ? $this->format : '', FALSE, array( 'placeholder' => "<strong>{title}:</strong> {content}" ), NULL, NULL, NULL, 'pf_format' ) );
$form->addheader( 'pfield_permissions' );
$form->add( new \IPS\Helpers\Form\YesNo( 'pf_admin_only', $this->id ? !$this->admin_only : TRUE, FALSE, array( 'togglesOn' => array( 'pf_show_on_reg', 'pf_member_edit', 'pf_member_hide' ) ) ) );
/* Remove the last two form elements, plus the header that was already added */
unset( $form->elements['']['pf_search_type'], $form->elements['']['pf_search_type_on_off'] );
array_pop( $form->elements[''] );
$form->addHeader( 'pfield_permissions' );
if ( \IPS\Login::registrationType() != 'full' )
{
/* Quick register is enabled, so do not allow required to be set */
@@ -295,7 +405,20 @@ class _Field extends \IPS\CustomField
}
$form->add( new \IPS\Helpers\Form\YesNo( 'pf_member_edit', $this->id ? $this->member_edit : TRUE, FALSE, array(), NULL, NULL, NULL, 'pf_member_edit' ) );
$form->add( new \IPS\Helpers\Form\YesNo( 'pf_member_hide', $this->id ? !$this->member_hide : TRUE, FALSE, array(), NULL, NULL, NULL, 'pf_member_hide' ) );
$form->add( new \IPS\Helpers\Form\Radio( 'pf_member_hide', $this->id ? $this->member_hide : 'all', TRUE, array( 'options' => array( 'hide' => 'custom_fields_hide', 'staff' => 'custom_fields_staff', 'owner' => 'custom_fields_staff_owner', 'all' => 'custom_fields_all' ), 'toggles' => array( 'staff' => array( 'pf_profile_format' ), 'all' => array( 'pf_profile_format' ), 'owner' => array( 'pf_profile_format' ) ) ), NULL, NULL, NULL, 'pf_member_hide' ) );
$form->add( new \IPS\Helpers\Form\Radio( 'pf_topic_hide', $this->id ? $this->topic_hide : 'hide', TRUE, array( 'options' => array( 'hide' => 'custom_fields_hide', 'staff' => 'custom_fields_staff', 'all' => 'custom_fields_all' ), 'toggles' => array( 'staff' => array( 'pf_format' ), 'all' => array( 'pf_format' ) ) ), NULL, NULL, NULL, 'pf_topic_hide' ) );
$form->addHeader( 'pfield_displayoptions' );
$form->add( new \IPS\Helpers\Form\Select( 'pf_search_type', $this->id ? $this->search_type : 'loose', FALSE, array( 'options' => array( 'exact' => 'pf_search_type_exact', 'loose' => 'pf_search_type_loose', '' => 'pf_search_type_none' ) ), NULL, NULL, NULL, 'pf_search_type' ) );
$form->add( new \IPS\Helpers\Form\Select( 'pf_search_type_on_off', $this->id ? $this->search_type : 'exact', FALSE, array( 'options' => array( 'exact' => 'pf_search_type_exact', '' => 'pf_search_type_none' ) ), NULL, NULL, NULL, 'pf_search_type_on_off' ) );
$formatOptions = array( 'default' => 'custom_field_format_default', 'custom' => 'custom_field_format_custom' );
$form->add( new \IPS\Helpers\Form\Radio( 'pf_profile_format', $this->profile_format ? 'custom' : 'default', TRUE, array( 'options' => $formatOptions, 'toggles' => array( 'custom' => array( 'pf_profile_format_custom' ) ) ), NULL, NULL, NULL, 'pf_profile_format' ) );
$form->add( new \IPS\Helpers\Form\TextArea( 'pf_profile_format_custom', $this->profile_format, FALSE, array( 'placeholder' => "<strong>{\$title}:</strong> {\$content}" ), NULL, NULL, NULL, 'pf_profile_format_custom' ) );
$form->add( new \IPS\Helpers\Form\Radio( 'pf_format', $this->format ? 'custom' : 'default', TRUE, array( 'options' => $formatOptions, 'toggles' => array( 'custom' => array( 'pf_format_custom' ) ) ), NULL, NULL, NULL, 'pf_format' ) );
$form->add( new \IPS\Helpers\Form\TextArea( 'pf_format_custom', $this->format, FALSE, array( 'placeholder' => "<strong>{\$title}:</strong> {\$content}" ), NULL, NULL, NULL, 'pf_format_custom' ) );
}
/**
@@ -306,16 +429,6 @@ class _Field extends \IPS\CustomField
*/
public function formatFormValues( $values )
{
if( isset( $values['pf_admin_only'] ) )
{
$values['pf_admin_only'] = $values['pf_admin_only'] ? FALSE : TRUE;
}
if( isset( $values['pf_member_hide'] ) )
{
$values['pf_member_hide'] = $values['pf_member_hide'] ? FALSE : TRUE;
}
if( array_key_exists( 'pf_search_type', $values ) )
{
$values['pf_search_type'] = (string) $values['pf_search_type'];
@@ -326,6 +439,34 @@ class _Field extends \IPS\CustomField
$values['pf_format'] = '';
}
if( isset( $values['pf_profile_format'] ) )
{
if( $values['pf_profile_format'] == 'default' )
{
$values['pf_profile_format'] = NULL;
}
else
{
$values['pf_profile_format'] = $values['pf_profile_format_custom'];
}
unset( $values['pf_profile_format_custom'] );
}
if( isset( $values['pf_format'] ) )
{
if( $values['pf_format'] == 'default' )
{
$values['pf_format'] = NULL;
}
else
{
$values['pf_format'] = $values['pf_format_custom'];
}
unset( $values['pf_format_custom'] );
}
return parent::formatFormValues( $values );
}
@@ -345,11 +486,15 @@ class _Field extends \IPS\CustomField
{
$return = parent::save();
}
unset( \IPS\Data\Store::i()->profileFields );
return $return;
}
/**
* @brief [ActiveRecord] Caches
* @note Defined cache keys will be cleared automatically as needed
*/
protected $caches = array( 'profileFields' );
/**
* [ActiveRecord] Delete Record
@@ -360,12 +505,6 @@ class _Field extends \IPS\CustomField
{
parent::delete();
\IPS\Db::i()->dropColumn( 'core_pfields_content', "field_{$this->id}" );
unset( \IPS\Data\Store::i()->profileFields );
if ( isset( static::$multitons[ $this->id ] ) )
{
unset( static::$multitons[ $this->id ] );
}
/* Do we need to do stuff with profile steps? */
\IPS\Member\ProfileStep::resync();
@@ -378,3 +517,5 @@ const REG = _Field::REG;
const STAFF = _Field::STAFF;
const CONTENT = _Field::CONTENT;
const SEARCH = _Field::SEARCH;
const EDIT = _Field::EDIT;
const PROFILE_COMPLETION = _Field::PROFILE_COMPLETION;