general.php

main 737 lines · 20.2 KB Raw
Alex Alex Commit Initial commit 01/10/2026 09:20
1<?php
2
3function setSession($key, $data) {
4 global $sessionPrefix;
5 $_SESSION[$sessionPrefix.$key] = $data;
6}
7function getSession($key) {
8 global $sessionPrefix;
9 return (isset($_SESSION[$sessionPrefix.$key])) ? $_SESSION[$sessionPrefix.$key] : false;
10}
11// Fetch and sanitize POST and GET values
12function getValue($value) {
13 return (!empty($value)) ? sanitize($value) : false;
14}
15
16function SendGet($getArray, $location = 'error.php') {
17 $string = "";
18 $count = 0;
19 foreach ($getArray as $getKey => $getValue) {
20 if ($count > 0) $string .= '&';
21 header('Location: ' . $location . '?' . http_build_query($getArray));
22 exit;
23 }
24 header("Location: {$location}?{$string}");
25 exit();
26}
27
28// Sweet error reporting
29function data_dump($print = false, $var = false, $title = false) {
30 if ($title !== false) echo "<pre><font color='red' size='5'>$title</font><br>";
31 else echo '<pre>';
32 if ($print !== false) {
33 echo 'Print: - ';
34 print_r($print);
35 echo "<br>";
36 }
37 if ($var !== false) {
38 echo 'Var_dump: - ';
39 var_dump($var);
40 }
41 echo '</pre><br>';
42}
43
44function accountAccess($accountId, $TFS) {
45 $accountId = (int)$accountId;
46 $access = 0;
47
48 // TFS 0.3/4
49 $yourChars = db()->fetchAll("SELECT `name`, `group_id`, `account_id` FROM `players` WHERE `account_id` = ?;", [$accountId]);
50 if ($yourChars !== false) {
51 foreach ($yourChars as $char) {
52 if ($TFS === 'TFS_03' || $TFS === 'OTHIRE') {
53 if ($char['group_id'] > $access) $access = $char['group_id'];
54 } else {
55 if ($char['group_id'] > 1) {
56 if ($access == 0) {
57 $acc = db()->fetchOne("SELECT `type` FROM `accounts` WHERE `id` = ? LIMIT 1;", [$char['account_id']]);
58 $access = $acc['type'];
59 }
60 }
61 }
62 }
63 if ($access == 0) $access++;
64 return $access;
65 } else return false;
66 //
67}
68// Generate recovery key
69function generate_recovery_key($length) {
70 return substr(bin2hex(random_bytes(32)), 0, (int)$length);
71}
72
73// Calculate discount
74function calculate_discount($orig, $new) {
75 $orig = (int)$orig;
76 $new = (int)$new;
77
78 $tmp = '';
79 if ($new >= $orig) {
80 if ($new != $orig) {
81 $calc = ($new/$orig) - 1;
82 $calc *= 100;
83 $tmp = '+'. floor($calc) .'%';
84 } else $tmp = '0%';
85 } else {
86 $calc = 1 - ($new/$orig);
87 $calc *= 100;
88 $tmp = '-'. floor($calc) .'%';
89 }
90 return $tmp;
91}
92
93// Proper URLs
94function url($path = false) {
95 $folder = dirname($_SERVER['SCRIPT_NAME']);
96 return config('site_url') . '/' . $path;
97}
98
99function getCache() {
100 $results = db()->fetchOne("SELECT `cached` FROM `znote`;");
101 return ($results !== false) ? $results['cached'] : false;
102}
103
104function setCache($time) {
105 db()->execute("UPDATE `znote` set `cached` = ?", [(int)$time]);
106}
107
108// Get visitor basic data
109function znote_visitors_get_data() {
110 return db()->fetchAll("SELECT `ip`, `value` FROM `znote_visitors` ORDER BY `id` DESC LIMIT 1000;");
111}
112
113// Set visitor basic data
114function znote_visitor_set_data($visitor_data) {
115 $exist = false;
116 $ip = getIPLong();
117
118 foreach ((array)$visitor_data as $row) {
119 if ($ip == $row['ip']) {
120 $exist = true;
121 $value = $row['value'];
122 }
123 }
124
125 if ($exist && isset($value)) {
126 // Update the value
127 $value++;
128 db()->execute("UPDATE `znote_visitors` SET `value` = ? WHERE `ip` = ?", [$value, $ip]);
129 } else {
130 // Insert new row
131 db()->execute("INSERT INTO `znote_visitors` (`ip`, `value`) VALUES (?, 1)", [$ip]);
132 }
133}
134
135// Get visitor basic data
136function znote_visitors_get_detailed_data($cache_time) {
137 $period = (int)time() - (int)$cache_time;
138 return db()->fetchAll("SELECT `ip`, `time`, `type`, `account_id` FROM `znote_visitors_details` WHERE `time` >= ? LIMIT 0, 50", [$period]);
139}
140
141function znote_visitor_insert_detailed_data($type) {
142 $type = (int)$type;
143 /*
144 type 0 = normal visits
145 type 1 = register form
146 type 2 = character creation
147 type 3 = fetch highscores
148 type 4 = search character
149 */
150 $time = time();
151 $ip = getIPLong();
152 $acc = user_logged_in() ? (int)getSession('user_id') : 0;
153 db()->execute("INSERT INTO `znote_visitors_details` (`ip`, `time`, `type`, `account_id`) VALUES (?, ?, ?, ?)", [$ip, $time, $type, $acc]);
154}
155
156function something () {
157 // Make acc data compatible:
158 $ip = getIPLong();
159}
160
161// Secret token
162function create_token() {
163 if (empty($_SESSION['token'])) {
164 $_SESSION['token'] = bin2hex(random_bytes(32));
165 }
166 echo '<input type="hidden" name="token" value="'.$_SESSION['token'].'">';
167}
168
169function reset_token() {
170 echo 'Reseting token<br />';
171 unset($_SESSION['token']);
172}
173
174// Time based functions
175// 60 seconds to 1 minute
176function second_to_minute($seconds) {
177 return ($seconds / 60);
178}
179
180// 1 minute to 60 seconds
181function minute_to_seconds($minutes) {
182 return ($minutes * 60);
183}
184
185// 60 minutes to 1 hour
186function minute_to_hour($minutes) {
187 return ($minutes / 60);
188}
189
190// 1 hour to 60 minutes
191function hour_to_minute($hours) {
192 return $hours * 60;
193}
194
195// seconds / 60 / 60 = hours.
196function seconds_to_hours($seconds) {
197 $minutes = second_to_minute($seconds);
198 $hours = minute_to_hour($minutes);
199 return $hours;
200}
201
202function remaining_seconds_to_clock($seconds) {
203 return date("(H:i)",time() + $seconds);
204}
205
206/**
207 * Check if name contains more than configured max words
208 *
209 * @param string $string
210 * @return string|boolean
211 */
212function validate_name($string) {
213 $max = config('maxW') ?? 3;
214 return (str_word_count(trim($string)) > $max) ? false : trim($string);
215}
216
217// Checks if an IPv4(or localhost IPv6) address is valid
218function validate_ip($ip) {
219 $ipL = safeIp2Long($ip);
220 $ipR = long2ip((int)$ipL);
221
222 if ($ip === $ipR) {
223 return true;
224 } elseif ($ip=='::1') {
225 return true;
226 } else {
227 return false;
228 }
229}
230
231// Fetch a config value. Etc config('vocations') will return vocation array from config.php.
232function config($value) {
233 global $config;
234 return $config[$value] ?? null;
235}
236
237function serverEngineReal() {
238 global $config;
239 return $config['ServerEngineReal'] ?? ($config['ServerEngine'] ?? 'TFS_10');
240}
241
242function engineIsCanary() {
243 return serverEngineReal() === 'CANARY';
244}
245
246function engineIsTFS16() {
247 return serverEngineReal() === 'TFS_16';
248}
249
250function accountField($field) {
251 if ($field === 'premium_ends_at') {
252 if (function_exists('znote_column_exists') && znote_column_exists('accounts', 'premium_ends_at')) {
253 return '`premium_ends_at`';
254 }
255 if (function_exists('znote_column_exists') && znote_column_exists('accounts', 'lastday') && znote_column_exists('accounts', 'premdays')) {
256 return '(`lastday` + (`premdays` * 86400)) AS `premium_ends_at`';
257 }
258 if (function_exists('znote_column_exists') && znote_column_exists('accounts', 'premdays')) {
259 return '(CASE WHEN `premdays` > 0 THEN UNIX_TIMESTAMP() + (`premdays` * 86400) ELSE 0 END) AS `premium_ends_at`';
260 }
261 }
262
263 if (engineIsCanary()) {
264 if ($field === 'secret') return 'NULL AS `secret`';
265 }
266 return '`' . $field . '`';
267}
268
269function accountFieldList(array $fields) {
270 return implode(', ', array_map('accountField', $fields));
271}
272
273function houseCol($name) {
274 if (!engineIsCanary()) return $name;
275 $map = array(
276 'bid' => 'internal_bid',
277 'bid_end' => 'bid_end_date',
278 'last_bid' => 'highest_bid',
279 'highest_bidder' => 'bidder',
280 );
281 return $map[$name] ?? $name;
282}
283
284function houseSelect(array $fields, $prefix = '') {
285 $p = ($prefix !== '') ? '`' . $prefix . '`.' : '';
286 $out = array();
287 foreach ($fields as $f) {
288 $phys = houseCol($f);
289 $out[] = ($phys === $f) ? $p . '`' . $f . '`' : $p . '`' . $phys . '` AS `' . $f . '`';
290 }
291 return implode(', ', $out);
292}
293
294function sqlIpWrite($ipLong) {
295 if (engineIsTFS16()) {
296 return "INET6_ATON('" . mysql_znote_escape_string(long2ip((int)$ipLong)) . "')";
297 }
298 return "'" . (int)$ipLong . "'";
299}
300
301function sqlIpSelect($column, $alias, $prefix = '') {
302 $p = ($prefix !== '') ? '`' . $prefix . '`.' : '';
303 if (engineIsTFS16()) {
304 return 'INET_ATON(INET6_NTOA(' . $p . '`' . $column . '`)) AS `' . $alias . '`';
305 }
306 return $p . '`' . $column . '` AS `' . $alias . '`';
307}
308
309// Some functions uses several configurations from config.php, so it sounds
310// smarter to give them the whole array instead of calling the function all the time.
311function fullConfig() {
312 global $config;
313 return $config;
314}
315
316// Capitalize Every Word In String.
317function format_character_name($name) {
318 return ucwords(strtolower($name));
319}
320
321// Gets you the actual IP address even from users behind ISP proxies and so on.
322function getIP() {
323 /*
324 $IP = '';
325 if (getenv('HTTP_CLIENT_IP')) {
326 $IP =getenv('HTTP_CLIENT_IP');
327 } elseif (getenv('HTTP_X_FORWARDED_FOR')) {
328 $IP =getenv('HTTP_X_FORWARDED_FOR');
329 } elseif (getenv('HTTP_X_FORWARDED')) {
330 $IP =getenv('HTTP_X_FORWARDED');
331 } elseif (getenv('HTTP_FORWARDED_FOR')) {
332 $IP =getenv('HTTP_FORWARDED_FOR');
333 } elseif (getenv('HTTP_FORWARDED')) {
334 $IP = getenv('HTTP_FORWARDED');
335 } else {
336 $IP = $_SERVER['REMOTE_ADDR'];
337 } */
338return $_SERVER['REMOTE_ADDR'];
339}
340
341function safeIp2Long($ip) {
342 return sprintf('%u', ip2long($ip));
343}
344
345// Gets you the actual IP address even from users in long type
346function getIPLong() {
347 return safeIp2Long(getIP());
348}
349
350// Deprecated, just use count($array) instead.
351function array_length($ar) {
352 return count($ar);
353}
354// Parameter: level, returns experience for that level from an experience table.
355function level_to_experience($level) {
356 return 50/3*(pow($level, 3) - 6*pow($level, 2) + 17*$level - 12);
357}
358
359// Parameter: players.hide_char returns: Status word inside a font with class identifier so it can be designed later on by CSS.
360function hide_char_to_name($id) {
361 $id = (int)$id;
362 if ($id == 1) {
363 return 'hidden';
364 } else {
365 return 'visible';
366 }
367}
368
369// Parameter: players.online returns: Status word inside a font with class identifier so it can be designed later on by CSS.
370function online_id_to_name($id) {
371 $id = (int)$id;
372 if ($id == 1) {
373 return '<font class="status_online">ONLINE</font>';
374 } else {
375 return '<font class="status_offline">offline</font>';
376 }
377}
378
379// Parameter: players.vocation_id. Returns: Configured vocation name.
380function vocation_id_to_name($id) {
381 $vocations = config('vocations');
382 return (isset($vocations[$id]['name'])) ? $vocations[$id]['name'] : "{$id} - Unknown";
383}
384
385// Parameter: players.name. Returns: Configured vocation id.
386function vocation_name_to_id($name) {
387 $vocations = config('vocations');
388 foreach ($vocations as $id => $vocation)
389 if ($vocation['name'] == $name)
390 return $id;
391 return false;
392}
393
394// Parameter: players.group_id. Returns: Configured group name.
395function group_id_to_name($id) {
396 $positions = config('ingame_positions');
397 return $positions[$id] ?? false;
398}
399
400function gender_exist($gender) {
401 // Range of allowed gender ids, fromid toid
402 if ($gender >= 0 && $gender <= 1) {
403 return true;
404 } else {
405 return false;
406 }
407}
408
409function skillid_to_name($skillid) {
410 $skillname = [
411 0 => 'fist fighting',
412 1 => 'club fighting',
413 2 => 'sword fighting',
414 3 => 'axe fighting',
415 4 => 'distance fighting',
416 5 => 'shielding',
417 6 => 'fishing',
418 7 => 'experience',
419 8 => 'magic level'
420 ];
421
422 return $skillname[$skillid] ?? false;
423}
424
425// Parameter: players.town_id. Returns: Configured town name.
426function town_id_to_name($id) {
427 $towns = config('towns');
428 return (array_key_exists($id, $towns)) ? $towns[$id] : 'Missing Town';
429}
430
431// On this version we included From, because without that nowdays the email is classed directly as spam, using a From prevents that.
432function email($to, $subject, $body) {
433 $headers = "From: noreply@znoteaac.com\r\nContent-Type: text/plain; charset=UTF-8";
434 mail($to, $subject, $body, $headers);
435}
436
437function logged_in_redirect() {
438 if (user_logged_in() === true) {
439 header('Location: myaccount.php');
440 exit;
441 }
442}
443
444function protect_page() {
445 if (user_logged_in() === false) {
446 header('Location: protected.php');
447 exit;
448 }
449}
450
451// When function is called, you will be redirected to protect_page and deny access to rest of page, as long as you are not admin.
452function admin_only($user_data) {
453 // Chris way
454 $gotAccess = is_admin($user_data);
455
456 if ($gotAccess == false) {
457 logged_in_redirect();
458 exit();
459 }
460}
461
462function admin_roles($user_data): array {
463 if (!is_array($user_data)) return array();
464
465 $identity = config('ServerEngine') === 'OTHIRE'
466 ? ($user_data['id'] ?? null)
467 : ($user_data['name'] ?? null);
468 $owners = (array)config('page_admin_access');
469 if (in_array($identity, $owners)) {
470 return array('owner');
471 }
472
473 $assignments = (array)config('page_admin_roles');
474 foreach ($assignments as $account => $assigned) {
475 if ((string)$account === (string)$identity) {
476 $modules = is_array($assigned) ? $assigned : array($assigned);
477 return array_values(array_unique(array_filter(array_map(
478 static fn($module) => strtolower(trim((string)$module)),
479 $modules
480 ), static fn($module) => $module !== '')));
481 }
482 }
483
484 return array();
485}
486
487function has_admin_panel_access($user_data): bool {
488 return admin_roles($user_data) !== array();
489}
490
491// Legacy full-admin checks deliberately remain owner-only. This prevents a
492// scoped ACP role from inheriting unrestricted forum or maintenance powers.
493function is_admin($user_data) {
494 return in_array('owner', admin_roles($user_data), true);
495}
496
497function array_sanitize(&$item) {
498 $item = htmlentities(strip_tags(mysql_znote_escape_string($item)));
499}
500
501function sanitize($data) {
502 return htmlspecialchars(strip_tags((string)($data ?? '')), ENT_QUOTES, 'UTF-8');
503}
504
505function output_errors($errors) {
506 return '<ul><li>'. implode('</li><li>', $errors) .'</li></ul>';
507}
508
509// Resize images and create image
510function resize_imagex($file, $width, $height) {
511 list($w, $h) = getimagesize($file['tmp']);
512 if (!function_exists('imagecreatefromstring')) {
513 return false;
514 }
515 $ratio = max($width/$w, $height/$h);
516 $h = ceil($height / $ratio);
517 $x = ($w - $width / $ratio) / 2;
518 $w = ceil($width / $ratio);
519
520 $path = 'engine/guildimg/'.$file['new_name'];
521
522 $imgString = file_get_contents($file['tmp']);
523
524 $image = imagecreatefromstring($imgString);
525 $tmp = imagecreatetruecolor($width, $height);
526 imagecopyresampled($tmp, $image,
527 0, 0,
528 $x, 0,
529 $width, $height,
530 $w, $h
531 );
532
533 imagegif($tmp, $path);
534 imagedestroy($image);
535 imagedestroy($tmp);
536
537 return true;
538}
539
540function guild_logo_safe_name(string $name): ?string {
541 $name = trim($name);
542 if ($name === '' || strlen($name) > 60) {
543 return null;
544 }
545 if (preg_match('#[\\\\/\x00]#', $name) || strpos($name, '..') !== false) {
546 return null;
547 }
548 return $name;
549}
550
551// Validate guild logo
552function check_image($image) {
553 $rawName = (string)($_GET['name'] ?? '');
554 $safeName = guild_logo_safe_name($rawName);
555 if ($safeName === null) {
556 header('Location: guilds.php?error=Invalid guild name.');
557 exit;
558 }
559
560 $image_data = array(
561 'new_name' => $safeName.'.gif',
562 'name' => $image['name'],
563 'tmp' => $image['tmp_name'],
564 'error' => $image['error'],
565 'size' => $image['size'],
566 'type' => $image['type']
567 );
568
569 if ($image_data['type'] !== 'image/gif') {
570 header('Location: guilds.php?error=Only gif images are accepted, you uploaded:['.$image_data['type'].'].&name='. urlencode($rawName));
571 exit;
572 }
573
574 $check = getimagesize($image_data['tmp']);
575 if (!$check) {
576 header('Location: guilds.php?error=Uploaded image is invalid.&name='. urlencode($rawName));
577 exit;
578 }
579
580 if ($check['mime'] !== 'image/gif') {
581 header('Location: guilds.php?error=Only gif images accepted, you uploaded:['.$check['mime'].'].&name='. urlencode($rawName));
582 exit;
583 }
584
585 $path_info = pathinfo($image_data['name']);
586 if ($path_info['extension'] !== 'gif') {
587 header('Location: guilds.php?error=Only gif images accepted, you uploaded:['.$path_info['extension'].'].&name='. urlencode($rawName));
588 exit;
589 }
590
591 // Resize image
592 if (resize_imagex($image_data, 100, 100)) {
593 header('Location: guilds.php?name='. urlencode($rawName));
594 exit;
595 }
596}
597
598function generateRandomString($length = 16) {
599 return substr(strtoupper(bin2hex(random_bytes($length))), 0, $length);
600}
601
602function verifyGoogleReCaptcha($postResponse = null) {
603 if(!isset($postResponse) || empty($postResponse)) {
604 return false;
605 }
606
607 $recaptcha_api_url = 'https://www.google.com/recaptcha/api/siteverify';
608 $secretKey = config('captcha_secret_key');
609 $ip = $_SERVER['REMOTE_ADDR'];
610 $params = 'secret='.$secretKey.'&response='.$postResponse.'&remoteip='.$ip;
611
612 $useCurl = config('captcha_use_curl');
613 if($useCurl) {
614 $curl_connection = curl_init($recaptcha_api_url);
615
616 curl_setopt($curl_connection, CURLOPT_CONNECTTIMEOUT, 5);
617 curl_setopt($curl_connection, CURLOPT_RETURNTRANSFER, true);
618 curl_setopt($curl_connection, CURLOPT_SSL_VERIFYPEER, false);
619 curl_setopt($curl_connection, CURLOPT_FOLLOWLOCATION, 0);
620 curl_setopt($curl_connection, CURLOPT_POSTFIELDS, $params);
621
622 $response = curl_exec($curl_connection);
623 curl_close($curl_connection);
624 } else {
625 $response = file_get_contents($recaptcha_api_url . '?' . $params);
626 }
627
628 $json = json_decode($response);
629 return isset($json->success) && $json->success;
630}
631
632// html encoding function (encode any string to valid UTF-8 HTML)
633function hhb_tohtml(/*string*/ $str)/*:string*/ {
634 return htmlentities($str, ENT_QUOTES | ENT_HTML401 | ENT_SUBSTITUTE | ENT_DISALLOWED, 'UTF-8', true);
635}
636
637// php5-compatibile version of php7's random_bytes()
638// $crypto_strong: a boolean value that determines if the algorithm used was "cryptographically strong"
639function random_bytes_compat($length, &$crypto_strong = null) {
640 $crypto_strong = false;
641 if (!is_int($length)) {
642 throw new \InvalidArgumentException("argument 1 must be an int, is " . gettype($length));
643 }
644 if ($length < 0) {
645 throw new \InvalidArgumentException("length must be >= 0");
646 }
647 if (is_callable("random_bytes")) {
648 $crypto_strong = true;
649 return random_bytes($length);
650 }
651 if (is_callable("openssl_random_pseudo_bytes")) {
652 return openssl_random_pseudo_bytes($length, $crypto_strong);
653 }
654 $ret = @file_get_contents("/dev/urandom", false, null, 0, $length);
655 if (is_string($ret) && strlen($ret) === $length) {
656 $crypto_strong = true;
657 return $ret;
658 }
659 // fallback to non-cryptographically-secure mt_rand() implementation...
660 $crypto_strong = false;
661 $ret = "";
662 for ($i = 0; $i < $length; ++$i) {
663 $ret .= chr(mt_rand(0, 255));
664 }
665 return $ret;
666}
667
668// hash_equals legacy support < 5.6
669if(!function_exists('hash_equals')) {
670 function hash_equals($str1, $str2) {
671 if(strlen($str1) != strlen($str2)) {
672 return false;
673 }
674 $res = $str1 ^ $str2;
675 $ret = 0;
676 for($i = strlen($res) - 1; $i >= 0; $i--) {
677 $ret |= ord($res[$i]);
678 }
679 return !$ret;
680 }
681}
682/**
683 * Path to the CA bundle shipped with ZnoteX, or '' when the PHP install
684 * already has one configured.
685 *
686 * PHP on Windows ships without a certificate store: unless curl.cainfo is set
687 * in php.ini, every HTTPS request fails with curl error 60. The repo carries a
688 * bundle for exactly this reason - ipn.php has always pointed CURLOPT_CAINFO
689 * at it - so anything making outbound HTTPS calls should use this rather than
690 * asking the admin to edit php.ini.
691 *
692 * Absolute on purpose: callers may run with any working directory.
693 */
694function znote_cainfo() {
695 if (ini_get('curl.cainfo') !== '' || ini_get('openssl.cafile') !== '') {
696 return '';
697 }
698
699 $bundle = dirname(__DIR__) . '/cert/cacert.pem';
700
701 return is_file($bundle) ? $bundle : '';
702}
703
704function znote_media_base(string $srv): string {
705 $srv = trim($srv);
706 if ($srv === '') return '';
707 if (preg_match('#^(https?:)?//#i', $srv)) return rtrim($srv, '/');
708 if (preg_match('#^[a-zA-Z]:[\\/]#', $srv) || strncmp($srv, '\\', 2) === 0) return '';
709 if ($srv[0] === '/') return rtrim($srv, '/');
710 return 'http://' . rtrim($srv, '/');
711}
712
713function znote_item_image_dir(): string {
714 static $dir = null;
715 if ($dir !== null) return $dir;
716 global $config;
717 $srv = trim((string) ($config['shop']['imageServer'] ?? ''));
718 if ($srv === '' || preg_match('#^(https?:)?//#i', $srv) || (isset($srv[0]) && $srv[0] === '/' && !@is_dir($srv))) return $dir = '';
719 $real = @realpath($srv);
720 return $dir = ($real !== false && @is_dir($real)) ? $real : '';
721}
722
723function znote_item_image_url(int $id, ?string $type = null): string {
724 if ($id <= 0) return '';
725 global $config;
726 if (znote_item_image_dir() !== '') return 'index.php?znote_item_img=' . $id;
727 $type = ($type !== null && $type !== '') ? $type : (string) ($config['shop']['imageType'] ?? 'gif');
728 $type = preg_replace('/[^a-z0-9]/i', '', $type) ?: 'gif';
729 $base = znote_media_base((string) ($config['shop']['imageServer'] ?? ''));
730 return $base === '' ? '' : $base . '/' . $id . '.' . $type;
731}
732
733function znote_outfit_image_base(): string {
734 global $config;
735 return znote_media_base((string) ($config['show_outfits']['imageServer'] ?? ''));
736}
737
Top