1<?php 2 3/** 4 * functions common to both the Zenphoto core and setup's basic environment 5 * 6 * @package core 7 * @subpackage functions\functions-common 8 */ 9 10/** 11 * 12 * Traps errors and insures thy are logged. 13 * @param int $errno 14 * @param string $errstr 15 * @param string $errfile 16 * @param string $errline 17 * @return void|boolean 18 */ 19function zpErrorHandler($errno, $errstr = '', $errfile = '', $errline = '') { 20 // check if function has been called by an exception 21 if (func_num_args() == 5) { 22 // called by trigger_error() 23 list($errno, $errstr, $errfile, $errline) = func_get_args(); 24 } else { 25 // caught exception 26 $exc = func_get_arg(0); 27 $errno = $exc->getCode(); 28 $errstr = $exc->getMessage(); 29 $errfile = $exc->getFile(); 30 $errline = $exc->getLine(); 31 } 32 // if error has been supressed with an @ 33 if (error_reporting() == 0 && !in_array($errno, array(E_USER_ERROR, E_USER_WARNING, E_USER_NOTICE))) { 34 return; 35 } 36 $errorType = array(E_ERROR => gettext('ERROR'), 37 E_WARNING => gettext('WARNING'), 38 E_NOTICE => gettext('NOTICE'), 39 E_USER_ERROR => gettext('USER ERROR'), 40 E_USER_WARNING => gettext('USER WARNING'), 41 E_USER_NOTICE => gettext('USER NOTICE'), 42 E_STRICT => gettext('STRICT NOTICE') 43 ); 44 45 // create error message 46 47 if (array_key_exists($errno, $errorType)) { 48 $err = $errorType[$errno]; 49 } else { 50 $err = gettext("EXCEPTION ($errno)"); 51 $errno = E_ERROR; 52 } 53 $msg = sprintf(gettext('%1$s: %2$s in %3$s on line %4$s'), $err, $errstr, $errfile, $errline); 54 debugLogBacktrace($msg, 1); 55 return false; 56} 57 58/** 59 * Converts a file system filename to UTF-8 for zenphoto internal storage 60 * 61 * @param string $filename the file name to convert 62 * @return string 63 */ 64function filesystemToInternal($filename) { 65 global $_zp_UTF8; 66 return str_replace('\\', '/', $_zp_UTF8->convert($filename, FILESYSTEM_CHARSET, LOCAL_CHARSET)); 67} 68 69/** 70 * Converts a Zenphoto Internal filename string to one compatible with the file system 71 * 72 * @param string $filename the file name to convert 73 * @return string 74 */ 75function internalToFilesystem($filename) { 76 global $_zp_UTF8; 77 return $_zp_UTF8->convert($filename, LOCAL_CHARSET, FILESYSTEM_CHARSET); 78} 79 80/** 81 * Takes user input meant to be used within a path to a file or folder and 82 * removes anything that could be insecure or malicious, or result in duplicate 83 * representations for the same physical file. 84 * 85 * This function is used primarily for album names. 86 * NOTE: The initial and trailing slashes are removed!!! 87 * 88 * Returns the sanitized path 89 * 90 * @param string $filename is the path text to filter. 91 * @return string 92 */ 93function sanitize_path($filename) { 94 $filename = strip_tags(str_replace('\\', '/', $filename)); 95 $filename = preg_replace(array('/x00/', '/\/\/+/', '/\/\.\./', '/\/\./', '/:/', '/</', '/>/', '/\?/', '/\*/', '/\"/', '/\|/', '/\/+$/', '/^\/+/'), '', $filename); 96 return $filename; 97} 98 99/** 100 * Checks if the input is numeric, rounds if so, otherwise returns false. 101 * 102 * @param mixed $num the number to be sanitized 103 * @return int 104 */ 105function sanitize_numeric($num) { 106 if (is_numeric($num)) { 107 return round($num); 108 } else { 109 return false; 110 } 111} 112 113/** 114 * removes script tags 115 * 116 * @param string $text 117 * @return string 118 */ 119function sanitize_script($text) { 120 return preg_replace('!<script.*>.*</script>!ixs', '', $text); 121} 122 123/** Make strings generally clean. Takes an input string and cleans out 124 * null-bytes, and optionally use KSES 125 * library to prevent XSS attacks and other malicious user input. 126 * @param string $input_string is a string that needs cleaning. 127 * @param string $sanitize_level is a number between 0 and 3 that describes the 128 * type of sanitizing to perform on $input_string. 129 * 0 - Basic sanitation. Only strips null bytes. Not recommended for submitted form data. 130 * 1 - User specified. (User defined code is allowed. Used for descriptions and comments.) 131 * 2 - Text style/formatting. (Text style codes allowed. Used for titles.) 132 * 3 - Full sanitation. (Default. No code allowed. Used for text only fields) 133 * @return string the sanitized string. 134 */ 135function sanitize($input_string, $sanitize_level = 3) { 136 if (is_array($input_string)) { 137 $output_string = array(); 138 foreach ($input_string as $output_key => $output_value) { 139 $output_string[$output_key] = sanitize($output_value, $sanitize_level); 140 } 141 } else { 142 $output_string = sanitize_string($input_string, $sanitize_level); 143 } 144 return $output_string; 145} 146 147/** 148 * Internal "helper" function to apply the tag removal 149 * 150 * @param string $input_string 151 * @param array $allowed_tags 152 * @return string 153 */ 154function ksesProcess($input_string, $allowed_tags) { 155 if (function_exists('kses')) { 156 return kses($input_string, $allowed_tags); 157 } else { 158 return getBare($input_string); 159 } 160} 161 162/** 163 * Cleans tags and some content. 164 * @param type $content 165 * @return type 166 */ 167function getBare($content) { 168 $content = preg_replace('~<script.*?/script>~is', '', $content); 169 $content = preg_replace('~<style.*?/style>~is', '', $content); 170 $content = preg_replace('~<!--.*?-->~is', '', $content); 171 $content = strip_tags($content); 172 $content = str_replace(' ', ' ', $content); 173 return $content; 174} 175 176/** returns a sanitized string for the sanitize function 177 * @param string $input_string 178 * @param string $sanitize_level See sanitize() 179 * @return string the sanitized string. 180 */ 181function sanitize_string($input, $sanitize_level) { 182 if (is_string($input)) { 183 $input = str_replace(chr(0), " ", $input); 184 switch ($sanitize_level) { 185 case 0: 186 return $input; 187 case 2: 188 // Strips non-style tags. 189 $input = sanitize_script($input); 190 return ksesProcess($input, getAllowedTags('style_tags')); 191 case 3: 192 // Full sanitation. Strips all code. 193 return getBare($input); 194 195 case 1: 196 // Text formatting sanititation. 197 $input = sanitize_script($input); 198 return ksesProcess($input, getAllowedTags('allowed_tags')); 199 case 4: 200 default: 201 // for internal use to eliminate security injections 202 return sanitize_script($input); 203 } 204 } 205 return $input; 206} 207 208///// database helper functions 209 210/** 211 * Prefix a table name with a user-defined string to avoid conflicts. 212 * This MUST be used in all database queries. 213 * @param string $tablename name of the table 214 * @return prefixed table name 215 * @since 0.6 216 */ 217function prefix($tablename = NULL) { 218 if(defined('DATABASE_PREFIX')) { 219 $prefix = DATABASE_PREFIX; 220 } else{ 221 $prefix = 'zp_'; // use default in case this constant is not set in setup primitive environments 222 } 223 return '`' . $prefix . $tablename . '`'; 224} 225 226/** 227 * Constructs a WHERE clause ("WHERE uniqueid1='uniquevalue1' AND uniqueid2='uniquevalue2' ...") 228 * from an array (map) of variables and their values which identifies a unique record 229 * in the database table. 230 * @param string $unique_set what to add to the WHERE clause 231 * @return contructed WHERE cleause 232 * @since 0.6 233 */ 234function getWhereClause($unique_set) { 235 if (empty($unique_set)) 236 return ' '; 237 $where = ' WHERE'; 238 foreach ($unique_set as $var => $value) { 239 $where .= ' `' . $var . '` = ' . db_quote($value) . ' AND'; 240 } 241 return substr($where, 0, -4); 242} 243 244/** 245 * Constructs a SET clause ("SET uniqueid1='uniquevalue1', uniqueid2='uniquevalue2' ...") 246 * from an array (map) of variables and their values which identifies a unique record 247 * in the database table. Used to 'move' records. Note: does not check anything. 248 * @param string $new_unique_set what to add to the SET clause 249 * @return contructed SET cleause 250 * @since 0.6 251 */ 252function getSetClause($new_unique_set) { 253 $i = 0; 254 $set = ' SET'; 255 foreach ($new_unique_set as $var => $value) { 256 $set .= ' `' . $var . '`='; 257 if (is_null($value)) { 258 $set .= 'NULL'; 259 } else { 260 $set .= db_quote($value) . ','; 261 } 262 } 263 return substr($set, 0, -1); 264} 265 266/* 267 * returns the connected database name 268 */ 269 270function db_name() { 271 global $_zp_conf_vars; 272 return $_zp_conf_vars['mysql_database']; 273} 274 275function db_count($table, $clause = NULL, $field = "*") { 276 $sql = 'SELECT COUNT(' . $field . ') FROM ' . prefix($table) . ' ' . $clause; 277 $result = query_single_row($sql); 278 if ($result) { 279 return array_shift($result); 280 } else { 281 return 0; 282 } 283} 284 285/** 286 * triggers an error 287 * 288 * @param string $message 289 * @param int $type the PHP error type to trigger; default to E_USER_ERROR 290 */ 291function zp_error($message, $fatal = E_USER_ERROR) { 292 // Print the error message, to be convenient. 293 printf(html_encode($message)); 294 trigger_error($message, $fatal); 295} 296 297function html_decode($string) { 298 return html_entity_decode($string, ENT_QUOTES, 'UTF-8'); 299} 300 301/** 302 * encodes a pre-sanitized string to be used in an HTML text-only field (value, alt, title, etc.) 303 * 304 * @param string $str 305 * @return string 306 */ 307function html_encode($str) { 308 return htmlspecialchars($str, ENT_FLAGS, LOCAL_CHARSET); 309} 310 311/** 312 * HTML encodes the non-metatag part of the string. 313 * 314 * @param string $original string to be encoded 315 * @param bool $allowScript set to false to prevent pass-through of script tags. 316 * @return string 317 */ 318function html_encodeTagged($original, $allowScript = true) { 319 $tags = array(); 320 $str = $original; 321 //javascript 322 if ($allowScript) { 323 preg_match_all('!<script.*>.*</script>!ixs', $str, $matches); 324 foreach (array_unique($matches[0]) as $key => $tag) { 325 $tags[2]['%' . $key . '$j'] = $tag; 326 $str = str_replace($tag, '%' . $key . '$j', $str); 327 } 328 } else { 329 $str = preg_replace('|<a(.*)href(.*)=(.*)javascript|ixs', '%$x', $str); 330 $tags[2]['%$x'] = '<a href=<strike>javascript</strike>'; 331 $str = preg_replace('|<(.*)onclick|ixs', '%$c', $str); 332 $tags[2]['%$c'] = '<<strike>onclick</strike>'; 333 } 334 //strip html comments 335 $str = preg_replace('~<!--.*?-->~is', '', $str); 336 // markup 337 preg_match_all("/<\/?\w+((\s+(\w|\w[\w-]*\w)(\s*=\s*(?:\".*?\"|'.*?'|[^'\">\s]+))?)+\s*|\s*)\/?>/i", $str, $matches); 338 foreach (array_unique($matches[0]) as $key => $tag) { 339 $tags[2]['%' . $key . '$s'] = $tag; 340 $str = str_replace($tag, '%' . $key . '$s', $str); 341 } 342 $str = htmLawed($str); 343 //entities 344 preg_match_all('/(&[a-z0-9#]+;)/i', $str, $matches); 345 foreach (array_unique($matches[0]) as $key => $entity) { 346 $tags[3]['%' . $key . '$e'] = $entity; 347 $str = str_replace($entity, '%' . $key . '$e', $str); 348 } 349 $str = htmlspecialchars($str, ENT_FLAGS, LOCAL_CHARSET); 350 foreach (array_reverse($tags, true) as $taglist) { 351 $str = strtr($str, $taglist); 352 } 353 if ($str != $original) { 354 $str = tidyHTML($str); 355 } 356 return $str; 357} 358 359/** 360 * Convenience wrapper of html_encode(pathurlencode($url)) 361 * Primarily intended for use with img src URLs 362 * 363 * @since ZenphotoCMS 1.5.8 364 * 365 * @param string $url 366 * @return string 367 */ 368function html_pathurlencode($url) { 369 return html_encode(pathurlencode($url)); 370} 371 372/** 373 * Makes directory recursively, returns TRUE if exists or was created sucessfuly. 374 * Note: PHP5 includes a recursive parameter to mkdir, but it apparently does not 375 * does not traverse symlinks! 376 * @param string $pathname The directory path to be created. 377 * @return boolean TRUE if exists or made or FALSE on failure. 378 */ 379function mkdir_recursive($pathname, $mode) { 380 if (!is_dir(dirname($pathname))) { 381 mkdir_recursive(dirname($pathname), $mode); 382 } 383 return is_dir($pathname) || @mkdir($pathname, $mode); 384} 385 386/** 387 * Logs the calling stack 388 * 389 * @param string $message Message to prefix the backtrace 390 */ 391function debugLogBacktrace($message, $omit = 0) { 392 $output = trim($message) . "\n"; 393 // Get a backtrace. 394 $bt = debug_backtrace(); 395 while ($omit >= 0) { 396 array_shift($bt); // Get rid of debug_backtrace, callers in the backtrace. 397 $omit--; 398 } 399 $prefix = ' '; 400 $line = ''; 401 $caller = ''; 402 foreach ($bt as $b) { 403 $caller = (isset($b['class']) ? $b['class'] : '') . (isset($b['type']) ? $b['type'] : '') . $b['function']; 404 if (!empty($line)) { // skip first output to match up functions with line where they are used. 405 $prefix .= ' '; 406 $output .= 'from ' . $caller . ' (' . $line . ")\n" . $prefix; 407 } else { 408 $output .= ' ' . $caller . " called "; 409 } 410 $date = false; 411 if (isset($b['file']) && isset($b['line'])) { 412 $line = basename($b['file']) . ' [' . $b['line'] . "]"; 413 } else { 414 $line = 'unknown'; 415 } 416 } 417 if (!empty($line)) { 418 $output .= 'from ' . $line; 419 } 420 debugLog($output); 421} 422 423/** 424 * Records a Var to the debug log 425 * 426 * @param string $message message to insert in log [optional] 427 * @param mixed $var the variable to record 428 */ 429function debugLogVar($message) { 430 $args = func_get_args(); 431 if (count($args) == 1) { 432 $var = $message; 433 $message = ''; 434 } else { 435 $message .= ' '; 436 $var = $args[1]; 437 } 438 ob_start(); 439 var_dump($var); 440 $str = ob_get_contents(); 441 ob_end_clean(); 442 debugLog(trim($message) . "\r" . html_decode(getBare($str))); 443} 444 445/** 446 * Returns the value of a cookie from either the cookies or from $_SESSION[] 447 * 448 * @param string $name the name of the cookie 449 */ 450function zp_getCookie($name) { 451 if (isset($_COOKIE[$name])) { 452 $cookiev = sanitize($_COOKIE[$name]); 453 } else { 454 $cookiev = ''; 455 } 456 if (DEBUG_LOGIN) { 457 if (isset($_SESSION[$name])) { 458 $sessionv = sanitize($_SESSION[$name]); 459 } else { 460 $sessionv = ''; 461 } 462 debugLog(zp_getCookie($name) . '=::' . 'album_session=' . GALLERY_SESSION . "; SESSION[" . session_id() . "]=" . sanitize($sessionv) . ", COOKIE=" . sanitize($cookiev)); 463 } 464 if (!empty($cookiev) && (defined('GALLERY_SESSION') && !GALLERY_SESSION)) { 465 return zp_cookieEncode($cookiev); 466 } 467 if (isset($_SESSION[$name])) { 468 return sanitize($_SESSION[$name]); 469 } 470 return NULL; 471} 472 473/** 474 * 475 * Encodes a cookie value tying it to the user IP 476 * @param $value 477 */ 478function zp_cookieEncode($value) { 479 if (IP_TIED_COOKIES) { 480 return rc4(getUserIP() . HASH_SEED, $value); 481 } else { 482 return $value; 483 } 484} 485 486/** 487 * Sets a cookie both in the browser cookies and in $_SESSION[] 488 * 489 * @param string $name The 'cookie' name 490 * @param string $value The value to be stored 491 * @param timestamp $time The time delta until the cookie expires 492 * @param string $path The path on the server in which the cookie will be available on 493 * @param bool $secure true if secure cookie 494 * @param bool $httponly true if access to this cookie should only be allowed via http (e.g. no access to JS etc.). Requires browser support though. 495 */ 496function zp_setCookie($name, $value, $time = NULL, $path = NULL, $secure = false, $httponly = false) { 497 if (empty($value)) { 498 $cookiev = ''; 499 } else { 500 $cookiev = zp_cookieEncode(sanitize($value)); 501 } 502 if (is_null($time)) { 503 $time = COOKIE_PERSISTENCE; 504 } 505 if (is_null($path)) { 506 $path = WEBPATH; 507 } 508 if (substr($path, -1, 1) != '/') 509 $path .= '/'; 510 if (DEBUG_LOGIN) { 511 debugLog("zp_setCookie($name, $value, $time, $path)::album_session=" . GALLERY_SESSION . "; SESSION=" . session_id()); 512 } 513 if (($time < 0) || !GALLERY_SESSION) { 514 if (version_compare(PHP_VERSION, '7.3.0', '>=')) { 515 $options = array( 516 'expires' => (time() + $time), 517 'path' => $path, 518 'secure' => $secure, 519 'httponly' => $httponly, 520 'samesite' => 'Lax' 521 ); 522 setcookie($name, $cookiev, $options); 523 } else { 524 setcookie($name, $cookiev, time() + $time, $path, '', $secure, $httponly); 525 } 526 } 527 if ($time < 0) { 528 if (isset($_SESSION)) 529 unset($_SESSION[$name]); 530 if (isset($_COOKIE)) 531 unset($_COOKIE[$name]); 532 } else { 533 $_SESSION[$name] = sanitize($value); 534 $_COOKIE[$name] = sanitize($cookiev); 535 } 536} 537 538/** 539 * Clears a cookie 540 * @param string $name The 'cookie' name 541 * @param string $path The path on the server in which the cookie will be available on 542 * @param bool $secure true if secure cookie 543 * @param bool $httponly true if access to this cookie should only be allowed via http (e.g. no access to JS etc.). Requires browser support though. 544 */ 545function zp_clearCookie($name, $path = NULl, $secure = false, $httponly = false) { 546 zp_setCookie($name, '', -368000, $path, $secure, $httponly); 547} 548 549/** 550 * if $string is an serialzied array it is unserialized otherwise an appropriate array is returned 551 * 552 * @param string $string 553 * 554 * @return array 555 */ 556function getSerializedArray($string) { 557 if (is_array($string)) { 558 return $string; 559 } 560 if (preg_match('/^a:[0-9]+:{/', $string)) { 561 $r = @unserialize($string); 562 if ($r) { 563 return $r; 564 } else { 565 return array(); 566 } 567 } else if (strlen($string) == 0 && !is_bool($string)) { 568 return array(); 569 } else { 570 return array($string); 571 } 572} 573 574?> 575