Skip to content

Commit 31c3632

Browse files
authored
Merge pull request phpbb#6917 from marc1706/ticket/17597
[ticket/17597] Use avatar twig function and placeholders everywhere
2 parents 840f9b1 + a4ec866 commit 31c3632

35 files changed

Lines changed: 260 additions & 107 deletions

phpBB/adm/style/acp_avatar_options_upload.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
{% INCLUDECSS T_ASSETS_PATH ~ '/css/cropper.min.css' %}
77
{% INCLUDEJS T_ASSETS_PATH ~ '/javascript/cropper.min.js' %}
88
{% INCLUDEJS T_ASSETS_PATH ~ '/javascript/jquery-cropper.js' %}
9+
{% INCLUDEJS T_ASSETS_PATH ~ '/javascript/hermite.js' %}
910
{% INCLUDEJS T_ASSETS_PATH ~ '/javascript/phpbb-avatars.js' %}
1011

1112
<input type="hidden" id="avatar-cropper-data" name="avatar_cropper_data" value=""

phpBB/adm/style/acp_groups.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ <h3>{L_WARNING}</h3>
113113
<legend>{L_GROUP_AVATAR}</legend>
114114
<dl>
115115
<dt><label>{L_CURRENT_IMAGE}{L_COLON}</label><br /><span>{L_AVATAR_EXPLAIN}</span></dt>
116-
<dd class="c-avatar-box">{% if AVATAR_HTML %}{{ AVATAR_HTML }}{% else %}<img src="{{ ADMIN_ROOT_PATH ~ 'images/no_avatar.gif' }}" alt="">{% endif %}</dd>
116+
<dd class="c-avatar-box">{% include 'avatar.html' with {avatar_data: AVATAR} only %}</dd>
117117
<dd><label for="avatar_delete"><input type="checkbox" name="avatar_delete" id="avatar_delete" /> {L_DELETE_AVATAR}</label></dd>
118118
</dl>
119119
<dl>

phpBB/adm/style/acp_users_avatar.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<!-- IF ERROR --><p class="error">{ERROR}</p><!-- ENDIF -->
66
<dl>
77
<dt><label>{L_CURRENT_IMAGE}{L_COLON}</label><br /><span>{L_AVATAR_EXPLAIN}</span></dt>
8-
<dd class="c-avatar-box">{% if AVATAR_HTML %}{{ AVATAR_HTML }}{% else %}<img src="{{ ADMIN_ROOT_PATH ~ 'images/no_avatar.gif' }}" alt="">{% endif %}</dd>
8+
<dd class="c-avatar-box">{% include 'avatar.html' with {avatar_data: AVATAR} only %}</dd>
99
<dd><label for="avatar_delete"><input type="checkbox" name="avatar_delete" id="avatar_delete" /> {L_DELETE_AVATAR}</label></dd>
1010
</dl>
1111
</fieldset>

phpBB/adm/style/admin.css

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2708,6 +2708,38 @@ fieldset.permissions .permissions-switch {
27082708
padding: 0;
27092709
}
27102710

2711+
/* Placeholder avatars
2712+
---------------------------------------- */
2713+
.avatar-color-default { background-color: hsl(207, 15%, 80%); }
2714+
.avatar-color-0 { background-color: #d32f2f; }
2715+
.avatar-color-1 { background-color: #c2185b; }
2716+
.avatar-color-2 { background-color: #7b1fa2; }
2717+
.avatar-color-3 { background-color: #512da8; }
2718+
.avatar-color-4 { background-color: #303f9f; }
2719+
2720+
.avatar-initials {
2721+
font-size: 36px;
2722+
font-weight: bold;
2723+
line-height: 1;
2724+
color: #ffffff;
2725+
}
2726+
2727+
.avatar-placeholder {
2728+
border-radius: 50%;
2729+
display: flex;
2730+
justify-content: center;
2731+
align-items: center;
2732+
width: 80px;
2733+
height: 80px;
2734+
}
2735+
2736+
.avatar-initials .o-icon {
2737+
font-size: 36px;
2738+
vertical-align: baseline !important;
2739+
width: auto;
2740+
height: auto;
2741+
}
2742+
27112743
/* Dropdown menu
27122744
---------------------------------------- */
27132745
.dropdown {

phpBB/adm/style/avatar.html

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{% if avatar_data.src %}
2+
{% if avatar_link %}
3+
<a href="{{ avatar_link }}" class="avatar">{{ avatar(avatar_data, lang('USER_AVATAR')) }}</a>
4+
{% else %}
5+
<span class="avatar">{{ avatar(avatar_data, lang('USER_AVATAR')) }}</span>
6+
{% endif %}
7+
{% elseif avatar_data.id %}
8+
{% set color_classes = ['avatar-color-0', 'avatar-color-1', 'avatar-color-2', 'avatar-color-3', 'avatar-color-4'] %}
9+
{% set color_class = not(avatar_data.id starts with 'g') ? color_classes[avatar_data.id % 5] : 'avatar-color-default' %}
10+
{% if avatar_link %}<a href="{{ avatar_link }}" class="avatar">{% endif %}
11+
<div class="avatar-placeholder {{ color_class }}">
12+
{% set username = username ?: avatar_data.username %}
13+
{% set initials = username|striptags|trim(' -+_[]', 'left')|slice(0, 1)|upper %}
14+
<div class="avatar-initials">{% if initials %}{{ initials }}{% else %}{{ Icon('font', 'user', '', true) }}{% endif %}</div>
15+
</div>
16+
{% if avatar_link %}</a>{% endif %}
17+
{% endif %}

phpBB/assets/javascript/phpbb-avatars.js

Lines changed: 83 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@
1111
cropper: null,
1212
image: null,
1313

14+
/** @type {jQuery|null} */
15+
$originalAvatar: null,
16+
1417
/** @type {jQuery} */
1518
$form: null,
1619

@@ -32,30 +35,44 @@
3235
/** @type {string} */
3336
driverUpload: 'avatar_driver_upload',
3437

38+
/** @type {{width: {min: number, max: number}, height: {min: number, max: number}}} Allowed avatar sizes */
39+
allowedSizes: {
40+
width: { min: -1, max: -1 },
41+
height: { min: -1, max: -1 },
42+
},
43+
3544
/**
3645
* Initialise avatar cropping.
3746
*/
3847
init() {
3948
// If the cropper library is not available
40-
if (!$.isFunction($.fn.cropper)) {
49+
if (!$.fn.hasOwnProperty('cropper') || typeof $.fn.cropper !== 'function') {
4150
return;
4251
}
4352

53+
// Set allowed sizes from data attributes
54+
const data = this.$data.data();
55+
this.allowedSizes.width.min = data.minWidth;
56+
this.allowedSizes.width.max = data.maxWidth;
57+
this.allowedSizes.height.min = data.minHeight;
58+
this.allowedSizes.height.max = data.maxHeight;
59+
4460
// Correctly position the cropper buttons
4561
this.$buttons.appendTo(this.$box);
4662

47-
// Ensure we have an img for the cropping
48-
if (this.$box.children('img').length === 0) {
49-
const $avatarImg = $('<img src="" alt="">');
50-
$avatarImg.setAttribute('width', phpbb.avatars.$data.data().maxWidth);
51-
$avatarImg.setAttribute('height', phpbb.avatars.$data.data().maxHeight);
52-
$avatarImg.addClass('avatar');
53-
this.image = $avatarImg;
54-
this.$box.prepend($avatarImg);
55-
} else {
56-
this.image = this.$box.children('img');
63+
// Add image for cropping but track original avatar if it exists
64+
const $existingImg = this.$box.find('img');
65+
if ($existingImg.length) {
66+
this.$originalAvatar = $existingImg;
5767
}
5868

69+
const $avatarImg = $('<img src="" alt="">');
70+
$avatarImg.attr('width', this.allowedSizes.width.max);
71+
$avatarImg.attr('height', this.allowedSizes.height.max);
72+
$avatarImg.addClass('avatar hidden');
73+
this.image = $avatarImg;
74+
this.$box.prepend($avatarImg);
75+
5976
this.bindInput();
6077
this.bindSelect();
6178
this.bindSubmit();
@@ -67,9 +84,11 @@
6784
destroy() {
6885
this.$buttons.find('[data-cropper-action]').off('click.phpbb.avatars');
6986
this.image.off('crop.phpbb.avatars');
87+
this.setAvatarVisible(true);
7088
this.$form.off('submit');
7189

7290
this.$data.val('');
91+
this.$input.val(null);
7392
this.$buttons.hide();
7493
this.$box.removeClass('c-cropper-avatar-box');
7594

@@ -87,10 +106,14 @@
87106
bindSelect() {
88107
this.$driver.on('change', function() {
89108
if ($(this).val() === phpbb.avatars.driverUpload) {
90-
if (phpbb.avatars.$input.val() !== '') {
91-
phpbb.avatars.$input.trigger('change');
92-
}
109+
// Rebind submit after switching back to upload driver
110+
phpbb.avatars.bindSubmit();
93111
} else {
112+
// Show placeholder avatar if it exists and was hidden
113+
if (phpbb.avatars.$box.children('.avatar-placeholder').length) {
114+
phpbb.avatars.$box.children('.avatar-placeholder').show();
115+
}
116+
94117
phpbb.avatars.destroy();
95118
}
96119
});
@@ -112,6 +135,7 @@
112135
fileReader.addEventListener('load', function() {
113136
phpbb.avatars.image.cropper('destroy').attr('src', this.result).addClass('avatar');
114137
phpbb.avatars.$box.addClass('c-cropper-avatar-box');
138+
phpbb.avatars.setAvatarVisible(false);
115139
phpbb.avatars.initCropper();
116140
phpbb.avatars.initButtons();
117141
});
@@ -121,14 +145,33 @@
121145
});
122146
},
123147

148+
/**
149+
* Show or hide the original avatar image.
150+
* @param {boolean} visible
151+
* @return {void}
152+
*/
153+
setAvatarVisible(visible) {
154+
if (this.$originalAvatar !== null) {
155+
if (visible) {
156+
phpbb.avatars.$originalAvatar.removeClass('hidden');
157+
} else {
158+
phpbb.avatars.$originalAvatar.addClass('hidden');
159+
}
160+
}
161+
},
162+
124163
/**
125164
* Bind submit button to be handled by ajax submit
126165
*/
127166
bindSubmit() {
128167
const $this = this;
129168
$this.$form = this.$input.closest('form');
130169
$this.$form.on('submit', () => {
131-
const data = phpbb.avatars.$data.data();
170+
if ($this.$form.find('#avatar_delete').is(':checked')) {
171+
return;
172+
}
173+
174+
const $submitButton = this.$form.find('fieldset > input[type=submit]').first();
132175

133176
const avatarCanvas = phpbb.avatars.cropper.getCroppedCanvas({
134177
maxWidth: 4096, // High values for max quality cropping
@@ -137,12 +180,12 @@
137180

138181
// eslint-disable-next-line no-undef
139182
const hermiteResize = new Hermite_class();
140-
hermiteResize.resample_single(avatarCanvas, data.maxWidth, data.maxHeight, true);
183+
hermiteResize.resample_single(avatarCanvas, phpbb.avatars.allowedSizes.width.max, phpbb.avatars.allowedSizes.height.max, true);
141184

142185
avatarCanvas.toBlob(blob => {
143186
const formData = new FormData($this.$form[0]);
144187
formData.set('avatar_upload_file', blob, $this.getUploadFileName());
145-
formData.set('submit', '1');
188+
formData.set($submitButton.attr('name'), $submitButton.val());
146189

147190
const canvasDataUrl = avatarCanvas.toDataURL('image/png');
148191

@@ -181,7 +224,9 @@
181224

182225
/**
183226
* Handle response from avatar submission
184-
* @param {Object} response AJAX response object
227+
* @param {{MESSAGE_TITLE: string, MESSAGE_TEXT: string,
228+
* REFRESH_DATA: {time: int, url: string},
229+
* error: {title: string, messages: string[]}}} response AJAX response object
185230
* @param {string} canvasDataUrl Uploaded canvas element as data URL
186231
*/
187232
uploadDone(response, canvasDataUrl) {
@@ -191,14 +236,24 @@
191236

192237
// Handle errors while deleting file
193238
if (typeof response.error === 'undefined') {
239+
// Ensure image is visible after upload
240+
phpbb.avatars.image.removeClass('hidden');
241+
194242
const alert = phpbb.alert(response.MESSAGE_TITLE, response.MESSAGE_TEXT);
195243

196244
setTimeout(() => {
197245
window.location = response.REFRESH_DATA.url.replace('&amp;', '&');
198246
alert.hide();
199247
}, response.REFRESH_DATA.time * 1000);
200248

201-
phpbb.avatars.image.attr('src', canvasDataUrl);
249+
// Update original avatar image if it exists or use added image
250+
if (phpbb.avatars.$originalAvatar !== null) {
251+
phpbb.avatars.$originalAvatar.attr('src', canvasDataUrl);
252+
phpbb.avatars.image.addClass('hidden');
253+
} else {
254+
phpbb.avatars.image.attr('src', canvasDataUrl);
255+
}
256+
202257
phpbb.avatars.destroy();
203258
} else {
204259
phpbb.alert(response.error.title, response.error.messages.join('<br>'));
@@ -239,9 +294,14 @@
239294
* and registers a callback function for the 'crop' event.
240295
*/
241296
initCropper() {
297+
// Hide placeholder avatar
298+
this.$box.children('.avatar-placeholder').hide();
299+
242300
this.cropper = this.image.cropper({
243301
aspectRatio: 1,
244302
autoCropArea: 1,
303+
minContainerHeight: this.allowedSizes.height.max * 2, // Double max size for better usability
304+
minContainerWidth: this.allowedSizes.width.max * 2, // Double max size for better usability
245305
}).data('cropper');
246306

247307
this.image.off('crop.phpbb.avatars').on('crop.phpbb.avatars', phpbb.avatars.onCrop);
@@ -261,12 +321,12 @@
261321
* @param {object} event
262322
*/
263323
onCrop(event) {
264-
const data = phpbb.avatars.$data.data();
265324
let { width, height } = event.detail;
325+
const allowedSizes = phpbb.avatars.allowedSizes;
266326

267-
if (width < data.minWidth || height < data.minHeight) {
268-
width = Math.max(data.minWidth, Math.min(data.maxWidth, width));
269-
height = Math.max(data.minHeight, Math.min(data.maxHeight, height));
327+
if (width < allowedSizes.width.min || height < allowedSizes.height.min) {
328+
width = Math.max(allowedSizes.width.min, Math.min(allowedSizes.width.max, width));
329+
height = Math.max(allowedSizes.height.min, Math.min(allowedSizes.height.max, height));
270330
phpbb.avatars.cropper.setData({
271331
width,
272332
height,

phpBB/config/default/container/services_notification.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ services:
2626
notification.type.base:
2727
abstract: true
2828
arguments:
29+
- '@avatar.helper'
2930
- '@controller.helper'
3031
- '@dbal.conn'
3132
- '@language'

phpBB/docs/events.md

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1543,20 +1543,6 @@ memberlist_view_rank_avatar_before
15431543
* Since: 3.1.6-RC1
15441544
* Purpose: Add information before rank in memberlist (with avatar)
15451545

1546-
memberlist_view_rank_no_avatar_after
1547-
===
1548-
* Locations:
1549-
+ styles/prosilver/template/memberlist_view.html
1550-
* Since: 3.1.6-RC1
1551-
* Purpose: Add information after rank in memberlist (without avatar)
1552-
1553-
memberlist_view_rank_no_avatar_before
1554-
===
1555-
* Locations:
1556-
+ styles/prosilver/template/memberlist_view.html
1557-
* Since: 3.1.6-RC1
1558-
* Purpose: Add information before rank in memberlist (without avatar)
1559-
15601546
memberlist_view_user_statistics_after
15611547
===
15621548
* Locations:

phpBB/includes/acp/acp_users.php

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
*/
1717

1818
use phpbb\controller\helper;
19+
use phpbb\language\language;
1920
use phpbb\messenger\method\messenger_interface;
2021

2122
if (!defined('IN_PHPBB'))
@@ -43,6 +44,9 @@ function main($id, $mode)
4344
/** @var helper $controller_helper */
4445
$controller_helper = $phpbb_container->get('controller.helper');
4546

47+
/** @var language $language Language object */
48+
$language = $phpbb_container->get('language');
49+
4650
$user->add_lang(array('posting', 'ucp', 'acp/users'));
4751
$this->tpl_name = 'acp_users';
4852

@@ -1889,9 +1893,32 @@ function main($id, $mode)
18891893
$sql = 'UPDATE ' . USERS_TABLE . '
18901894
SET ' . $db->sql_build_array('UPDATE', $result) . '
18911895
WHERE user_id = ' . (int) $user_id;
1892-
18931896
$db->sql_query($sql);
1894-
trigger_error($user->lang['USER_AVATAR_UPDATED'] . adm_back_link($this->u_action . '&amp;u=' . $user_id));
1897+
1898+
if ($request->is_ajax())
1899+
{
1900+
/** @var \phpbb\avatar\helper $avatar_helper */
1901+
$avatar_helper = $phpbb_container->get('avatar.helper');
1902+
1903+
$avatar = $avatar_helper->get_user_avatar($user->data, 'USER_AVATAR', true);
1904+
1905+
$json_response = new \phpbb\json_response;
1906+
$json_response->send([
1907+
'success' => true,
1908+
1909+
'MESSAGE_TITLE' => $language->lang('INFORMATION'),
1910+
'MESSAGE_TEXT' => $language->lang('USER_AVATAR_UPDATED'),
1911+
'REFRESH_DATA' => [
1912+
'time' => 3,
1913+
'url' => $this->u_action . '&amp;u=' . $user_id,
1914+
'text' => $language->lang('BACK_TO_PREV'),
1915+
]
1916+
]);
1917+
}
1918+
else
1919+
{
1920+
trigger_error($user->lang['USER_AVATAR_UPDATED'] . adm_back_link($this->u_action . '&amp;u=' . $user_id));
1921+
}
18951922
}
18961923
}
18971924
}

0 commit comments

Comments
 (0)