| 1 |
<?php |
| 2 |
|
| 3 |
namespace FluentCommunity\App\Services; |
| 4 |
|
| 5 |
use FluentCommunity\App\Models\Meta; |
| 6 |
use FluentCommunity\App\Models\XProfile; |
| 7 |
|
| 8 |
class ProfileHelper |
| 9 |
{ |
| 10 |
|
| 11 |
public static function getXProfilePublicFields() |
| 12 |
{ |
| 13 |
return ['user_id', 'total_points', 'is_verified', 'status', 'display_name', 'username', 'avatar', 'created_at', 'last_activity', 'short_description', 'meta']; |
| 14 |
} |
| 15 |
|
| 16 |
public static function socialLinkProviders() |
| 17 |
{ |
| 18 |
return apply_filters('fluent_community/social_link_providers', [ |
| 19 |
'instagram' => [ |
| 20 |
'title' => 'Instagram', |
| 21 |
'icon_svg' => '<svg viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M6 0C7.6302 0 7.8336 0.00599997 8.4732 0.036C9.1122 0.066 9.5472 0.1662 9.93 0.315C10.326 0.4674 10.6596 0.6738 10.9932 1.0068C11.2983 1.30674 11.5344 1.66955 11.685 2.07C11.8332 2.4522 11.934 2.8878 11.964 3.5268C11.9922 4.1664 12 4.3698 12 6C12 7.6302 11.994 7.8336 11.964 8.4732C11.934 9.1122 11.8332 9.5472 11.685 9.93C11.5348 10.3307 11.2987 10.6936 10.9932 10.9932C10.6932 11.2982 10.3304 11.5343 9.93 11.685C9.5478 11.8332 9.1122 11.934 8.4732 11.964C7.8336 11.9922 7.6302 12 6 12C4.3698 12 4.1664 11.994 3.5268 11.964C2.8878 11.934 2.4528 11.8332 2.07 11.685C1.6694 11.5347 1.30652 11.2986 1.0068 10.9932C0.701644 10.6933 0.465559 10.3305 0.315 9.93C0.1662 9.5478 0.066 9.1122 0.036 8.4732C0.00779997 7.8336 0 7.6302 0 6C0 4.3698 0.00599997 4.1664 0.036 3.5268C0.066 2.8872 0.1662 2.4528 0.315 2.07C0.465142 1.66931 0.701282 1.30639 1.0068 1.0068C1.3066 0.701539 1.66946 0.465438 2.07 0.315C2.4528 0.1662 2.8872 0.066 3.5268 0.036C4.1664 0.00779997 4.3698 0 6 0ZM6 3C5.20435 3 4.44129 3.31607 3.87868 3.87868C3.31607 4.44129 3 5.20435 3 6C3 6.79565 3.31607 7.55871 3.87868 8.12132C4.44129 8.68393 5.20435 9 6 9C6.79565 9 7.55871 8.68393 8.12132 8.12132C8.68393 7.55871 9 6.79565 9 6C9 5.20435 8.68393 4.44129 8.12132 3.87868C7.55871 3.31607 6.79565 3 6 3ZM9.9 2.85C9.9 2.65109 9.82098 2.46032 9.68033 2.31967C9.53968 2.17902 9.34891 2.1 9.15 2.1C8.95109 2.1 8.76032 2.17902 8.61967 2.31967C8.47902 2.46032 8.4 2.65109 8.4 2.85C8.4 3.04891 8.47902 3.23968 8.61967 3.38033C8.76032 3.52098 8.95109 3.6 9.15 3.6C9.34891 3.6 9.53968 3.52098 9.68033 3.38033C9.82098 3.23968 9.9 3.04891 9.9 2.85ZM6 4.2C6.47739 4.2 6.93523 4.38964 7.27279 4.72721C7.61036 5.06477 7.8 5.52261 7.8 6C7.8 6.47739 7.61036 6.93523 7.27279 7.27279C6.93523 7.61036 6.47739 7.8 6 7.8C5.52261 7.8 5.06477 7.61036 4.72721 7.27279C4.38964 6.93523 4.2 6.47739 4.2 6C4.2 5.52261 4.38964 5.06477 4.72721 4.72721C5.06477 4.38964 5.52261 4.2 6 4.2Z" fill="currentColor"/></svg>', |
| 22 |
'placeholder' => 'instagram @username', |
| 23 |
'domain' => 'https://instagram.com/', |
| 24 |
], |
| 25 |
'twitter' => [ |
| 26 |
'title' => 'Twitter/X', |
| 27 |
'icon_svg' => '<svg viewBox="0 0 12 10" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M9.1024 0.125H10.7564L7.1429 4.255L11.3939 9.875H8.0654L5.4584 6.4665L2.47542 9.875H0.820422L4.68542 5.4575L0.607422 0.125H4.02042L6.3769 3.2405L9.1024 0.125ZM8.5219 8.885H9.4384L3.52242 1.063H2.53892L8.5219 8.885Z" fill="currentColor"/></svg>', |
| 28 |
'placeholder' => 'twitter/X @username', |
| 29 |
'domain' => 'https://x.com/', |
| 30 |
], |
| 31 |
'youtube' => [ |
| 32 |
'title' => 'YouTube', |
| 33 |
'icon_svg' => '<svg viewBox="0 0 12 10" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M11.7258 1.699C12 2.7682 12 5.0002 12 5.0002C12 5.0002 12 7.2322 11.7258 8.3014C11.5734 8.8924 11.1276 9.3574 10.563 9.5146C9.5376 9.8002 6 9.8002 6 9.8002C6 9.8002 2.4642 9.8002 1.437 9.5146C0.87 9.355 0.4248 8.8906 0.2742 8.3014C1.78814e-08 7.2322 0 5.0002 0 5.0002C0 5.0002 1.78814e-08 2.7682 0.2742 1.699C0.4266 1.108 0.8724 0.642995 1.437 0.485795C2.4642 0.200195 6 0.200195 6 0.200195C6 0.200195 9.5376 0.200195 10.563 0.485795C11.13 0.645395 11.5752 1.1098 11.7258 1.699ZM4.8 7.1002L8.4 5.0002L4.8 2.9002V7.1002Z" fill="currentColor"/></svg>', |
| 34 |
'placeholder' => 'youtube @username', |
| 35 |
'domain' => 'https://youtube.com/', |
| 36 |
], |
| 37 |
'linkedin' => [ |
| 38 |
'title' => 'LinkedIn', |
| 39 |
'icon_svg' => '<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M9.80061 9.8035H8.20161V7.2973C8.20161 6.6997 8.18961 5.9305 7.36761 5.9305C6.53421 5.9305 6.40701 6.5809 6.40701 7.2535V9.8035H4.80741V4.6501H6.34341V5.3521H6.36441C6.57921 4.9477 7.10121 4.5199 7.88121 4.5199C9.50121 4.5199 9.80121 5.5867 9.80121 6.9745V9.8035H9.80061ZM3.00141 3.9451C2.87934 3.94526 2.75845 3.92132 2.64565 3.87466C2.53285 3.828 2.43037 3.75954 2.34408 3.6732C2.2578 3.58686 2.1894 3.48433 2.14282 3.37151C2.09623 3.25868 2.07237 3.13776 2.07261 3.0157C2.07273 2.832 2.12732 2.65246 2.22947 2.49979C2.33163 2.34711 2.47677 2.22816 2.64653 2.15797C2.81629 2.08778 3.00305 2.06951 3.1832 2.10546C3.36334 2.14142 3.52878 2.22998 3.65859 2.35996C3.78841 2.48994 3.87676 2.65549 3.91248 2.83569C3.9482 3.01588 3.92969 3.20262 3.85928 3.37229C3.78887 3.54196 3.66973 3.68694 3.51692 3.7889C3.36412 3.89086 3.18451 3.94522 3.00081 3.9451H3.00141ZM3.80301 9.8035H2.19921V4.6501H3.80361V9.8035H3.80301ZM10.6016 0.600098H1.39701C0.955409 0.600098 0.599609 0.948098 0.599609 1.3783V10.6219C0.599609 11.0521 0.956009 11.4001 1.39641 11.4001H10.5992C11.0396 11.4001 11.3996 11.0521 11.3996 10.6219V1.3783C11.3996 0.948098 11.0396 0.600098 10.5992 0.600098H10.601H10.6016Z" fill="currentColor"/></svg>', |
| 40 |
'placeholder' => 'linkedin username', |
| 41 |
'domain' => 'https://linkedin.com/in/', |
| 42 |
], |
| 43 |
'fb' => [ |
| 44 |
'title' => 'Facebook', |
| 45 |
'icon_svg' => '<svg width="7" height="12" viewBox="0 0 7 12" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M4.2 6.9H5.7L6.3 4.5H4.2V3.3C4.2 2.682 4.2 2.1 5.4 2.1H6.3V0.0840001C6.1044 0.0582001 5.3658 0 4.5858 0C2.9568 0 1.8 0.9942 1.8 2.82V4.5H0V6.9H1.8V12H4.2V6.9Z" fill="currentColor"/></svg>', |
| 46 |
'placeholder' => 'fb username', |
| 47 |
'domain' => 'https://facebook.com/', |
| 48 |
], |
| 49 |
]); |
| 50 |
} |
| 51 |
|
| 52 |
public static function getReservedUserNames() |
| 53 |
{ |
| 54 |
return [ |
| 55 |
'admin', 'administrator', 'me', 'moderator', 'mod', 'superuser', 'root', 'system', 'official', 'staff', 'support', 'helpdesk', 'user', 'guest', 'anonymous', 'everyone', 'anybody', 'someone', 'webmaster', 'postmaster', 'hostmaster', 'abuse', 'security', 'ssl', 'firewall', 'no-reply', 'noreply', 'mail', 'email', 'mailer', 'smtp', 'pop', 'imap', 'ftp', 'sftp', 'ssh', 'ceo', 'cfo', 'cto', 'founder', 'cofounder', 'owner', 'president', 'vicepresident', 'director', 'manager', 'supervisor', 'executive', 'info', 'contact', 'sales', 'marketing', 'support', 'billing', 'accounting', 'finance', 'hr', 'humanresources', 'legal', 'compliance', 'it', 'itsupport', 'customerservice', 'customersupport', 'dev', 'developer', 'api', 'sdk', 'app', 'bot', 'chatbot', 'sysadmin', 'devops', 'infosec', 'security', 'test', 'testing', 'beta', 'alpha', 'staging', 'production', 'development', 'home', 'about', 'contact', 'faq', 'help', 'news', 'blog', 'forum', 'community', 'events', 'calendar', 'shop', 'store', 'cart', 'checkout', 'social', 'follow', 'like', 'share', 'tweet', 'post', 'status', 'privacy', 'terms', 'copyright', 'trademark', 'legal', 'policy', 'all', 'none', 'null', 'undefined', 'true', 'false', 'default', 'example', 'sample', 'demo', 'temporary', 'delete', 'remove', 'profanity', 'explicit', 'offensive', 'yourappname', 'yourbrandname', 'yourdomain', |
| 56 |
]; |
| 57 |
} |
| 58 |
|
| 59 |
public static function isUsernameAvailable($userName) |
| 60 |
{ |
| 61 |
$userName = strtolower($userName); |
| 62 |
$reservedUserNames = self::getReservedUserNames(); |
| 63 |
if (in_array($userName, $reservedUserNames)) { |
| 64 |
return false; |
| 65 |
} |
| 66 |
|
| 67 |
$user = get_user_by('login', $userName); |
| 68 |
|
| 69 |
if ($user) { |
| 70 |
return false; |
| 71 |
} |
| 72 |
|
| 73 |
$xProfile = XProfile::where('username', $userName)->exists(); |
| 74 |
|
| 75 |
if ($xProfile) { |
| 76 |
return false; |
| 77 |
} |
| 78 |
|
| 79 |
return true; |
| 80 |
} |
| 81 |
|
| 82 |
public static function generateUserName($user) |
| 83 |
{ |
| 84 |
if (!$user instanceof \WP_User) { |
| 85 |
$user = get_user_by('ID', $user); |
| 86 |
} |
| 87 |
|
| 88 |
$firstName = $user->first_name; |
| 89 |
if (!$firstName || strlen($firstName) < 3) { |
| 90 |
$firstName = $user->last_name; |
| 91 |
} |
| 92 |
|
| 93 |
if (!$firstName || strlen($firstName) < 3) { |
| 94 |
$firstName = $user->user_nicename; |
| 95 |
} |
| 96 |
|
| 97 |
if (!$firstName) { |
| 98 |
$firstName = $user->display_name; |
| 99 |
} |
| 100 |
|
| 101 |
$userName = strtolower($firstName); |
| 102 |
$userName = CustomSanitizer::sanitizeUserName($userName); |
| 103 |
$userName = substr($userName, 0, 20); |
| 104 |
|
| 105 |
if (!$userName) { |
| 106 |
$userName = 'user' . '-' . $user->ID; |
| 107 |
} |
| 108 |
|
| 109 |
$finalUserName = $userName; |
| 110 |
// loop until we find a unique username |
| 111 |
$counter = 2; |
| 112 |
while (!self::isUsernameAvailable($userName)) { |
| 113 |
$userName = $finalUserName . $counter; |
| 114 |
$counter++; |
| 115 |
if ($counter % 100 === 0) { |
| 116 |
$finalUserName = $finalUserName . $user->ID . '-'; |
| 117 |
} |
| 118 |
} |
| 119 |
|
| 120 |
return $userName; |
| 121 |
} |
| 122 |
|
| 123 |
public static function getUserAuthHash($userId = null) |
| 124 |
{ |
| 125 |
if (!$userId) { |
| 126 |
return ''; |
| 127 |
} |
| 128 |
|
| 129 |
static $cached = []; |
| 130 |
|
| 131 |
if (isset($cached[$userId])) { |
| 132 |
return $cached[$userId]; |
| 133 |
} |
| 134 |
|
| 135 |
$exist = Meta::byType('user') |
| 136 |
->byMetaKey('auth_hash') |
| 137 |
->byObjectId($userId) |
| 138 |
->first(); |
| 139 |
|
| 140 |
$validTil = strtotime('+1 day'); |
| 141 |
|
| 142 |
if ($exist) { |
| 143 |
$hashes = (array)$exist->value; |
| 144 |
|
| 145 |
$validHashes = array_filter($hashes, function ($hash) use ($validTil) { |
| 146 |
return $hash['valid_til'] > $validTil; |
| 147 |
}); |
| 148 |
|
| 149 |
if ($validHashes) { |
| 150 |
$lastHash = end($validHashes); |
| 151 |
return $lastHash['hash'] . '__' . $exist->id; |
| 152 |
} |
| 153 |
$newHash = md5(wp_generate_uuid4() . '_' . $userId . '_' . '_' . time() . '__' . $userId); |
| 154 |
|
| 155 |
$hashes[] = [ |
| 156 |
'hash' => $newHash, |
| 157 |
'valid_til' => strtotime('+2 days') |
| 158 |
]; |
| 159 |
|
| 160 |
// remove expired hashes |
| 161 |
$hashes = array_filter($hashes, function ($hash) use ($validTil) { |
| 162 |
return $hash['valid_til'] > time(); |
| 163 |
}); |
| 164 |
|
| 165 |
$exist->value = array_values($hashes); |
| 166 |
$exist->save(); |
| 167 |
|
| 168 |
$cached[$userId] = $newHash . '__' . $exist->id; |
| 169 |
|
| 170 |
return $cached[$userId]; |
| 171 |
} |
| 172 |
|
| 173 |
$newHash = md5(wp_generate_uuid4() . '_' . $userId . '_' . '_' . time() . '__' . $userId); |
| 174 |
|
| 175 |
$meta = Meta::create([ |
| 176 |
'object_type' => 'user', |
| 177 |
'object_id' => $userId, |
| 178 |
'meta_key' => 'auth_hash', |
| 179 |
'value' => [ |
| 180 |
[ |
| 181 |
'hash' => $newHash, |
| 182 |
'valid_til' => strtotime('+2 days') |
| 183 |
] |
| 184 |
] |
| 185 |
]); |
| 186 |
|
| 187 |
$cached[$userId] = $newHash . '__' . $meta->id; |
| 188 |
|
| 189 |
return $cached[$userId]; |
| 190 |
} |
| 191 |
|
| 192 |
public static function signUserUrlWithAuthHash($url, $userId = null) |
| 193 |
{ |
| 194 |
$hash = self::getUserAuthHash($userId); |
| 195 |
if (!$hash) { |
| 196 |
return $url; |
| 197 |
} |
| 198 |
|
| 199 |
return add_query_arg([ |
| 200 |
'fcom_action' => 'signed_url', |
| 201 |
'fcom_url_hash' => $hash |
| 202 |
], $url); |
| 203 |
} |
| 204 |
|
| 205 |
public static function getSignedNotificationPrefUrl($userId) |
| 206 |
{ |
| 207 |
$notificationPref = Helper::baseUrl('fcom_route?route=user_notification_settings&auth=yes'); |
| 208 |
return self::signUserUrlWithAuthHash($notificationPref, $userId); |
| 209 |
} |
| 210 |
|
| 211 |
/* |
| 212 |
* Get WP User by URL Hash |
| 213 |
* @param string $hash |
| 214 |
* @return \WP_User|null |
| 215 |
*/ |
| 216 |
public static function getUserByUrlHash($hash) |
| 217 |
{ |
| 218 |
$hashParts = explode('__', $hash); |
| 219 |
if (count($hashParts) !== 2) { |
| 220 |
return null; |
| 221 |
} |
| 222 |
|
| 223 |
$metaId = $hashParts[1]; |
| 224 |
$meta = Meta::where('object_type', 'user') |
| 225 |
->where('meta_key', 'auth_hash') |
| 226 |
->where('id', $metaId) |
| 227 |
->first(); |
| 228 |
|
| 229 |
if (!$meta) { |
| 230 |
return null; |
| 231 |
} |
| 232 |
|
| 233 |
$hashes = (array)$meta->value; |
| 234 |
$hash = $hashParts[0]; |
| 235 |
|
| 236 |
$validHash = array_filter($hashes, function ($hashData) use ($hash) { |
| 237 |
return $hashData['hash'] == $hash; |
| 238 |
}); |
| 239 |
|
| 240 |
if (!$validHash) { |
| 241 |
return null; |
| 242 |
} |
| 243 |
|
| 244 |
return get_user_by('ID', $meta->object_id); |
| 245 |
} |
| 246 |
} |
| 247 |
|