1<?php 2// +-----------------------------------------------------------------------+ 3// | This file is part of Piwigo. | 4// | | 5// | For copyright and license information, please view the COPYING.txt | 6// | file that was distributed with this source code. | 7// +-----------------------------------------------------------------------+ 8 9/** 10 * @package functions\___ 11 */ 12 13include_once( PHPWG_ROOT_PATH .'include/functions_plugins.inc.php' ); 14include_once( PHPWG_ROOT_PATH .'include/functions_user.inc.php' ); 15include_once( PHPWG_ROOT_PATH .'include/functions_cookie.inc.php' ); 16include_once( PHPWG_ROOT_PATH .'include/functions_session.inc.php' ); 17include_once( PHPWG_ROOT_PATH .'include/functions_category.inc.php' ); 18include_once( PHPWG_ROOT_PATH .'include/functions_html.inc.php' ); 19include_once( PHPWG_ROOT_PATH .'include/functions_tag.inc.php' ); 20include_once( PHPWG_ROOT_PATH .'include/functions_url.inc.php' ); 21include_once( PHPWG_ROOT_PATH .'include/derivative_params.inc.php'); 22include_once( PHPWG_ROOT_PATH .'include/derivative_std_params.inc.php'); 23include_once( PHPWG_ROOT_PATH .'include/derivative.inc.php'); 24 25 26/** 27 * returns the current microsecond since Unix epoch 28 * 29 * @return int 30 */ 31function micro_seconds() 32{ 33 $t1 = explode(' ', microtime()); 34 $t2 = explode('.', $t1[0]); 35 $t2 = $t1[1].substr($t2[1], 0, 6); 36 return $t2; 37} 38 39/** 40 * returns a float value coresponding to the number of seconds since 41 * the unix epoch (1st January 1970) and the microseconds are precised 42 * e.g. 1052343429.89276600 43 * 44 * @return float 45 */ 46function get_moment() 47{ 48 return microtime(true); 49} 50 51/** 52 * returns the number of seconds (with 3 decimals precision) 53 * between the start time and the end time given 54 * 55 * @param float $start 56 * @param float $end 57 * @return string "$TIME s" 58 */ 59function get_elapsed_time($start, $end) 60{ 61 return number_format($end - $start, 3, '.', ' ').' s'; 62} 63 64/** 65 * returns the part of the string after the last "." 66 * 67 * @param string $filename 68 * @return string 69 */ 70function get_extension( $filename ) 71{ 72 return substr( strrchr( $filename, '.' ), 1, strlen ( $filename ) ); 73} 74 75/** 76 * returns the part of the string before the last ".". 77 * get_filename_wo_extension( 'test.tar.gz' ) = 'test.tar' 78 * 79 * @param string $filename 80 * @return string 81 */ 82function get_filename_wo_extension( $filename ) 83{ 84 $pos = strrpos( $filename, '.' ); 85 return ($pos===false) ? $filename : substr( $filename, 0, $pos); 86} 87 88/** no option for mkgetdir() */ 89define('MKGETDIR_NONE', 0); 90/** sets mkgetdir() recursive */ 91define('MKGETDIR_RECURSIVE', 1); 92/** sets mkgetdir() exit script on error */ 93define('MKGETDIR_DIE_ON_ERROR', 2); 94/** sets mkgetdir() add a index.htm file */ 95define('MKGETDIR_PROTECT_INDEX', 4); 96/** sets mkgetdir() add a .htaccess file*/ 97define('MKGETDIR_PROTECT_HTACCESS', 8); 98/** default options for mkgetdir() = MKGETDIR_RECURSIVE | MKGETDIR_DIE_ON_ERROR | MKGETDIR_PROTECT_INDEX */ 99define('MKGETDIR_DEFAULT', MKGETDIR_RECURSIVE | MKGETDIR_DIE_ON_ERROR | MKGETDIR_PROTECT_INDEX); 100 101/** 102 * creates directory if not exists and ensures that directory is writable 103 * 104 * @param string $dir 105 * @param int $flags combination of MKGETDIR_xxx 106 * @return bool 107 */ 108function mkgetdir($dir, $flags=MKGETDIR_DEFAULT) 109{ 110 if ( !is_dir($dir) ) 111 { 112 global $conf; 113 if (substr(PHP_OS, 0, 3) == 'WIN') 114 { 115 $dir = str_replace('/', DIRECTORY_SEPARATOR, $dir); 116 } 117 $umask = umask(0); 118 $mkd = @mkdir($dir, $conf['chmod_value'], ($flags&MKGETDIR_RECURSIVE) ? true:false ); 119 umask($umask); 120 if ($mkd==false) 121 { 122 !($flags&MKGETDIR_DIE_ON_ERROR) or fatal_error( "$dir ".l10n('no write access')); 123 return false; 124 } 125 if( $flags&MKGETDIR_PROTECT_HTACCESS ) 126 { 127 $file = $dir.'/.htaccess'; 128 file_exists($file) or @file_put_contents( $file, 'deny from all' ); 129 } 130 if( $flags&MKGETDIR_PROTECT_INDEX ) 131 { 132 $file = $dir.'/index.htm'; 133 file_exists($file) or @file_put_contents( $file, 'Not allowed!' ); 134 } 135 } 136 if ( !is_writable($dir) ) 137 { 138 !($flags&MKGETDIR_DIE_ON_ERROR) or fatal_error( "$dir ".l10n('no write access')); 139 return false; 140 } 141 return true; 142} 143 144/** 145 * finds out if a string is in ASCII, UTF-8 or other encoding 146 * 147 * @param string $str 148 * @return int *0* if _$str_ is ASCII, *1* if UTF-8, *-1* otherwise 149 */ 150function qualify_utf8($Str) 151{ 152 $ret = 0; 153 for ($i=0; $i<strlen($Str); $i++) 154 { 155 if (ord($Str[$i]) < 0x80) continue; # 0bbbbbbb 156 $ret = 1; 157 if ((ord($Str[$i]) & 0xE0) == 0xC0) $n=1; # 110bbbbb 158 elseif ((ord($Str[$i]) & 0xF0) == 0xE0) $n=2; # 1110bbbb 159 elseif ((ord($Str[$i]) & 0xF8) == 0xF0) $n=3; # 11110bbb 160 elseif ((ord($Str[$i]) & 0xFC) == 0xF8) $n=4; # 111110bb 161 elseif ((ord($Str[$i]) & 0xFE) == 0xFC) $n=5; # 1111110b 162 else return -1; # Does not match any model 163 for ($j=0; $j<$n; $j++) 164 { # n bytes matching 10bbbbbb follow ? 165 if ((++$i == strlen($Str)) || ((ord($Str[$i]) & 0xC0) != 0x80)) 166 return -1; 167 } 168 } 169 return $ret; 170} 171 172/** 173 * Remove accents from a UTF-8 or ISO-8859-1 string (from wordpress) 174 * 175 * @param string $string 176 * @return string 177 */ 178function remove_accents($string) 179{ 180 $utf = qualify_utf8($string); 181 if ( $utf == 0 ) 182 { 183 return $string; // ascii 184 } 185 186 if ( $utf > 0 ) 187 { 188 $chars = array( 189 // Decompositions for Latin-1 Supplement 190 "\xc3\x80"=>'A', "\xc3\x81"=>'A', 191 "\xc3\x82"=>'A', "\xc3\x83"=>'A', 192 "\xc3\x84"=>'A', "\xc3\x85"=>'A', 193 "\xc3\x87"=>'C', "\xc3\x88"=>'E', 194 "\xc3\x89"=>'E', "\xc3\x8a"=>'E', 195 "\xc3\x8b"=>'E', "\xc3\x8c"=>'I', 196 "\xc3\x8d"=>'I', "\xc3\x8e"=>'I', 197 "\xc3\x8f"=>'I', "\xc3\x91"=>'N', 198 "\xc3\x92"=>'O', "\xc3\x93"=>'O', 199 "\xc3\x94"=>'O', "\xc3\x95"=>'O', 200 "\xc3\x96"=>'O', "\xc3\x99"=>'U', 201 "\xc3\x9a"=>'U', "\xc3\x9b"=>'U', 202 "\xc3\x9c"=>'U', "\xc3\x9d"=>'Y', 203 "\xc3\x9f"=>'s', "\xc3\xa0"=>'a', 204 "\xc3\xa1"=>'a', "\xc3\xa2"=>'a', 205 "\xc3\xa3"=>'a', "\xc3\xa4"=>'a', 206 "\xc3\xa5"=>'a', "\xc3\xa7"=>'c', 207 "\xc3\xa8"=>'e', "\xc3\xa9"=>'e', 208 "\xc3\xaa"=>'e', "\xc3\xab"=>'e', 209 "\xc3\xac"=>'i', "\xc3\xad"=>'i', 210 "\xc3\xae"=>'i', "\xc3\xaf"=>'i', 211 "\xc3\xb1"=>'n', "\xc3\xb2"=>'o', 212 "\xc3\xb3"=>'o', "\xc3\xb4"=>'o', 213 "\xc3\xb5"=>'o', "\xc3\xb6"=>'o', 214 "\xc3\xb9"=>'u', "\xc3\xba"=>'u', 215 "\xc3\xbb"=>'u', "\xc3\xbc"=>'u', 216 "\xc3\xbd"=>'y', "\xc3\xbf"=>'y', 217 // Decompositions for Latin Extended-A 218 "\xc4\x80"=>'A', "\xc4\x81"=>'a', 219 "\xc4\x82"=>'A', "\xc4\x83"=>'a', 220 "\xc4\x84"=>'A', "\xc4\x85"=>'a', 221 "\xc4\x86"=>'C', "\xc4\x87"=>'c', 222 "\xc4\x88"=>'C', "\xc4\x89"=>'c', 223 "\xc4\x8a"=>'C', "\xc4\x8b"=>'c', 224 "\xc4\x8c"=>'C', "\xc4\x8d"=>'c', 225 "\xc4\x8e"=>'D', "\xc4\x8f"=>'d', 226 "\xc4\x90"=>'D', "\xc4\x91"=>'d', 227 "\xc4\x92"=>'E', "\xc4\x93"=>'e', 228 "\xc4\x94"=>'E', "\xc4\x95"=>'e', 229 "\xc4\x96"=>'E', "\xc4\x97"=>'e', 230 "\xc4\x98"=>'E', "\xc4\x99"=>'e', 231 "\xc4\x9a"=>'E', "\xc4\x9b"=>'e', 232 "\xc4\x9c"=>'G', "\xc4\x9d"=>'g', 233 "\xc4\x9e"=>'G', "\xc4\x9f"=>'g', 234 "\xc4\xa0"=>'G', "\xc4\xa1"=>'g', 235 "\xc4\xa2"=>'G', "\xc4\xa3"=>'g', 236 "\xc4\xa4"=>'H', "\xc4\xa5"=>'h', 237 "\xc4\xa6"=>'H', "\xc4\xa7"=>'h', 238 "\xc4\xa8"=>'I', "\xc4\xa9"=>'i', 239 "\xc4\xaa"=>'I', "\xc4\xab"=>'i', 240 "\xc4\xac"=>'I', "\xc4\xad"=>'i', 241 "\xc4\xae"=>'I', "\xc4\xaf"=>'i', 242 "\xc4\xb0"=>'I', "\xc4\xb1"=>'i', 243 "\xc4\xb2"=>'IJ', "\xc4\xb3"=>'ij', 244 "\xc4\xb4"=>'J', "\xc4\xb5"=>'j', 245 "\xc4\xb6"=>'K', "\xc4\xb7"=>'k', 246 "\xc4\xb8"=>'k', "\xc4\xb9"=>'L', 247 "\xc4\xba"=>'l', "\xc4\xbb"=>'L', 248 "\xc4\xbc"=>'l', "\xc4\xbd"=>'L', 249 "\xc4\xbe"=>'l', "\xc4\xbf"=>'L', 250 "\xc5\x80"=>'l', "\xc5\x81"=>'L', 251 "\xc5\x82"=>'l', "\xc5\x83"=>'N', 252 "\xc5\x84"=>'n', "\xc5\x85"=>'N', 253 "\xc5\x86"=>'n', "\xc5\x87"=>'N', 254 "\xc5\x88"=>'n', "\xc5\x89"=>'N', 255 "\xc5\x8a"=>'n', "\xc5\x8b"=>'N', 256 "\xc5\x8c"=>'O', "\xc5\x8d"=>'o', 257 "\xc5\x8e"=>'O', "\xc5\x8f"=>'o', 258 "\xc5\x90"=>'O', "\xc5\x91"=>'o', 259 "\xc5\x92"=>'OE', "\xc5\x93"=>'oe', 260 "\xc5\x94"=>'R', "\xc5\x95"=>'r', 261 "\xc5\x96"=>'R', "\xc5\x97"=>'r', 262 "\xc5\x98"=>'R', "\xc5\x99"=>'r', 263 "\xc5\x9a"=>'S', "\xc5\x9b"=>'s', 264 "\xc5\x9c"=>'S', "\xc5\x9d"=>'s', 265 "\xc5\x9e"=>'S', "\xc5\x9f"=>'s', 266 "\xc5\xa0"=>'S', "\xc5\xa1"=>'s', 267 "\xc5\xa2"=>'T', "\xc5\xa3"=>'t', 268 "\xc5\xa4"=>'T', "\xc5\xa5"=>'t', 269 "\xc5\xa6"=>'T', "\xc5\xa7"=>'t', 270 "\xc5\xa8"=>'U', "\xc5\xa9"=>'u', 271 "\xc5\xaa"=>'U', "\xc5\xab"=>'u', 272 "\xc5\xac"=>'U', "\xc5\xad"=>'u', 273 "\xc5\xae"=>'U', "\xc5\xaf"=>'u', 274 "\xc5\xb0"=>'U', "\xc5\xb1"=>'u', 275 "\xc5\xb2"=>'U', "\xc5\xb3"=>'u', 276 "\xc5\xb4"=>'W', "\xc5\xb5"=>'w', 277 "\xc5\xb6"=>'Y', "\xc5\xb7"=>'y', 278 "\xc5\xb8"=>'Y', "\xc5\xb9"=>'Z', 279 "\xc5\xba"=>'z', "\xc5\xbb"=>'Z', 280 "\xc5\xbc"=>'z', "\xc5\xbd"=>'Z', 281 "\xc5\xbe"=>'z', "\xc5\xbf"=>'s', 282 // Decompositions for Latin Extended-B 283 "\xc8\x98"=>'S', "\xc8\x99"=>'s', 284 "\xc8\x9a"=>'T', "\xc8\x9b"=>'t', 285 // Euro Sign 286 "\xe2\x82\xac"=>'E', 287 // GBP (Pound) Sign 288 "\xc2\xa3"=>''); 289 290 $string = strtr($string, $chars); 291 } 292 else 293 { 294 // Assume ISO-8859-1 if not UTF-8 295 $chars['in'] = chr(128).chr(131).chr(138).chr(142).chr(154).chr(158) 296 .chr(159).chr(162).chr(165).chr(181).chr(192).chr(193).chr(194) 297 .chr(195).chr(196).chr(197).chr(199).chr(200).chr(201).chr(202) 298 .chr(203).chr(204).chr(205).chr(206).chr(207).chr(209).chr(210) 299 .chr(211).chr(212).chr(213).chr(214).chr(216).chr(217).chr(218) 300 .chr(219).chr(220).chr(221).chr(224).chr(225).chr(226).chr(227) 301 .chr(228).chr(229).chr(231).chr(232).chr(233).chr(234).chr(235) 302 .chr(236).chr(237).chr(238).chr(239).chr(241).chr(242).chr(243) 303 .chr(244).chr(245).chr(246).chr(248).chr(249).chr(250).chr(251) 304 .chr(252).chr(253).chr(255); 305 306 $chars['out'] = "EfSZszYcYuAAAAAACEEEEIIIINOOOOOOUUUUYaaaaaaceeeeiiiinoooooouuuuyy"; 307 308 $string = strtr($string, $chars['in'], $chars['out']); 309 $double_chars['in'] = array(chr(140), chr(156), chr(198), chr(208), chr(222), chr(223), chr(230), chr(240), chr(254)); 310 $double_chars['out'] = array('OE', 'oe', 'AE', 'DH', 'TH', 'ss', 'ae', 'dh', 'th'); 311 $string = str_replace($double_chars['in'], $double_chars['out'], $string); 312 } 313 314 return $string; 315} 316 317if (function_exists('mb_strtolower') && defined('PWG_CHARSET')) 318{ 319 /** 320 * removes accents from a string and converts it to lower case 321 * 322 * @param string $term 323 * @return string 324 */ 325 function pwg_transliterate($term) 326 { 327 return remove_accents( mb_strtolower($term, PWG_CHARSET) ); 328 } 329} 330else 331{ 332 /** 333 * @ignore 334 */ 335 function pwg_transliterate($term) 336 { 337 return remove_accents( strtolower($term) ); 338 } 339} 340 341/** 342 * simplify a string to insert it into an URL 343 * 344 * @param string $str 345 * @return string 346 */ 347function str2url($str) 348{ 349 $str = $safe = pwg_transliterate($str); 350 $str = preg_replace('/[^\x80-\xffa-z0-9_\s\'\:\/\[\],-]/','',$str); 351 $str = preg_replace('/[\s\'\:\/\[\],-]+/',' ',trim($str)); 352 $res = str_replace(' ','_',$str); 353 354 if (empty($res)) 355 { 356 $res = str_replace(' ','_', $safe); 357 } 358 359 return $res; 360} 361 362/** 363 * returns an array with a list of {language_code => language_name} 364 * 365 * @return string[] 366 */ 367function get_languages() 368{ 369 $query = ' 370SELECT id, name 371 FROM '.LANGUAGES_TABLE.' 372 ORDER BY name ASC 373;'; 374 $result = pwg_query($query); 375 376 $languages = array(); 377 while ($row = pwg_db_fetch_assoc($result)) 378 { 379 if (is_dir(PHPWG_ROOT_PATH.'language/'.$row['id'])) 380 { 381 $languages[ $row['id'] ] = $row['name']; 382 } 383 } 384 385 return $languages; 386} 387 388/** 389 * log the visit into history table 390 * 391 * @param int $image_id 392 * @param string $image_type 393 * @return bool 394 */ 395function pwg_log($image_id = null, $image_type = null, $format_id = null) 396{ 397 global $conf, $user, $page; 398 399 $update_last_visit = false; 400 if (empty($user['last_visit']) or strtotime($user['last_visit']) < time()-$conf['session_length']) 401 { 402 $update_last_visit = true; 403 } 404 $update_last_visit = trigger_change('pwg_log_update_last_visit', $update_last_visit); 405 406 if ($update_last_visit) 407 { 408 $query = ' 409UPDATE '.USER_INFOS_TABLE.' 410 SET last_visit = NOW(), 411 lastmodified = lastmodified 412 WHERE user_id = '.$user['id'].' 413'; 414 pwg_query($query); 415 } 416 417 $do_log = $conf['log']; 418 if (is_admin()) 419 { 420 $do_log = $conf['history_admin']; 421 } 422 if (is_a_guest()) 423 { 424 $do_log = $conf['history_guest']; 425 } 426 427 $do_log = trigger_change('pwg_log_allowed', $do_log, $image_id, $image_type); 428 429 if (!$do_log) 430 { 431 return false; 432 } 433 434 $tags_string = null; 435 if ('tags'==@$page['section']) 436 { 437 $tags_string = implode(',', $page['tag_ids']); 438 } 439 440 $ip = $_SERVER['REMOTE_ADDR']; 441 // In case of "too long" ipv6 address, we take only the 15 first chars. 442 // 443 // It would be "cleaner" to increase length of history.IP to 50 chars, but 444 // the alter table is very long on such a big table. We should plan this 445 // for a future version, once history table is kept "smaller". 446 if (strpos($ip,':') !== false and strlen($ip) > 15) 447 { 448 $ip = substr($ip, 0, 15); 449 } 450 451 // If plugin developers add their own sections, Piwigo will automatically add it in the history.section enum column 452 if (isset($page['section'])) 453 { 454 // set cache if not available 455 if (!isset($conf['history_sections_cache'])) 456 { 457 conf_update_param('history_sections_cache', get_enums(HISTORY_TABLE, 'section'), true); 458 } 459 460 $conf['history_sections_cache'] = safe_unserialize($conf['history_sections_cache']); 461 462 if (in_array($page['section'], $conf['history_sections_cache'])) 463 { 464 $section = $page['section']; 465 } 466 elseif (preg_match('/^[a-zA-Z0-9_-]+$/', $page['section'])) 467 { 468 $history_sections = get_enums(HISTORY_TABLE, 'section'); 469 $history_sections[] = $page['section']; 470 471 // alter history table structure, to include a new section 472 pwg_query('ALTER TABLE '.HISTORY_TABLE.' CHANGE section section enum(\''.implode("','", array_unique($history_sections)).'\') DEFAULT NULL;'); 473 474 // and refresh cache 475 conf_update_param('history_sections_cache', get_enums(HISTORY_TABLE, 'section'), true); 476 477 $section = $page['section']; 478 } 479 } 480 481 $query = ' 482INSERT INTO '.HISTORY_TABLE.' 483 ( 484 date, 485 time, 486 user_id, 487 IP, 488 section, 489 category_id, 490 image_id, 491 image_type, 492 format_id, 493 auth_key_id, 494 tag_ids 495 ) 496 VALUES 497 ( 498 CURRENT_DATE, 499 CURRENT_TIME, 500 '.$user['id'].', 501 \''.$ip.'\', 502 '.(isset($section) ? "'".$section."'" : 'NULL').', 503 '.(isset($page['category']['id']) ? $page['category']['id'] : 'NULL').', 504 '.(isset($image_id) ? $image_id : 'NULL').', 505 '.(isset($image_type) ? "'".$image_type."'" : 'NULL').', 506 '.(isset($format_id) ? $format_id : 'NULL').', 507 '.(isset($page['auth_key_id']) ? $page['auth_key_id'] : 'NULL').', 508 '.(isset($tags_string) ? "'".$tags_string."'" : 'NULL').' 509 ) 510;'; 511 pwg_query($query); 512 513 $history_id = pwg_db_insert_id(HISTORY_TABLE); 514 if ($history_id % 1000 == 0) 515 { 516 include_once(PHPWG_ROOT_PATH.'admin/include/functions_history.inc.php'); 517 history_summarize(50000); 518 } 519 520 if ($conf['history_autopurge_every'] > 0 and $history_id % $conf['history_autopurge_every'] == 0) 521 { 522 include_once(PHPWG_ROOT_PATH.'admin/include/functions_history.inc.php'); 523 history_autopurge(); 524 } 525 526 return true; 527} 528 529function pwg_activity($object, $object_id, $action, $details=array()) 530{ 531 global $user; 532 533 $object_ids = $object_id; 534 if (!is_array($object_id)) 535 { 536 $object_ids = array($object_id); 537 } 538 539 if (isset($_REQUEST['method'])) 540 { 541 $details['method'] = $_REQUEST['method']; 542 } 543 else 544 { 545 $details['script'] = script_basename(); 546 547 if ('admin' == $details['script'] and isset($_GET['page'])) 548 { 549 $details['script'].= '/'.$_GET['page']; 550 } 551 } 552 553 if ('user' == $object and 'login' == $action) 554 { 555 $details['agent'] = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : 'unknown'; 556 } 557 558 if ('photo' == $object and 'add' == $action and !isset($details['sync'])) 559 { 560 $details['added_with'] = 'app'; 561 if (isset($_SERVER['HTTP_REFERER']) and preg_match('/page=photos_add/', $_SERVER['HTTP_REFERER'])) 562 { 563 $details['added_with'] = 'browser'; 564 } 565 $details['agent'] = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : 'unknown'; 566 } 567 568 if (in_array($object, array('album', 'photo')) and 'delete' == $action and isset($_GET['page']) and 'site_update' == $_GET['page']) 569 { 570 $details['sync'] = true; 571 } 572 573 if ('tag' == $object and 'delete' == $action and isset($_POST['destination_tag'])) 574 { 575 $details['action'] = 'merge'; 576 $details['destination_tag'] = $_POST['destination_tag']; 577 } 578 579 $inserts = array(); 580 $details_insert = pwg_db_real_escape_string(serialize($details)); 581 $ip_address = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : null; 582 583 foreach ($object_ids as $loop_object_id) 584 { 585 $inserts[] = array( 586 'object' => $object, 587 'object_id' => $loop_object_id, 588 'action' => $action, 589 'performed_by' => $user['id'], 590 'session_idx' => session_id(), 591 'ip_address' => $ip_address, 592 'details' => $details_insert, 593 ); 594 } 595 596 mass_inserts(ACTIVITY_TABLE, array_keys($inserts[0]), $inserts); 597} 598 599/** 600 * Computes the difference between two dates. 601 * returns a DateInterval object or a stdClass with the same attributes 602 * http://stephenharris.info/date-intervals-in-php-5-2 603 * 604 * @param DateTime $date1 605 * @param DateTime $date2 606 * @return DateInterval|stdClass 607 */ 608function dateDiff($date1, $date2) 609{ 610 if (version_compare(PHP_VERSION, '5.3.0') >= 0) 611 { 612 return $date1->diff($date2); 613 } 614 615 $diff = new stdClass(); 616 617 //Make sure $date1 is ealier 618 $diff->invert = $date2 < $date1; 619 if ($diff->invert) 620 { 621 list($date1, $date2) = array($date2, $date1); 622 } 623 624 //Calculate R values 625 $R = ($date1 <= $date2 ? '+' : '-'); 626 $r = ($date1 <= $date2 ? '' : '-'); 627 628 //Calculate total days 629 $diff->days = round(abs($date1->format('U') - $date2->format('U'))/86400); 630 631 //A leap year work around - consistent with DateInterval 632 $leap_year = $date1->format('m-d') == '02-29'; 633 if ($leap_year) 634 { 635 $date1->modify('-1 day'); 636 } 637 638 //Years, months, days, hours 639 $periods = array('years'=>-1, 'months'=>-1, 'days'=>-1, 'hours'=>-1); 640 641 foreach ($periods as $period => &$i) 642 { 643 if ($period == 'days' && $leap_year) 644 { 645 $date1->modify('+1 day'); 646 } 647 648 while ($date1 <= $date2 ) 649 { 650 $date1->modify('+1 '.$period); 651 $i++; 652 } 653 654 //Reset date and record increments 655 $date1->modify('-1 '.$period); 656 } 657 658 list($diff->y, $diff->m, $diff->d, $diff->h) = array_values($periods); 659 660 //Minutes, seconds 661 $diff->s = round(abs($date1->format('U') - $date2->format('U'))); 662 $diff->i = floor($diff->s/60); 663 $diff->s = $diff->s - $diff->i*60; 664 665 return $diff; 666} 667 668/** 669 * converts a string into a DateTime object 670 * 671 * @param int|string timestamp or datetime string 672 * @param string $format input format respecting date() syntax 673 * @return DateTime|false 674 */ 675function str2DateTime($original, $format=null) 676{ 677 if (empty($original)) 678 { 679 return false; 680 } 681 682 if ($original instanceof DateTime) 683 { 684 return $original; 685 } 686 687 if (!empty($format) && version_compare(PHP_VERSION, '5.3.0') >= 0)// from known date format 688 { 689 return DateTime::createFromFormat('!'.$format, $original); // ! char to reset fields to UNIX epoch 690 } 691 else 692 { 693 $t = trim($original, '0123456789'); 694 if (empty($t)) // from timestamp 695 { 696 return new DateTime('@'.$original); 697 } 698 else // from unknown date format (assuming something like Y-m-d H:i:s) 699 { 700 $ymdhms = array(); 701 $tok = strtok($original, '- :/'); 702 while ($tok !== false) 703 { 704 $ymdhms[] = $tok; 705 $tok = strtok('- :/'); 706 } 707 708 if (count($ymdhms)<3) return false; 709 if (!isset($ymdhms[3])) $ymdhms[3] = 0; 710 if (!isset($ymdhms[4])) $ymdhms[4] = 0; 711 if (!isset($ymdhms[5])) $ymdhms[5] = 0; 712 713 $date = new DateTime(); 714 $date->setDate($ymdhms[0], $ymdhms[1], $ymdhms[2]); 715 $date->setTime($ymdhms[3], $ymdhms[4], $ymdhms[5]); 716 return $date; 717 } 718 } 719} 720 721/** 722 * returns a formatted and localized date for display 723 * 724 * @param int|string timestamp or datetime string 725 * @param array $show list of components displayed, default is ['day_name', 'day', 'month', 'year'] 726 * THIS PARAMETER IS PLANNED TO CHANGE 727 * @param string $format input format respecting date() syntax 728 * @return string 729 */ 730function format_date($original, $show=null, $format=null) 731{ 732 global $lang; 733 734 $date = str2DateTime($original, $format); 735 736 if (!$date) 737 { 738 return l10n('N/A'); 739 } 740 741 if ($show === null || $show === true) 742 { 743 $show = array('day_name', 'day', 'month', 'year'); 744 } 745 746 // TODO use IntlDateFormatter for proper i18n 747 748 $print = ''; 749 if (in_array('day_name', $show)) 750 $print.= $lang['day'][ $date->format('w') ].' '; 751 752 if (in_array('day', $show)) 753 $print.= $date->format('j').' '; 754 755 if (in_array('month', $show)) 756 $print.= $lang['month'][ $date->format('n') ].' '; 757 758 if (in_array('year', $show)) 759 $print.= $date->format('Y').' '; 760 761 if (in_array('time', $show)) 762 { 763 $temp = $date->format('H:i'); 764 if ($temp != '00:00') 765 { 766 $print.= $temp.' '; 767 } 768 } 769 770 return trim($print); 771} 772 773/** 774 * Format a "From ... to ..." string from two dates 775 * @param string $from 776 * @param string $to 777 * @param boolean $full 778 * @return string 779 */ 780function format_fromto($from, $to, $full=false) 781{ 782 $from = str2DateTime($from); 783 $to = str2DateTime($to); 784 785 if ($from->format('Y-m-d') == $to->format('Y-m-d')) 786 { 787 return format_date($from); 788 } 789 else 790 { 791 if ($full || $from->format('Y') != $to->format('Y')) 792 { 793 $from_str = format_date($from); 794 } 795 else if ($from->format('m') != $to->format('m')) 796 { 797 $from_str = format_date($from, array('day_name', 'day', 'month')); 798 } 799 else 800 { 801 $from_str = format_date($from, array('day_name', 'day')); 802 } 803 $to_str = format_date($to); 804 805 return l10n('from %s to %s', $from_str, $to_str); 806 } 807} 808 809/** 810 * Works out the time since the given date 811 * 812 * @param int|string timestamp or datetime string 813 * @param string $stop year,month,week,day,hour,minute,second 814 * @param string $format input format respecting date() syntax 815 * @param bool $with_text append "ago" or "in the future" 816 * @param bool $with_weeks 817 * @return string 818 */ 819function time_since($original, $stop='minute', $format=null, $with_text=true, $with_week=true) 820{ 821 $date = str2DateTime($original, $format); 822 823 if (!$date) 824 { 825 return l10n('N/A'); 826 } 827 828 $now = new DateTime(); 829 $diff = dateDiff($now, $date); 830 831 $chunks = array( 832 'year' => $diff->y, 833 'month' => $diff->m, 834 'week' => 0, 835 'day' => $diff->d, 836 'hour' => $diff->h, 837 'minute' => $diff->i, 838 'second' => $diff->s, 839 ); 840 841 // DateInterval does not contain the number of weeks 842 if ($with_week) 843 { 844 $chunks['week'] = (int)floor($chunks['day']/7); 845 $chunks['day'] = $chunks['day'] - $chunks['week']*7; 846 } 847 848 $j = array_search($stop, array_keys($chunks)); 849 850 $print = ''; $i=0; 851 foreach ($chunks as $name => $value) 852 { 853 if ($value != 0) 854 { 855 $print.= ' '.l10n_dec('%d '.$name, '%d '.$name.'s', $value); 856 } 857 if (!empty($print) && $i >= $j) 858 { 859 break; 860 } 861 $i++; 862 } 863 864 $print = trim($print); 865 866 if ($with_text) 867 { 868 if ($diff->invert) 869 { 870 $print = l10n('%s ago', $print); 871 } 872 else 873 { 874 $print = l10n('%s in the future', $print); 875 } 876 } 877 878 return $print; 879} 880 881/** 882 * transform a date string from a format to another (MySQL to d/M/Y for instance) 883 * 884 * @param string $original 885 * @param string $format_in respecting date() syntax 886 * @param string $format_out respecting date() syntax 887 * @param string $default if _$original_ is empty 888 * @return string 889 */ 890function transform_date($original, $format_in, $format_out, $default=null) 891{ 892 if (empty($original)) return $default; 893 $date = str2DateTime($original, $format_in); 894 return $date->format($format_out); 895} 896 897/** 898 * append a variable to _$debug_ global 899 * 900 * @param string $string 901 */ 902function pwg_debug( $string ) 903{ 904 global $debug,$t2,$page; 905 906 $now = explode( ' ', microtime() ); 907 $now2 = explode( '.', $now[0] ); 908 $now2 = $now[1].'.'.$now2[1]; 909 $time = number_format( $now2 - $t2, 3, '.', ' ').' s'; 910 $debug .= '<p>'; 911 $debug.= '['.$time.', '; 912 $debug.= $page['count_queries'].' queries] : '.$string; 913 $debug.= "</p>\n"; 914} 915 916/** 917 * Redirects to the given URL (HTTP method). 918 * once this function called, the execution doesn't go further 919 * (presence of an exit() instruction. 920 * 921 * @param string $url 922 * @return void 923 */ 924function redirect_http( $url ) 925{ 926 if (ob_get_length () !== FALSE) 927 { 928 ob_clean(); 929 } 930 // default url is on html format 931 $url = html_entity_decode($url); 932 header('Request-URI: '.$url); 933 header('Content-Location: '.$url); 934 header('Location: '.$url); 935 exit(); 936} 937 938/** 939 * Redirects to the given URL (HTML method). 940 * once this function called, the execution doesn't go further 941 * (presence of an exit() instruction. 942 * 943 * @param string $url 944 * @param string $msg 945 * @param integer $refresh_time 946 * @return void 947 */ 948function redirect_html( $url , $msg = '', $refresh_time = 0) 949{ 950 global $user, $template, $lang_info, $conf, $lang, $t2, $page, $debug; 951 952 if (!isset($lang_info) || !isset($template) ) 953 { 954 $user = build_user( $conf['guest_id'], true); 955 load_language('common.lang'); 956 trigger_notify('loading_lang'); 957 load_language('lang', PHPWG_ROOT_PATH.PWG_LOCAL_DIR, array('no_fallback'=>true, 'local'=>true) ); 958 $template = new Template(PHPWG_ROOT_PATH.'themes', get_default_theme()); 959 } 960 elseif (defined('IN_ADMIN') and IN_ADMIN) 961 { 962 $template = new Template(PHPWG_ROOT_PATH.'themes', get_default_theme()); 963 } 964 965 if (empty($msg)) 966 { 967 $msg = nl2br(l10n('Redirection...')); 968 } 969 970 $refresh = $refresh_time; 971 $url_link = $url; 972 $title = 'redirection'; 973 974 $template->set_filenames( array( 'redirect' => 'redirect.tpl' ) ); 975 976 include( PHPWG_ROOT_PATH.'include/page_header.php' ); 977 978 $template->set_filenames( array( 'redirect' => 'redirect.tpl' ) ); 979 $template->assign('REDIRECT_MSG', $msg); 980 981 $template->parse('redirect'); 982 983 include( PHPWG_ROOT_PATH.'include/page_tail.php' ); 984 985 exit(); 986} 987 988/** 989 * Redirects to the given URL (automatically choose HTTP or HTML method). 990 * once this function called, the execution doesn't go further 991 * (presence of an exit() instruction. 992 * 993 * @param string $url 994 * @param string $msg 995 * @param integer $refresh_time 996 * @return void 997 */ 998function redirect( $url , $msg = '', $refresh_time = 0) 999{ 1000 global $conf; 1001 1002 // with RefeshTime <> 0, only html must be used 1003 if ($conf['default_redirect_method']=='http' 1004 and $refresh_time==0 1005 and !headers_sent() 1006 ) 1007 { 1008 redirect_http($url); 1009 } 1010 else 1011 { 1012 redirect_html($url, $msg, $refresh_time); 1013 } 1014} 1015 1016/** 1017 * returns available themes 1018 * 1019 * @param bool $show_mobile 1020 * @return array 1021 */ 1022function get_pwg_themes($show_mobile=false) 1023{ 1024 global $conf; 1025 1026 $themes = array(); 1027 1028 $query = ' 1029SELECT 1030 id, 1031 name 1032 FROM '.THEMES_TABLE.' 1033 ORDER BY name ASC 1034;'; 1035 $result = pwg_query($query); 1036 while ($row = pwg_db_fetch_assoc($result)) 1037 { 1038 if ($row['id'] == $conf['mobile_theme']) 1039 { 1040 if (!$show_mobile) 1041 { 1042 continue; 1043 } 1044 $row['name'] .= ' ('.l10n('Mobile').')'; 1045 } 1046 if (check_theme_installed($row['id'])) 1047 { 1048 $themes[ $row['id'] ] = $row['name']; 1049 } 1050 } 1051 1052 // plugins want remove some themes based on user status maybe? 1053 $themes = trigger_change('get_pwg_themes', $themes); 1054 1055 return $themes; 1056} 1057 1058/** 1059 * check if a theme is installed (directory exsists) 1060 * 1061 * @param string $theme_id 1062 * @return bool 1063 */ 1064function check_theme_installed($theme_id) 1065{ 1066 global $conf; 1067 1068 return file_exists($conf['themes_dir'].'/'.$theme_id.'/'.'themeconf.inc.php'); 1069} 1070 1071/** 1072 * Transforms an original path to its pwg representative 1073 * 1074 * @param string $path 1075 * @param string $representative_ext 1076 * @return string 1077 */ 1078function original_to_representative($path, $representative_ext) 1079{ 1080 $pos = strrpos($path, '/'); 1081 $path = substr_replace($path, 'pwg_representative/', $pos+1, 0); 1082 $pos = strrpos($path, '.'); 1083 return substr_replace($path, $representative_ext, $pos+1); 1084} 1085 1086/** 1087 * Transforms an original path to its format 1088 * 1089 * @param string $path 1090 * @param string $format_ext 1091 * @return string 1092 */ 1093function original_to_format($path, $format_ext) 1094{ 1095 $pos = strrpos($path, '/'); 1096 $path = substr_replace($path, 'pwg_format/', $pos+1, 0); 1097 $pos = strrpos($path, '.'); 1098 return substr_replace($path, $format_ext, $pos+1); 1099} 1100 1101/** 1102 * get the full path of an image 1103 * 1104 * @param array $element_info element information from db (at least 'path') 1105 * @return string 1106 */ 1107function get_element_path($element_info) 1108{ 1109 $path = $element_info['path']; 1110 if ( !url_is_remote($path) ) 1111 { 1112 $path = PHPWG_ROOT_PATH.$path; 1113 } 1114 return $path; 1115} 1116 1117 1118/** 1119 * fill the current user caddie with given elements, if not already in caddie 1120 * 1121 * @param int[] $elements_id 1122 */ 1123function fill_caddie($elements_id) 1124{ 1125 global $user; 1126 1127 $query = ' 1128SELECT element_id 1129 FROM '.CADDIE_TABLE.' 1130 WHERE user_id = '.$user['id'].' 1131;'; 1132 $in_caddie = query2array($query, null, 'element_id'); 1133 1134 $caddiables = array_diff($elements_id, $in_caddie); 1135 1136 $datas = array(); 1137 1138 foreach ($caddiables as $caddiable) 1139 { 1140 $datas[] = array( 1141 'element_id' => $caddiable, 1142 'user_id' => $user['id'], 1143 ); 1144 } 1145 1146 if (count($caddiables) > 0) 1147 { 1148 mass_inserts(CADDIE_TABLE, array('element_id','user_id'), $datas); 1149 } 1150} 1151 1152/** 1153 * returns the element name from its filename. 1154 * removes file extension and replace underscores by spaces 1155 * 1156 * @param string $filename 1157 * @return string name 1158 */ 1159function get_name_from_file($filename) 1160{ 1161 return str_replace('_',' ',get_filename_wo_extension($filename)); 1162} 1163 1164/** 1165 * translation function. 1166 * returns the corresponding value from _$lang_ if existing else the key is returned 1167 * if more than one parameter is provided sprintf is applied 1168 * 1169 * @param string $key 1170 * @param mixed $args,... optional arguments 1171 * @return string 1172 */ 1173function l10n($key) 1174{ 1175 global $lang, $conf; 1176 1177 if ( ($val=@$lang[$key]) === null) 1178 { 1179 if ($conf['debug_l10n'] and !isset($lang[$key]) and !empty($key)) 1180 { 1181 trigger_error('[l10n] language key "'. $key .'" not defined', E_USER_WARNING); 1182 } 1183 $val = $key; 1184 } 1185 1186 if (func_num_args() > 1) 1187 { 1188 $args = func_get_args(); 1189 $val = vsprintf($val, array_slice($args, 1)); 1190 } 1191 1192 return $val; 1193} 1194 1195/** 1196 * returns the printf value for strings including %d 1197 * returned value is concorded with decimal value (singular, plural) 1198 * 1199 * @param string $singular_key 1200 * @param string $plural_key 1201 * @param int $decimal 1202 * @return string 1203 */ 1204function l10n_dec($singular_key, $plural_key, $decimal) 1205{ 1206 global $lang_info; 1207 1208 return 1209 sprintf( 1210 l10n(( 1211 (($decimal > 1) or ($decimal == 0 and $lang_info['zero_plural'])) 1212 ? $plural_key 1213 : $singular_key 1214 )), $decimal); 1215} 1216 1217/** 1218 * returns a single element to use with l10n_args 1219 * 1220 * @param string $key translation key 1221 * @param mixed $args arguments to use on sprintf($key, args) 1222 * if args is a array, each values are used on sprintf 1223 * @return string 1224 */ 1225function get_l10n_args($key, $args='') 1226{ 1227 if (is_array($args)) 1228 { 1229 $key_arg = array_merge(array($key), $args); 1230 } 1231 else 1232 { 1233 $key_arg = array($key, $args); 1234 } 1235 return array('key_args' => $key_arg); 1236} 1237 1238/** 1239 * returns a string formated with l10n elements. 1240 * it is usefull to "prepare" a text and translate it later 1241 * @see get_l10n_args() 1242 * 1243 * @param array $key_args one l10n_args element or array of l10n_args elements 1244 * @param string $sep used when translated elements are concatened 1245 * @return string 1246 */ 1247function l10n_args($key_args, $sep = "\n") 1248{ 1249 if (is_array($key_args)) 1250 { 1251 foreach ($key_args as $key => $element) 1252 { 1253 if (isset($result)) 1254 { 1255 $result .= $sep; 1256 } 1257 else 1258 { 1259 $result = ''; 1260 } 1261 1262 if ($key === 'key_args') 1263 { 1264 array_unshift($element, l10n(array_shift($element))); // translate the key 1265 $result .= call_user_func_array('sprintf', $element); 1266 } 1267 else 1268 { 1269 $result .= l10n_args($element, $sep); 1270 } 1271 } 1272 } 1273 else 1274 { 1275 fatal_error('l10n_args: Invalid arguments'); 1276 } 1277 1278 return $result; 1279} 1280 1281/** 1282 * returns the corresponding value from $themeconf if existing or an empty string 1283 * 1284 * @param string $key 1285 * @return string 1286 */ 1287function get_themeconf($key) 1288{ 1289 return $GLOBALS['template']->get_themeconf($key); 1290} 1291 1292/** 1293 * Returns webmaster mail address depending on $conf['webmaster_id'] 1294 * 1295 * @return string 1296 */ 1297function get_webmaster_mail_address() 1298{ 1299 global $conf; 1300 1301 $query = ' 1302SELECT '.$conf['user_fields']['email'].' 1303 FROM '.USERS_TABLE.' 1304 WHERE '.$conf['user_fields']['id'].' = '.$conf['webmaster_id'].' 1305;'; 1306 list($email) = pwg_db_fetch_row(pwg_query($query)); 1307 1308 $email = trigger_change('get_webmaster_mail_address', $email); 1309 1310 return $email; 1311} 1312 1313/** 1314 * Add configuration parameters from database to global $conf array 1315 * 1316 * @param string $condition SQL condition 1317 * @return void 1318 */ 1319function load_conf_from_db($condition = '') 1320{ 1321 global $conf; 1322 1323 $query = ' 1324SELECT param, value 1325 FROM '.CONFIG_TABLE.' 1326 '.(!empty($condition) ? 'WHERE '.$condition : '').' 1327;'; 1328 $result = pwg_query($query); 1329 1330 if ((pwg_db_num_rows($result) == 0) and !empty($condition)) 1331 { 1332 fatal_error('No configuration data'); 1333 } 1334 1335 while ($row = pwg_db_fetch_assoc($result)) 1336 { 1337 $val = isset($row['value']) ? $row['value'] : ''; 1338 // If the field is true or false, the variable is transformed into a boolean value. 1339 if ($val == 'true') 1340 { 1341 $val = true; 1342 } 1343 elseif ($val == 'false') 1344 { 1345 $val = false; 1346 } 1347 $conf[ $row['param'] ] = $val; 1348 } 1349 1350 trigger_notify('load_conf', $condition); 1351} 1352 1353/** 1354 * Add or update a config parameter 1355 * 1356 * @param string $param 1357 * @param string $value 1358 * @param boolean $updateGlobal update global *$conf* variable 1359 * @param callable $parser function to apply to the value before save in database 1360 (eg: serialize, json_encode) will not be applied to *$conf* if *$parser* is *true* 1361 */ 1362function conf_update_param($param, $value, $updateGlobal=false, $parser=null) 1363{ 1364 if ($parser != null) 1365 { 1366 $dbValue = call_user_func($parser, $value); 1367 } 1368 else if (is_array($value) || is_object($value)) 1369 { 1370 $dbValue = addslashes(serialize($value)); 1371 } 1372 else 1373 { 1374 $dbValue = boolean_to_string($value); 1375 } 1376 1377 $query = ' 1378INSERT INTO 1379 '.CONFIG_TABLE.' (param, value) 1380 VALUES(\''.$param.'\', \''.$dbValue.'\') 1381 ON DUPLICATE KEY UPDATE value = \''.$dbValue.'\' 1382;'; 1383 1384 pwg_query($query, false); 1385 1386 if ($updateGlobal) 1387 { 1388 global $conf; 1389 $conf[$param] = $value; 1390 } 1391} 1392 1393/** 1394 * Delete one or more config parameters 1395 * @since 2.6 1396 * 1397 * @param string|string[] $params 1398 */ 1399function conf_delete_param($params) 1400{ 1401 global $conf; 1402 1403 if (!is_array($params)) 1404 { 1405 $params = array($params); 1406 } 1407 if (empty($params)) 1408 { 1409 return; 1410 } 1411 1412 $query = ' 1413DELETE FROM '.CONFIG_TABLE.' 1414 WHERE param IN(\''. implode('\',\'', $params) .'\') 1415;'; 1416 pwg_query($query); 1417 1418 foreach ($params as $param) 1419 { 1420 unset($conf[$param]); 1421 } 1422} 1423 1424/** 1425 * Return a default value for a configuration parameter. 1426 * @since 2.8 1427 * 1428 * @param string $param the configuration value to be extracted (if it exists) 1429 * @param mixed $default_value the default value for the configuration value if it does not exist. 1430 * 1431 * @return mixed The configuration value if the variable exists, otherwise the default. 1432 */ 1433function conf_get_param($param, $default_value=null) 1434{ 1435 global $conf; 1436 1437 if (isset($conf[$param])) 1438 { 1439 return $conf[$param]; 1440 } 1441 return $default_value; 1442} 1443 1444 1445/** 1446 * Apply *unserialize* on a value only if it is a string 1447 * @since 2.7 1448 * 1449 * @param array|string $value 1450 * @return array 1451 */ 1452function safe_unserialize($value) 1453{ 1454 if (is_string($value)) 1455 { 1456 return unserialize($value); 1457 } 1458 return $value; 1459} 1460 1461/** 1462 * Apply *json_decode* on a value only if it is a string 1463 * @since 2.7 1464 * 1465 * @param array|string $value 1466 * @return array 1467 */ 1468function safe_json_decode($value) 1469{ 1470 if (is_string($value)) 1471 { 1472 return json_decode($value, true); 1473 } 1474 return $value; 1475} 1476 1477/** 1478 * Prepends and appends strings at each value of the given array. 1479 * 1480 * @param array $array 1481 * @param string $prepend_str 1482 * @param string $append_str 1483 * @return array 1484 */ 1485function prepend_append_array_items($array, $prepend_str, $append_str) 1486{ 1487 array_walk($array, function(&$value, $key) use($prepend_str,$append_str) { $value = "$prepend_str$value$append_str"; } ); 1488 return $array; 1489} 1490 1491/** 1492 * creates an simple hashmap based on a SQL query. 1493 * choose one to be the key, another one to be the value. 1494 * @deprecated 2.6 1495 * 1496 * @param string $query 1497 * @param string $keyname 1498 * @param string $valuename 1499 * @return array 1500 */ 1501function simple_hash_from_query($query, $keyname, $valuename) 1502{ 1503 return query2array($query, $keyname, $valuename); 1504} 1505 1506/** 1507 * creates an associative array based on a SQL query. 1508 * choose one to be the key 1509 * @deprecated 2.6 1510 * 1511 * @param string $query 1512 * @param string $keyname 1513 * @return array 1514 */ 1515function hash_from_query($query, $keyname) 1516{ 1517 return query2array($query, $keyname); 1518} 1519 1520/** 1521 * creates a numeric array based on a SQL query. 1522 * if _$fieldname_ is empty the returned value will be an array of arrays 1523 * if _$fieldname_ is provided the returned value will be a one dimension array 1524 * @deprecated 2.6 1525 * 1526 * @param string $query 1527 * @param string $fieldname 1528 * @return array 1529 */ 1530function array_from_query($query, $fieldname=false) 1531{ 1532 if (false === $fieldname) 1533 { 1534 return query2array($query); 1535 } 1536 else 1537 { 1538 return query2array($query, null, $fieldname); 1539 } 1540} 1541 1542/** 1543 * Return the basename of the current script. 1544 * The lowercase case filename of the current script without extension 1545 * 1546 * @return string 1547 */ 1548function script_basename() 1549{ 1550 global $conf; 1551 1552 foreach (array('SCRIPT_NAME', 'SCRIPT_FILENAME', 'PHP_SELF') as $value) 1553 { 1554 if (!empty($_SERVER[$value])) 1555 { 1556 $filename = strtolower($_SERVER[$value]); 1557 if ($conf['php_extension_in_urls'] and get_extension($filename)!=='php') 1558 continue; 1559 $basename = basename($filename, '.php'); 1560 if (!empty($basename)) 1561 { 1562 return $basename; 1563 } 1564 } 1565 } 1566 return ''; 1567} 1568 1569/** 1570 * Return $conf['filter_pages'] value for the current page 1571 * 1572 * @param string $value_name 1573 * @return mixed 1574 */ 1575function get_filter_page_value($value_name) 1576{ 1577 global $conf; 1578 1579 $page_name = script_basename(); 1580 1581 if (isset($conf['filter_pages'][$page_name][$value_name])) 1582 { 1583 return $conf['filter_pages'][$page_name][$value_name]; 1584 } 1585 elseif (isset($conf['filter_pages']['default'][$value_name])) 1586 { 1587 return $conf['filter_pages']['default'][$value_name]; 1588 } 1589 else 1590 { 1591 return null; 1592 } 1593} 1594 1595/** 1596 * return the character set used by Piwigo 1597 * @return string 1598 */ 1599function get_pwg_charset() 1600{ 1601 $pwg_charset = 'utf-8'; 1602 if (defined('PWG_CHARSET')) 1603 { 1604 $pwg_charset = PWG_CHARSET; 1605 } 1606 return $pwg_charset; 1607} 1608 1609/** 1610 * returns the parent (fallback) language of a language. 1611 * if _$lang_id_ is null it applies to the current language 1612 * @since 2.6 1613 * 1614 * @param string $lang_id 1615 * @return string|null 1616 */ 1617function get_parent_language($lang_id=null) 1618{ 1619 if (empty($lang_id)) 1620 { 1621 global $lang_info; 1622 return !empty($lang_info['parent']) ? $lang_info['parent'] : null; 1623 } 1624 else 1625 { 1626 $f = PHPWG_ROOT_PATH.'language/'.$lang_id.'/common.lang.php'; 1627 if (file_exists($f)) 1628 { 1629 include($f); 1630 return !empty($lang_info['parent']) ? $lang_info['parent'] : null; 1631 } 1632 } 1633 return null; 1634} 1635 1636/** 1637 * includes a language file or returns the content of a language file 1638 * 1639 * tries to load in descending order: 1640 * param language, user language, default language 1641 * 1642 * @param string $filename 1643 * @param string $dirname 1644 * @param mixed options can contain 1645 * @option string language - language to load 1646 * @option bool return - if true the file content is returned 1647 * @option bool no_fallback - if true do not load default language 1648 * @option bool|string force_fallback - force pre-loading of another language 1649 * default language if *true* or specified language 1650 * @option bool local - if true load file from local directory 1651 * @return boolean|string 1652 */ 1653function load_language($filename, $dirname = '', $options = array()) 1654{ 1655 global $user, $language_files; 1656 1657 // keep trace of plugins loaded files for switch_lang_to() function 1658 if (!empty($dirname) && !empty($filename) && !@$options['return'] 1659 && !isset($language_files[$dirname][$filename])) 1660 { 1661 $language_files[$dirname][$filename] = $options; 1662 } 1663 1664 if (!@$options['return']) 1665 { 1666 $filename .= '.php'; 1667 } 1668 if (empty($dirname)) 1669 { 1670 $dirname = PHPWG_ROOT_PATH; 1671 } 1672 $dirname .= 'language/'; 1673 1674 $default_language = (defined('PHPWG_INSTALLED') and !defined('UPGRADES_PATH')) ? 1675 get_default_language() : PHPWG_DEFAULT_LANGUAGE; 1676 1677 // construct list of potential languages 1678 $languages = array(); 1679 if (!empty($options['language'])) 1680 { // explicit language 1681 $languages[] = $options['language']; 1682 } 1683 if (!empty($user['language'])) 1684 { // use language 1685 $languages[] = $user['language']; 1686 } 1687 if (($parent = get_parent_language()) != null) 1688 { // parent language 1689 // this is only for when the "child" language is missing 1690 $languages[] = $parent; 1691 } 1692 if (isset($options['force_fallback'])) 1693 { // fallback language 1694 // this is only for when the main language is missing 1695 if ($options['force_fallback'] === true) 1696 { 1697 $options['force_fallback'] = $default_language; 1698 } 1699 $languages[] = $options['force_fallback']; 1700 } 1701 if (!@$options['no_fallback']) 1702 { // default language 1703 $languages[] = $default_language; 1704 } 1705 1706 $languages = array_unique($languages); 1707 1708 // find first existing 1709 $source_file = ''; 1710 $selected_language = ''; 1711 foreach ($languages as $language) 1712 { 1713 $f = @$options['local'] ? 1714 $dirname.$language.'.'.$filename: 1715 $dirname.$language.'/'.$filename; 1716 1717 if (file_exists($f)) 1718 { 1719 $selected_language = $language; 1720 $source_file = $f; 1721 break; 1722 } 1723 } 1724 1725 if (!empty($source_file)) 1726 { 1727 if (!@$options['return']) 1728 { 1729 // load forced fallback 1730 if (isset($options['force_fallback']) && $options['force_fallback'] != $selected_language) 1731 { 1732 @include(str_replace($selected_language, $options['force_fallback'], $source_file)); 1733 } 1734 1735 // load language content 1736 @include($source_file); 1737 $load_lang = @$lang; 1738 $load_lang_info = @$lang_info; 1739 1740 // access already existing values 1741 global $lang, $lang_info; 1742 if (!isset($lang)) $lang = array(); 1743 if (!isset($lang_info)) $lang_info = array(); 1744 1745 // load parent language content directly in global 1746 if (!empty($load_lang_info['parent'])) 1747 $parent_language = $load_lang_info['parent']; 1748 else if (!empty($lang_info['parent'])) 1749 $parent_language = $lang_info['parent']; 1750 else 1751 $parent_language = null; 1752 1753 if (!empty($parent_language) && $parent_language != $selected_language) 1754 { 1755 @include(str_replace($selected_language, $parent_language, $source_file)); 1756 } 1757 1758 // merge contents 1759 $lang = array_merge($lang, (array)$load_lang); 1760 $lang_info = array_merge($lang_info, (array)$load_lang_info); 1761 return true; 1762 } 1763 else 1764 { 1765 $content = @file_get_contents($source_file); 1766 //Note: target charset is always utf-8 $content = convert_charset($content, 'utf-8', $target_charset); 1767 return $content; 1768 } 1769 } 1770 1771 return false; 1772} 1773 1774/** 1775 * converts a string from a character set to another character set 1776 * 1777 * @param string $str 1778 * @param string $source_charset 1779 * @param string $dest_charset 1780 */ 1781function convert_charset($str, $source_charset, $dest_charset) 1782{ 1783 if ($source_charset==$dest_charset) 1784 return $str; 1785 if ($source_charset=='iso-8859-1' and $dest_charset=='utf-8') 1786 { 1787 return utf8_encode($str); 1788 } 1789 if ($source_charset=='utf-8' and $dest_charset=='iso-8859-1') 1790 { 1791 return utf8_decode($str); 1792 } 1793 if (function_exists('iconv')) 1794 { 1795 return iconv($source_charset, $dest_charset.'//TRANSLIT', $str); 1796 } 1797 if (function_exists('mb_convert_encoding')) 1798 { 1799 return mb_convert_encoding( $str, $dest_charset, $source_charset ); 1800 } 1801 return $str; // TODO 1802} 1803 1804/** 1805 * makes sure a index.htm protects the directory from browser file listing 1806 * 1807 * @param string $dir 1808 */ 1809function secure_directory($dir) 1810{ 1811 $file = $dir.'/index.htm'; 1812 if (!file_exists($file)) 1813 { 1814 @file_put_contents($file, 'Not allowed!'); 1815 } 1816} 1817 1818/** 1819 * returns a "secret key" that is to be sent back when a user posts a form 1820 * 1821 * @param int $valid_after_seconds - key validity start time from now 1822 * @param string $aditionnal_data_to_hash 1823 * @return string 1824 */ 1825function get_ephemeral_key($valid_after_seconds, $aditionnal_data_to_hash = '') 1826{ 1827 global $conf; 1828 $time = round(microtime(true), 1); 1829 return $time.':'.$valid_after_seconds.':' 1830 .hash_hmac( 1831 'md5', 1832 $time.substr($_SERVER['REMOTE_ADDR'],0,5).$valid_after_seconds.$aditionnal_data_to_hash, 1833 $conf['secret_key']); 1834} 1835 1836/** 1837 * verify a key sent back with a form 1838 * 1839 * @param string $key 1840 * @param string $aditionnal_data_to_hash 1841 * @return bool 1842 */ 1843function verify_ephemeral_key($key, $aditionnal_data_to_hash = '') 1844{ 1845 global $conf; 1846 $time = microtime(true); 1847 $key = explode( ':', @$key ); 1848 if ( count($key)!=3 1849 or $key[0]>$time-(float)$key[1] // page must have been retrieved more than X sec ago 1850 or $key[0]<$time-3600 // 60 minutes expiration 1851 or hash_hmac( 1852 'md5', $key[0].substr($_SERVER['REMOTE_ADDR'],0,5).$key[1].$aditionnal_data_to_hash, $conf['secret_key'] 1853 ) != $key[2] 1854 ) 1855 { 1856 return false; 1857 } 1858 return true; 1859} 1860 1861/** 1862 * return an array which will be sent to template to display navigation bar 1863 * 1864 * @param string $url base url of all links 1865 * @param int $nb_elements 1866 * @param int $start 1867 * @param int $nb_element_page 1868 * @param bool $clean_url 1869 * @param string $param_name 1870 * @return array 1871 */ 1872function create_navigation_bar($url, $nb_element, $start, $nb_element_page, $clean_url = false, $param_name='start') 1873{ 1874 global $conf; 1875 1876 $navbar = array(); 1877 $pages_around = $conf['paginate_pages_around']; 1878 $start_str = $clean_url ? '/'.$param_name.'-' : (strpos($url, '?')===false ? '?':'&').$param_name.'='; 1879 1880 if (!isset($start) or !is_numeric($start) or (is_numeric($start) and $start < 0)) 1881 { 1882 $start = 0; 1883 } 1884 1885 // navigation bar useful only if more than one page to display ! 1886 if ($nb_element > $nb_element_page) 1887 { 1888 $url_start = $url.$start_str; 1889 1890 $cur_page = $navbar['CURRENT_PAGE'] = $start / $nb_element_page + 1; 1891 $maximum = ceil($nb_element / $nb_element_page); 1892 1893 $start = $nb_element_page * round( $start / $nb_element_page ); 1894 $previous = $start - $nb_element_page; 1895 $next = $start + $nb_element_page; 1896 $last = ($maximum - 1) * $nb_element_page; 1897 1898 // link to first page and previous page? 1899 if ($cur_page != 1) 1900 { 1901 $navbar['URL_FIRST'] = $url; 1902 $navbar['URL_PREV'] = $previous > 0 ? $url_start.$previous : $url; 1903 } 1904 // link on next page and last page? 1905 if ($cur_page != $maximum) 1906 { 1907 $navbar['URL_NEXT'] = $url_start.($next < $last ? $next : $last); 1908 $navbar['URL_LAST'] = $url_start.$last; 1909 } 1910 1911 // pages to display 1912 $navbar['pages'] = array(); 1913 $navbar['pages'][1] = $url; 1914 for ($i = max( floor($cur_page) - $pages_around , 2), $stop = min( ceil($cur_page) + $pages_around + 1, $maximum); 1915 $i < $stop; $i++) 1916 { 1917 $navbar['pages'][$i] = $url.$start_str.(($i - 1) * $nb_element_page); 1918 } 1919 $navbar['pages'][$maximum] = $url_start.$last; 1920 $navbar['NB_PAGE']=$maximum; 1921 } 1922 return $navbar; 1923} 1924 1925/** 1926 * return an array which will be sent to template to display recent icon 1927 * 1928 * @param string $date 1929 * @param bool $is_child_date 1930 * @return array 1931 */ 1932function get_icon($date, $is_child_date = false) 1933{ 1934 global $cache, $user; 1935 1936 if (empty($date)) 1937 { 1938 return false; 1939 } 1940 1941 if (!isset($cache['get_icon']['title'])) 1942 { 1943 $cache['get_icon']['title'] = l10n( 1944 'photos posted during the last %d days', 1945 $user['recent_period'] 1946 ); 1947 } 1948 1949 $icon = array( 1950 'TITLE' => $cache['get_icon']['title'], 1951 'IS_CHILD_DATE' => $is_child_date, 1952 ); 1953 1954 if (isset($cache['get_icon'][$date])) 1955 { 1956 return $cache['get_icon'][$date] ? $icon : array(); 1957 } 1958 1959 if (!isset($cache['get_icon']['sql_recent_date'])) 1960 { 1961 // Use MySql date in order to standardize all recent "actions/queries" 1962 $cache['get_icon']['sql_recent_date'] = pwg_db_get_recent_period($user['recent_period']); 1963 } 1964 1965 $cache['get_icon'][$date] = $date > $cache['get_icon']['sql_recent_date']; 1966 1967 return $cache['get_icon'][$date] ? $icon : array(); 1968} 1969 1970/** 1971 * check token comming from form posted or get params to prevent csrf attacks. 1972 * if pwg_token is empty action doesn't require token 1973 * else pwg_token is compare to server token 1974 * 1975 * @return void access denied if token given is not equal to server token 1976 */ 1977function check_pwg_token() 1978{ 1979 if (!empty($_REQUEST['pwg_token'])) 1980 { 1981 if (get_pwg_token() != $_REQUEST['pwg_token']) 1982 { 1983 access_denied(); 1984 } 1985 } 1986 else 1987 { 1988 bad_request('missing token'); 1989 } 1990} 1991 1992/** 1993 * get pwg_token used to prevent csrf attacks 1994 * 1995 * @return string 1996 */ 1997function get_pwg_token() 1998{ 1999 global $conf; 2000 2001 return hash_hmac('md5', session_id(), $conf['secret_key']); 2002} 2003 2004/* 2005 * breaks the script execution if the given value doesn't match the given 2006 * pattern. This should happen only during hacking attempts. 2007 * 2008 * @param string $param_name 2009 * @param array $param_array 2010 * @param boolean $is_array 2011 * @param string $pattern 2012 * @param boolean $mandatory 2013 */ 2014function check_input_parameter($param_name, $param_array, $is_array, $pattern, $mandatory=false) 2015{ 2016 $param_value = null; 2017 if (isset($param_array[$param_name])) 2018 { 2019 $param_value = $param_array[$param_name]; 2020 } 2021 2022 // it's ok if the input parameter is null 2023 if (empty($param_value)) 2024 { 2025 if ($mandatory) 2026 { 2027 fatal_error('[Hacking attempt] the input parameter "'.$param_name.'" is not valid'); 2028 } 2029 return true; 2030 } 2031 2032 if ($is_array) 2033 { 2034 if (!is_array($param_value)) 2035 { 2036 fatal_error('[Hacking attempt] the input parameter "'.$param_name.'" should be an array'); 2037 } 2038 2039 foreach ($param_value as $key => $item_to_check) 2040 { 2041 if (!preg_match(PATTERN_ID, $key) or !preg_match($pattern, $item_to_check)) 2042 { 2043 fatal_error('[Hacking attempt] an item is not valid in input parameter "'.$param_name.'"'); 2044 } 2045 } 2046 } 2047 else 2048 { 2049 if (!preg_match($pattern, $param_value)) 2050 { 2051 fatal_error('[Hacking attempt] the input parameter "'.$param_name.'" is not valid'); 2052 } 2053 } 2054} 2055 2056/** 2057 * get localized privacy level values 2058 * 2059 * @return string[] 2060 */ 2061function get_privacy_level_options() 2062{ 2063 global $conf; 2064 2065 $options = array(); 2066 $label = ''; 2067 foreach (array_reverse($conf['available_permission_levels']) as $level) 2068 { 2069 if (0 == $level) 2070 { 2071 $label = l10n('Everybody'); 2072 } 2073 else 2074 { 2075 if (strlen($label)) 2076 { 2077 $label .= ', '; 2078 } 2079 $label .= l10n( sprintf('Level %d', $level) ); 2080 } 2081 $options[$level] = $label; 2082 } 2083 return $options; 2084} 2085 2086 2087/** 2088 * return the branch from the version. For example version 11.1.2 is on branch 11 2089 * 2090 * @param string $version 2091 * @return string 2092 */ 2093function get_branch_from_version($version) 2094{ 2095 // the algorithm is a bit complicated to just retrieve the first digits before 2096 // the first ".". It's because before version 11.0.0, we used to take the 2 first 2097 // digits, ie version 2.2.4 was on branch 2.2 2098 return implode('.', array_slice(explode('.', $version), 0, 1)); 2099} 2100 2101/** 2102 * return the device type: mobile, tablet or desktop 2103 * 2104 * @return string 2105 */ 2106function get_device() 2107{ 2108 $device = pwg_get_session_var('device'); 2109 2110 if (is_null($device)) 2111 { 2112 include_once(PHPWG_ROOT_PATH.'include/mdetect.php'); 2113 $uagent_obj = new uagent_info(); 2114 if ($uagent_obj->DetectSmartphone()) 2115 { 2116 $device = 'mobile'; 2117 } 2118 elseif ($uagent_obj->DetectTierTablet()) 2119 { 2120 $device = 'tablet'; 2121 } 2122 else 2123 { 2124 $device = 'desktop'; 2125 } 2126 pwg_set_session_var('device', $device); 2127 } 2128 2129 return $device; 2130} 2131 2132/** 2133 * return true if mobile theme should be loaded 2134 * 2135 * @return bool 2136 */ 2137function mobile_theme() 2138{ 2139 global $conf; 2140 2141 if (empty($conf['mobile_theme'])) 2142 { 2143 return false; 2144 } 2145 2146 if (isset($_GET['mobile'])) 2147 { 2148 $is_mobile_theme = get_boolean($_GET['mobile']); 2149 pwg_set_session_var('mobile_theme', $is_mobile_theme); 2150 } 2151 else 2152 { 2153 $is_mobile_theme = pwg_get_session_var('mobile_theme'); 2154 } 2155 2156 if (is_null($is_mobile_theme)) 2157 { 2158 $is_mobile_theme = (get_device() == 'mobile'); 2159 pwg_set_session_var('mobile_theme', $is_mobile_theme); 2160 } 2161 2162 return $is_mobile_theme; 2163} 2164 2165/** 2166 * check url format 2167 * 2168 * @param string $url 2169 * @return bool 2170 */ 2171function url_check_format($url) 2172{ 2173 if (strpos($url, '"') !== false) 2174 { 2175 return false; 2176 } 2177 2178 if (strncmp($url, 'http://', 7) !== 0 and strncmp($url, 'https://', 8) !== 0) 2179 { 2180 return false; 2181 } 2182 2183 return filter_var($url, FILTER_VALIDATE_URL, FILTER_FLAG_SCHEME_REQUIRED | FILTER_FLAG_HOST_REQUIRED)!==false; 2184} 2185 2186/** 2187 * check email format 2188 * 2189 * @param string $mail_address 2190 * @return bool 2191 */ 2192function email_check_format($mail_address) 2193{ 2194 return filter_var($mail_address, FILTER_VALIDATE_EMAIL)!==false; 2195} 2196 2197/** 2198 * returns the number of available comments for the connected user 2199 * 2200 * @return int 2201 */ 2202function get_nb_available_comments() 2203{ 2204 global $user; 2205 if (!isset($user['nb_available_comments'])) 2206 { 2207 $where = array(); 2208 if ( !is_admin() ) 2209 $where[] = 'validated=\'true\''; 2210 $where[] = get_sql_condition_FandF 2211 ( 2212 array 2213 ( 2214 'forbidden_categories' => 'category_id', 2215 'forbidden_images' => 'ic.image_id' 2216 ), 2217 '', true 2218 ); 2219 2220 $query = ' 2221SELECT COUNT(DISTINCT(com.id)) 2222 FROM '.IMAGE_CATEGORY_TABLE.' AS ic 2223 INNER JOIN '.COMMENTS_TABLE.' AS com 2224 ON ic.image_id = com.image_id 2225 WHERE '.implode(' 2226 AND ', $where); 2227 list($user['nb_available_comments']) = pwg_db_fetch_row(pwg_query($query)); 2228 2229 single_update(USER_CACHE_TABLE, 2230 array('nb_available_comments'=>$user['nb_available_comments']), 2231 array('user_id'=>$user['id']) 2232 ); 2233 } 2234 return $user['nb_available_comments']; 2235} 2236 2237/** 2238 * Compare two versions with version_compare after having converted 2239 * single chars to their decimal values. 2240 * Needed because version_compare does not understand versions like '2.5.c'. 2241 * @since 2.6 2242 * 2243 * @param string $a 2244 * @param string $b 2245 * @param string $op 2246 */ 2247function safe_version_compare($a, $b, $op=null) 2248{ 2249 $replace_chars = function($m) { return ord(strtolower($m[1])); }; 2250 2251 // add dot before groups of letters (version_compare does the same thing) 2252 $a = preg_replace('#([0-9]+)([a-z]+)#i', '$1.$2', $a); 2253 $b = preg_replace('#([0-9]+)([a-z]+)#i', '$1.$2', $b); 2254 2255 // apply ord() to any single letter 2256 $a = preg_replace_callback('#\b([a-z]{1})\b#i', $replace_chars, $a); 2257 $b = preg_replace_callback('#\b([a-z]{1})\b#i', $replace_chars, $b); 2258 2259 if (empty($op)) 2260 { 2261 return version_compare($a, $b); 2262 } 2263 else 2264 { 2265 return version_compare($a, $b, $op); 2266 } 2267} 2268 2269?> 2270