This commit is contained in:
rxu 2025-05-30 01:52:38 -05:00 committed by GitHub
commit 8b130a4143
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 272 additions and 3 deletions

View file

@ -91,6 +91,12 @@
<dd><input type="checkbox" class="radio" id="field_is_contact" name="field_is_contact" value="1"<!-- IF S_FIELD_CONTACT --> checked="checked"<!-- ENDIF --> /></dd>
<dd><input class="text medium" type="text" name="field_contact_desc" id="field_contact_desc" value="{FIELD_CONTACT_DESC}" /> <label for="field_contact_desc">{L_FIELD_CONTACT_DESC}</label></dd>
<dd><input class="text medium" type="text" name="field_contact_url" id="field_contact_url" value="{FIELD_CONTACT_URL}" /> <label for="field_contact_url">{L_FIELD_CONTACT_URL}</label></dd>
<dt><label for="field_icon">{{ lang('FIELD_ICON') ~ lang('COLON') }}</label><br /><span>{{ lang('FIELD_ICON_EXPLAIN') }}</span></dt>
<dd><input name="field_icon" id="field_icon" type="text" size="15" maxlength="255" value="{{ FIELD_ICON }}" placeholder="{{ lang('FIELD_ICON') }}" />{{ Icon('font', FIELD_ICON, '', true, 'acp-icon', {'style': 'margin:0 6px;' ~ (FIELD_ICON_COLOR ? (' color: #' ~ FIELD_ICON_COLOR ~ ';') : '')}) }}</dd>
<dd>
<input name="field_icon_color" type="text" id="contact_field_icon_bgcolor" value="{{ FIELD_ICON_COLOR }}" size="7" maxlength="6" placeholder="{{ lang('FIELD_ICON_COLOR') }}" />
<input type="color" id="field_icon_color_picker" aria-label="{{ lang('FIELD_ICON_COLOR') }}">
</dd>
<!-- EVENT acp_profile_contact_last -->
</dl>
{% EVENT acp_profile_contact_after %}

View file

@ -3152,3 +3152,29 @@ span + .o-icon {
.acp-icon-disabled {
color: #d0d0d0;
}
input[type="color"] {
background-color: transparent;
border: solid 1px #d3d3d3;
border-radius: 50%;
width: 24px;
height: 24px;
padding: 2px;
cursor: pointer;
-webkit-appearance: none;
}
input[type="color"]::-webkit-color-swatch-wrapper {
padding: 0;
}
input[type="color"]::-webkit-color-swatch {
border: 0;
border-radius: 50%;
}
input[type="color"]::-moz-color-swatch {
border: 0;
border-radius: 50%;
}

View file

@ -244,6 +244,67 @@ function parse_document(container)
});
}
/**
* Automatically display custom profile fields FontAwesome icon
*/
const DEFAULT_COLOR = '#000000';
const HEX_REGEX = /^#[A-Fa-f0-9]{6}$/;
const colorPicker = document.getElementById('field_icon_color_picker');
const colorText = colorPicker.previousElementSibling;
const syncColors = (source, target) => {
const value = '#' + source.value.trim();
target.value = HEX_REGEX.test(value) ? value : DEFAULT_COLOR;
};
const handleInput = ({ target }) => {
if (target === colorPicker) {
colorText.value = target.value.substring(1);
} else {
syncColors(colorText, colorPicker);
}
const icon = field_icon?.nextElementSibling;
if (icon && icon.tagName.toLowerCase() === 'i') {
icon.style.color = colorPicker.value;
}
};
colorPicker.addEventListener('input', handleInput);
colorText.addEventListener('input', handleInput);
colorText.addEventListener('blur', () => {
if (!colorText.value.trim()) {
colorPicker.value = DEFAULT_COLOR;
}
});
syncColors(colorText, colorPicker);
var field_icon = document.getElementById('field_icon');
if (!field_icon.nextElementSibling) {
icon_demo = document.createElement('i');
icon_demo.setAttribute('style', `margin:0 6px; color: ${colorPicker.value}`);
icon_demo.setAttribute('class', `o-icon o-icon-font fa-fw fas acp-icon`);
field_icon.after(icon_demo);
}
const updateIconClass = (element, newClass) => {
element.classList.forEach(className => {
if (className.startsWith('fa-') && className !== 'fa-fw') {
element.classList.remove(className);
}
});
element.classList.add(`fa-${newClass}`);
};
field_icon.addEventListener('keyup', function() {
updateIconClass(this.nextElementSibling, this.value);
});
field_icon.addEventListener('blur', function() {
updateIconClass(this.nextElementSibling, this.value);
});
/**
* Run onload functions
*/

View file

@ -360,6 +360,7 @@ class acp_profile
$field_row = array_merge($profile_field->get_default_option_values(), array(
'field_ident' => str_replace(' ', '_', utf8_clean_string($request->variable('field_ident', '', true))),
'field_required' => 0,
'field_icon' => json_encode(['name' => '', 'color' => '']),
'field_show_novalue'=> 0,
'field_hide' => 0,
'field_show_profile'=> 0,
@ -381,7 +382,7 @@ class acp_profile
// $exclude contains the data we gather in each step
$exclude = array(
1 => array('field_ident', 'lang_name', 'lang_explain', 'field_option_none', 'field_show_on_reg', 'field_show_on_pm', 'field_show_on_vt', 'field_show_on_ml', 'field_required', 'field_show_novalue', 'field_hide', 'field_show_profile', 'field_no_view', 'field_is_contact', 'field_contact_desc', 'field_contact_url'),
1 => array('field_ident', 'field_icon', 'field_icon_color', 'lang_name', 'lang_explain', 'field_option_none', 'field_show_on_reg', 'field_show_on_pm', 'field_show_on_vt', 'field_show_on_ml', 'field_required', 'field_show_novalue', 'field_hide', 'field_show_profile', 'field_no_view', 'field_is_contact', 'field_contact_desc', 'field_contact_url'),
2 => array('field_length', 'field_maxlen', 'field_minlen', 'field_validation', 'field_novalue', 'field_default_value'),
3 => array('l_lang_name', 'l_lang_explain', 'l_lang_default_value', 'l_lang_options')
);
@ -427,6 +428,13 @@ class acp_profile
$options = $profile_field->prepare_options_form($exclude, $visibility_ary);
$field_icon_data = json_decode($field_row['field_icon'], true);
$field_icon_name = $request->variable('field_icon', $field_icon_data['name'] ?: '');
$field_icon_color = $field_icon_name ? $request->variable('field_icon_color', $field_icon_data['color'] ?: '') : '';
$cp->vars['field_icon'] = json_encode([
'name' => $field_icon_name,
'color' => $field_icon_color,
]);
$cp->vars['field_ident'] = ($action == 'create' && $step == 1) ? utf8_clean_string($request->variable('field_ident', $field_row['field_ident'], true)) : $request->variable('field_ident', $field_row['field_ident']);
$cp->vars['lang_name'] = $request->variable('lang_name', $field_row['lang_name'], true);
$cp->vars['lang_explain'] = $request->variable('lang_explain', $field_row['lang_explain'], true);
@ -630,6 +638,7 @@ class acp_profile
{
// Create basic options - only small differences between field types
case 1:
$field_icon_data = json_decode($cp->vars['field_icon'], true);
$template_vars = array(
'S_STEP_ONE' => true,
'S_FIELD_REQUIRED' => ($cp->vars['field_required']) ? true : false,
@ -648,6 +657,8 @@ class acp_profile
'L_LANG_SPECIFIC' => sprintf($user->lang['LANG_SPECIFIC_OPTIONS'], $config['default_lang']),
'FIELD_TYPE' => $profile_field->get_name(),
'FIELD_IDENT' => $cp->vars['field_ident'],
'FIELD_ICON' => $field_icon_data['name'],
'FIELD_ICON_COLOR' => $field_icon_data['color'],
'LANG_NAME' => $cp->vars['lang_name'],
'LANG_EXPLAIN' => $cp->vars['lang_explain'],
);
@ -984,6 +995,7 @@ class acp_profile
'field_is_contact' => $cp->vars['field_is_contact'],
'field_contact_desc' => $cp->vars['field_contact_desc'],
'field_contact_url' => $cp->vars['field_contact_url'],
'field_icon' => $cp->vars['field_icon'],
);
$field_data = $cp->vars;

View file

@ -373,8 +373,11 @@ function view_message($id, $mode, $folder_id, $msg_id, $folder, $message_row)
if ($cp_block_row['S_PROFILE_CONTACT'])
{
$icon_data = json_decode($cp_block_row['PROFILE_FIELD_ICON'], true);
$template->assign_block_vars('contact', array(
'ID' => $cp_block_row['PROFILE_FIELD_IDENT'],
'ICON' => $icon_data['name'],
'ICON_COLOR'=> $icon_data['color'],
'NAME' => $cp_block_row['PROFILE_FIELD_NAME'],
'U_CONTACT' => $cp_block_row['PROFILE_FIELD_CONTACT'],
));

View file

@ -93,6 +93,9 @@ $lang = array_merge($lang, array(
'FIELD_DESCRIPTION' => 'Field description',
'FIELD_DESCRIPTION_EXPLAIN' => 'The explanation for this field presented to the user.',
'FIELD_DROPDOWN' => 'Dropdown box',
'FIELD_ICON' => 'Field icon',
'FIELD_ICON_COLOR' => 'Icon colour',
'FIELD_ICON_EXPLAIN' => 'Enter a Font Awesome icon name to display with this contact field. Optionally, set its colour using a 6-digit hex code or the colour picker. Leave blank to use phpBBs default icon and clear the colour.',
'FIELD_IDENT' => 'Field identification',
'FIELD_IDENT_ALREADY_EXIST' => 'The chosen field identification already exist. Please choose another name.',
'FIELD_IDENT_EXPLAIN' => 'The field identification is a name to identify the profile field within the database and the templates.',

View file

@ -0,0 +1,51 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @license GNU General Public License, version 2 (GPL-2.0)
*
* For full copyright and license information, please see
* the docs/CREDITS.txt file.
*
*/
namespace phpbb\db\migration\data\v400;
class custom_profile_field_contact_icon extends \phpbb\db\migration\migration
{
public function effectively_installed()
{
return $this->db_tools->sql_column_exists($this->table_prefix . 'profile_fields', 'field_icon');
}
public static function depends_on()
{
return [
'\phpbb\db\migration\data\v400\dev',
];
}
public function update_schema()
{
return array(
'add_columns' => [
$this->table_prefix . 'profile_fields' => [
'field_icon' => array('VCHAR:255', json_encode(['name' => '', 'color' => ''])),
],
],
);
}
public function revert_schema()
{
return array(
'drop_columns' => array(
$this->table_prefix . 'profile_fields' => array(
'field_icon',
),
),
);
}
}

View file

@ -332,6 +332,7 @@ class manager
$tpl_fields[] = [
'PROFILE_FIELD_IDENT' => $field_ident,
'PROFILE_FIELD_ICON' => $field_data['field_icon'],
'PROFILE_FIELD_TYPE' => $field_data['field_type'],
'PROFILE_FIELD_NAME' => $profile_field->get_field_name($field_data['lang_name']),
'PROFILE_FIELD_EXPLAIN' => $this->language->lang($field_data['lang_explain']),
@ -484,6 +485,7 @@ class manager
$tpl_fields['row'] += [
"PROFILE_{$ident_upper}_IDENT" => $ident,
"PROFILE_{$ident_upper}_ICON" => $ident_ary['data']['field_icon'],
"PROFILE_{$ident_upper}_VALUE" => $value,
"PROFILE_{$ident_upper}_VALUE_RAW" => $value_raw,
"PROFILE_{$ident_upper}_CONTACT" => $contact_url,
@ -498,6 +500,7 @@ class manager
$tpl_fields['blockrow'][] = [
'PROFILE_FIELD_IDENT' => $ident,
'PROFILE_FIELD_ICON' => $ident_ary['data']['field_icon'],
'PROFILE_FIELD_VALUE' => $value,
'PROFILE_FIELD_VALUE_RAW' => $value_raw,
'PROFILE_FIELD_CONTACT' => $contact_url,

View file

@ -71,7 +71,10 @@
<div>
<!-- ENDIF -->
<a href="<!-- IF contact.U_CONTACT -->{contact.U_CONTACT}<!-- ELSE -->{contact.U_PROFILE_AUTHOR}<!-- ENDIF -->" title="{contact.NAME}"<!-- IF $S_LAST_CELL --> class="last-cell"<!-- ENDIF -->>
{% if contact.ID == 'pm' %}
{% if contact.ICON %}
{% set color = contact.ICON_COLOR ? ({style: 'color: #' ~ contact.ICON_COLOR}) : [] %}
{{ Icon('font', contact.ICON, '', true, '', color) }}
{% elseif contact.ID == 'pm' %}
{{ Icon('font', 'message', '', true, 'far contact-icon') }}
{% elseif contact.ID == 'email' %}
{{ Icon('font', 'at', '', true, 'fas contact-icon') }}

View file

@ -193,7 +193,10 @@
<!-- ENDIF -->
<a href="<!-- IF postrow.contact.U_CONTACT -->{postrow.contact.U_CONTACT}<!-- ELSE -->{postrow.U_POST_AUTHOR}<!-- ENDIF -->" title="{postrow.contact.NAME}"<!-- IF $S_LAST_CELL --> class="last-cell"<!-- ENDIF -->>
{% EVENT viewtopic_body_contact_icon_prepend %}
{% if postrow.contact.ID == 'pm' %}
{% if postrow.contact.ICON %}
{% set color = postrow.contact.ICON_COLOR ? ({style: 'color: #' ~ contact.ICON_COLOR}) : [] %}
{{ Icon('font', contact.ICON, '', true, '', color) }}
{% elseif postrow.contact.ID == 'pm' %}
{{ Icon('font', 'message', '', true, 'far contact-icon') }}
{% elseif postrow.contact.ID == 'email' %}
{{ Icon('font', 'at', '', true, 'fas contact-icon') }}

View file

@ -2193,8 +2193,11 @@ for ($i = 0, $end = count($post_list); $i < $end; ++$i)
if ($field_data['S_PROFILE_CONTACT'])
{
$icon_data = json_decode($field_data['PROFILE_FIELD_ICON'], true);
$template->assign_block_vars('postrow.contact', array(
'ID' => $field_data['PROFILE_FIELD_IDENT'],
'ICON' => $icon_data['name'],
'ICON_COLOR'=> $icon_data['color'],
'NAME' => $field_data['PROFILE_FIELD_NAME'],
'U_CONTACT' => $field_data['PROFILE_FIELD_CONTACT'],
));

View file

@ -0,0 +1,95 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @license GNU General Public License, version 2 (GPL-2.0)
*
* For full copyright and license information, please see
* the docs/CREDITS.txt file.
*
*/
/**
* @group functional
*/
class phpbb_functional_profile_field_contact_icon_test extends phpbb_functional_test_case
{
protected function setUp(): void
{
parent::setUp();
$this->login();
$this->admin_login();
$this->add_lang('acp/profile');
}
public function test_add_contact_field_icon()
{
// Custom profile fields page
$crawler = self::request('GET', 'adm/index.php?i=acp_profile&mode=profile&sid=' . $this->sid);
// Get any contact profile field, f.e. phpbb_twitter
$twitter_field = $crawler->filter('tbody tr')
->reduce(
function ($node, $i) {
$text = $node->text();
return (strpos($text, 'phpbb_twitter') !== false);
});
$twitter_edit_url = $twitter_field->filter('.actions a')->eq(2)->attr('href');
$crawler = self::request('GET', 'adm/' . $twitter_edit_url . '&sid=' . $this->sid);
$this->assertStringContainsString('phpbb_twitter', $crawler->text());
$form = $crawler->selectButton('Profile type specific options')->form([
'field_icon' => 'twitter',
'field_icon_color' => '1da1f2',
]);
$crawler= self::submit($form);
$this->assertStringContainsString('Profile type specific options', $crawler->text());
$form = $crawler->selectButton('Save')->form();
$crawler= self::submit($form);
$this->assertContainsLang('CHANGED_PROFILE_FIELD', $crawler->text());
// Ensure contact filed icon was saved correctly
$crawler = self::request('GET', 'adm/' . $twitter_edit_url . '&sid=' . $this->sid);
$this->assertEquals('twitter', $crawler->filter('#field_icon')->attr('value'));
$this->assertEquals('1da1f2', $crawler->filter('#contact_field_icon_bgcolor')->attr('value'));
$this->assertEquals(1, $crawler->filter('i.fa-twitter')->count());
$this->assertStringContainsString('#1da1f2;', $crawler->filter('i.fa-twitter')->attr('style'));
}
/**
* @depends test_add_contact_field_icon
*/
public function test_display_field_icon()
{
$this->add_lang('ucp');
// Set Twitter profile field
$crawler = self::request('GET', 'ucp.php?i=ucp_profile&mode=profile_info');
$this->assertContainsLang('UCP_PROFILE_PROFILE_INFO', $crawler->filter('#cp-main h2')->text());
$form = $crawler->selectButton('Submit')->form([
'pf_phpbb_twitter' => 'phpbb_twitter',
]);
$crawler = self::submit($form);
$this->assertContainsLang('PROFILE_UPDATED', $crawler->filter('#message')->text());
// Ensure Twitter icon displays in topic
$crawler = self::request('GET', 'viewtopic.php?t=1');
$this->assertEquals('Twitter', $crawler->filter('#profile1 a[title="Twitter"]')->attr('title'));
$this->assertStringContainsString('#1da1f2', $crawler->filter('#profile1 a[title="Twitter"] > i.fa-twitter')->attr('style'));
// Ensure Twitter icon displays on view private message screen
$message_id = $this->create_private_message('Self PM', 'Self PM', [2]);
$crawler = self::request('GET', 'ucp.php?i=pm&mode=view&p=' . $message_id . '&sid=' . $this->sid);
$this->assertEquals('Twitter', $crawler->filter('.profile-contact a[title="Twitter"]')->attr('title'));
$this->assertStringContainsString('#1da1f2', $crawler->filter('.profile-contact a[title="Twitter"] > i.fa-twitter')->attr('style'));
}
}