Improved profile fields code.

This commit is contained in:
flash 2023-07-20 19:36:43 +00:00
parent 2e49940260
commit f32624c61d
11 changed files with 528 additions and 221 deletions

View file

@ -4,6 +4,7 @@ namespace Misuzu;
use InvalidArgumentException; use InvalidArgumentException;
use Index\ByteFormat; use Index\ByteFormat;
use Misuzu\Parsers\Parser; use Misuzu\Parsers\Parser;
use Misuzu\Profile\ProfileFields;
use Misuzu\Users\User; use Misuzu\Users\User;
use Misuzu\Users\UserNotFoundException; use Misuzu\Users\UserNotFoundException;
use Misuzu\Users\UserSession; use Misuzu\Users\UserSession;
@ -34,6 +35,7 @@ if($profileUser->isDeleted()) {
$notices = []; $notices = [];
$profileFields = new ProfileFields($db);
$currentUser = User::getCurrent(); $currentUser = User::getCurrent();
$viewingAsGuest = $currentUser === null; $viewingAsGuest = $currentUser === null;
$currentUserId = $viewingAsGuest ? 0 : $currentUser->getId(); $currentUserId = $viewingAsGuest ? 0 : $currentUser->getId();
@ -72,19 +74,37 @@ if($isEditing) {
if(!CSRF::validateRequest()) { if(!CSRF::validateRequest()) {
$notices[] = 'Couldn\'t verify you, please refresh the page and retry.'; $notices[] = 'Couldn\'t verify you, please refresh the page and retry.';
} else { } else {
if(!empty($_POST['profile']) && is_array($_POST['profile'])) { $profileFieldsSubmit = filter_input(INPUT_POST, 'profile', FILTER_DEFAULT, FILTER_REQUIRE_ARRAY);
if(!empty($profileFieldsSubmit)) {
if(!$perms['edit_profile']) { if(!$perms['edit_profile']) {
$notices[] = 'You\'re not allowed to edit your profile'; $notices[] = 'You\'re not allowed to edit your profile';
} else { } else {
$profileFields = $profileUser->profileFields(false); $profileFieldInfos = $profileFields->getFields();
$profileFieldsSetInfos = [];
$profileFieldsSetValues = [];
$profileFieldsRemove = [];
foreach($profileFields as $profileField) { foreach($profileFieldInfos as $fieldInfo) {
if(isset($_POST['profile'][$profileField->field_key]) $fieldName = $fieldInfo->getName();
&& $profileField->field_value !== $_POST['profile'][$profileField->field_key] $fieldValue = empty($profileFieldsSubmit[$fieldName]) ? '' : (string)filter_var($profileFieldsSubmit[$fieldName]);
&& !$profileField->setFieldValue($_POST['profile'][$profileField->field_key])) {
$notices[] = sprintf('%s was formatted incorrectly!', $profileField->field_title); if(empty($profileFieldsSubmit[$fieldName])) {
$profileFieldsRemove[] = $fieldInfo;
continue;
} }
if($fieldInfo->checkValue($fieldValue)) {
$profileFieldsSetInfos[] = $fieldInfo;
$profileFieldsSetValues[] = $fieldValue;
} else
$notices[] = sprintf('%s isn\'t properly formatted.', $fieldInfo->getTitle());
} }
if(!empty($profileFieldsRemove))
$profileFields->removeFieldValues($profileUser, $profileFieldsRemove);
if(!empty($profileFieldsSetInfos))
$profileFields->setFieldValues($profileUser, $profileFieldsSetInfos, $profileFieldsSetValues);
} }
} }
@ -97,7 +117,7 @@ if($isEditing) {
$aboutValid = User::validateProfileAbout($aboutParse, $aboutText); $aboutValid = User::validateProfileAbout($aboutParse, $aboutText);
if($aboutValid === '') if($aboutValid === '')
$currentUser->setProfileAboutText($aboutText)->setProfileAboutParser($aboutParse); $profileUser->setProfileAboutText($aboutText)->setProfileAboutParser($aboutParse);
else switch($aboutValid) { else switch($aboutValid) {
case 'parser': case 'parser':
$notices[] = 'The selected about section parser is invalid.'; $notices[] = 'The selected about section parser is invalid.';
@ -121,7 +141,7 @@ if($isEditing) {
$sigValid = User::validateForumSignature($sigParse, $sigText); $sigValid = User::validateForumSignature($sigParse, $sigText);
if($sigValid === '') if($sigValid === '')
$currentUser->setForumSignatureText($sigText)->setForumSignatureParser($sigParse); $profileUser->setForumSignatureText($sigText)->setForumSignatureParser($sigParse);
else switch($sigValid) { else switch($sigValid) {
case 'parser': case 'parser':
$notices[] = 'The selected forum signature parser is invalid.'; $notices[] = 'The selected forum signature parser is invalid.';
@ -146,7 +166,7 @@ if($isEditing) {
$birthValid = User::validateBirthdate($birthYear, $birthMonth, $birthDay); $birthValid = User::validateBirthdate($birthYear, $birthMonth, $birthDay);
if($birthValid === '') if($birthValid === '')
$currentUser->setBirthdate($birthYear, $birthMonth, $birthDay); $profileUser->setBirthdate($birthYear, $birthMonth, $birthDay);
else switch($birthValid) { else switch($birthValid) {
case 'year': case 'year':
$notices[] = 'The given birth year is invalid.'; $notices[] = 'The given birth year is invalid.';
@ -352,21 +372,64 @@ switch($profileMode) {
$template = 'profile.index'; $template = 'profile.index';
$warnings = $profileUser->getProfileWarnings($currentUser); $warnings = $profileUser->getProfileWarnings($currentUser);
$activeCategoryStats = $viewingAsGuest ? null : forum_get_user_most_active_category_info($profileUser->getId());
$activeCategoryInfo = empty($activeCategoryStats->forum_id) ? null : forum_get($activeCategoryStats->forum_id);
$activeTopicStats = $viewingAsGuest ? null : forum_get_user_most_active_topic_info($profileUser->getId());
$activeTopicInfo = empty($activeTopicStats->topic_id) ? null : forum_topic_get($activeTopicStats->topic_id);
Template::set([ Template::set([
'profile_warnings' => $warnings, 'profile_warnings' => $warnings,
'profile_warnings_view_private' => $canManageWarnings, 'profile_warnings_view_private' => $canManageWarnings,
'profile_warnings_can_manage' => $canManageWarnings, 'profile_warnings_can_manage' => $canManageWarnings,
'profile_active_category_stats' => $activeCategoryStats,
'profile_active_category_info' => $activeCategoryInfo,
'profile_active_topic_stats' => $activeTopicStats,
'profile_active_topic_info' => $activeTopicInfo,
]); ]);
if(!$viewingAsGuest) {
$activeCategoryStats = forum_get_user_most_active_category_info($profileUser->getId());
$activeCategoryInfo = empty($activeCategoryStats->forum_id) ? null : forum_get($activeCategoryStats->forum_id);
$activeTopicStats = forum_get_user_most_active_topic_info($profileUser->getId());
$activeTopicInfo = empty($activeTopicStats->topic_id) ? null : forum_topic_get($activeTopicStats->topic_id);
$profileFieldValues = $profileFields->getFieldValues($profileUser);
$profileFieldInfos = $profileFieldInfos ?? $profileFields->getFields(fieldValueInfos: $isEditing ? null : $profileFieldValues);
$profileFieldFormats = $profileFields->getFieldFormats(fieldValueInfos: $profileFieldValues);
$profileFieldRawValues = [];
$profileFieldLinkValues = [];
$profileFieldDisplayValues = [];
// using field infos as the basis for now, uses the correct ordering
foreach($profileFieldInfos as $fieldInfo) {
foreach($profileFieldValues as $fieldValueTest)
if($fieldValueTest->getFieldId() === $fieldInfo->getId()) {
$fieldValue = $fieldValueTest;
break;
}
$fieldName = $fieldInfo->getName();
if(isset($fieldValue)) {
foreach($profileFieldFormats as $fieldFormatTest)
if($fieldFormatTest->getId() === $fieldValue->getFormatId()) {
$fieldFormat = $fieldFormatTest;
break;
}
$profileFieldRawValues[$fieldName] = $fieldValue->getValue();
$profileFieldDisplayValues[$fieldName] = $fieldFormat->formatDisplay($fieldValue->getValue());
if($fieldFormat->hasLinkFormat())
$profileFieldLinkValues[$fieldName] = $fieldFormat->formatLink($fieldValue->getValue());
}
unset($fieldValue);
}
Template::set([
'profile_active_category_stats' => $activeCategoryStats,
'profile_active_category_info' => $activeCategoryInfo,
'profile_active_topic_stats' => $activeTopicStats,
'profile_active_topic_info' => $activeTopicInfo,
'profile_fields_infos' => $profileFieldInfos,
'profile_fields_raw_values' => $profileFieldRawValues,
'profile_fields_display_values' => $profileFieldDisplayValues,
'profile_fields_link_values' => $profileFieldLinkValues,
]);
}
break; break;
} }
@ -380,5 +443,6 @@ if(!empty($template)) {
'profile_can_edit' => $canEdit, 'profile_can_edit' => $canEdit,
'profile_is_editing' => $isEditing, 'profile_is_editing' => $isEditing,
'profile_is_banned' => $isBanned, 'profile_is_banned' => $isBanned,
'profile_is_guest' => $viewingAsGuest,
]); ]);
} }

View file

@ -122,7 +122,7 @@ class Changelog {
$query .= (++$args > 1 ? ' AND' : ' WHERE'); $query .= (++$args > 1 ? ' AND' : ' WHERE');
$query .= sprintf( $query .= sprintf(
' change_id IN (SELECT change_id FROM msz_changelog_change_tags WHERE tag_id IN (%s))', ' change_id IN (SELECT change_id FROM msz_changelog_change_tags WHERE tag_id IN (%s))',
implode(', ', array_fill(0, count($tags), '?')) msz_where_in_list($tags)
); );
} }
$stmt = $this->cache->get($query); $stmt = $this->cache->get($query);
@ -177,7 +177,7 @@ class Changelog {
$query .= (++$args > 1 ? ' AND' : ' WHERE'); $query .= (++$args > 1 ? ' AND' : ' WHERE');
$query .= sprintf( $query .= sprintf(
' change_id IN (SELECT change_id FROM msz_changelog_change_tags WHERE tag_id IN (%s))', ' change_id IN (SELECT change_id FROM msz_changelog_change_tags WHERE tag_id IN (%s))',
implode(', ', array_fill(0, count($tags), '?')) msz_where_in_list($tags)
); );
} }
$query .= ' GROUP BY change_created, change_id ORDER BY change_created DESC, change_id DESC'; $query .= ' GROUP BY change_created, change_id ORDER BY change_created DESC, change_id DESC';

View file

@ -23,10 +23,6 @@ class DbConfig implements IConfig {
return preg_match('#^([a-z][a-zA-Z0-9._]+)$#', $name) === 1; return preg_match('#^([a-z][a-zA-Z0-9._]+)$#', $name) === 1;
} }
private static function whereInList(int $count): string {
return implode(', ', array_fill(0, $count, '?'));
}
public function reset(): void { public function reset(): void {
$this->values = []; $this->values = [];
} }
@ -61,7 +57,7 @@ class DbConfig implements IConfig {
$stmt = $this->cache->get(sprintf( $stmt = $this->cache->get(sprintf(
'SELECT COUNT(*) FROM msz_config WHERE config_name IN (%s)', 'SELECT COUNT(*) FROM msz_config WHERE config_name IN (%s)',
self::whereInList($nameCount) msz_where_in_list($nameCount)
)); ));
for($i = 0; $i < $nameCount; ++$i) for($i = 0; $i < $nameCount; ++$i)
$stmt->addParameter($i + 1, $names[$i]); $stmt->addParameter($i + 1, $names[$i]);
@ -87,7 +83,7 @@ class DbConfig implements IConfig {
$nameCount = count($names); $nameCount = count($names);
$stmt = $this->cache->get(sprintf( $stmt = $this->cache->get(sprintf(
'DELETE FROM msz_config WHERE config_name IN (%s)', 'DELETE FROM msz_config WHERE config_name IN (%s)',
self::whereInList($nameCount) msz_where_in_list($nameCount)
)); ));
for($i = 0; $i < $nameCount; ++$i) for($i = 0; $i < $nameCount; ++$i)
@ -147,7 +143,7 @@ class DbConfig implements IConfig {
$stmt = $this->cache->get(sprintf( $stmt = $this->cache->get(sprintf(
'SELECT config_name, config_value FROM msz_config WHERE config_name IN (%s)', 'SELECT config_name, config_value FROM msz_config WHERE config_name IN (%s)',
self::whereInList($nameCount) msz_where_in_list($nameCount)
)); ));
for($i = 0; $i < $nameCount; ++$i) for($i = 0; $i < $nameCount; ++$i)
$stmt->addParameter($i + 1, $names[$i]); $stmt->addParameter($i + 1, $names[$i]);
@ -271,7 +267,7 @@ class DbConfig implements IConfig {
$stmt = $this->cache->get(sprintf( $stmt = $this->cache->get(sprintf(
'REPLACE INTO msz_config (config_name, config_value) VALUES %s', 'REPLACE INTO msz_config (config_name, config_value) VALUES %s',
implode(', ', array_fill(0, $valueCount, '(?, ?)')) msz_where_in_list($valueCount, '(?, ?)')
)); ));
$args = 0; $args = 0;

View file

@ -0,0 +1,56 @@
<?php
namespace Misuzu\Profile;
use Index\Data\IDbResult;
class ProfileFieldFormatInfo {
private string $id;
private string $fieldId;
private ?string $regex;
private ?string $linkFormat;
private string $displayFormat;
public function __construct(IDbResult $result) {
$this->id = (string)$result->getInteger(0);
$this->fieldId = (string)$result->getInteger(1);
$this->regex = $result->isNull(2) ? null : $result->getString(2);
$this->linkFormat = $result->isNull(3) ? null : $result->getString(3);
$this->displayFormat = $result->getString(4);
}
public function getId(): string {
return $this->id;
}
public function getFieldId(): string {
return $this->fieldId;
}
public function hasRegEx(): bool {
return $this->regex !== null;
}
public function getRegEx(): ?string {
return $this->regex;
}
public function hasLinkFormat(): bool {
return $this->linkFormat !== null;
}
public function getLinkFormat(): ?string {
return $this->linkFormat;
}
public function formatLink(string $value): ?string {
return $this->linkFormat === null ? null : sprintf($this->linkFormat, $value);
}
public function getDisplayFormat(): string {
return $this->displayFormat;
}
public function formatDisplay(string $value): string {
return sprintf($this->displayFormat, $value);
}
}

View file

@ -0,0 +1,48 @@
<?php
namespace Misuzu\Profile;
use Index\Data\IDbResult;
class ProfileFieldInfo {
private string $id;
private int $order;
private string $name;
private string $title;
private string $regex;
public function __construct(IDbResult $result) {
$this->id = (string)$result->getInteger(0);
$this->order = $result->getInteger(1);
$this->name = $result->getString(2);
$this->title = $result->getString(3);
$this->regex = $result->getString(4);
}
public function getId(): string {
return $this->id;
}
public function getOrder(): int {
return $this->order;
}
public function getName(): string {
return $this->name;
}
public function getTitle(): string {
return $this->title;
}
public function getRegEx(): string {
return $this->regex;
}
public function checkValue(string $value): bool {
return preg_match($this->regex, $value) === 1;
}
public function matchValue(string $value): string|false {
return preg_match($this->regex, $value, $matches) === 1 ? $matches[1] : false;
}
}

View file

@ -0,0 +1,34 @@
<?php
namespace Misuzu\Profile;
use Index\Data\IDbResult;
class ProfileFieldValueInfo {
private string $fieldId;
private string $userId;
private string $formatId;
private string $value;
public function __construct(IDbResult $result) {
$this->fieldId = (string)$result->getInteger(0);
$this->userId = (string)$result->getInteger(1);
$this->formatId = (string)$result->getInteger(2);
$this->value = $result->getString(3);
}
public function getFieldId(): string {
return $this->fieldId;
}
public function getUserId(): string {
return $this->userId;
}
public function getFormatId(): string {
return $this->formatId;
}
public function getValue(): string {
return $this->value;
}
}

View file

@ -0,0 +1,273 @@
<?php
namespace Misuzu\Profile;
use InvalidArgumentException;
use RuntimeException;
use Index\Data\IDbConnection;
use Index\Data\IDbResult;
use Misuzu\DbStatementCache;
use Misuzu\Users\User;
class ProfileFields {
private IDbConnection $dbConn;
private DbStatementCache $cache;
public function __construct(IDbConnection $dbConn) {
$this->dbConn = $dbConn;
$this->cache = new DbStatementCache($dbConn);
}
public function getFields(?array $fieldValueInfos = null): array {
$hasFieldValueInfos = $fieldValueInfos !== null;
if($hasFieldValueInfos && empty($fieldValueInfos))
return [];
$query = 'SELECT field_id, field_order, field_key, field_title, field_regex FROM msz_profile_fields';
if($hasFieldValueInfos)
$query .= sprintf(' WHERE field_id IN (%s)', msz_where_in_list($fieldValueInfos));
$query .= ' ORDER BY field_order ASC';
$stmt = $this->cache->get($query);
$args = 0;
if($hasFieldValueInfos)
foreach($fieldValueInfos as $fieldValueInfo) {
if(!($fieldValueInfo instanceof ProfileFieldValueInfo))
throw new InvalidArgumentException('All values in $fieldValueInfos must be of ProfileFieldValueInfo type.');
$stmt->addParameter(++$args, $fieldValueInfo->getFieldId());
}
$stmt->execute();
$result = $stmt->getResult();
$fields = [];
while($result->next())
$fields[] = new ProfileFieldInfo($result);
return $fields;
}
public function getField(string $fieldId): ProfileFieldInfo {
$stmt = $this->cache->get('SELECT field_id, field_order, field_key, field_title, field_regex FROM msz_profile_fields WHERE field_id = ?');
$stmt->addParameter(1, $fieldId);
$stmt->execute();
$result = $stmt->getResult();
if(!$result->next())
throw new RuntimeException('No field found with the provided field id.');
return new ProfileFieldInfo($result);
}
public function getFieldFormats(
?array $fieldInfos = null,
?array $fieldValueInfos = null
): array {
$hasFieldInfos = $fieldInfos !== null;
$hasFieldValueInfos = $fieldValueInfos !== null;
if($hasFieldInfos && empty($fieldInfos))
return [];
if($hasFieldValueInfos && empty($fieldValueInfos))
return [];
$args = 0;
$query = 'SELECT format_id, field_id, format_regex, format_link, format_display FROM msz_profile_fields_formats';
if($hasFieldInfos) {
++$args;
$query .= sprintf(' WHERE field_id IN (%s)', msz_where_in_list($fieldInfos));
}
if($hasFieldValueInfos)
$query .= sprintf(' %s format_id IN (%s)',
(++$args > 1 ? 'AND' : 'WHERE'),
msz_where_in_list($fieldValueInfos)
);
$stmt = $this->cache->get($query);
$args = 0;
if($hasFieldInfos)
foreach($fieldInfos as $fieldInfo) {
if(!($fieldInfo instanceof ProfileFieldInfo))
throw new InvalidArgumentException('All values in $fieldInfos must be of ProfileFieldInfo type.');
$stmt->addParameter(++$args, $fieldInfo->getId());
}
if($hasFieldValueInfos)
foreach($fieldValueInfos as $fieldValueInfo) {
if(!($fieldValueInfo instanceof ProfileFieldValueInfo))
throw new InvalidArgumentException('All values in $fieldValueInfos must be of ProfileFieldValueInfo type.');
$stmt->addParameter(++$args, $fieldValueInfo->getFormatId());
}
$stmt->execute();
$result = $stmt->getResult();
$formats = [];
while($result->next())
$formats[] = new ProfileFieldFormatInfo($result);
return $formats;
}
public function getFieldFormat(string $formatId): ProfileFieldFormatInfo {
$stmt = $this->cache->get('SELECT format_id, field_id, format_regex, format_link, format_display FROM msz_profile_fields_formats WHERE format_id = ?');
$stmt->addParameter(1, $formatId);
$stmt->execute();
$result = $stmt->getResult();
if(!$result->next())
throw new RuntimeException('No format found with the provided format id.');
return new ProfileFieldFormatInfo($result);
}
public function selectFieldFormat(
ProfileFieldInfo|string $fieldInfo,
string $value
): ProfileFieldFormatInfo {
if($fieldInfo instanceof ProfileFieldInfo)
$fieldInfo = $fieldInfo->getId();
$stmt = $this->cache->get('SELECT format_id, field_id, format_regex, format_link, format_display FROM msz_profile_fields_formats WHERE field_id = ? AND (format_regex IS NULL OR ? REGEXP format_regex) ORDER BY format_regex IS NULL ASC');
$stmt->addParameter(1, $fieldInfo);
$stmt->addParameter(2, $value);
$stmt->execute();
$result = $stmt->getResult();
if(!$result->next())
throw new RuntimeException('Could not determine an appropriate format for this field (missing default formatting)');
return new ProfileFieldFormatInfo($result);
}
public function getFieldValues(User|string $userInfo): array {
if($userInfo instanceof User)
$userInfo = $userInfo->getId();
// i don't really want to bother with the join for the ordering so i'll just do that somewhere in PHP for now
// will probably add the ability for people to order them in whatever way they want, as well as visibility controls
$stmt = $this->cache->get('SELECT field_id, user_id, format_id, field_value FROM msz_profile_fields_values WHERE user_id = ?');
$stmt->addParameter(1, $userInfo);
$stmt->execute();
$result = $stmt->getResult();
$values = [];
while($result->next())
$values[] = new ProfileFieldValueInfo($result);
return $values;
}
public function getFieldValue(
ProfileFieldInfo|string $fieldInfo,
User|string $userInfo
): ProfileFieldValueInfo {
if($fieldInfo instanceof ProfileFieldInfo)
$fieldInfo = $fieldInfo->getId();
if($userInfo instanceof User)
$userInfo = $userInfo->getId();
$stmt = $this->cache->get('SELECT field_id, user_id, format_id, field_value FROM msz_profile_fields_values WHERE field_id = ? AND user_id = ?');
$stmt->addParameter(1, $fieldInfo);
$stmt->addParameter(2, $userInfo);
$stmt->execute();
$result = $stmt->getResult();
if(!$result->next())
throw new RuntimeException('No value for this field and user combination found.');
return new ProfileFieldValueInfo($result);
}
public function setFieldValues(
User|string $userInfo,
ProfileFieldInfo|string|array $fieldInfos,
string|array $values
): void {
if(empty($fieldInfos))
return;
if(!is_array($fieldInfos)) {
if(is_array($values))
throw new InvalidArgumentException('If $fieldInfos is not an array, $values may not be either');
$fieldInfos = [$fieldInfos];
$values = [$values];
} elseif(!is_array($values))
throw new InvalidArgumentException('If $fieldInfos is an array, $values must be as well.');
$fieldsCount = count($fieldInfos);
if($fieldsCount !== count($values))
throw new InvalidArgumentException('$fieldsInfos and $values have the same amount of values and be in the same order.');
if($userInfo instanceof User)
$userInfo = $userInfo->getId();
$rows = [];
foreach($fieldInfos as $key => $fieldInfo) {
if(is_string($fieldInfo))
$fieldInfo = $this->getField($fieldId);
elseif(!($fieldInfo instanceof ProfileFieldInfo))
throw new InvalidArgumentException('Entries of $fieldInfos must either be field IDs or instances of ProfileFieldInfo.');
$value = $fieldInfo->matchValue($values[$key]);
if($value === false)
throw new InvalidArgumentException('One of the values in $values is not correct formatted.');
$rows[] = [
$fieldInfo->getId(),
$this->selectFieldFormat($fieldInfo, $value)->getId(),
$value,
];
}
$args = 0;
$stmt = $this->cache->get(
'REPLACE INTO msz_profile_fields_values (field_id, user_id, format_id, field_value) VALUES '
. msz_where_in_list($rows, '(?, ?, ?, ?)')
);
foreach($rows as $row) {
$stmt->addParameter(++$args, $row[0]);
$stmt->addParameter(++$args, $userInfo);
$stmt->addParameter(++$args, $row[1]);
$stmt->addParameter(++$args, $row[2]);
}
$stmt->execute();
}
public function removeFieldValues(
User|string $userInfo,
ProfileFieldInfo|string|array $fieldInfos
): void {
if(empty($fieldInfos))
return;
if($userInfo instanceof User)
$userInfo = $userInfo->getId();
if(!is_array($fieldInfos))
$fieldInfos = [$fieldInfos];
foreach($fieldInfos as $key => $value) {
if($value instanceof ProfileFieldInfo)
$fieldInfos[$key] = $value->getId();
elseif(is_string($value))
throw new InvalidArgumentException('$fieldInfos array may only contain string IDs or instances of ProfileFieldInfo');
}
$args = 0;
$stmt = $this->cache->get(sprintf(
'DELETE FROM msz_profile_fields_values WHERE user_id = ? AND field_id IN (%s)',
msz_where_in_list($fieldInfos)
));
$stmt->addParameter(++$args, $userInfo);
foreach($fieldInfos as $value)
$stmt->addParameter(++$args, $value);
$stmt->execute();
}
}

View file

@ -1,164 +0,0 @@
<?php
namespace Misuzu\Users;
use Misuzu\DB;
class ProfileField {
// Database fields
public $field_id;
public $user_id;
public $field_regex;
public $field_value;
public $field_order;
public $field_key;
public $field_title;
public $format_id;
public $format_regex;
public $format_link;
public $format_display;
public static function createField(
string $fieldKey,
string $fieldTitle,
string $fieldRegex,
int $fieldOrder
): ?ProfileField {
$createField = DB::prepare('
INSERT INTO `msz_profile_fields` (
`field_order`, `field_key`, `field_title`, `field_regex`
) VALUES (:order, :key, :title, :regex)
')->bind('order', $fieldOrder)->bind('key', $fieldKey)
->bind('title', $fieldTitle)->bind('regex', $fieldRegex)
->executeGetId();
if($createField < 1)
return null;
return static::get($createField);
}
public static function createFormat(
int $fieldId,
string $formatDisplay = '%s',
?string $formatLink = null,
?string $formatRegex = null
): ?ProfileField {
$createFormat = DB::prepare('
INSERT INTO `msz_profile_fields_formats` (
`field_id`, `format_regex`, `format_link`, `format_display`
) VALUES (:field, :regex, :link, :display)
')->bind('field', $fieldId) ->bind('regex', $formatRegex)
->bind('link', $formatLink)->bind('display', $formatDisplay)
->executeGetId();
if($createFormat < 1)
return null;
return static::get($createFormat);
}
public static function get(int $fieldId): ?ProfileField {
return DB::prepare(
'SELECT `field_id`, `field_order`, `field_key`, `field_title`, `field_regex`'
. ' FROM `msz_profile_fields`'
. ' WHERE `field_id` = :field_id'
)->bind('field_id', $fieldId)->fetchObject(ProfileField::class);
}
public static function user(int $userId, bool $filterEmpty = true): array {
$fields = DB::prepare(
'SELECT pf.`field_id`, pf.`field_order`, pf.`field_key`, pf.`field_title`, pf.`field_regex`'
. ', pff.`format_id`, pff.`format_regex`, pff.`format_link`, pff.`format_display`'
. ', COALESCE(pfv.`user_id`, :user2) AS `user_id`, pfv.`field_value`'
. ' FROM `msz_profile_fields` AS pf'
. ' LEFT JOIN `msz_profile_fields_values` AS pfv ON pfv.`field_id` = pf.`field_id` AND pfv.`user_id` = :user1'
. ' LEFT JOIN `msz_profile_fields_formats` AS pff ON pff.`field_id` = pf.`field_id` AND pff.`format_id` = pfv.`format_id`'
. ' ORDER BY pf.`field_order`'
)->bind('user1', $userId)->bind('user2', $userId)->fetchObjects(ProfileField::class);
if($filterEmpty) {
$newFields = [];
foreach($fields as $field) {
if(!empty($field->field_value))
$newFields[] = $field;
}
$fields = $newFields;
}
return $fields;
}
public function findDisplayFormat(string $value): int {
if(!isset($this->field_id))
return 0;
$format = DB::prepare('
SELECT `format_id`
FROM `msz_profile_fields_formats`
WHERE `field_id` = :field
AND `format_regex` IS NOT NULL
AND :value REGEXP `format_regex`
')->bind('field', $this->field_id)
->bind('value', $value)
->fetchColumn();
if($format < 1) {
$format = DB::prepare('
SELECT `format_id`
FROM `msz_profile_fields_formats`
WHERE `field_id` = :field
AND `format_regex` IS NULL
')->bind('field', $this->field_id)
->fetchColumn(0, 0);
}
return $format;
}
// todo: use exceptions
public function setFieldValue(string $value): bool {
if(!isset($this->user_id, $this->field_id, $this->field_regex))
return false;
if(empty($value)) {
DB::prepare('
DELETE FROM `msz_profile_fields_values`
WHERE `user_id` = :user
AND `field_id` = :field
')->bind('user', $this->user_id)
->bind('field', $this->field_id)
->execute();
$this->field_value = '';
return true;
}
if(preg_match($this->field_regex, $value, $matches)) {
$value = $matches[1];
} else {
return false;
}
$displayFormat = $this->findDisplayFormat($value);
if($displayFormat < 1)
return false;
$updateField = DB::prepare('
REPLACE INTO `msz_profile_fields_values`
(`field_id`, `user_id`, `format_id`, `field_value`)
VALUES
(:field, :user, :format, :value)
')->bind('field', $this->field_id)
->bind('user', $this->user_id)
->bind('format', $displayFormat)
->bind('value', $value)
->execute();
if(!$updateField)
return false;
$this->field_value = $value;
return true;
}
}

View file

@ -335,12 +335,6 @@ class User implements HasRankInterface {
return (int)$this->getBirthdate()->diff(new DateTime('now', new DateTimeZone('UTC')))->format('%y'); return (int)$this->getBirthdate()->diff(new DateTime('now', new DateTimeZone('UTC')))->format('%y');
} }
public function profileFields(bool $filterEmpty = true): array {
if(($userId = $this->getId()) < 1)
return [];
return ProfileField::user($userId, $filterEmpty);
}
public function bumpActivity(string $lastRemoteAddress): void { public function bumpActivity(string $lastRemoteAddress): void {
$this->user_active = time(); $this->user_active = time();
$this->last_ip = $lastRemoteAddress; $this->last_ip = $lastRemoteAddress;

View file

@ -77,12 +77,11 @@
{% endif %} {% endif %}
<div class="profile__content"> <div class="profile__content">
{% set profile_fields = profile_user.profileFields(not (profile_is_editing and perms.edit_profile))|default([]) %} {% set show_profile_fields = not profile_is_guest and (profile_is_editing ? perms.edit_profile : profile_fields_display_values is not empty) %}
{% set show_profile_fields = profile_is_editing ? perms.edit_profile : profile_fields|length > 0 %}
{% set show_background_settings = profile_is_editing and perms.edit_background %} {% set show_background_settings = profile_is_editing and perms.edit_background %}
{% set show_birthdate = profile_is_editing and perms.edit_birthdate %} {% set show_birthdate = profile_is_editing and perms.edit_birthdate %}
{% set show_active_forum_info = not profile_is_editing and (profile_active_category_info.forum_id|default(0) > 0 or profile_active_topic_info.topic_id|default(0) > 0) %} {% set show_active_forum_info = not profile_is_editing and (profile_active_category_info.forum_id|default(0) > 0 or profile_active_topic_info.topic_id|default(0) > 0) %}
{% set show_sidebar = show_profile_fields or show_background_settings or show_birthdate or show_active_forum_info %} {% set show_sidebar = profile_is_guest or show_profile_fields or show_background_settings or show_birthdate or show_active_forum_info %}
{% if show_sidebar %} {% if show_sidebar %}
<div class="profile__content__side"> <div class="profile__content__side">
@ -114,25 +113,26 @@
{{ container_title('Elsewhere') }} {{ container_title('Elsewhere') }}
<div class="profile__accounts__content"> <div class="profile__accounts__content">
{% for field in profile_fields %} {% for fieldInfo in profile_fields_infos %}
<label class="profile__accounts__item"> {% if profile_is_editing or profile_fields_display_values[fieldInfo.name] is defined %}
<div class="profile__accounts__title"> <label class="profile__accounts__item">
{{ field.field_title }} <div class="profile__accounts__title">
</div> {{ fieldInfo.title }}
{% if profile_is_editing %}
{{ input_text('profile[' ~ field.field_key ~ ']', 'profile__accounts__input', field.field_value, data.type|default('text')) }}
{% else %}
<div class="profile__accounts__value">
{% set profile_field_value = field.format_display|format(field.field_value) %}
{% if field.format_link is empty %}
{{ profile_field_value }}
{% else %}
<a href="{{ field.format_link|format(field.field_value) }}" class="profile__accounts__link" target="_blank" rel="noreferrer noopener">{{ profile_field_value }}</a>
{% endif %}
</div> </div>
{% endif %}
</label> {% if profile_is_editing %}
{{ input_text('profile[' ~ fieldInfo.name ~ ']', 'profile__accounts__input', profile_fields_raw_values[fieldInfo.name]|default('')) }}
{% else %}
<div class="profile__accounts__value">
{% if profile_fields_link_values[fieldInfo.name] is defined %}
<a href="{{ profile_fields_link_values[fieldInfo.name] }}" class="profile__accounts__link" target="_blank" rel="noreferrer noopener">{{ profile_fields_display_values[fieldInfo.name] }}</a>
{% else %}
{{ profile_fields_display_values[fieldInfo.name] }}
{% endif %}
</div>
{% endif %}
</label>
{% endif %}
{% endfor %} {% endfor %}
</div> </div>
</div> </div>

View file

@ -1,6 +1,12 @@
<?php <?php
use Index\Colour\Colour; use Index\Colour\Colour;
function msz_where_in_list(Countable|array|int $count, string $repeat = '?', string $separator = ', '): string {
if(is_countable($count))
$count = count($count);
return implode($separator, array_fill(0, $count, $repeat));
}
// render_error and render_info need to be nuked from orbit // render_error and render_info need to be nuked from orbit
function render_error(int $code, string $template = 'errors.%d'): string { function render_error(int $code, string $template = 'errors.%d'): string {