Skip to content

Instantly share code, notes, and snippets.

@lewislarsen
Last active August 25, 2021 00:18
Show Gist options
  • Save lewislarsen/9e5f47de5a243470616d4480d23b16ce to your computer and use it in GitHub Desktop.
Save lewislarsen/9e5f47de5a243470616d4480d23b16ce to your computer and use it in GitHub Desktop.
Custom Avatar
<?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.
*
*/
/**
* DO NOT CHANGE
*/
if (!defined('IN_PHPBB'))
{
exit;
}
if (empty($lang) || !is_array($lang))
{
$lang = array();
}
// DEVELOPERS PLEASE NOTE
//
// All language files should use UTF-8 as their encoding and the files must not contain a BOM.
//
// Placeholders can now contain order information, e.g. instead of
// 'Page %s of %s' you can (and should) write 'Page %1$s of %2$s', this allows
// translators to re-order the output of data while ensuring it remains correct
//
// You do not need this where single placeholders are used, e.g. 'Message %d' is fine
// equally where a string contains only two placeholders which are used to wrap text
// in a url you again do not need to specify an order e.g., 'Click %sHERE%s' is fine
$lang = array_merge($lang, array(
'AVATAR' => 'Avatar',
'UPLOAD_AVATAR' => 'Upload avatar',
'IMAGICK_NOT_ENABLED' => 'This script requires Imagick to work. Please ensure that your PHP environment has the Imagick module installed.',
'AVATARS_NOT_ENABLED' => 'As avatars are disabled on this board this script has been automatically deactivated.',
'FILE_TOO_LARGE' => 'The file you have tried to upload is too large.',
'FILE_WRONG_TYPE' => 'The file you have tried to upload is not an acceptable file type.',
'AVATAR_EXPLAIN_LIMIT' => 'The avatar file limit is',
'AVATAR_EXPLAIN_NO_LIMIT' => 'There is no limit on the file size of avatars.',
'AVATAR_ALLOWED_TYPES' => 'You are allowed to upload a .PNG, .JPEG/JPG, .GIF or .WEBP.',
'CURRENT_AVATAR' => 'Current avatar',
'CURRENT_AVATAR_EXPLAINED' => 'This is your current avatar.',
'SM_AVATAR' => 'Small avatar',
'MD_AVATAR' => 'Medium avatar',
'LG_AVATAR' => 'Large avatar',
'VIEW_AVATAR' => 'View avatar',
'AVATAR_INDEX_EXPLANATION' => 'Customise your user experience by uploading a personal avatar to your account.',
'VISIT_LEGACY_UPLOADER' => 'Use legacy uploader',
'AVATAR_SIZES_EXPLAIN' => 'Your avatar in all three sizes can be seen above.',
'AVATAR_SMALL_SIZE_EXPLANATION' => '48x48',
'AVATAR_MEDIUM_SIZE_EXPLANATION' => '96x96',
'AVATAR_LARGE_SIZE_EXPLANATION' => '192x192',
'AVATAR_TYPE_EXPLANATION' => 'You can upload using the legacy uploader however there are limitations. We recommend using the new uploader for a more modern approach.',
'LEGACY_AVATAR_DEPRECATION_NOTICE' => 'Once out of beta the legacy avatar uploader will be retired.',
'AVATAR_SIZE_BUTTON_EXPLANATION' => 'View your avatar directly in one of three sizes.',
'AVATAR_UPLOAD_SUCCESS' => 'Your avatar was updated successfully.',
));
{% include 'overall_header.html' %}
<div class="flex justify-between items-center">
<div>
<h2 class="mb-1 text-2xl font-medium">
{% if S_IN_CUST_AVATAR_UPLOAD %}
{L_UPLOAD_AVATAR}
{% else %}
{L_TITLE}
{% endif %}
</h2>
</div>
<div clas="space-x-1">
<a class="button button-secondary" title="{L_LG_AVATAR}" target="_blank" href="{LG_AVATAR_URL}">
{L_LG_AVATAR}
</a>
<a class="button button-secondary" title="{L_MD_AVATAR}" target="_blank" href="{MD_AVATAR_URL}">
{L_MD_AVATAR}
</a>
<a class="button button-secondary" title="{L_SM_AVATAR}" target="_blank" href="{SM_AVATAR_URL}">
{L_SM_AVATAR}
</a>
<p class="text-center mt-2 text-gray-600 text-sm">{L_AVATAR_SIZE_BUTTON_EXPLANATION}</p>
</div>
</div>
{% if S_IN_CUST_AVATAR_INDEX %}
<div class="rounded bg-yellow-100 border border-yellow-200 px-3 py-3 text-yellow-700 font-medium text-sm my-3 max-w-3xl mx-auto">
<div class="flex justify-between items-center">
<div class="text-center">
This feature is in beta and may break unexpectedly. Please report any issues you find.
</div>
<div>
<svg class="h-5 w-5 inline text-yellow-700" xmlns="http://www.w3.org/2000/svg" width="24" height="24"
viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
stroke-linejoin="round">
<circle cx="12" cy="12" r="10"/>
<path d="M12 7h.01"/>
<path d="M10 11h2v5"/>
<path d="M10 16h4"/>
</svg>
</div>
</div>
</div>
<p class="max-w-xl mx-auto my-6 text-gray-800 text-center">{L_AVATAR_INDEX_EXPLANATION}</p>
<div class="border-b border-gray-100 my-2 max-w-xl mx-auto"></div>
<div class="flex space-x-1 mx-auto max-w-md items-center">
<div class="flex-auto">
<img src="{LG_AVATAR_URL}" class="w-auto h-auto avatar" id="largeAvatar"/>
</div>
<div class="flex-auto">
<img src="{MD_AVATAR_URL}" class="w-auto h-auto avatar" id="mediumAvatar"/>
</div>
<div class="flex-auto">
<img src="{SM_AVATAR_URL}" class="w-auto h-auto avatar" id="smallAvatar"/>
</div>
</div>
<div class="border-b border-gray-100 my-2 max-w-xl mx-auto"></div>
<p class="max-w-xl mx-auto my-6 text-gray-800 text-center">{L_AVATAR_TYPE_EXPLANATION}</p>
<div class="border-b border-gray-100 my-2 max-w-xl mx-auto"></div>
<p class="text-sm text-gray-600 text-center">{L_LEGACY_AVATAR_DEPRECATION_NOTICE}</p>
<div class="space-x-1 mx-auto text-center my-4">
<a href="{S_UPLOAD_ACTION}" title="{L_UPLOAD_AVATAR}" class="button">
{L_UPLOAD_AVATAR}
</a>
<a href="ucp.php?i=ucp_profile&mode=avatar" title="{L_VISIT_LEGACY_UPLOADER}" class="button button-secondary">
{L_VISIT_LEGACY_UPLOADER}
</a>
</div>
{% elseif S_IN_CUST_AVATAR_UPLOAD %}
{% if ERROR %}
<div class="max-w-3xl mx-auto my-3">
<div class="rounded-lg bg-red-50 text-red-600 border border-red-100 font-medium py-2 px-3 text-center">
{ERROR}
</div>
</div>
{% endif %}
<div class="grid-cols-6 gap-2 md:grid mt-4">
<div class="py-1 px-2 md:hidden">
<label for="current_avatar"
class="text-base font-medium text-left text-gray-800">{L_CURRENT_AVATAR}{L_COLON}</label>
</div>
<div class="hidden md:block col-span-2 text-right py-1.5">
<label for="current_avatar"
class="text-base text-right text-gray-700 font-medium">{L_CURRENT_AVATAR}{L_COLON}</label>
</div>
<div class="col-span-3 px-2 md:px-4">
<img src="{MY_AVATAR}" class="w-32 h-32 avatar"/>
<div>
<p class="text-gray-600 my-1 text-sm">{L_CURRENT_AVATAR_EXPLAINED}</p>
</div>
</div>
</div>
<form id="avatar" method="post" action="{S_UPLOAD_ACTION}" {S_FORM_ENCTYPE}>
<div class="grid-cols-6 gap-2 md:grid mt-4">
<div class="py-1 px-2 md:hidden">
<label for="avatar"
class="text-base font-medium text-left text-gray-800">{L_AVATAR}{L_COLON}</label>
</div>
<div class="hidden md:block col-span-2 text-right py-1.5">
<label for="avatar"
class="text-base text-right text-gray-700 font-medium">{L_AVATAR}{L_COLON}</label>
</div>
<div class="col-span-3 px-2 md:px-4">
<input type="file" name="avatar" class="input" required/>
<div>
<p class="text-gray-600 my-1 text-sm">{L_AVATAR_ALLOWED_TYPES}</p>
</div>
</div>
</div>
<div class="mx-auto my-4 text-center">
<div class="space-x-1">
{S_HIDDEN_FIELDS}
<input type="submit" name="submit" value="{L_SUBMIT}" class="button"/>
{S_FORM_TOKEN}
</div>
</div>
</form>
{% endif %}
<script src="https://unpkg.com/@popperjs/core@2"></script>
<script src="https://unpkg.com/tippy.js@6"></script>
<script>
tippy('#largeAvatar', {
content: '{L_AVATAR_LARGE_SIZE_EXPLANATION}',
});
tippy('#mediumAvatar', {
content: '{L_AVATAR_MEDIUM_SIZE_EXPLANATION}',
});
tippy('#smallAvatar', {
content: '{L_AVATAR_SMALL_SIZE_EXPLANATION}',
});
</script>
{% include 'overall_footer.html' %}
<?php
// ####################### SET PHP ENVIRONMENT ###########################
error_reporting(E_ALL & ~E_NOTICE);
// #################### DEFINE CONFIG VALUES #######################
$enableScript = true;
$compressionQuality = "20";
// #################### DEFINE IMPORTANT CONSTANTS #######################
const IN_PHPBB = true;
// #################### DEFINE PHPBB PATH & FILE EXTENSION #######################
$phpbb_root_path = (defined('PHPBB_ROOT_PATH')) ? PHPBB_ROOT_PATH : './';
$phpEx = substr(strrchr(__FILE__, '.'), 1);
// #################### INCLUDE CORE FILES #######################
include_once($phpbb_root_path . 'common.' . $phpEx);
include_once($phpbb_root_path . 'includes/functions_user.' . $phpEx);
include_once($phpbb_root_path . 'includes/functions_module.' . $phpEx);
include_once($phpbb_root_path . 'includes/functions_display.' . $phpEx);
include_once($phpbb_root_path . 'includes/functions_custom.' . $phpEx);
global $auth, $template, $config, $user, $request, $db;
// ################## SET SCRIPT TEMPLATE VARIABLE #####################
$template->assign_var('S_IN_CUST_AVATAR_INDEX', true);
// #################### SETUP THE USER #######################
$user->session_begin();
$auth->acl($user->data);
$user->setup();
$me = $user->data;
$meUserID = $user->data['user_id'];
// #################### INCLUDE LANGUAGE FILES #######################
$user->setup(array('ucp', 'avatar'));
// #################### DEFINE SCRIPT VALUES #######################
$pageTitle = $user->lang['AVATAR'];
$scriptName = 'avatar';
$templateName = 'avatar';
// #################### BEGIN SCRIPT CODE #######################
// set the root breadcrumb here
// can't do it below otherwise its last instead of first
$template->assign_block_vars('navlinks', array(
'BREADCRUMB_NAME' => $pageTitle,
'U_BREADCRUMB' => append_sid("{$phpbb_root_path}$scriptName.$phpEx"),
));
// adds the action parameter
$action = $request->variable('action', '');
// Check our action
if (!in_array($action, array('', 'upload', 'view')))
{
redirect("{$phpbb_root_path}$scriptName.$phpEx");
}
// directory things
// check if avatar upload directory exists - if not then make it!
$fullPath = realpath($phpbb_root_path . $config['avatar_path']);
if (!file_exists($fullPath))
{
mkdir($fullPath);
}
switch ($action)
{
case 'upload':
// set post variable & register data
$submit = $request->variable('submit', false, false, \phpbb\request\request_interface::POST);
$error = $data = array();
if ($user->data['user_id'] == ANONYMOUS)
{
login_box('', $user->lang['LOGIN']);
}
// define where we are in the template
$template->assign_var('S_IN_CUST_AVATAR_UPLOAD', true);
$template->assign_var('S_IN_CUST_AVATAR_INDEX', false);
// define the variables to send to the template
$template->assign_vars(array(
'L_TITLE' => $user->lang['UPLOAD_AVATAR'],
'S_FORM_ENCTYPE' => ' enctype="multipart/form-data"',
));
// Check if the imagick module is set
if (!extension_loaded('imagick'))
{
trigger_error($user->lang['IMAGICK_NOT_ENABLED']);
}
// check if avatars are enabled
if (!$auth->acl_get('u_chgavatar'))
{
trigger_error($user->lang['AVATARS_NOT_ENABLED']);
}
// set the page title
page_header($user->lang['UPLOAD_AVATAR']);
add_form_key('custom_upload_avatar');
if ($submit)
{
if (check_form_key('custom_upload_avatar'))
{
// get avatar
$avatar = $request->file('avatar');
// reject if mimetype doesn't conform to the specified types
if (!in_array($avatar['type'], ['image/png', 'image/jpeg', 'image/webp', 'image/gif', 'image/bmp']))
{
trigger_error($user->lang['FILE_WRONG_TYPE']);
}
// same as above but for the file size limit - this is retrieved from the admin control panel
if ($avatar['size'] < !$config['avatar_filesize'])
{
trigger_error($user->lang['FILE_TOO_LARGE']);
}
$file = file_get_contents($avatar['tmp_name']);
// get the base64 contents
$base64 = base64_decode(base64_encode($file));
// file creation time for later
//print_r($request->variable('creation_time', ''));
// imagick magic!
$imagick = new Imagick();
try
{
$imagick->readImageBlob($base64);
}
catch (ImagickException $e)
{
trigger_error($e->getMessage());
}
/*header('Content-Type:'. $avatar['type']);
echo $imagick->getImageBlob();*/
// getImagesBlob due to possibility of gifs - do an if check later with $imagick->getImageBlob();
// if avatar files exist for the current user, delete them
foreach (glob($fullPath . '/avatar_' . $meUserID . '_*') as $image)
{
if (is_file($image))
{
unlink($image);
}
}
$generatedAvatarSizes = [
'lg' => '192',
'md' => '96',
'sm' => '48',
];
// if the avatar is a gif, do all this fancy shmancy nonsense.
if ($avatar['type'] === 'image/gif')
{
$fileExt = ".gif";
// pre-defined sizes
foreach ($generatedAvatarSizes as $key => $img)
{
$image = $imagick->coalesceImages();
foreach ($image as $frame)
{
$frame->cropThumbnailImage($img, $img);
$frame->setImagePage(0, 0, 0, 0);
}
$image->setCompressionQuality($compressionQuality);
$image->writeImages($fullPath . '/avatar_' . $meUserID . '_' . $key . $fileExt, true);
}
}
else
{
// determine the file extension from the uploaded file type
$fileExt = match ($avatar['type'])
{
'image/jpeg' => ".jpeg",
'image/jpg' => ".jpg",
'image/png' => ".png",
};
$defaultFileTypeForStaticImages = ".png";
foreach ($generatedAvatarSizes as $key => $img)
{
$imagick->cropThumbnailImage($img, $img);
$imagick->setCompressionQuality($compressionQuality);
$imagick->writeImages($fullPath . '/avatar_' . $meUserID . '_' . $key . $defaultFileTypeForStaticImages, true);
}
}
// redirect to avatar homepage upon successful avatar upload
meta_refresh(3, append_sid("{$phpbb_root_path}$scriptName.$phpEx"));
trigger_error($user->lang['AVATAR_UPLOAD_SUCCESS']);
}
}
// set the breadcrumbs
$template->assign_block_vars('navlinks', array(
'BREADCRUMB_NAME' => $user->lang['UPLOAD_AVATAR'],
'U_BREADCRUMB' => append_sid("{$phpbb_root_path}$scriptName.$phpEx?action=upload"),
));
break;
case 'view':
// user parameters
$user_id = $request->variable('u', ANONYMOUS);
$username = $request->variable('un', '', true);
if ($user_id == ANONYMOUS && !$username)
{
trigger_error('NO_USER');
}
$fallbackSize = "md";
$avatarSize = $request->variable('s', $fallbackSize);
// Check our size
if (!in_array($avatarSize, array('sm', 'md', 'lg')))
{
redirect("{$phpbb_root_path}$scriptName.$phpEx?action=view&u=$user_id&s=$fallbackSize");
}
$sql = 'SELECT u.user_avatar, u.user_id, u.user_avatar_type, u.user_email
FROM ' . USERS_TABLE . '
AS u
WHERE u.user_id =' . $user_id;
$result = $db->sql_query($sql);
$userResults = $db->sql_fetchrow($result);
$db->sql_freeresult($result);
// throw a message if the user doesn't exist - the 227-230 line code doesn't work for some reason!
if (!$userResults)
{
trigger_error('NO_USER');
}
// query the logged in user to obtain their theme
$sql = 'SELECT s.style_path
FROM ' . USERS_TABLE . '
AS u
LEFT JOIN ' . STYLES_TABLE . '
AS s
ON s.style_id = u.user_style
WHERE u.user_id =' . $meUserID;
$result = $db->sql_query($sql);
$authQuery = $db->sql_fetchrow($result);
$db->sql_freeresult($result);
// if script is disabled either in code or through ACP, return disabled avatars
if (!$config['allow_avatar'])
{
header('Content-Type: image/jpeg');
$path = $phpbb_root_path . 'styles/' . $authQuery['style_path'] . '/theme/images/no_avatar.jpg';
echo file_get_contents($path);
}
// get the extension from the user's table column
$ext = pathinfo($userResults['user_avatar'], PATHINFO_EXTENSION);
// build the fallback path (default phpbb avatars NOT using this script's uploader)
$phpBBNativeFilePath = $phpbb_root_path . $config['avatar_path'] . '/' . $config['avatar_salt'] . '_' . $userResults['user_id'] . '.' . $ext;
if (in_array($avatarSize, array('sm', 'md', 'lg')))
{
$filePath = $phpbb_root_path . $config['avatar_path'] . '/';
$file = glob($filePath . 'avatar_' . $user_id . '_' . $avatarSize . '.*')[0] ?? null;
// Don't cache anything!
header("Cache-Control: no-store, no-cache, must-revalidate, max-age=0");
header("Cache-Control: post-check=0, pre-check=0", false);
header("Pragma: no-cache");
if ($userResults['user_avatar_type'] === 'avatar.driver.gravatar')
{
$gravatarEmail = $userResults['user_avatar'] ?? $userResults['user_email'];
$gravatarHash = md5(strtolower(trim($gravatarEmail)));
$gravatarSize = "200";
$gravatar = 'https://gravatar.com/avatar/' . $gravatarHash . '?s=' . $gravatarSize;
header('Content-Type: image/png');
echo file_get_contents($gravatar);
}
elseif ((@file_exists($file) && @is_readable($file)) && !headers_sent())
{
$image_data = @getimagesize($file);
header('Content-Type: ' . image_type_to_mime_type($image_data[2]));
$size = @filesize($file);
if ($size)
{
header("Content-Length: $size");
}
if (@readfile($file))
{
echo file_get_contents($file);
}
}
elseif ((@file_exists($phpBBNativeFilePath) && @is_readable($phpBBNativeFilePath)) && !headers_sent())
{
$image_data = @getimagesize($phpBBNativeFilePath);
header('Content-Type: ' . image_type_to_mime_type($image_data[2]));
$size = @filesize($phpBBNativeFilePath);
if ($size)
{
header("Content-Length: $size");
}
if (@readfile($phpBBNativeFilePath) == false)
{
$fp = @fopen($phpBBNativeFilePath, 'rb');
if ($fp !== false)
{
while (!feof($fp))
{
echo fread($fp, 8192);
}
fclose($fp);
}
}
flush();
}
else
{
header('Content-Type: image/jpeg');
$path = $phpbb_root_path . 'styles/' . $authQuery['style_path'] . '/theme/images/no_avatar.jpg';
echo file_get_contents($path);
//echo file_get_contents('https://i.imgur.com/PzaYuY8.jpg');
}
}
break;
}
// set login required for the index page
if ($user->data['user_id'] == ANONYMOUS)
{
login_box('', $user->lang['LOGIN']);
}
// #################### END SCRIPT CODE #######################
// #################### DETERMINE SCRIPT STATE #######################
if (!$enableScript)
{
trigger_error($pageTitle . 's have been disabled.');
}
// ###### NOW YOUR TEMPLATE IS BEING RENDERED ######
page_header($pageTitle);
$template_vars = array(
'L_TITLE' => $pageTitle,
'L_UPLOAD_AVATAR' => $user->lang['UPLOAD_AVATAR'],
'MY_AVATAR' => get_custom_user_avatar($meUserID, 'lg'),
'S_UPLOAD_ACTION' => append_sid("{$phpbb_root_path}$scriptName.$phpEx?action=upload"),
'SM_AVATAR_URL' => append_sid("{$phpbb_root_path}$scriptName.$phpEx?action=view&u=$meUserID&s=sm"),
'MD_AVATAR_URL' => append_sid("{$phpbb_root_path}$scriptName.$phpEx?action=view&u=$meUserID&s=md"),
'LG_AVATAR_URL' => append_sid("{$phpbb_root_path}$scriptName.$phpEx?action=view&u=$meUserID&s=lg"),
);
$template->assign_vars($template_vars);
$template->set_filenames(array(
'body' => $templateName . '.html',
));
page_footer();
// ###### END TEMPLATE RENDERING ######
function phpbb_get_avatar($row, $alt, $ignore_config = false, $lazy = false)
{
return '<img class="avatar" src="' . generate_board_url() . '/avatar.php?action=view&u=' . $user->data['user_id'] . '&s=' . 'lg' . '" ' .
'alt="' . ((!empty($user->lang[$alt])) ? $user->lang[$alt] : $alt) . '" />';
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment