diff --git a/core/classes/Avatars/AvatarSource.php b/core/classes/Avatars/AvatarSource.php index fc647fe956..3ebdfa6429 100644 --- a/core/classes/Avatars/AvatarSource.php +++ b/core/classes/Avatars/AvatarSource.php @@ -1,175 +1,102 @@ getAvatar($uuid, self::getDefaultPerspective(), $size); + protected function __construct() { + $this->_cache = new Cache(['name' => 'nameless', 'extension' => '.cache', 'path' => ROOT_PATH . '/cache/']); } /** - * Get a user's avatar from their raw data object. - * Used by the API for TinyMCE mention avatars to avoid reloading the user from the database. - * - * @param object $data User data to use - * @param bool $allow_gifs Whether to allow GIFs or not () - * @param int $size Size in pixels to render avatar at. Default 128 - * @param bool $full Whether to return the full URL or just the path + * Get an avatar URL for a user. * - * @return string Full URL of avatar image. + * @param int|User $user User to fetch avatar for, or their ID. + * @param int $size Size of avatar to fetch in pixels. + * @param bool $full_url Whether to return the full external URL (ie: for display in Discord embed) or just the path. + * @return string The URL to the avatar. */ - public static function getAvatarFromUserData(object $data, bool $allow_gifs = false, int $size = 128, bool $full = false): string { - // If custom avatars are enabled, first check if they have gravatar enabled, and then fallback to normal image - if (defined('CUSTOM_AVATARS')) { - if ($data->gravatar) { - return 'https://secure.gravatar.com/avatar/' . md5(strtolower(trim($data->email))) . '?s=' . $size; - } - - if ($data->has_avatar) { - $exts = ['png', 'jpg', 'jpeg']; - - if ($allow_gifs) { - $exts[] = 'gif'; - } - - foreach ($exts as $ext) { - if (file_exists(ROOT_PATH . '/uploads/avatars/' . $data->id . '.' . $ext)) { - // We don't check the validity here since we know the file exists for sure - return ($full ? rtrim(URL::getSelfURL(), '/') : '') . ((defined('CONFIG_PATH')) ? CONFIG_PATH . '/' : '/') . 'uploads/avatars/' . $data->id . '.' . $ext . '?v=' . urlencode($data->avatar_updated); - } - } - } + public function getAvatarForUser($user, int $size = 128, bool $full_url = false): string { + if ($user instanceof User) { + $user_id = $user->data()->id; + } else { + $user_id = (int) $user; } - // Fallback to default avatar image if it is set and the avatar type is custom - if (defined('DEFAULT_AVATAR_TYPE') && DEFAULT_AVATAR_TYPE == 'custom' && DEFAULT_AVATAR_IMAGE !== '') { - if (file_exists(ROOT_PATH . '/uploads/avatars/defaults/' . DEFAULT_AVATAR_IMAGE)) { - // We don't check the validity here since we know the file exists for sure - return ($full ? rtrim(URL::getSelfURL(), '/') : '') . ((defined('CONFIG_PATH')) ? CONFIG_PATH . '/' : '/') . 'uploads/avatars/defaults/' . DEFAULT_AVATAR_IMAGE; + $this->_cache->setCache('avatars'); + + foreach ($this->getAllSources() as $source) { + if (!$source->isEnabled() && $source->canBeDisabled()) { + continue; } - } - // Attempt to get their MC avatar if Minecraft integration is enabled - if (Settings::get('mc_integration')) { - if ($data->uuid != null && $data->uuid != 'none') { - $uuid = $data->uuid; - } else { - $uuid = $data->username; - // Fallback to steve avatar if they have an invalid username - if (preg_match('#[^][_A-Za-z0-9]#', $uuid)) { - $uuid = 'Steve'; + $cache_key = $user_id . '_' . $source->getSafeName() . '_' . $size . '_' . (int) $full_url; + if ($this->_cache->isCached($cache_key)) { + $avatar = $this->_cache->retrieve($cache_key); + if ($avatar) { + return $avatar; } } - $url = self::getAvatarFromUUID($uuid, $size); - // The avatar might be invalid if they are using - // an MC avatar service that uses only UUIDs - // and this user doesn't have one - if (self::validImageUrl($url)) { - return $url; + if (!($user instanceof User)) { + $user = new User($user_id); } - } - return "https://api.dicebear.com/5.x/initials/png?seed={$data->username}&size={$size}"; - } - - /** - * Determine if a URL is a valid image URL for avatars. - * - * @param string $url URL to check - * @return bool Whether the URL is a valid image URL - */ - private static function validImageUrl(string $url): bool { - $cache = new Cache(['name' => 'nameless', 'extension' => '.cache', 'path' => ROOT_PATH . '/cache/']); - $cache->setCache('avatar_validity'); - - if ($cache->isCached($url)) { - return $cache->retrieve($url); - } - - $is_valid = false; - try { - $response = HttpClient::createClient()->head($url); - $headers = $response->getHeaders(); - if (isset($headers['Content-Type']) && $headers['Content-Type'][0] === 'image/png') { - $is_valid = true; + $avatar = $source->getAvatar($user, $size, $full_url); + if ($avatar) { + $url = $avatar; + // Cache for an hour incase a module does not reset the users avatar cache + $this->_cache->store($cache_key, $url, 3600); + break; } - } catch (Exception $ignored) { } - $cache->store($url, $is_valid, 3600); - return $is_valid; - } + // Fallback to initials avatar + if (!isset($url)) { + $url = $this->_sources[InitialsAvatarSource::class]->getAvatar($user, $size, $full_url); + } - /** - * Get the currently active avatar source. - * - * @return AvatarSourceBase The active source. - */ - public static function getActiveSource(): AvatarSourceBase { - return self::$_active_source; + return $url; } /** - * Set the active source to the source by name. - * Fallsback to Cravatar if name was not found. - * - * @param string $name Name of source to set as active. + * @param int|User $user + * @param string|null $source_class + * @return void */ - public static function setActiveSource(string $name): void { - $source = self::getSourceByName($name); - if ($source === null) { - $source = self::getSourceByName('cravatar'); + public function clearUserAvatarCache($user, string $source_class = null): void { + if ($user instanceof User) { + $user_id = $user->data()->id; + } else { + $user_id = (int) $user; } - self::$_active_source = $source; - } + $this->_cache->setCache('avatars'); - /** - * Get default perspective to pass to the active avatar source. - * - * @return string Perspective. - */ - private static function getDefaultPerspective(): string { - if (defined('DEFAULT_AVATAR_PERSPECTIVE')) { - return DEFAULT_AVATAR_PERSPECTIVE; + foreach (array_keys($this->_cache->retrieveAll()) as $cache_key) { + if (str_starts_with($cache_key, $user_id . '_' . ($source_class ?? ''))) { + $this->_cache->erase($cache_key); + } } - - return 'face'; } - /** - * Find an avatar source instance by it's name. - * - * @return AvatarSourceBase|null Instance if found, null if not found. - */ - public static function getSourceByName(string $name): ?AvatarSourceBase { - foreach (self::getAllSources() as $source) { - if (strtolower($source->getName()) == strtolower($name)) { - return $source; + public function clearSourceAvatarCache(string $source_class): void { + $this->_cache->setCache('avatars'); + + foreach (array_keys($this->_cache->retrieveAll()) as $cache_key) { + if (str_contains($cache_key, $source_class)) { + $this->_cache->erase($cache_key); } } - - return null; } /** @@ -177,23 +104,16 @@ public static function getSourceByName(string $name): ?AvatarSourceBase { * * @return AvatarSourceBase[] */ - public static function getAllSources(): iterable { - return self::$_sources; + public function getAllSources(): array { + $sources = $this->_sources; + uasort($sources, static function (AvatarSourceBase $a, AvatarSourceBase $b) { + return $a->getOrder() - $b->getOrder(); + }); + return $sources; } - /** - * Get raw url of active avatar source with placeholders. - * - * @return string URL with placeholders. - */ - public static function getUrlToFormat(): string { - // Default to Cravatar - if (!isset(self::$_active_source)) { - require_once(ROOT_PATH . '/modules/Core/classes/Avatars/CravatarAvatarSource.php'); - return (new CravatarAvatarSource())->getUrlToFormat(self::getDefaultPerspective()); - } - - return self::getActiveSource()->getUrlToFormat(self::getDefaultPerspective()); + public function getSourceBySafeName(string $safe_name): ?AvatarSourceBase { + return $this->_sources[$safe_name] ?? null; } /** @@ -201,41 +121,7 @@ public static function getUrlToFormat(): string { * * @param AvatarSourceBase $source Instance of avatar source to register. */ - public static function registerSource(AvatarSourceBase $source): void { - self::$_sources[] = $source; - } - - /** - * Get the names and base urls of all the registered avatar sources for displaying. - * Used for showing list of sources in staffcp. - * - * @return array List of names. - */ - public static function getAllSourceNames(): array { - $names = []; - - foreach (self::getAllSources() as $source) { - $names[$source->getName()] = rtrim($source->getBaseUrl(), '/'); - } - - return $names; - } - - /** - * Get key value array of all registered sources and their available perspectives. - * Used for autoupdating dropdown selector in staffcp. - * - * @return array> Array of source => [] perspectives. - */ - public static function getAllPerspectives(): array { - $perspectives = []; - - foreach (self::getAllSources() as $source) { - foreach ($source->getPerspectives() as $perspective) { - $perspectives[$source->getName()][] = $perspective; - } - } - - return $perspectives; + public function registerSource(AvatarSourceBase $source): void { + $this->_sources[$source->getSafeName()] = $source; } } diff --git a/core/classes/Avatars/AvatarSourceBase.php b/core/classes/Avatars/AvatarSourceBase.php index 14ecde017c..28eb65a762 100644 --- a/core/classes/Avatars/AvatarSourceBase.php +++ b/core/classes/Avatars/AvatarSourceBase.php @@ -1,109 +1,75 @@ _size = $size; + $this->_full_url = $full_url; + + return $this->get($user); + } - /** - * Base URL all avatars from this source will add on to. - */ - protected string $_base_url = ''; - - /** - * A map of `NamelessMC perspective name` => `Avatar source route`, - * as not all avatar sources will have the same name, and subsequentally route, for each perspective. - */ - protected array $_perspectives_map = []; - - /** - * Get the name of this avatar source. - * - * @return string Name of this avatar source. - */ public function getName(): string { return $this->_name; } - /** - * Get base url of this avatar source. - * - * @return string Base url of this source. - */ - public function getBaseUrl(): string { - return $this->_base_url; + public function getModule(): string { + return $this->_module; } - /** - * Get "NamelessMC names" of supported perspectives for this avatar source. - * - * @return array Array of perspective names. - */ - public function getPerspectives(): array { - return array_keys($this->_perspectives_map); + public function getSettings(): ?string { + return $this->_settings; + } + + public function getSettingsUrl(): ?string { + if ($this->_settings === null) { + return null; + } + + return URL::build('/panel/core/avatars/', 'action=settings&source=' . $this->getSafeName()); } - /** - * Get the URL for this users avatar. - * - * @param string $uuid UUID of avatar to get. - * @param string $perspective Perspective to render avatar with. - * @param int $size Size in pixels to render avatar at. Default 128 - * - * @return string Compiled URL of avatar image. - */ - public function getAvatar(string $uuid, string $perspective, int $size = 128): string { - return $this->formatUrl($this->getUrlToFormat($perspective), $uuid, $size); + public function isEnabled(): bool { + return $this->getDatabaseSettings()['enabled']; } - /** - * Replace placeholders in raw url with uuid and size of requested avatar. - * - * @param string $url_to_format Raw url to replace placeholders in. - * @param string $uuid uuid (or username, yuck!) of avatar to get. - * @param int $size Size of avatar image in pixels to get. - * - * @return string Formatted url. - */ - public function formatUrl(string $url_to_format, string $uuid, int $size): string { - return str_replace( - ['{identifier}', '{size}'], - [$uuid, $size], - $url_to_format - ); + public function canBeDisabled(): bool { + return $this->_can_be_disabled; } - /** - * Get raw URL with placeholders to format. - * - `{identifier} = UUID / username` - * - `{size} = size in pixels` - * - * @param string $perspective Perspective to use in url. - * - * @return string URL with placeholders to format. - */ - abstract public function getUrlToFormat(string $perspective): string; - - /** - * Translate NamelessMC perspective name to the relative name for this avatar source. - * - * @param string $perspective NamelessMC perspective name to translate. - * @return string Translated perspective name. - * @throws InvalidArgumentException When an invalid perspective is passed. - */ - public function getRelativePerspective(string $perspective): string { - $perspective = strtolower($perspective); - if (isset($this->_perspectives_map[$perspective])) { - return $this->_perspectives_map[$perspective]; + public function getOrder(): int { + return $this->getDatabaseSettings()['order']; + } + + public function getSafeName(): string { + return static::class; + } + + private function getDatabaseSettings(): array { + $settings = json_decode(Settings::get('avatar_source_settings'), true); + if (isset($settings[$this->getSafeName()])) { + return $settings[$this->getSafeName()]; } - $class = static::class; - throw new InvalidArgumentException("Attempted to get invalid perspective of: {$perspective} on {$class}"); + $settings[$this->getSafeName()] = [ + 'enabled' => true, + 'order' => 10, + ]; + + Settings::set('avatar_source_settings', json_encode($settings)); + + return $settings[$this->getSafeName()]; } + + abstract protected function get(User $user): ?string; + } diff --git a/core/classes/Core/Config.php b/core/classes/Core/Config.php index 1e82dede81..1fd52efd93 100644 --- a/core/classes/Core/Config.php +++ b/core/classes/Core/Config.php @@ -159,12 +159,6 @@ private static function parsePath(string $path) { return explode('.', $path); } - // TODO: Remove for 2.1.0 - if (str_contains($path, '/')) { - ErrorHandler::logWarning("Legacy config path: {$path}. Please use periods to seperate paths."); - return explode('/', $path); - } - return $path; } diff --git a/core/classes/Core/Configuration.php b/core/classes/Core/Configuration.php deleted file mode 100644 index 232a699aee..0000000000 --- a/core/classes/Core/Configuration.php +++ /dev/null @@ -1,54 +0,0 @@ -_module = $module; - } - - /** - * Get a configuration value - * - * @param string $setting Setting name - * - * @return mixed The configuration value - */ - public function get(string $setting) { - $table = 'nl2_' . preg_replace('/[^A-Za-z0-9_]+/', '', $this->_module) . '_settings'; - $data = DB::getInstance()->query("SELECT value FROM $table WHERE `name` = ?", [$setting]); - if ($data->count()) { - $results = $data->results(); - return $results[0]->value; - } - - return null; - } - - /** - * Set configuration value - * - * @param string $setting Setting name - * @param mixed $value New value - */ - public function set(string $setting, $value): void { - $table = 'nl2_' . preg_replace('/[^A-Za-z0-9_]+/', '', $this->_module) . '_settings'; - DB::getInstance()->query("UPDATE $table SET `value` = ? WHERE `name` = ?", [ - $value, - $setting, - ]); - } -} diff --git a/core/classes/Core/User.php b/core/classes/Core/User.php index 9545289bd8..6912a25ec6 100644 --- a/core/classes/Core/User.php +++ b/core/classes/Core/User.php @@ -431,25 +431,12 @@ public function getSignature(): string { * Get this user's avatar. * * @param int $size Size of image to render in pixels. - * @param bool $full Whether to use full site URL or not, for external loading - ie discord webhooks. + * @param bool $full_url Whether to use full site URL or not, for external loading - ie discord webhooks. * * @return string URL to their avatar image. */ - public function getAvatar(int $size = 128, bool $full = false): string { - $data_obj = new stdClass(); - // Convert UserData object to stdClass so we can dynamically add the 'uuid' property - foreach (get_object_vars($this->data()) as $key => $value) { - $data_obj->{$key} = $value; - } - - $integrationUser = $this->getIntegration('Minecraft'); - if ($integrationUser != null) { - $data_obj->uuid = $integrationUser->data()->identifier; - } else { - $data_obj->uuid = ''; - } - - return AvatarSource::getAvatarFromUserData($data_obj, $this->hasPermission('usercp.gif_avatar'), $size, $full); + public function getAvatar(int $size = 128, bool $full_url = false): string { + return AvatarSource::getInstance()->getAvatarForUser($this, $size, $full_url); } /** diff --git a/core/classes/Database/DatabaseInitialiser.php b/core/classes/Database/DatabaseInitialiser.php index d18147b3ca..af2dd18b39 100644 --- a/core/classes/Database/DatabaseInitialiser.php +++ b/core/classes/Database/DatabaseInitialiser.php @@ -208,11 +208,10 @@ private function initialiseSettings(): void { Settings::set('nameless_version', '2.1.1'); Settings::set('version_checked', date('U')); Settings::set('phpmailer', '0'); - Settings::set('user_avatars', '0'); - Settings::set('avatar_site', 'cravatar'); + Settings::set('minecraft_avatar_source', CravatarMinecraftAvatarSource::class); Settings::set(Settings::MINECRAFT_INTEGRATION, '1'); Settings::set('discord_integration', '0'); - Settings::set('avatar_type', 'helmavatar'); + Settings::set('minecraft_avatar_perspective', 'face'); Settings::set('home_type', 'news'); Settings::set('forum_reactions', '1'); Settings::set('error_reporting', '0'); @@ -225,7 +224,6 @@ private function initialiseSettings(): void { Settings::set('timezone', $_SESSION['install_timezone']); Settings::set('maintenance', '0'); Settings::set('maintenance_message', 'This website is currently in maintenance mode.'); - Settings::set('default_avatar_type', 'minecraft'); Settings::set('private_profile', '1'); Settings::set('validate_user_action', '{"action":"promote","group":1}'); Settings::set('login_method', 'email'); diff --git a/core/classes/Minecraft/MCQuery.php b/core/classes/Minecraft/MCQuery.php index bdd07578cb..8f0c071245 100644 --- a/core/classes/Minecraft/MCQuery.php +++ b/core/classes/Minecraft/MCQuery.php @@ -183,11 +183,11 @@ public static function formatPlayerList(array $player_list): array { $avatar = $user->getAvatar(); $profile = $user->getProfileURL(); } else { - $avatar = AvatarSource::getAvatarFromUUID($player['id']); + $avatar = MinecraftAvatarSource::getAvatarFromIdentifier($player['id']); $profile = '#'; } } else { - $avatar = AvatarSource::getAvatarFromUUID($player['id']); + $avatar = MinecraftAvatarSource::getAvatarFromIdentifier($player['id']); $profile = '#'; } diff --git a/core/classes/Misc/Report.php b/core/classes/Misc/Report.php index 03087e6ab3..4660be5e7d 100644 --- a/core/classes/Misc/Report.php +++ b/core/classes/Misc/Report.php @@ -57,7 +57,7 @@ public static function create(Language $language, User $user_reporting, User $re $reported_user->data()->username, $language->get('general', 'reported_by', ['author' => $user_reporting->data()->username]), $data['report_reason'], - $data['reported_id'] == 0 ? null : ($data['reported_uuid'] !== null ? AvatarSource::getAvatarFromUUID($data['reported_uuid']) : $reported_user->getAvatar()), + $data['reported_id'] == 0 ? null : ($data['reported_uuid'] !== null ? MinecraftAvatarSource::getAvatarFromIdentifier($data['reported_uuid']) : $reported_user->getAvatar()), $language->get('general', 'view_report'), rtrim(URL::getSelfURL(), '/') . URL::build('/panel/users/reports/', 'id=' . $id), )); diff --git a/core/includes/image_upload.php b/core/includes/image_upload.php index 5351d29295..de89f1b9e7 100644 --- a/core/includes/image_upload.php +++ b/core/includes/image_upload.php @@ -100,10 +100,6 @@ default: // Default to normal avatar upload - if (!defined('CUSTOM_AVATARS')) { - die('Custom avatar uploading is disabled'); - } - $folder = 'avatars'; $image->setName($user->data()->id); break; diff --git a/core/includes/updates/211.php b/core/includes/updates/211.php new file mode 100644 index 0000000000..8e9b888674 --- /dev/null +++ b/core/includes/updates/211.php @@ -0,0 +1,58 @@ +runMigrations(); + + $cache = new Cache(['name' => 'nameless', 'extension' => '.cache', 'path' => ROOT_PATH . '/cache/']); + + // Update avatar settings cache to use the class name for minecraft default source and swap to DB + $cache->setCache('avatar_settings_cache'); + if ($cache->isCached('avatar_source')) { + $default_source = $cache->retrieve('avatar_source'); + switch($default_source) { + case 'crafthead': + $default_source = CraftheadMinecraftAvatarSource::class; + break; + case 'crafatar': + $default_source = CrafatarMinecraftAvatarSource::class; + break; + case 'mc-heads': + $default_source = MCHeadsMinecraftAvatarSource::class; + break; + case 'minotar': + $default_source = MinotarMinecraftAvatarSource::class; + break; + case 'nameless': + $default_source = NamelessMCMinecraftAvatarSource::class; + break; + case 'visage': + $default_source = VisageMinecraftAvatarSource::class; + break; + default: + $default_source = CravatarMinecraftAvatarSource::class; + break; + } + + Settings::set('minecraft_avatar_source', $default_source); + $cache->erase('avatar_source'); + } + + // Rename `avatar_type` to `minecraft_avatar_perspective` and move to DB + // Ensure admin group has administrator perm + $cache->setCache('avatar_settings_cache'); + if ($cache->isCached('avatar_perspective')) { + $avatar_type = $cache->retrieve('avatar_perspective'); + Settings::set('avatar_perspective', $avatar_type); + $cache->erase('avatar_type'); + } + + // Move `custom_avatars` to DB + // TODO: make sure this is correct + $cache->setCache('avatar_settings_cache'); + if ($cache->isCached('custom_avatars')) { + $custom_avatars = $cache->retrieve('custom_avatars'); + Settings::set('custom_user_avatars', $custom_avatars); + $cache->erase('custom_avatars'); + } + } +}; diff --git a/core/init.php b/core/init.php index b02d0778a1..05a4dc731f 100644 --- a/core/init.php +++ b/core/init.php @@ -346,35 +346,7 @@ $smarty->assign('OG_IMAGE', rtrim(URL::getSelfURL(), '/') . $cache->retrieve('og_image')); } - // Avatars - $cache->setCache('avatar_settings_cache'); - if ($cache->isCached('custom_avatars') && $cache->retrieve('custom_avatars') == 1) { - define('CUSTOM_AVATARS', true); - } - - if ($cache->isCached('default_avatar_type')) { - define('DEFAULT_AVATAR_TYPE', $cache->retrieve('default_avatar_type')); - if (DEFAULT_AVATAR_TYPE == 'custom' && $cache->isCached('default_avatar_image')) { - define('DEFAULT_AVATAR_IMAGE', $cache->retrieve('default_avatar_image')); - } else { - define('DEFAULT_AVATAR_IMAGE', ''); - } - } else { - define('DEFAULT_AVATAR_TYPE', 'minecraft'); - } - - if ($cache->isCached('avatar_source')) { - define('DEFAULT_AVATAR_SOURCE', $cache->retrieve('avatar_source')); - } else { - define('DEFAULT_AVATAR_SOURCE', 'cravatar'); - } - - if ($cache->isCached('avatar_perspective')) { - define('DEFAULT_AVATAR_PERSPECTIVE', $cache->retrieve('avatar_perspective')); - } else { - define('DEFAULT_AVATAR_PERSPECTIVE', 'face'); - } - + /** @var Widgets $widgets */ $widgets = $container->get(Widgets::class); // Navbar links diff --git a/custom/languages/en_UK.json b/custom/languages/en_UK.json index 2544defff2..ac9bb649ec 100644 --- a/custom/languages/en_UK.json +++ b/custom/languages/en_UK.json @@ -47,6 +47,7 @@ "admin/author_x": "Author: {{author}}", "admin/auto_language_help": "If enabled, guests & logged out users will be able to let the website automatically detect their preferred language.", "admin/avatar_settings_updated_successfully": "Avatar settings updated successfully.", + "admin/avatars_info": "You can change the priority order of how avatars are displayed for users, and modify specific settings for avatar sources.", "admin/avatars": "Avatars", "admin/background_colour": "Background Colour", "admin/background_colour_required": "Background Colour is required", @@ -110,7 +111,6 @@ "admin/creating_profile_field": "Creating Profile Field", "admin/creating_reaction": "Creating Reaction", "admin/current_version_x": "Current version: {{version}}", - "admin/custom_avatar": "Custom avatar", "admin/custom_fields": "Custom Profile Fields", "admin/custom_pages": "Custom Pages", "admin/cloning_group": "Cloning group {{group}}", @@ -160,6 +160,7 @@ "admin/editing_announcement": "Editing Announcement", "admin/editing_announcement_failure": "Announcement update failed.", "admin/editing_announcement_success": "Announcement updated successfully.", + "admin/editing_avatar_source_x": "Editing avatar source {{avatarSource}}", "admin/editing_hook": "Editing Webhook", "admin/editing_integration_for_x": "Editing {{integration}} integration for {{user}}", "admin/editing_integration_x": "Editing integration {{integration}}", @@ -358,7 +359,6 @@ "admin/metadata_updated_successfully": "Metadata updated successfully.", "admin/method": "Method", "admin/minecraft": "Minecraft", - "admin/minecraft_avatar": "Minecraft avatar", "admin/minecraft_avatar_perspective": "Minecraft avatar perspective", "admin/minecraft_avatar_source": "Minecraft avatar source", "admin/minecraft_servers": "Minecraft Servers", @@ -703,6 +703,10 @@ "admin/updated_user_languages": "User languages have been updated.", "admin/updated_x": "Updated at: {{updatedAt}}", "admin/upload_new_image": "Upload New Image", + "admin/avatar_source_uploaded_image": "Uploaded Image", + "admin/avatar_source_initials": "Initials", + "admin/avatar_source_gravatar": "Gravatar", + "admin/avatar_source_minecraft": "Minecraft", "admin/use_friendly_urls": "Friendly URLs", "admin/use_friendly_urls_help": "If enabled, cleaner-looking web addresses will be used. You must allow the use of mod_rewrite and .htaccess files for this to work. {{docLinkStart}}Read more ยป{{docLinkEnd}}", "admin/user": "User", @@ -1159,6 +1163,7 @@ "user/email_changed_successfully": "Email address changed successfully.", "user/email_or_username": "Email or Username", "user/email_required": "An email address is required.", + "user/email_invalid": "The email address you have entered is invalid.", "user/enable": "Enable", "user/enabled": "Enabled", "user/enter_new_password": "Please confirm your email address and enter a new password below.", diff --git a/custom/panel_templates/Default/core/avatar_source_settings.tpl b/custom/panel_templates/Default/core/avatar_source_settings.tpl new file mode 100644 index 0000000000..418fbd4d9e --- /dev/null +++ b/custom/panel_templates/Default/core/avatar_source_settings.tpl @@ -0,0 +1,108 @@ +{include file='header.tpl'} + + + + +
+ + + {include file='sidebar.tpl'} + + +
+ + +
+ + + {include file='navbar.tpl'} + + +
+ + +
+

{$AVATARS}

+ +
+ + + {include file='includes/update.tpl'} + +
+
+ +
+
+
{$EDITING_AVATAR_SOURCE}
+
+
+ + {$BACK} + +
+
+
+ + + {include file='includes/alerts.tpl'} + + {include file=$SETTINGS_TEMPLATE} +
+
+ + +
+ + +
+ + +
+ + {include file='footer.tpl'} + + +
+ + + + + +
+ + {include file='scripts.tpl'} + + + + diff --git a/custom/panel_templates/Default/core/avatar_sources/minecraft.tpl b/custom/panel_templates/Default/core/avatar_sources/minecraft.tpl new file mode 100644 index 0000000000..d9473f85d4 --- /dev/null +++ b/custom/panel_templates/Default/core/avatar_sources/minecraft.tpl @@ -0,0 +1,58 @@ +
+
+ + +
+
+ + +
+
+ + +
+
+ + diff --git a/custom/panel_templates/Default/core/avatar_sources/uploaded_image.tpl b/custom/panel_templates/Default/core/avatar_sources/uploaded_image.tpl new file mode 100644 index 0000000000..1bfa3f35fc --- /dev/null +++ b/custom/panel_templates/Default/core/avatar_sources/uploaded_image.tpl @@ -0,0 +1,42 @@ +
+
+ + + +
+
+ + +
+
+ +
+ +{$DEFAULT_AVATAR} + +

+ + + +
+ +{if count($IMAGES)} +
+
+ + +
+
+ + +
+
+{else} + {$NO_AVATARS} +{/if} diff --git a/custom/panel_templates/Default/core/avatars.tpl b/custom/panel_templates/Default/core/avatars.tpl index 252ce28721..f2f106420d 100644 --- a/custom/panel_templates/Default/core/avatars.tpl +++ b/custom/panel_templates/Default/core/avatars.tpl @@ -38,78 +38,59 @@ {include file='includes/alerts.tpl'} - -
-
- - - -
-
- - -
-
- - +
+
+
{$INFO}
+ {$AVATARS_INFO}
-
- - -
-
- - -
- - -
- - {$DEFAULT_AVATAR} - -

- - - -

+
+
- {if count($IMAGES)}
-
- - + isEnabled() || !$source->canBeDisabled() eq 1}checked{/if} {if !$source->canBeDisabled()}disabled{/if} /> + +
+ + +
+
+ +
+ {if $source->getSettings()} + Settings + {/if} +
+ + {/foreach} - -
+ +
- {else} - {$NO_AVATARS} - {/if} - @@ -127,79 +108,49 @@ - - - - - {include file='scripts.tpl'} - - \ No newline at end of file + diff --git a/custom/panel_templates/Default/template.php b/custom/panel_templates/Default/template.php index 7c880f0054..fd95ae1dbc 100644 --- a/custom/panel_templates/Default/template.php +++ b/custom/panel_templates/Default/template.php @@ -177,26 +177,6 @@ public function onPageLoad() { break; - case 'avatars': - $this->assets()->include([ - AssetTree::DROPZONE, - AssetTree::IMAGE_PICKER, - ]); - - $this->addJSScript(' - // Dropzone options - Dropzone.options.upload_avatar_dropzone = { - maxFilesize: 2, - dictDefaultMessage: "' . $this->_language->get('admin', 'drag_files_here') . '", - dictInvalidFileType: "' . $this->_language->get('admin', 'invalid_file_type') . '", - dictFileTooBig: "' . $this->_language->get('admin', 'file_too_big') . '" - }; - - $(".image-picker").imagepicker(); - '); - - break; - case 'debugging_and_maintenance': $this->addCSSStyle(' .error_log { diff --git a/custom/templates/DefaultRevamp/css/custom.css b/custom/templates/DefaultRevamp/css/custom.css index 12a1fa23ed..31ee33ea9a 100755 --- a/custom/templates/DefaultRevamp/css/custom.css +++ b/custom/templates/DefaultRevamp/css/custom.css @@ -276,7 +276,7 @@ body.pushable>.pusher { } #reactions { - margin-top: 1rem; + margin-top: auto; padding: .7em 1.2em .7em 1.2em; min-height: 32.5px; font-size: 12px; @@ -288,7 +288,6 @@ body.pushable>.pusher { } #topic-post #post-content { - margin-top: auto; padding: .7em 1.2em .7em 1.2em; min-height: 32.5px; font-size: .85em; @@ -1090,6 +1089,17 @@ select { vertical-align: middle; } +/* + * [ USER MENTIONS ] + */ + +.user-mention { + background: #e7edf3; + padding: 2px 5px; + border-radius: 3px; + font-weight: bold; +} + /* * [ DARK MODE ] */ diff --git a/custom/templates/DefaultRevamp/leaderboards.tpl b/custom/templates/DefaultRevamp/leaderboards.tpl index bd27fb361b..166dcfb088 100644 --- a/custom/templates/DefaultRevamp/leaderboards.tpl +++ b/custom/templates/DefaultRevamp/leaderboards.tpl @@ -39,7 +39,7 @@ {if $data->name eq $placeholder->name and $data->server_id eq $placeholder->server_id} - {$data->username} + {$data->username} {$data->username} diff --git a/custom/templates/DefaultRevamp/template.php b/custom/templates/DefaultRevamp/template.php index ce487af751..edd31db918 100755 --- a/custom/templates/DefaultRevamp/template.php +++ b/custom/templates/DefaultRevamp/template.php @@ -90,7 +90,6 @@ public function onPageLoad() { 'siteURL' => URL::build('/'), 'fullSiteURL' => URL::getSelfURL() . ltrim(URL::build('/'), '/'), 'page' => PAGE, - 'avatarSource' => AvatarSource::getUrlToFormat(), 'copied' => $this->_language->get('general', 'copied'), 'cookieNotice' => $this->_language->get('general', 'cookie_notice'), 'noMessages' => $this->_language->get('user', 'no_messages'), @@ -114,6 +113,10 @@ public function onPageLoad() { 'csrfToken' => Token::get(), ]; + if (Settings::get('mc_integration')) { + $JSVariables['avatarSource'] = MinecraftAvatarSource::getUrlToFormat(); + } + // Logo $cache = new Cache(['name' => 'nameless', 'extension' => '.cache', 'path' => ROOT_PATH . '/cache/']); $cache->setCache('backgroundcache'); diff --git a/custom/templates/DefaultRevamp/user/settings.tpl b/custom/templates/DefaultRevamp/user/settings.tpl index ed6627b982..9451db3c9a 100755 --- a/custom/templates/DefaultRevamp/user/settings.tpl +++ b/custom/templates/DefaultRevamp/user/settings.tpl @@ -92,7 +92,7 @@ {/if} - {if isset($CUSTOM_AVATARS)} + {if $GRAVATAR_ENABLED}