1<?php 2// (c) Copyright by authors of the Tiki Wiki CMS Groupware Project 3// 4// All Rights Reserved. See copyright.txt for details and a complete list of authors. 5// Licensed under the GNU LESSER GENERAL PUBLIC LICENSE. See license.txt for details. 6// $Id$ 7 8require_once(__DIR__ . '/../lib/debug/Tracer.php'); 9 10// this script may only be included - so its better to die if called directly. 11if (strpos($_SERVER["SCRIPT_NAME"], basename(__FILE__)) !== false) { 12 header("location: index.php"); 13 exit; 14} 15 16// This class is included by all the Tiki php scripts, so it's important 17// to keep the class as small as possible to improve performance. 18// What goes in this class: 19// * generic functions that MANY scripts must use 20// * shared functions (marked as /*shared*/) are functions that are 21// called from Tiki modules. 22 23/** 24 * 25 */ 26class TikiLib extends TikiDb_Bridge 27{ 28 public $buffer; 29 public $flag; 30 public $usergroups_cache = []; 31 32 public $num_queries = 0; 33 public $now; 34 35 public $cache_page_info = []; 36 public $sessionId = null; 37 38 /** 39 * Collection of Tiki libraries. 40 * Populated by TikiLib::lib() 41 * @var array 42 */ 43 protected static $libraries = []; 44 45 protected static $isExternalContext = false; 46 47 /** Gets a library reference 48 * 49 * @param $name string The name of the library as specified in the id attribute in db/config/tiki.xml 50 * @return object|\AccountingLib|\ActivityLib|\AdminLib|\AreasLib|\ArtLib|\AttributeLib|\AutoSaveLib|\BannerLib|\BigBlueButtonLib|\blacklistLib|\ocrLib|\BlogLib|\CacheLib|\CalendarLib|\Captcha|\CartLib|\CategLib|\Comments|\ContactLib|\ContributionLib|\CreditsLib|\CryptLib|\cssLib|\Tiki\CustomRoute\CustomRouteLib|\DCSLib|\EditLib|\ErrorReportLib|\FaqLib|\FederatedSearchLib|\FileGalBatchLib|\FileGalLib|\FlaggedRevisionLib|\FreetagLib|\GeoLib|\GoalEventLib|\GoalLib|\GoalRewardLib|\GroupAlertLib|\H5PLib|\HeaderLib|\HistLib|\IconsetLib|\ImageGalsLib|\KalturaLib|\KalturaLib|\Language|\LanguageTranslations|\LdapLib|\LoginLib|\LogsLib|\LogsQueryLib|\MailinLib|\Memcachelib|\MenuLib|\Messu|\MimeLib|\ModLib|\MonitorLib|\MonitorMailLib|\MultilingualLib|\NotificationLib|\OAuthLib|\ObjectLib|\PageContentLib|\ParserLib|\PaymentLib|\PdfImagesLib\PerspectiveLib|\PollLib|\PreferencesLib|\QuantifyLib|\QueueLib|\QuizLib|\RatingConfigLib|\RatingLib|\ReferencesLib|\RegistrationLib|\RelationLib|\RSSLib|\SchedulersLib|\ScoreLib|\ScormLib|\SearchStatsLib|\SemanticLib|\ServiceLib|\SheetLib|\Smarty_Tiki|\SocialLib|\StatsLib|\StoredSearchLib|\StructLib|\TemplatesLib|\ThemeControlLib|\ThemeLib|\Tiki_Connect_Client|\Tiki_Connect_Server|\Tiki_Event_Manager|\Tiki_Profile_SymbolLoader|\Tiki\Object\Selector|\Tiki\Recommendation\BatchProcessor|\Tiki\Wiki\SlugManager|\TikiAccessLib|\TikiCalendarLib|\TikiDate|\TodoLib|\Tracker\Tabular\Manager|\TrackerLib|\TWVersion|\UnifiedSearchLib|\UserMailinLib|\UserModulesLib|\UserPrefsLib|\UsersLib|\Validators|\VimeoLib|\VueJsLib|\WikiLib|\WizardLib|\WYSIWYGLib|\XMPPLib|\ZoteroLib 51 * @throws Exception 52 */ 53 public static function lib($name) 54 { 55 if (isset(self::$libraries[$name])) { 56 return self::$libraries[$name]; 57 } 58 59 $container = TikiInit::getContainer(); 60 61 //if no period in the lib name, default to tiki.lib prefix. 62 if (strpos($name, ".") !== false) { 63 $service = $name; 64 } else { 65 $service = "tiki.lib.$name"; 66 } 67 68 if ($lib = $container->get($service, \Symfony\Component\DependencyInjection\ContainerInterface::NULL_ON_INVALID_REFERENCE)) { 69 return $lib; 70 } 71 72 // One-time inits of the libraries provided 73 switch ($name) { 74 case 'tiki': 75 global $tikilib; 76 return self::$libraries[$name] = $tikilib; 77 } 78 79 if (file_exists(__DIR__ . '/../temp/cache/container.php')) { 80 unlink(__DIR__ . '/../temp/cache/container.php'); // Remove the container cache to help transition 81 } 82 83 throw new Exception(tr("%0 library not found. This may be due to a typo or caused by a recent update.", $name)); 84 } 85 86 /** 87 * @return Tiki_Event_Manager 88 * @throws Exception 89 */ 90 public static function events() 91 { 92 return self::lib('events'); 93 } 94 95 /** 96 * @return Tiki_Profile_SymbolLoader 97 * @throws Exception 98 */ 99 public static function symbols() 100 { 101 return self::lib('symbols'); 102 } 103 104 /** 105 * @return mixed 106 */ 107 public function get_site_hash() 108 { 109 global $prefs; 110 111 if (! isset($prefs['internal_site_hash'])) { 112 $hash = $this->generate_unique_sequence(); 113 114 $this->set_preference('internal_site_hash', $hash); 115 } 116 117 return $prefs['internal_site_hash']; 118 } 119 120 /** 121 * Generates cryptographically secure pseudo-random sequence of bytes encoded into the base 64 character set 122 * 123 * @param int $entropy Number of bytes to return 124 * @param bool $urlSafe If true, substitutes '-' and '_', for '+' and '_', and strips the '=' padding 125 * character for url safe sequence. 126 * @return string 127 */ 128 public function generate_unique_sequence($entropy = 100, $urlSafe = false) 129 { 130 $random_value = \phpseclib\Crypt\Random::string($entropy); 131 $encoded_value = base64_encode($random_value); 132 return $urlSafe ? strtr(str_replace('=', '', $encoded_value), '+/', '-_') 133 : $encoded_value; 134 } 135 136 // DB param left for interface compatibility, although not considered 137 /** 138 * @param null $db 139 */ 140 function __construct($db = null) 141 { 142 $this->now = time(); 143 } 144 145 function allocate_extra($type, $callback) 146 { 147 global $prefs; 148 149 $memory_name = 'allocate_memory_' . $type; 150 $time_name = 'allocate_time_' . $type; 151 152 if (! empty($prefs[$memory_name])) { 153 $memory_limit = new Tiki_MemoryLimit($prefs[$memory_name]); 154 } 155 156 if (! empty($prefs[$time_name])) { 157 $time_limit = new Tiki_TimeLimit($prefs[$time_name]); 158 } 159 160 return call_user_func($callback); 161 } 162 163 /** 164 * @param bool $url 165 * @param array $options 166 * @return mixed|Zend\Http\Client 167 */ 168 function get_http_client($url = false, $options = null) 169 { 170 global $prefs; 171 172 $config = [ 173 'timeout' => 10, 174 'keepalive' => true, 175 ]; 176 177 if ($prefs['use_proxy'] == 'y') { 178 $config['adapter'] = 'Laminas\Http\Client\Adapter\Proxy'; 179 $config["proxy_host"] = $prefs['proxy_host']; 180 $config["proxy_port"] = $prefs['proxy_port']; 181 182 if ($prefs['proxy_user'] || $prefs['proxy_pass']) { 183 $config["proxy_user"] = $prefs['proxy_user']; 184 $config["proxy_pass"] = $prefs['proxy_pass']; 185 } 186 } elseif (function_exists('curl_init') && $prefs['zend_http_use_curl'] === 'y') { 187 // Zend\Http\Client defaults to sockets, which aren't allowed in all environments so use curl when available if selected 188 $config['adapter'] = 'Laminas\Http\Client\Adapter\Curl'; 189 } 190 191 if ($prefs['zend_http_sslverifypeer'] == 'y') { 192 $config['sslverifypeer'] = true; 193 } else { 194 $config['sslverifypeer'] = false; 195 } 196 197 198 if (is_array($options)) { 199 foreach ($options as $key => $value) { 200 $config[$key] = $value; 201 } 202 } 203 204 $client = new Zend\Http\Client(null, $config); 205 $client->setArgSeparator('&'); 206 207 if ($url) { 208 $client = $this->prepare_http_client($client, $url); 209 210 $client->setUri($this->urlencode_accent($url)); // Zend\Http\Client seems to fail with accents in urls (jb june 2011) 211 } 212 213 return $client; 214 } 215 216 /** 217 * @param $client 218 * @param $url 219 * @return mixed 220 */ 221 private function prepare_http_client($client, $url) 222 { 223 $info = parse_url($url); 224 225 // Obtain all methods matching the scheme and domain 226 $table = $this->table('tiki_source_auth'); 227 $authentications = $table->fetchAll( 228 ['path', 'method', 'arguments'], 229 ['scheme' => $info['scheme'],'domain' => $info['host']] 230 ); 231 232 // Obtain the method with the longest path matching 233 $max = -1; 234 $method = false; 235 $arguments = false; 236 foreach ($authentications as $auth) { 237 if (0 === strpos($info['path'], $auth['path'])) { 238 $len = strlen($auth['path']); 239 if ($len > $max) { 240 $max = $len; 241 $method = $auth['method']; 242 $arguments = $auth['arguments']; 243 } 244 } 245 } 246 247 if ($method) { 248 $functionName = 'prepare_http_auth_' . $method; 249 if (method_exists($this, $functionName)) { 250 $arguments = json_decode($arguments, true); 251 return $this->$functionName($client, $arguments); 252 } 253 } else { 254 // Nothing special to do 255 return $client; 256 } 257 } 258 259 /** 260 * @param $client 261 * @param $arguments 262 * @return mixed 263 */ 264 private function prepare_http_auth_basic($client, $arguments) 265 { 266 $client->setAuth($arguments['username'], $arguments['password'], Zend\Http\Client::AUTH_BASIC); 267 268 return $client; 269 } 270 271 /** 272 * @param $client 273 * @param $arguments 274 * @return mixed 275 */ 276 private function prepare_http_auth_get($client, $arguments) 277 { 278 $url = $arguments['url']; 279 280 $client->setUri($this->urlencode_accent($url)); // Zend\Http\Client seems to fail with accents in urls 281 $client->setMethod(Zend\Http\Request::METHOD_GET); 282 $response = $client->send(); 283 $client->resetParameters(); 284 285 return $client; 286 } 287 288 /** 289 * @param $client 290 * @param $arguments 291 * @return mixed 292 */ 293 private function prepare_http_auth_post($client, $arguments) 294 { 295 $url = $arguments['post_url']; 296 unset($arguments['post_url']); 297 298 $client->setUri($this->urlencode_accent($url)); // Zend\Http\Client seems to fail with accents in urls 299 $client->setMethod(Zend\Http\Request::METHOD_GET); 300 $response = $client->send(); 301 $client->resetParameters(); 302 303 $client->setUri($this->urlencode_accent($url)); // Zend\Http\Client seems to fail with accents in urls 304 $client->setParameterPost($arguments); 305 $client->setMethod(Zend\Http\Request::METHOD_POST); 306 $response = $client->send(); 307 $client->resetParameters(); 308 309 // check for oauth2 password post returning a Authorization: Bearer token 310 if (! empty($arguments['grant_type']) && $arguments['grant_type'] === 'password') { // TODO other grant_types may need this too 311 $body = json_decode($response->getBody()); 312 if ($body && $body->access_token) { 313 $headers = $client->getRequest()->getHeaders(); 314 // add the Bearer token to the request headers 315 $headers->addHeader(new Zend\Http\Header\Authorization('Bearer ' . $body->access_token)); 316 $client->setHeaders($headers); 317 } 318 } 319 320 return $client; 321 } 322 323 /** 324 * Authorization header method 325 * 326 * @param $client \Zend\Http\Client 327 * @param $arguments array 328 * @return \Zend\Http\Client 329 */ 330 private function prepare_http_auth_header($client, $arguments) 331 { 332 $url = $arguments['url']; 333 334 $client->setUri($this->urlencode_accent($url)); // Zend\Http\Client seems to fail with accents in urls 335 $client->setMethod(Zend\Http\Request::METHOD_GET); 336 337 $headers = $client->getRequest()->getHeaders(); 338 $headers->addHeader(new Zend\Http\Header\Authorization($arguments['header'])); 339 $client->setHeaders($headers); 340 341 return $client; 342 } 343 344 /** 345 * @param $client 346 * @return mixed 347 */ 348 function http_perform_request($client) 349 { 350 global $prefs; 351 $response = $client->send(); 352 353 $attempts = 0; 354 while ($response->isRedirect() && $attempts < 10) { // prevent redirect loop 355 $client->setUri($client->getUri()); 356 $response = $client->send(); 357 $attempts++; 358 } 359 360 if ($prefs['http_skip_frameset'] == 'y') { 361 if ($outcome = $this->http_perform_request_skip_frameset($client, $response)) { 362 return $outcome; 363 } 364 } 365 366 return $response; 367 } 368 369 /** 370 * @param $client 371 * @param $response 372 * @return mixed 373 */ 374 private function http_perform_request_skip_frameset($client, $response) 375 { 376 // Only attempt if document is declared as HTML 377 if (0 === strpos($response->getHeaders()->get('Content-Type'), 'text/html')) { 378 $use_int_errors = libxml_use_internal_errors(true); // suppress errors and warnings due to bad HTML 379 $dom = new DOMDocument; 380 if ($response->getBody() && $dom->loadHTML($response->getBody())) { 381 $frames = $dom->getElementsByTagName('frame'); 382 383 if (count($frames)) { 384 // Frames were found 385 foreach ($frames as $f) { 386 // Request with the first frame where scrolling is not disabled (likely to be a menu or some other web 2.0 helper) 387 if ($f->getAttribute('scrolling') != 'no') { 388 $client->setUri($this->http_get_uri($client->getUri(), $this->urlencode_accent($f->getAttribute('src')))); 389 libxml_clear_errors(); 390 libxml_use_internal_errors($use_int_errors); 391 return $client->send(); 392 } 393 } 394 } 395 } 396 libxml_clear_errors(); 397 libxml_use_internal_errors($use_int_errors); 398 } 399 } 400 401 /** 402 * @param Zend\Uri\Http $uri 403 * @param $relative 404 * @return Zend\Uri\Http 405 */ 406 function http_get_uri(Zend\Uri\Http $uri, $relative) 407 { 408 if (strpos($relative, 'http://') === 0 || strpos($relative, 'https://') === 0) { 409 $uri = new Zend\Uri\Http($relative); 410 } else { 411 $uri = clone $uri; 412 $uri->setQuery([]); 413 $parts = explode('?', $relative, 2); 414 $relative = $parts[0]; 415 416 if ($relative{0} === '/') { 417 $uri->setPath($relative); 418 } else { 419 $path = dirname($uri->getPath()); 420 if ($path === '/') { 421 $path = ''; 422 } 423 424 $uri->setPath("$path/$relative"); 425 } 426 427 if (isset($parts[1])) { 428 $uri->setQuery($parts[1]); 429 } 430 } 431 432 return $uri; 433 } 434 435 /** 436 * @param $url 437 * @param string $reqmethod 438 * @return bool 439 */ 440 function httprequest($url, $reqmethod = "GET") 441 { 442 // test url : 443 // rewrite url if sloppy # added a case for https urls 444 if ((substr($url, 0, 7) <> "http://") and 445 (substr($url, 0, 8) <> "https://") 446 ) { 447 $url = "http://" . $url; 448 } 449 450 try { 451 $client = $this->get_http_client($url); 452 /* @var $response Zend\Http\Response */ 453 $response = $this->http_perform_request($client); 454 455 if (! $response->isSuccess()) { 456 return false; 457 } 458 459 return $response->getBody(); 460 } catch (Zend\Http\Exception\ExceptionInterface $e) { 461 return false; 462 } 463 } 464 465 /*shared*/ 466 /** 467 * @param $name 468 * @return bool 469 */ 470 function get_dsn_by_name($name) 471 { 472 if ($name == 'local') { 473 return true; 474 } 475 return $this->table('tiki_dsn')->fetchOne('dsn', ['name' => $name]); 476 } 477 478 /** 479 * @param $name 480 * @return array 481 */ 482 function get_dsn_info($name) 483 { 484 $info = []; 485 486 $dsnsqlplugin = $this->get_dsn_by_name($name); 487 488 $parsedsn = $dsnsqlplugin; 489 $info['driver'] = strtok($parsedsn, ":"); 490 $parsedsn = substr($parsedsn, strlen($info['driver']) + 3); 491 $info['user'] = strtok($parsedsn, ":"); 492 $parsedsn = substr($parsedsn, strlen($info['user']) + 1); 493 $info['password'] = strtok($parsedsn, "@"); 494 $parsedsn = substr($parsedsn, strlen($info['password']) + 1); 495 $info['host'] = strtok($parsedsn, "/"); 496 $parsedsn = substr($parsedsn, strlen($info['host']) + 1); 497 $info['database'] = $parsedsn; 498 499 return $info; 500 } 501 502 /** 503 * @param $name 504 * @return mixed 505 */ 506 function get_db_by_name($name) 507 { 508 include_once('tiki-setup.php'); 509 if ($name == 'local' || empty($name)) { 510 return TikiDb::get(); 511 } 512 513 try { 514 static $connectionMap = []; 515 516 if (! isset($connectionMap[$name])) { 517 $connectionMap[$name] = false; 518 519 $info = $this->get_dsn_info($name); 520 $dbdriver = $info['driver']; 521 $dbuserid = $info['user']; 522 $dbpassword = $info['password']; 523 $dbhost = $info['host']; 524 $database = $info['database']; 525 526 $api_tiki = null; 527 require 'db/local.php'; 528 if (isset($api_tiki) && $api_tiki == 'adodb') { 529 // Force autoloading 530 if (! class_exists('ADOConnection')) { 531 return null; 532 } 533 534 $dbsqlplugin = ADONewConnection($dbdriver); 535 if ($dbsqlplugin->NConnect($dbhost, $dbuserid, $dbpassword, $database)) { 536 $connectionMap[$name] = new TikiDb_AdoDb($dbsqlplugin); 537 } 538 } else { 539 $dbsqlplugin = new PDO("$dbdriver:host=$dbhost;dbname=$database", $dbuserid, $dbpassword); 540 $connectionMap[$name] = new TikiDb_Pdo($dbsqlplugin); 541 } 542 } 543 return $connectionMap[$name]; 544 } catch (Exception $e) { 545 Feedback::error($e->getMessage()); 546 } 547 } 548 549 /*shared*/ 550 // Returns IP address or IP address forwarded by the proxy if feature load balancer is set 551 /** 552 * @param $firewall true to detect ip behind a firewall 553 * @return null|string 554 */ 555 function get_ip_address($firewall = 0) 556 { 557 global $prefs; 558 if ($firewall || (isset($prefs['feature_loadbalancer']) && $prefs['feature_loadbalancer'] === "y")) { 559 $header_checks = [ 560 'HTTP_CF_CONNECTING_IP', 561 'HTTP_CLIENT_IP', 562 'HTTP_PRAGMA', 563 'HTTP_XONNECTION', 564 'HTTP_CACHE_INFO', 565 'HTTP_XPROXY', 566 'HTTP_PROXY', 567 'HTTP_PROXY_RENAMED', 568 'HTTP_PROXY_CONNECTION', 569 'HTTP_VIA', 570 'HTTP_X_COMING_FROM', 571 'HTTP_COMING_FROM', 572 'HTTP_X_FORWARDED_FOR', 573 'HTTP_X_FORWARDED', 574 'HTTP_X_CLUSTER_CLIENT_IP', 575 'HTTP_FORWARDED_FOR', 576 'HTTP_FORWARDED', 577 'HTTP_CACHE_CONTROL', 578 'HTTP_X_REAL_IP', 579 'REMOTE_ADDR']; 580 581 foreach ($header_checks as $key) { 582 if (array_key_exists($key, $_SERVER) === true) { 583 foreach (explode(',', $_SERVER[$key]) as $ip) { 584 $ip = trim($ip); 585 586 //filter the ip with filter functions 587 if (filter_var($ip, FILTER_VALIDATE_IP) !== false) { 588 return $ip; 589 } 590 } 591 } 592 } 593 } 594 if (isset($_SERVER['REMOTE_ADDR']) && filter_var($_SERVER['REMOTE_ADDR'], FILTER_VALIDATE_IP)) { 595 return $_SERVER['REMOTE_ADDR']; 596 } else { 597 return '0.0.0.0'; 598 } 599 } 600 601 /*shared*/ 602 /** 603 * @param $user 604 * @param $section 605 * @return bool 606 */ 607 function check_rules($user, $section) 608 { 609 // Admin is never banned 610 if ($user == 'admin') { 611 return false; 612 } 613 614 $fullip = $this->get_ip_address(); 615 $ips = explode(".", $fullip); 616 $query = "select tb.`message`,tb.`user`,tb.`ip1`,tb.`ip2`,tb.`ip3`,tb.`ip4`,tb.`mode` from `tiki_banning` tb, `tiki_banning_sections` tbs where tbs.`banId`=tb.`banId` and tbs.`section`=? and ( (tb.`use_dates` = ?) or (tb.`date_from` <= FROM_UNIXTIME(?) and tb.`date_to` >= FROM_UNIXTIME(?)))"; 617 $result = $this->fetchAll($query, [$section,'n',(int)$this->now,(int)$this->now]); 618 619 foreach ($result as $res) { 620 if (! $res['message']) { 621 $res['message'] = tra('You are banned from') . ': ' . $section; 622 } 623 624 if ($user && $res['mode'] == 'user') { 625 // check user 626 $pattern = '/' . $res['user'] . '/'; 627 628 if (preg_match($pattern, $user)) { 629 return $res['message']; 630 } 631 } else { 632 // check ip 633 if (count($ips) == 4) { 634 if (($ips[0] == $res['ip1'] || $res['ip1'] == '*') && ($ips[1] == $res['ip2'] || $res['ip2'] == '*') 635 && ($ips[2] == $res['ip3'] || $res['ip3'] == '*') && ($ips[3] == $res['ip4'] || $res['ip4'] == '*')) { 636 return $res['message']; 637 } 638 } 639 } 640 } 641 return false; 642 } 643 644 // $noteId 0 means create a new note 645 /** 646 * @param $user 647 * @param $noteId 648 * @param $name 649 * @param $data 650 * @param null $parse_mode 651 * @return mixed 652 */ 653 function replace_note($user, $noteId, $name, $data, $parse_mode = null) 654 { 655 $data = $this->convertAbsoluteLinksToRelative($data); 656 $size = strlen($data); 657 658 $queryData = [ 659 'user' => $user, 660 'name' => $name, 661 'data' => $data, 662 'created' => $this->now, 663 'lastModif' => $this->now, 664 'size' => (int) $size, 665 'parse_mode' => $parse_mode, 666 ]; 667 668 $userNotes = $this->table('tiki_user_notes'); 669 if ($noteId) { 670 $userNotes->update($queryData, ['noteId' => (int) $noteId,]); 671 } else { 672 $noteId = $userNotes->insert($queryData); 673 } 674 675 return $noteId; 676 } 677 678 /** 679 * @param $offset 680 * @param $maxRecords 681 * @param $sort_mode 682 * @param $find 683 * @return array 684 */ 685 function list_watches($offset, $maxRecords, $sort_mode, $find) 686 { 687 $mid = ''; 688 $mid2 = ''; 689 $bindvars1 = $bindvars2 = []; 690 if ($find) { 691 $mid = ' where `event` like ? or `email` like ? or `user` like ? or `object` like ? or `type` like ?'; 692 $mid2 = ' where `event` like ? or `group` like ? or `object` like ? or `type` like ?'; 693 $bindvars1 = ["%$find%", "%$find%", "%$find%", "%$find%", "%$find%"]; 694 $bindvars2 = ["%$find%", "%$find%", "%$find%", "%$find%"]; 695 } 696 $query = "select 'user' as watchtype, `watchId`, `user`, `event`, `object`, `title`, `type`, `url`, `email` from `tiki_user_watches` $mid 697 UNION ALL 698 select 'group' as watchtype, `watchId`, `group`, `event`, `object`, `title`, `type`, `url`, '' as `email` 699 from `tiki_group_watches` $mid2 700 order by " . $this->convertSortMode($sort_mode); 701 $query_cant = 'select count(*) from `tiki_user_watches` ' . $mid; 702 $query_cant2 = 'select count(*) from `tiki_group_watches` ' . $mid2; 703 $ret = $this->fetchAll($query, array_merge($bindvars1, $bindvars2), $maxRecords, $offset); 704 $cant = $this->getOne($query_cant, $bindvars1) + $this->getOne($query_cant2, $bindvars2); 705 $retval = []; 706 $retval["data"] = $ret; 707 $retval["cant"] = $cant; 708 return $retval; 709 } 710 711 712 /*shared*/ 713 /** 714 * @param $user 715 * @param $event 716 * @param $object 717 * @param null $type 718 * @param null $title 719 * @param null $url 720 * @param null $email 721 * 722 * @return int 723 * @throws Exception 724 */ 725 function add_user_watch($user, $event, $object, $type = null, $title = null, $url = null, $email = null) 726 { 727 // Allow a warning when the watch won't be effective 728 if (empty($email)) { 729 $userlib = TikiLib::lib('user'); 730 731 $email = $userlib->get_user_email($user); 732 if (empty($email)) { 733 return false; 734 } 735 } 736 737 if ($event != 'auth_token_called') { 738 $this->remove_user_watch($user, $event, $object, $type, $email); 739 } 740 741 $userWatches = $this->table('tiki_user_watches'); 742 return $userWatches->insert( 743 [ 744 'user' => $user, 745 'event' => $event, 746 'object' => $object, 747 'email' => $email, 748 'type' => $type, 749 'title' => $title, 750 'url' => $url, 751 ] 752 ); 753 } 754 755 /** 756 * @param $group 757 * @param $event 758 * @param $object 759 * @param null $type 760 * @param null $title 761 * @param null $url 762 * @return bool 763 */ 764 function add_group_watch($group, $event, $object, $type = null, $title = null, $url = null) 765 { 766 767 if ($type == 'Category' && $object == 0) { 768 return false; 769 } else { 770 $this->remove_group_watch($group, $event, $object, $type); 771 $groupWatches = $this->table('tiki_group_watches'); 772 $groupWatches->insert( 773 [ 774 'group' => $group, 775 'event' => $event, 776 'object' => $object, 777 'type' => $type, 778 'title' => $title, 779 'url' => $url, 780 ] 781 ); 782 return true; 783 } 784 } 785 786 /** 787 * get_user_notification: returns the owner (user) related to a watchId 788 * 789 * @param mixed $id watchId 790 * @access public 791 * @return the user login related to the watchId 792 */ 793 function get_user_notification($id) 794 { 795 796 return $this->table('tiki_user_watches')->fetchOne('user', ['watchId' => $id]); 797 } 798 /*shared*/ 799 /** 800 * @param $id 801 * 802 * @return bool|TikiDb_Adodb_Result|TikiDb_Pdo_Result 803 */ 804 function remove_user_watch_by_id($id) 805 { 806 global $tiki_p_admin_notifications, $user; 807 if ($tiki_p_admin_notifications === 'y' or $user === $this->get_user_notification($id)) { 808 return $this->table('tiki_user_watches')->delete(['watchId' => (int) $id]); 809 } 810 811 return false; 812 } 813 814 /** 815 * @param $id 816 * 817 * @return TikiDb_Adodb_Result|TikiDb_Pdo_Result 818 */ 819 function remove_group_watch_by_id($id) 820 { 821 return $this->table('tiki_group_watches')->delete(['watchId' => (int) $id,]); 822 } 823 824 /*shared*/ 825 /** 826 * @param string $user 827 * @param string $event 828 * @param string $object 829 * @param string $type = 'wiki page' 830 * @param string $email = '' 831 * 832 * @return TikiDb_Adodb_Result|TikiDb_Pdo_Result 833 */ 834 function remove_user_watch($user, $event, $object, $type = 'wiki page', $email = '') 835 { 836 $conditions = [ 837 'user' => $user, 838 'event' => $event, 839 'object' => $object, 840 'type' => $type, 841 ]; 842 843 if ($email) { 844 $conditions['email'] = $email; 845 } 846 847 return $this->table('tiki_user_watches')->deleteMultiple($conditions); 848 } 849 850 /*token notification*/ 851 /** 852 * @param $event 853 * @param $object 854 * @param string $type 855 */ 856 function remove_user_watch_object($event, $object, $type = 'wiki page') 857 { 858 $query = "delete from `tiki_user_watches` where `event`=? and `object`=? and `type` = ?"; 859 $this->query($query, [$event,$object,$type]); 860 } 861 862 function remove_stale_comment_watches() 863 { 864 $query = "DELETE FROM `tiki_user_watches` WHERE `event` = 'thread_comment_replied' AND `object` NOT IN (SELECT `threadId` FROM `tiki_comments`)"; 865 $this->query($query); 866 } 867 868 /** 869 * @param $group 870 * @param $event 871 * @param $object 872 * @param string $type 873 */ 874 function remove_group_watch($group, $event, $object, $type = 'wiki page') 875 { 876 $conditions = [ 877 'group' => $group, 878 'event' => $event, 879 'object' => $object, 880 ]; 881 if (isset($type)) { 882 $conditions['type'] = $type; 883 } 884 885 $this->table('tiki_group_watches')->deleteMultiple($conditions); 886 } 887 888 /*shared*/ 889 /** 890 * @param $user 891 * @param string $event 892 * @return mixed 893 */ 894 function get_user_watches($user, $event = '') 895 { 896 $userWatches = $this->table('tiki_user_watches'); 897 898 $conditions = [ 899 'user' => $userWatches->exactly($user), 900 ]; 901 902 if ($event) { 903 $conditions['event'] = $event; 904 } 905 906 return $userWatches->fetchAll($userWatches->all(), $conditions); 907 } 908 909 /*shared*/ 910 /** 911 * @return array 912 */ 913 function get_watches_events() 914 { 915 $query = "select distinct `event` from `tiki_user_watches`"; 916 $result = $this->fetchAll($query, []); 917 $ret = []; 918 foreach ($result as $res) { 919 $ret[] = $res['event']; 920 } 921 return $ret; 922 } 923 924 /*shared*/ 925 /** 926 * @param $user 927 * @param $event 928 * @param $object 929 * @param null $type 930 * @return bool 931 */ 932 function user_watches($user, $event, $object, $type = null) 933 { 934 $userWatches = $this->table('tiki_user_watches'); 935 936 $conditions = [ 937 'user' => $user, 938 'object' => $object, 939 ]; 940 941 if ($type) { 942 $conditions['type'] = $type; 943 } 944 945 if (is_array($event)) { 946 $conditions['event'] = $userWatches->in($event); 947 948 $ret = $userWatches->fetchColumn('event', $conditions); 949 950 return empty($ret) ? false : $ret; 951 } else { 952 return $userWatches->fetchCount($conditions); 953 } 954 } 955 956 /** 957 * @param $object 958 * @param $event 959 * @param null $type 960 * @return mixed 961 */ 962 function get_groups_watching($object, $event, $type = null) 963 { 964 $groupWatches = $this->table('tiki_group_watches'); 965 $conditions = [ 966 'object' => $object, 967 'event' => $event, 968 ]; 969 970 if ($type) { 971 $conditions['type'] = $type; 972 } 973 974 return $groupWatches->fetchColumn('group', $conditions); 975 } 976 977 /*shared*/ 978 /** 979 * @param $user 980 * @param $event 981 * @param $object 982 * @return mixed 983 */ 984 function get_user_event_watches($user, $event, $object) 985 { 986 $userWatches = $this->table('tiki_user_watches'); 987 return $userWatches->fetchAll( 988 $userWatches->all(), 989 [ 990 'user' => $user, 991 'event' => $event, 992 'object' => is_array($object) ? $userWatches->in($object) : $object, 993 ] 994 ); 995 } 996 997 /*shared*/ 998 /** 999 * @param $event 1000 * @param $object 1001 * @param null $info 1002 * @return array 1003 */ 1004 function get_event_watches($event, $object, $info = null) 1005 { 1006 global $prefs; 1007 $ret = []; 1008 1009 $mid = ''; 1010 if ($prefs['feature_user_watches_translations'] == 'y' && $event == 'wiki_page_changed') { 1011 // If $prefs['feature_user_watches_translations'] is turned on, also look for 1012 // pages in a translation group. 1013 $mid = "`event`=?"; 1014 $bindvars[] = $event; 1015 $multilinguallib = TikiLib::lib('multilingual'); 1016 $page_info = $this->get_page_info($object); 1017 $pages = $multilinguallib->getTranslations('wiki page', $page_info['page_id'], $object, ''); 1018 foreach ($pages as $page) { 1019 $mids[] = "`object`=?"; 1020 $bindvars[] = $page['objName']; 1021 } 1022 $mid .= ' and (' . implode(' or ', $mids) . ')'; 1023 } elseif ($prefs['feature_user_watches_translations'] == 'y' 1024 && $event == 'wiki_page_created' ) { 1025 $page_info = $this->get_page_info($object); 1026 $mid = "`event`='wiki_page_in_lang_created' and `object`=? and `type`='lang'"; 1027 $bindvars[] = $page_info['lang']; 1028 } elseif ($prefs['feature_user_watches_languages'] == 'y' && $event == 'category_changed') { 1029 $mid = "`object`=? and ((`event`='category_changed_in_lang' and `type`=? ) or (`event`='category_changed'))"; 1030 $bindvars[] = $object; 1031 $bindvars[] = $info['lang']; 1032 } elseif ($event == 'forum_post_topic') { 1033 $mid = "(`event`=? or `event`=?) and `object`=?"; 1034 $bindvars[] = $event; 1035 $bindvars[] = 'forum_post_topic_and_thread'; 1036 $bindvars[] = $object; 1037 } elseif ($event == 'forum_post_thread') { 1038 $mid = "(`event`=? and `object`=?) or ( `event`=? and `object`=?)"; 1039 $bindvars[] = $event; 1040 $bindvars[] = $object; 1041 $bindvars[] = 'forum_post_topic_and_thread'; 1042 $forumId = $info['forumId']; 1043 $bindvars[] = $forumId; 1044 } else { 1045 $extraEvents = ""; 1046 if (substr_count($event, 'article_')) { 1047 $extraEvents = " or `event`='article_*'"; 1048 } elseif ($event == 'wiki_comment_changes') { 1049 $extraEvents = " or `event`='wiki_page_changed'"; 1050 // Blog comment mail 1051 } elseif ($event == 'blog_comment_changes') { 1052 $extraEvents = " or `event`='blog_page_changed'"; 1053 } 1054 $mid = "(`event`=?$extraEvents) and (`object`=? or `object`='*')"; 1055 $bindvars[] = $event; 1056 $bindvars[] = $object; 1057 } 1058 1059 // Obtain the list of watches on event/object for user watches 1060 // Union obtains all users member of groups being watched 1061 // Distinct union insures there are no duplicates 1062 $query = "select tuw.`watchId`, tuw.`user`, tuw.`event`, tuw.`object`, tuw.`title`, tuw.`type`, tuw.`url`, tuw.`email`, 1063 tup1.`value` as language, tup2.`value` as mailCharset 1064 from 1065 `tiki_user_watches` tuw 1066 left join `tiki_user_preferences` tup1 on (tup1.`user`=tuw.`user` and tup1.`prefName`='language') 1067 left join `tiki_user_preferences` tup2 on (tup2.`user`=tuw.`user` and tup2.`prefName`='mailCharset') 1068 where $mid 1069 UNION DISTINCT 1070 select tgw.`watchId`, uu.`login`, tgw.`event`, tgw.`object`, tgw.`title`, tgw.`type`, tgw.`url`, uu.`email`, 1071 tup1.`value` as language, tup2.`value` as mailCharset 1072 from 1073 `tiki_group_watches` tgw 1074 inner join `users_usergroups` ug on tgw.`group` = ug.`groupName` 1075 inner join `users_users` uu on ug.`userId` = uu.`userId` and uu.`email` is not null and uu.`email` <> '' 1076 left join `tiki_user_preferences` tup1 on (tup1.`user`=uu.`login` and tup1.`prefName`='language') 1077 left join `tiki_user_preferences` tup2 on (tup2.`user`=uu.`login` and tup2.`prefName`='mailCharset') 1078 where $mid 1079 "; 1080 $result = $this->fetchAll($query, array_merge($bindvars, $bindvars)); 1081 1082 if (count($result) > 0) { 1083 foreach ($result as $res) { 1084 if (empty($res['language'])) { 1085 $res['language'] = $this->get_preference('site_language'); 1086 } 1087 switch ($event) { 1088 case 'wiki_page_changed': 1089 case 'wiki_page_created': 1090 $res['perm'] = ($this->user_has_perm_on_object($res['user'], $object, 'wiki page', 'tiki_p_view') || 1091 $this->user_has_perm_on_object($res['user'], $object, 'wiki page', 'tiki_p_admin_wiki')); 1092 break; 1093 case 'tracker_modified': 1094 $res['perm'] = $this->user_has_perm_on_object($res['user'], $object, 'tracker', 'tiki_p_view_trackers'); 1095 break; 1096 case 'tracker_item_modified': 1097 $res['perm'] = $this->user_has_perm_on_object($res['user'], $object, 'trackeritem', 'tiki_p_view_trackers'); 1098 break; 1099 case 'blog_post': 1100 $res['perm'] = ($this->user_has_perm_on_object($res['user'], $object, 'blog', 'tiki_p_read_blog') || 1101 $this->user_has_perm_on_object($res['user'], $object, 'blog', 'tiki_p_admin_blog')); 1102 break; 1103 // Blog comment mail 1104 case 'blog_comment_changes': 1105 $res['perm'] = ($this->user_has_perm_on_object($res['user'], $object, 'blog', 'tiki_p_read_blog') || 1106 $this->user_has_perm_on_object($res['user'], $object, 'comments', 'tiki_p_read_comments')); 1107 break; 1108 case 'forum_post_topic': 1109 $res['perm'] = ($this->user_has_perm_on_object($res['user'], $object, 'forum', 'tiki_p_forum_read') || 1110 $this->user_has_perm_on_object($res['user'], $object, 'forum', 'tiki_p_admin_forum')); 1111 break; 1112 case 'forum_post_thread': 1113 $res['perm'] = ($this->user_has_perm_on_object($res['user'], $object, 'thread', 'tiki_p_forum_read') || 1114 $this->user_has_perm_on_object($res['user'], $object, 'forum', 'tiki_p_admin_forum')); 1115 break; 1116 case 'file_gallery_changed': 1117 $res['perm'] = ($this->user_has_perm_on_object($res['user'], $object, 'file gallery', 'tiki_p_view_file_gallery') || 1118 $this->user_has_perm_on_object($res['user'], $object, 'file gallery', 'tiki_p_download_files')); 1119 break; 1120 case 'article_submitted': 1121 case 'article_edited': 1122 case 'article_deleted': 1123 $userlib = TikiLib::lib('user'); 1124 $res['perm'] = (empty($object) && $userlib->user_has_permission($res['user'], 'tiki_p_read_article')) 1125 || $this->user_has_perm_on_object($res['user'], $object, 'article', 'tiki_p_read_article'); 1126 break; 1127 case 'topic_article_created': 1128 case 'topic_article_edited': 1129 case 'topic_article_deleted': 1130 $userlib = TikiLib::lib('user'); 1131 $res['perm'] = (empty($object) && $userlib->user_has_permission($res['user'], 'tiki_p_read_article')) 1132 || $this->user_has_perm_on_object($res['user'], $object, 'topic', 'tiki_p_read_article'); 1133 break; 1134 case 'calendar_changed': 1135 $res['perm'] = $this->user_has_perm_on_object($res['user'], $object, 'calendar', 'tiki_p_view_calendar'); 1136 break; 1137 case 'image_gallery_changed': 1138 $res['perm'] = $this->user_has_perm_on_object($res['user'], $object, 'image gallery', 'tiki_p_view_image_gallery'); 1139 break; 1140 case 'category_changed': 1141 $categlib = TikiLib::lib('categ'); 1142 $res['perm'] = $categlib->has_view_permission($res['user'], $object); 1143 break; 1144 case 'fgal_quota_exceeded': 1145 global $tiki_p_admin_file_galleries; 1146 $res['perm'] = ($tiki_p_admin_file_galleries == 'y'); 1147 break; 1148 case 'article_commented': 1149 case 'wiki_comment_changes': 1150 $res['perm'] = $this->user_has_perm_on_object($res['user'], $object, 'comments', 'tiki_p_read_comments'); 1151 break; 1152 case 'user_registers': 1153 $userlib = TikiLib::lib('user'); 1154 $res['perm'] = $userlib->user_has_permission($res['user'], 'tiki_p_admin'); 1155 break; 1156 case 'auth_token_called': 1157 $res['perm'] = true; 1158 break; 1159 case 'user_joins_group': 1160 $res['perm'] = $this->user_has_perm_on_object($res['user'], $object, 'group', 'tiki_p_group_view_members'); 1161 break; 1162 case 'thread_comment_replied': 1163 $res['perm'] = true; 1164 break; 1165 default: 1166 // for security we deny all others. 1167 $res['perm'] = false; 1168 break; 1169 } 1170 1171 if ($res['perm'] || empty($res['user']) && ! empty($res['email'])) { 1172 // Allow admin created email (non-user) watches 1173 $ret[] = $res; 1174 } 1175 } 1176 } 1177 1178 // Also include users that are watching a category to which this object belongs to. 1179 if ($event != 'category_changed') { 1180 if ($prefs['feature_categories'] == 'y') { 1181 $categlib = TikiLib::lib('categ'); 1182 $objectType = ""; 1183 switch ($event) { 1184 case 'wiki_page_changed': 1185 $objectType = "wiki page"; 1186 break; 1187 case 'wiki_page_created': 1188 $objectType = "wiki page"; 1189 break; 1190 case 'blog_post': 1191 $objectType = "blog"; 1192 break; 1193 // Blog comment mail 1194 case 'blog_page_changed': 1195 $objectType = "blog page"; 1196 break; 1197 case 'map_changed': 1198 $objectType = "map_changed"; 1199 break; 1200 case 'forum_post_topic': 1201 $objectType = "forum"; 1202 break; 1203 case 'forum_post_thread': 1204 $objectType = "forum"; 1205 break; 1206 case 'file_gallery_changed': 1207 $objectType = "file gallery"; 1208 break; 1209 case 'article_submitted': 1210 $objectType = "topic"; 1211 break; 1212 case 'image_gallery_changed': 1213 $objectType = "image gallery"; 1214 break; 1215 case 'tracker_modified': 1216 $objectType = "tracker"; 1217 break; 1218 case 'tracker_item_modified': 1219 $objectType = "tracker"; 1220 break; 1221 case 'calendar_changed': 1222 $objectType = "calendar"; 1223 break; 1224 } 1225 if ($objectType != "") { 1226 // If a forum post was changed, check the categories of the forum. 1227 if ($event == "forum_post_thread") { 1228 $commentslib = TikiLib::lib('comments'); 1229 $object = $commentslib->get_comment_forum_id($object); 1230 } 1231 1232 // If a tracker item was changed, check the categories of the tracker. 1233 if ($event == "tracker_item_modified") { 1234 $trklib = TikiLib::lib('trk'); 1235 $object = $trklib->get_tracker_for_item($object); 1236 } 1237 1238 $categs = $categlib->get_object_categories($objectType, $object); 1239 1240 foreach ($categs as $category) { 1241 $watching_users = $this->get_event_watches('category_changed', $category, $info); 1242 1243 // Add all users that are not already included 1244 foreach ($watching_users as $wu) { 1245 $included = false; 1246 foreach ($ret as $item) { 1247 if ($item['user'] == $wu['user']) { 1248 $included = true; 1249 } 1250 } 1251 if (! $included) { 1252 $ret[] = $wu; 1253 } 1254 } 1255 } 1256 } 1257 } 1258 } 1259 return $ret; 1260 } 1261 1262 /*shared*/ 1263 /** 1264 * @return array 1265 */ 1266 function dir_stats() 1267 { 1268 $sites = $this->table('tiki_directory_sites'); 1269 $categories = $this->table('tiki_directory_categories'); 1270 $search = $this->table('tiki_directory_search'); 1271 1272 $aux = []; 1273 $aux["valid"] = $sites->fetchCount(['isValid' => 'y']); 1274 $aux["invalid"] = $sites->fetchCount(['isValid' => 'n']); 1275 $aux["categs"] = $categories->fetchCount([]); 1276 $aux["searches"] = $search->fetchOne($search->sum('hits'), []); 1277 $aux["visits"] = $search->fetchOne($sites->sum('hits'), []); 1278 return $aux; 1279 } 1280 1281 /*shared*/ 1282 /** 1283 * @param $offset 1284 * @param $maxRecords 1285 * @param $sort_mode 1286 * @param $find 1287 * @return array 1288 */ 1289 function dir_list_all_valid_sites2($offset, $maxRecords, $sort_mode, $find) 1290 { 1291 $sites = $this->table('tiki_directory_sites'); 1292 $conditions = [ 1293 'isValid' => 'y', 1294 ]; 1295 1296 if ($find) { 1297 $conditions['search'] = $sites->expr('(`name` like ? or `description` like ?)', ["%$find%", "%$find%"]); 1298 } 1299 1300 return [ 1301 'data' => $sites->fetchAll($sites->all(), $conditions, $maxRecords, $offset, $sites->expr($this->convertSortMode($sort_mode))), 1302 'cant' => $sites->fetchCount($conditions), 1303 ]; 1304 } 1305 1306 /*shared*/ 1307 /** 1308 * @param $categId 1309 * @return mixed 1310 */ 1311 function get_directory($categId) 1312 { 1313 return $this->table('tiki_directory_categories')->fetchFullRow(['categId' => $categId]); 1314 } 1315 1316 /*shared*/ 1317 /** 1318 * @param $user 1319 * @return mixed 1320 */ 1321 function user_unread_messages($user) 1322 { 1323 $messages = $this->table('messu_messages'); 1324 return $messages->fetchCount( 1325 [ 1326 'user' => $user, 1327 'isRead' => 'n', 1328 ] 1329 ); 1330 } 1331 1332 /*shared*/ 1333 /** 1334 * @return array 1335 */ 1336 function get_online_users() 1337 { 1338 if (! isset($this->online_users_cache)) { 1339 $this->update_session(); 1340 $this->online_users_cache = []; 1341 $query = "select s.`user`, p.`value` as `realName`, `timestamp`, `tikihost` from `tiki_sessions` s left join `tiki_user_preferences` p on s.`user`<>? and s.`user` = p.`user` and p.`prefName` = 'realName' where s.`user` is not null;"; 1342 $result = $this->fetchAll($query, ['']); 1343 foreach ($result as $res) { 1344 $res['user_information'] = $this->get_user_preference($res['user'], 'user_information', 'public'); 1345 $res['allowMsgs'] = $this->get_user_preference($res['user'], 'allowMsgs', 'y'); 1346 $this->online_users_cache[$res['user']] = $res; 1347 } 1348 } 1349 return $this->online_users_cache; 1350 } 1351 1352 /*shared*/ 1353 /** 1354 * @param $whichuser 1355 * @return bool 1356 */ 1357 function is_user_online($whichuser) 1358 { 1359 if (! isset($this->online_users_cache)) { 1360 $this->get_online_users(); 1361 } 1362 1363 return(isset($this->online_users_cache[$whichuser])); 1364 } 1365 1366 /* 1367 * Score methods begin 1368 */ 1369 // All information about an event type 1370 // shared 1371 /** 1372 * @param $event 1373 * @return mixed 1374 */ 1375 function get_event($event) 1376 { 1377 return $this->table('tiki_score')->fetchFullRow(['event' => $event]); 1378 } 1379 1380 // List users by best scoring 1381 // shared 1382 /** 1383 * @param int $limit 1384 * @param int $start 1385 * @return mixed 1386 */ 1387 function rank_users($limit = 10, $start = 0) 1388 { 1389 global $prefs; 1390 $score_expiry_days = $prefs['feature_score_expday']; 1391 1392 if (! $start) { 1393 $start = "0"; 1394 } 1395 1396 if (empty($score_expiry_days)) { 1397 // score does not expire 1398 $query = "select `recipientObjectId` as `login`, 1399 `pointsBalance` as `score` 1400 from `tiki_object_scores` tos 1401 where `recipientObjectType`='user' 1402 and tos.`id` = (select max(id) from `tiki_object_scores` where `recipientObjectId` = tos.`recipientObjectId` and `recipientObjectType`='user' group by `recipientObjectId`) 1403 group by `recipientObjectId`, `pointsBalance` order by `score` desc"; 1404 1405 $result = $this->fetchAll($query, null, $limit, $start); 1406 } else { 1407 // score expires 1408 $query = "select `recipientObjectId` as `login`, 1409 `pointsBalance` - ifnull((select `pointsBalance` from `tiki_object_scores` 1410 where `recipientObjectId`=tos.`recipientObjectId` 1411 and `recipientObjectType`='user' 1412 and `date` < UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL ? DAY)) 1413 order by id desc limit 1), 0) as `score` 1414 from `tiki_object_scores` tos 1415 where `recipientObjectType`='user' 1416 and tos.`id` = (select max(id) from `tiki_object_scores` where `recipientObjectId` = tos.`recipientObjectId` and `recipientObjectType`='user' group by `recipientObjectId`) 1417 group by `recipientObjectId`, `pointsBalance` order by `score` desc"; 1418 1419 $result = $this->fetchAll($query, $score_expiry_days, $limit, $start); 1420 } 1421 1422 foreach ($result as & $res) { 1423 $res['position'] = ++$start; 1424 } 1425 return $result; 1426 } 1427 1428 // Returns html <img> tag to star corresponding to user's score 1429 // shared 1430 /** 1431 * @param $score 1432 * @return string 1433 */ 1434 function get_star($score) 1435 { 1436 global $prefs; 1437 $star = ''; 1438 $star_colors = [0 => 'grey', 1439 100 => 'blue', 1440 500 => 'green', 1441 1000 => 'yellow', 1442 2500 => 'orange', 1443 5000 => 'red', 1444 10000 => 'purple']; 1445 foreach ($star_colors as $boundary => $color) { 1446 if ($score >= $boundary) { 1447 $star = 'star_' . $color . '.gif'; 1448 } 1449 } 1450 if (! empty($star)) { 1451 $alt = sprintf(tra("%d points"), $score); 1452 if ($prefs['theme_iconset'] === 'legacy') { 1453 $star = "<img src='img/icons/$star' height='11' width='11' alt='$alt' /> "; 1454 } else { 1455 $smarty = TikiLib::lib('smarty'); 1456 $smarty->loadPlugin('smarty_function_icon'); 1457 $star = smarty_function_icon(['name' => 'star', 'istyle' => 'color:' . $color, 'iclass' => 'tips', 1458 'ititle' => ':' . $alt], $smarty->getEmptyInternalTemplate()) . " "; 1459 } 1460 } 1461 return $star; 1462 } 1463 1464 /* 1465 * Score methods end 1466 */ 1467 //shared 1468 // \todo remove all hardcoded html in get_user_avatar() 1469 /** 1470 * @param $user 1471 * @param string $float 1472 * @return string 1473 */ 1474 function get_user_avatar($user, $float = '') 1475 { 1476 global $prefs; 1477 1478 if (empty($user)) { 1479 return ''; 1480 } 1481 1482 if (is_array($user)) { 1483 $res = $user; 1484 $user = $user['login']; 1485 } else { 1486 $res = $this->table('users_users')->fetchRow(['login', 'avatarType', 'avatarLibName', 'email'], ['login' => $user]); 1487 } 1488 1489 if (! $res) { 1490 return ''; 1491 } 1492 1493 if ($prefs['user_use_gravatar'] == 'y' && $res['email']) { 1494 $https_mode = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on'; 1495 $hash = md5(strtolower(trim($res['email']))); 1496 1497 if ($https_mode) { 1498 $url = "https://secure.gravatar.com/avatar/$hash?s=45"; 1499 } else { 1500 $url = "http://www.gravatar.com/avatar/$hash?s=45"; 1501 } 1502 $type = 'g'; 1503 } else { 1504 $type = $res["avatarType"] ? $res["avatarType"] : 'u'; 1505 $libname = $res["avatarLibName"]; 1506 $ret = ''; 1507 } 1508 1509 $style = ''; 1510 1511 if (strcasecmp($float, "left") == 0) { 1512 $style = "style='float:left;margin-right:5px;'"; 1513 } elseif (strcasecmp($float, "right") == 0) { 1514 $style = "style='float:right;margin-left:5px;'"; 1515 } 1516 1517 $username = htmlspecialchars( 1518 TikiLib::lib('user')->clean_user($user), 1519 ENT_COMPAT 1520 ); 1521 1522 switch ($type) { 1523 case 'l': 1524 if ($libname) { 1525 $ret = '<img class="user-profile-picture rounded" width="45" height="45" src="' . $libname . '" ' . $style . ' alt="' . $username . '">'; 1526 } 1527 break; 1528 case 'u': 1529 $userprefslib = TikiLib::lib('userprefs'); 1530 $path = $userprefslib->get_public_avatar_path($user); 1531 1532 if ($path) { 1533 $url = $this->tikiUrlOpt($path); 1534 $ret = '<img class="user-profile-picture rounded" src="' . htmlspecialchars($url, ENT_NOQUOTES) . '" ' . $style . ' alt="' . $username . '">'; 1535 } 1536 break; 1537 case 'g': 1538 $ret = '<img class="user-profile-picture rounded" src="' . htmlspecialchars($url, ENT_NOQUOTES) . '" ' . $style . ' alt="' . $username . '">'; 1539 break; 1540 case 'n': 1541 default: 1542 $ret = ''; 1543 break; 1544 } 1545 return $ret; 1546 } 1547 1548 /** 1549 * Return user avatar as Base64 encoded inline image. 1550 */ 1551 function get_user_avatar_inline($user) 1552 { 1553 global $prefs; 1554 1555 if (empty($user)) { 1556 return ''; 1557 } 1558 1559 if (is_array($user)) { 1560 $res = $user; 1561 $user = $user['login']; 1562 } else { 1563 $res = $this->table('users_users')->fetchRow(['login', 'avatarType', 'avatarFileType', 'avatarData', 'avatarLibName', 'email'], ['login' => $user]); 1564 } 1565 1566 if (! $res) { 1567 return ''; 1568 } 1569 1570 if ($prefs['user_use_gravatar'] == 'y' && $res['email']) { 1571 $hash = md5(strtolower(trim($res['email']))); 1572 $url = "https://secure.gravatar.com/avatar/$hash.jpg?s=45"; 1573 $data = file_get_contents($url); 1574 $mime = 'image/jpeg'; 1575 } elseif ($res['avatarType'] == 'l') { 1576 $url = $this->tikiUrlOpt($res['avatarLibName']); 1577 $data = file_get_contents($url); 1578 if (class_exists('finfo')) { 1579 $finfo = new finfo(FILEINFO_MIME_TYPE); 1580 $mime = $finfo->buffer($data); 1581 } else { 1582 $mime = 'image/jpeg'; 1583 } 1584 } else { 1585 $data = $res['avatarData']; 1586 $mime = $res['avatarFileType']; 1587 } 1588 1589 if ($data && $mime) { 1590 return "data:$mime;base64,".base64_encode($data); 1591 } else { 1592 return ''; 1593 } 1594 } 1595 1596 /*shared*/ 1597 /** 1598 * @return array 1599 */ 1600 function get_forum_sections() 1601 { 1602 $query = "select distinct `section` from `tiki_forums` where `section`<>?"; 1603 $result = $this->fetchAll($query, ['']); 1604 $ret = []; 1605 foreach ($result as $res) { 1606 $ret[] = $res["section"]; 1607 } 1608 return $ret; 1609 } 1610 1611 /* Referer stats */ 1612 /*shared*/ 1613 /** 1614 * @param $referer 1615 * @param $fullurl 1616 */ 1617 function register_referer($referer, $fullurl) 1618 { 1619 $refererStats = $this->table('tiki_referer_stats'); 1620 1621 $cant = $refererStats->fetchCount(['referer' => $referer]); 1622 1623 if ($cant) { 1624 $refererStats->update( 1625 [ 1626 'hits' => $refererStats->increment(1), 1627 'last' => $this->now, 1628 'lasturl' => $fullurl, 1629 ], 1630 ['referer' => $referer] 1631 ); 1632 } else { 1633 $refererStats->insert( 1634 [ 1635 'last' => $this->now, 1636 'referer' => $referer, 1637 'hits' => 1, 1638 'lasturl' => $fullurl, 1639 ] 1640 ); 1641 } 1642 } 1643 1644 // File attachments functions for the wiki //// 1645 /*shared*/ 1646 /** 1647 * @param $id 1648 * @return bool 1649 */ 1650 function add_wiki_attachment_hit($id) 1651 { 1652 global $prefs, $user; 1653 if (StatsLib::is_stats_hit()) { 1654 $wikiAttachments = $this->table('tiki_wiki_attachments'); 1655 $wikiAttachments->update( 1656 ['hits' => $wikiAttachments->increment(1)], 1657 ['attId' => (int) $id] 1658 ); 1659 } 1660 return true; 1661 } 1662 1663 /*shared*/ 1664 /** 1665 * @param $attId 1666 * @return mixed 1667 */ 1668 function get_wiki_attachment($attId) 1669 { 1670 return $this->table('tiki_wiki_attachments')->fetchFullRow(['attId' => (int) $attId]); 1671 } 1672 1673 /*shared*/ 1674 /** 1675 * @param $id 1676 * @return mixed 1677 */ 1678 function get_gallery($id) 1679 { 1680 return $this->table('tiki_galleries')->fetchFullRow(['galleryId' => (int) $id]); 1681 } 1682 1683 // Last visit module //// 1684 /*shared*/ 1685 /** 1686 * @param $user 1687 * @return array|bool 1688 */ 1689 function get_news_from_last_visit($user) 1690 { 1691 if (! $user) { 1692 return false; 1693 } 1694 1695 $last = $this->table('users_users')->fetchOne('lastLogin', ['login' => $user]); 1696 1697 $ret = []; 1698 if (! $last) { 1699 $last = time(); 1700 } 1701 $ret["lastVisit"] = $last; 1702 $ret["images"] = $this->getOne("select count(*) from `tiki_images` where `created`>?", [(int)$last]); 1703 $ret["pages"] = $this->getOne("select count(*) from `tiki_pages` where `lastModif`>?", [(int)$last]); 1704 $ret["files"] = $this->getOne("select count(*) from `tiki_files` where `created`>?", [(int)$last]); 1705 $ret["comments"] = $this->getOne("select count(*) from `tiki_comments` where `commentDate`>?", [(int)$last]); 1706 $ret["users"] = $this->getOne("select count(*) from `users_users` where `registrationDate`>? and `provpass`=?", [(int)$last, '']); 1707 $ret["trackers"] = $this->getOne("select count(*) from `tiki_tracker_items` where `lastModif`>?", [(int)$last]); 1708 $ret["calendar"] = $this->getOne("select count(*) from `tiki_calendar_items` where `lastmodif`>?", [(int)$last]); 1709 return $ret; 1710 } 1711 1712 /** 1713 * @return mixed|string 1714 */ 1715 function pick_cookie() 1716 { 1717 $cant = $this->getOne("select count(*) from `tiki_cookies`", []); 1718 if (! $cant) { 1719 return ''; 1720 } 1721 1722 $bid = rand(0, $cant - 1); 1723 //$cookie = $this->getOne("select `cookie` from `tiki_cookies` limit $bid,1"); getOne seems not to work with limit 1724 $result = $this->query("select `cookie` from `tiki_cookies`", [], 1, $bid); 1725 if ($res = $result->fetchRow()) { 1726 $cookie = str_replace("\n", "", $res['cookie']); 1727 return preg_replace('/^(.+?)(\s*--.+)?$/', '<em>"$1"</em>$2', $cookie); 1728 } else { 1729 return ""; 1730 } 1731 } 1732 1733 function get_usage_chart_data() 1734 { 1735 TikiLib::lib('quiz')->compute_quiz_stats(); 1736 1737 $data['xdata'][] = tra('wiki'); 1738 $data['ydata'][] = $this->getOne('select sum(`hits`) from `tiki_pages`', []); 1739 $data['xdata'][] = tra('img-g'); 1740 $data['ydata'][] = $this->getOne('select sum(`hits`) from `tiki_galleries`', []); 1741 1742 $data['xdata'][] = tra('file-g'); 1743 $data['ydata'][] = $this->getOne('select sum(`hits`) from `tiki_file_galleries`', []); 1744 1745 $data['xdata'][] = tra('FAQs'); 1746 $data['ydata'][] = $this->getOne('select sum(`hits`) from `tiki_faqs`', []); 1747 1748 $data['xdata'][] = tra('quizzes'); 1749 $data['ydata'][] = $this->getOne('select sum(`timesTaken`) from `tiki_quiz_stats_sum`', []); 1750 1751 $data['xdata'][] = tra('arts'); 1752 $data['ydata'][] = $this->getOne('select sum(`nbreads`) from `tiki_articles`', []); 1753 1754 $data['xdata'][] = tra('blogs'); 1755 $data['ydata'][] = $this->getOne('select sum(`hits`) from `tiki_blogs`', []); 1756 1757 $data['xdata'][] = tra('forums'); 1758 $data['ydata'][] = $this->getOne('select sum(`hits`) from `tiki_forums`', []); 1759 1760 return $data; 1761 } 1762 1763 // User assigned modules //// 1764 /*shared*/ 1765 /** 1766 * @param $id 1767 * @return mixed 1768 */ 1769 function get_user_login($id) 1770 { 1771 return $this->table('users_users')->fetchOne('login', ['userId' => (int) $id]); 1772 } 1773 1774 /** 1775 * @param $u 1776 * @return int 1777 */ 1778 function get_user_id($u) 1779 { 1780 // Anonymous is not in db 1781 if ($u == '') { 1782 return -1; 1783 } 1784 1785 // If we ask for the current user id and if we already know it in session 1786 $current = ( isset($_SESSION['u_info']) && $u == $_SESSION['u_info']['login'] ); 1787 if (isset($_SESSION['u_info']['id']) && $current) { 1788 return $_SESSION['u_info']['id']; 1789 } 1790 1791 // In other cases, we look in db 1792 $id = $this->table('users_users')->fetchOne('userId', ['login' => $u]); 1793 $id = ($id === false) ? -1 : $id; 1794 if ($current) { 1795 $_SESSION['u_info']['id'] = $id; 1796 } 1797 return $id; 1798 } 1799 1800 /*shared*/ 1801 /** 1802 * @param $group 1803 * @return array 1804 */ 1805 function get_groups_all($group) 1806 { 1807 $result = $this->table('tiki_group_inclusion')->fetchColumn('groupName', ['includeGroup' => $group]); 1808 $ret = $result; 1809 foreach ($result as $res) { 1810 $ret = array_merge($ret, $this->get_groups_all($res)); 1811 } 1812 return array_unique($ret); 1813 } 1814 1815 /*shared*/ 1816 /** 1817 * @param $group 1818 * @return array 1819 */ 1820 function get_included_groups($group) 1821 { 1822 $result = $this->table('tiki_group_inclusion')->fetchColumn('includeGroup', ['groupName' => $group]); 1823 $ret = $result; 1824 foreach ($result as $res) { 1825 $ret = array_merge($ret, $this->get_included_groups($res)); 1826 } 1827 return array_unique($ret); 1828 } 1829 1830 /*shared*/ 1831 /** 1832 * @param string $user username 1833 * @param bool $included_groups include inherited/included groups 1834 * 1835 * @return array 1836 */ 1837 function get_user_groups($user, $included_groups = true) 1838 { 1839 global $prefs; 1840 $userlib = TikiLib::lib('user'); 1841 if (empty($user) || $user === 'Anonymous') { 1842 $ret = []; 1843 $ret[] = "Anonymous"; 1844 return $ret; 1845 } 1846 if ($prefs['feature_intertiki'] == 'y' and empty($prefs['feature_intertiki_mymaster']) and strstr($user, '@')) { 1847 $realm = substr($user, strpos($user, '@') + 1); 1848 if (isset($prefs['interlist'][$realm])) { 1849 $user = substr($user, 0, strpos($user, '@')); 1850 $groups = $prefs['interlist'][$realm]['groups'] . ',Anonymous'; 1851 return explode(',', $groups); 1852 } 1853 } 1854 $cachekey = $user . ($included_groups ? '' : '_direct'); 1855 if (! isset($this->usergroups_cache[$cachekey])) { 1856 $userid = $this->get_user_id($user); 1857 $result = $this->table('users_usergroups')->fetchColumn('groupName', ['userId' => $userid]); 1858 $ret = $result; 1859 if ($included_groups) { 1860 foreach ($result as $res) { 1861 $ret = array_merge($ret, $userlib->get_included_groups($res)); 1862 } 1863 } 1864 $ret[] = "Registered"; 1865 1866 if (isset($_SESSION["groups_are_emulated"]) && $_SESSION["groups_are_emulated"] == "y") { 1867 if (in_array('Admins', $ret)) { 1868 // Members of group 'Admins' can emulate being in any list of groups 1869 $ret = unserialize($_SESSION['groups_emulated']); 1870 } else { 1871 // For security purposes, user can only emulate a subset of user's list of groups 1872 // This prevents privilege escalation 1873 $ret = array_intersect($ret, unserialize($_SESSION['groups_emulated'])); 1874 } 1875 } 1876 $ret = array_values(array_unique($ret)); 1877 $this->usergroups_cache[$cachekey] = $ret; 1878 return $ret; 1879 } else { 1880 return $this->usergroups_cache[$cachekey]; 1881 } 1882 } 1883 1884 /** 1885 * @param $user 1886 */ 1887 function invalidate_usergroups_cache($user) 1888 { 1889 unset($this->usergroups_cache[$user]); 1890 unset($this->usergroups_cache[$user . '_direct']); 1891 } 1892 1893 /** 1894 * @param $user 1895 * @return string 1896 */ 1897 function get_user_cache_id($user) 1898 { 1899 $groups = $this->get_user_groups($user); 1900 sort($groups, SORT_STRING); 1901 $cacheId = implode(":", $groups); 1902 if ($user == 'admin') { 1903 // in this case user get permissions from no group 1904 $cacheId = 'ADMIN:' . $cacheId; 1905 } 1906 return $cacheId; 1907 } 1908 1909 /*shared*/ 1910 /** 1911 * @return string 1912 * @see UsersLib::genPass(), which generates passwords easier to remember 1913 * TODO: Merge with above 1914 */ 1915 static function genPass() 1916 { 1917 global $prefs; 1918 $length = max($prefs['min_pass_length'], 8); 1919 $list = ['aeiou', 'AEIOU', 'bcdfghjklmnpqrstvwxyz', 'BCDFGHJKLMNPQRSTVWXYZ', '0123456789']; 1920 $list[] = $prefs['pass_chr_special'] == 'y' ? '_*&+!*-=$@' : '_'; 1921 shuffle($list); 1922 $r = ''; 1923 for ($i = 0; $i < $length; $i++) { 1924 $ch = $list[$i % count($list)]; 1925 $r .= $ch{rand(0, strlen($ch) - 1)}; 1926 } 1927 return $r; 1928 } 1929 1930 // generate a random string (for unsubscription code etc.) 1931 /** 1932 * @param string $base 1933 * @return string 1934 */ 1935 function genRandomString($base = "") 1936 { 1937 if ($base == "") { 1938 $base = $this->genPass(); 1939 } 1940 $base .= microtime(); 1941 return md5($base); 1942 } 1943 1944 // This function calculates the pageRanks for the tiki_pages 1945 // it can be used to compute the most relevant pages 1946 // according to the number of links they have 1947 // this can be a very interesting ranking for the Wiki 1948 // More about this on version 1.3 when we add the pageRank 1949 // column to tiki_pages 1950 /** 1951 * @param int $loops 1952 * @return array 1953 */ 1954 function pageRank($loops = 16) 1955 { 1956 $pagesTable = $this->table('tiki_pages'); 1957 1958 $ret = $pagesTable->fetchColumn('pageName', []); 1959 1960 // Now calculate the loop 1961 $pages = []; 1962 1963 foreach ($ret as $page) { 1964 $val = 1 / count($ret); 1965 1966 $pages[$page] = $val; 1967 1968 $pagesTable->update(['pageRank' => (int) $val], ['pageName' => $page]); 1969 } 1970 1971 for ($i = 0; $i < $loops; $i++) { 1972 foreach ($pages as $pagename => $rank) { 1973 // Get all the pages linking to this one 1974 // Fixed query. -rlpowell 1975 $query = "select `fromPage` from `tiki_links` where `toPage` = ? and `fromPage` not like 'objectlink:%'"; 1976 // page rank does not count links from non-page objects TODO: full feature allowing this with options 1977 $result = $this->fetchAll($query, [$pagename]); 1978 $sum = 0; 1979 1980 foreach ($result as $res) { 1981 $linking = $res["fromPage"]; 1982 1983 if (isset($pages[$linking])) { 1984 // Fixed query. -rlpowell 1985 $q2 = "select count(*) from `tiki_links` where `fromPage`= ? and `fromPage` not like 'objectlink:%'"; 1986 // page rank does not count links from non-page objects TODO: full feature allowing this with options 1987 $cant = $this->getOne($q2, [$linking]); 1988 if ($cant == 0) { 1989 $cant = 1; 1990 } 1991 $sum += $pages[$linking] / $cant; 1992 } 1993 } 1994 1995 $val = (1 - 0.85) + 0.85 * $sum; 1996 $pages[$pagename] = $val; 1997 1998 $pagesTable->update(['pageRank' => (int) $val], ['pageName' => $pagename]); 1999 } 2000 } 2001 arsort($pages); 2002 return $pages; 2003 } 2004 2005 /** 2006 * @param $maxRecords 2007 * @return array 2008 */ 2009 function list_recent_forum_topics($maxRecords) 2010 { 2011 $bindvars = ['forum', 0]; 2012 2013 $query = 'select `threadId`, `forumId` from `tiki_comments`,`tiki_forums`' 2014 . " where `object`=`forumId` and `objectType`=? and `parentId`=? order by " . $this->convertSortMode('commentDate_desc'); 2015 $result = $this->fetchAll($query, $bindvars, $maxRecords * 3, 0); // Load a little more, for permission filters 2016 $res = $ret = $retids = []; 2017 $n = 0; 2018 2019 foreach ($result as $res) { 2020 $objperm = $this->get_perm_object($res['threadId'], 'thread', '', false); 2021 if ($objperm['tiki_p_forum_read'] == 'y') { 2022 $retids[] = $res['threadId']; 2023 2024 $n++; 2025 2026 if ($n >= $maxRecords) { 2027 break; 2028 } 2029 } 2030 } 2031 2032 if ($n > 0) { 2033 $query = 'select * from `tiki_comments`' 2034 . ' where `threadId` in (' . implode(',', $retids) . ') order by ' . $this->convertSortMode('commentDate_desc'); 2035 $ret = $this->fetchAll($query); 2036 } 2037 2038 $retval = []; 2039 $retval['data'] = $ret; 2040 $retval['cant'] = $n; 2041 return $retval; 2042 } 2043 2044 /*shared*/ 2045 /** 2046 * @param $forumId 2047 * @param $offset 2048 * @param $maxRecords 2049 * @param $sort_mode 2050 * @param $find 2051 * @return array 2052 */ 2053 function list_forum_topics($forumId, $offset, $maxRecords, $sort_mode, $find) 2054 { 2055 $bindvars = [$forumId,$forumId,'forum',0]; 2056 if ($find) { 2057 $findesc = '%' . $find . '%'; 2058 $mid = " and (`title` like ? or `data` like ?)"; 2059 $bindvars[] = $findesc; 2060 $bindvars[] = $findesc; 2061 } else { 2062 $mid = ""; 2063 } 2064 2065 $query = "select * from `tiki_comments`,`tiki_forums` where "; 2066 $query .= " `forumId`=? and `object`=? and `objectType`=? and `parentId`=? $mid order by " . $this->convertSortMode($sort_mode); 2067 $query_cant = "select count(*) from `tiki_comments`,`tiki_forums` where "; 2068 $query_cant .= " `forumId`=? and `object`=? and `objectType`=? and `parentId`=? $mid"; 2069 $ret = $this->fetchAll($query, $bindvars, $maxRecords, $offset); 2070 $cant = $this->getOne($query_cant, $bindvars); 2071 2072 $retval = []; 2073 $retval["data"] = $ret; 2074 $retval["cant"] = $cant; 2075 return $retval; 2076 } 2077 2078 /*shared*/ 2079 /** 2080 * @param $type 2081 * @param $id 2082 * @return bool 2083 */ 2084 function remove_object($type, $id) 2085 { 2086 global $prefs; 2087 $categlib = TikiLib::lib('categ'); 2088 $objectlib = TikiLib::lib('object'); 2089 $categlib->uncategorize_object($type, $id); 2090 2091 // Now remove comments 2092 $threads = $this->table('tiki_comments')->fetchColumn('threadId', ['object' => $id, 'objectType' => $type]); 2093 if (! empty($threads)) { 2094 $commentslib = TikiLib::lib('comments'); 2095 2096 foreach ($threads as $threadId) { 2097 $commentslib->remove_comment($threadId); 2098 } 2099 } 2100 2101 // Remove individual permissions for this object if they exist 2102 $object = $type . $id; 2103 $this->table('users_objectpermissions')->deleteMultiple(['objectId' => md5($object), 'objectType' => $type]); 2104 // remove links from this object to pages 2105 $linkhandle = "objectlink:$type:$id"; 2106 $this->table('tiki_links')->deleteMultiple(['fromPage' => $linkhandle]); 2107 // remove fgal backlinks 2108 if ($prefs['feature_file_galleries'] == 'y') { 2109 $filegallib = TikiLib::lib('filegal'); 2110 $filegallib->deleteBacklinks(['type' => $type, 'object' => $id]); 2111 } 2112 // remove object 2113 $objectlib->delete_object($type, $id); 2114 2115 $objectAttributes = $this->table('tiki_object_attributes'); 2116 $objectAttributes->deleteMultiple(['type' => $type,'itemId' => $id]); 2117 2118 $objectRelations = $this->table('tiki_object_relations'); 2119 $objectRelations->deleteMultiple(['source_type' => $type, 'source_itemId' => $id]); 2120 $objectRelations->deleteMultiple(['target_type' => $type, 'target_itemId' => $id]); 2121 2122 return true; 2123 } 2124 2125 /*shared*/ 2126 /** 2127 * @param $offset 2128 * @param $maxRecords 2129 * @param $sort_mode 2130 * @param string $find 2131 * @param string $type 2132 * @param string $structureName 2133 * @return array 2134 */ 2135 function list_received_pages($offset, $maxRecords, $sort_mode, $find = '', $type = '', $structureName = '') 2136 { 2137 $bindvars = []; 2138 if ($type == 's') { 2139 $mid = ' `trp`.`structureName` is not null '; 2140 } 2141 if (! $sort_mode) { 2142 $sort_mode = '`structureName_asc'; 2143 } elseif ($type == 'p') { 2144 $mid = ' `trp`.`structureName` is null '; 2145 } 2146 if (! $sort_mode) { 2147 $sort_mode = '`pageName_asc'; 2148 } else { 2149 $mid = ''; 2150 } 2151 2152 if ($find) { 2153 $findesc = '%' . $find . '%'; 2154 if ($mid) { 2155 $mid .= ' and '; 2156 } 2157 $mid .= '(`trp`.`pageName` like ? or `trp`.`structureName` like ? or `trp`.`data` like ?)'; 2158 $bindvars[] = $findesc; 2159 $bindvars[] = $findesc; 2160 $bindvars[] = $findesc; 2161 } 2162 if ($structureName) { 2163 if ($mid) { 2164 $mid .= ' and '; 2165 } 2166 $mid .= ' `trp`.`structureName`=? '; 2167 $bindvars[] = $structureName; 2168 } 2169 if ($mid) { 2170 $mid = "where $mid"; 2171 } 2172 2173 $query = "select trp.*, tp.`pageName` as pageExists from `tiki_received_pages` trp left join `tiki_pages` tp on (tp.`pageName`=trp.`pageName`) $mid order by `structureName` asc, `pos` asc," . $this->convertSortMode($sort_mode); 2174 $query_cant = "select count(*) from `tiki_received_pages` trp $mid"; 2175 $ret = $this->fetchAll($query, $bindvars, $maxRecords, $offset); 2176 $cant = $this->getOne($query_cant, $bindvars); 2177 2178 $retval = []; 2179 $retval["data"] = $ret; 2180 $retval["cant"] = $cant; 2181 return $retval; 2182 } 2183 2184 // User voting system //// 2185 // Used to vote everything (polls,comments,files,submissions,etc) //// 2186 // Checks if a user has voted 2187 /*shared*/ 2188 /** 2189 * @param $user 2190 * @param $id 2191 * @return bool 2192 */ 2193 function user_has_voted($user, $id) 2194 { 2195 global $prefs; 2196 2197 $ret = false; 2198 2199 if (isset($_SESSION['votes'])) { 2200 $votes = $_SESSION['votes']; 2201 if (is_array($votes) && in_array($id, $votes)) { // has already voted in the session (logged or not) 2202 return true; 2203 } 2204 } 2205 2206 if (! $user) { 2207 if ($prefs['ip_can_be_checked'] != 'y' && ! isset($_COOKIE[ session_name() ])) {// cookie has not been activated too bad for him 2208 $ret = true; 2209 } elseif (isset($_COOKIE[md5("tiki_wiki_poll_$id")])) { 2210 $ret = true; 2211 } 2212 // we have no idea if cookie was deleted or if really he has not voted 2213 } else { 2214 $query = "select count(*) from `tiki_user_votings` where `user`=? and `id`=?"; 2215 if ($this->getOne($query, [$user,(string) $id]) > 0) { 2216 $ret = true; 2217 } 2218 } 2219 if ($prefs['ip_can_be_checked'] == 'y') { 2220 $query = 'select count(*) from `tiki_user_votings` where `ip`=? and `id`=?'; 2221 if ($this->getOne($query, [$this->get_ip_address(), $id]) > 0) { 2222 return true; // IP has already voted logged or not 2223 } 2224 } 2225 return $ret; 2226 } 2227 2228 // Registers a user vote 2229 /*shared*/ 2230 /** 2231 * @param $user 2232 * @param $id 2233 * @param bool $optionId 2234 * @param array $valid_options 2235 * @param bool $allow_revote 2236 * @return bool 2237 */ 2238 function register_user_vote($user, $id, $optionId = false, array $valid_options = [], $allow_revote = false) 2239 { 2240 global $prefs; 2241 2242 // If an option is specified and the valid options are specified, skip the vote entirely if not valid 2243 if (false !== $optionId && count($valid_options) > 0 && ! in_array($optionId, $valid_options)) { 2244 return false; 2245 } 2246 2247 if ($user && ! $allow_revote && $this->user_has_voted($user, $id)) { 2248 return false; 2249 } 2250 2251 $userVotings = $this->table('tiki_user_votings'); 2252 2253 $ip = $this->get_ip_address(); 2254 $_SESSION['votes'][] = $id; 2255 setcookie(md5("tiki_wiki_poll_$id"), $ip, time() + 60 * 60 * 24 * 300); 2256 if (! $user) { 2257 if ($prefs['ip_can_be_checked'] == 'y') { 2258 $userVotings->delete(['ip' => $ip, 'id' => $id, 'user' => '']); 2259 if ($optionId !== false && $optionId != 'NULL') { 2260 $userVotings->insert( 2261 [ 2262 'user' => '', 2263 'ip' => $ip, 2264 'id' => (string) $id, 2265 'optionId' => (int) $optionId, 2266 'time' => $this->now, 2267 ] 2268 ); 2269 } 2270 } elseif (isset($_COOKIE[md5("tiki_wiki_poll_$id")])) { 2271 return false; 2272 } elseif ($optionId !== false && $optionId != 'NULL') { 2273 $userVotings->insert( 2274 [ 2275 'user' => '', 2276 'ip' => $ip, 2277 'id' => (string) $id, 2278 'optionId' => (int) $optionId, 2279 'time' => $this->now, 2280 ] 2281 ); 2282 } 2283 } else { 2284 if ($prefs['ip_can_be_checked'] == 'y') { 2285 $userVotings->delete(['user' => $user,'id' => $id]); 2286 $userVotings->delete(['ip' => $ip,'id' => $id]); 2287 } else { 2288 $userVotings->delete(['user' => $user,'id' => $id]); 2289 } 2290 if ($optionId !== false && $optionId !== 'NULL') { 2291 $userVotings->insert( 2292 [ 2293 'user' => $user, 2294 'ip' => $ip, 2295 'id' => (string) $id, 2296 'optionId' => (int) $optionId, 2297 'time' => $this->now, 2298 ] 2299 ); 2300 } 2301 } 2302 2303 return true; 2304 } 2305 2306 /** 2307 * @param $id 2308 * @param $user 2309 * @return null 2310 */ 2311 function get_user_vote($id, $user) 2312 { 2313 global $prefs; 2314 $vote = null; 2315 if ($user) { 2316 $vote = $this->getOne("select `optionId` from `tiki_user_votings` where `user` = ? and `id` = ? order by `time` desc", [ $user, $id]); 2317 } 2318 if ($vote == null && $prefs['ip_can_be_checked'] == 'y') { 2319 $vote = $this->getOne("select `optionId` from `tiki_user_votings` where `ip` = ? and `id` = ? order by `time` desc", [ $user, $id]); 2320 } 2321 return $vote; 2322 } 2323 // end of user voting methods 2324 2325 /** 2326 * @param int $offset 2327 * @param $maxRecords 2328 * @param string $sort_mode 2329 * @param string $find 2330 * @param bool $include_prefs 2331 * @return array 2332 */ 2333 function list_users($offset = 0, $maxRecords = -1, $sort_mode = 'pref:realName', $find = '', $include_prefs = false) 2334 { 2335 global $user, $prefs; 2336 $userprefslib = TikiLib::lib('userprefs'); 2337 2338 $bindvars = []; 2339 if ($find) { 2340 $findesc = '%' . $find . '%'; 2341 $mid = 'where (`login` like ? or p1.`value` like ?)'; 2342 $mid_cant = $mid; 2343 $bindvars[] = $findesc; 2344 $bindvars[] = $findesc; 2345 $bindvars2 = [$findesc, $findesc]; 2346 $find_join = " left join `tiki_user_preferences` p1 on (u.`login` = p1.`user` and p1.`prefName` = 'realName')"; 2347 $find_join_cant = $find_join; 2348 } else { 2349 $mid = ''; 2350 $bindvars2 = []; 2351 $find_join = ''; 2352 $find_join_cant = ''; 2353 $mid_cant = ''; 2354 } 2355 2356 // This allows to use a sort_mode by prefs 2357 // In this case, sort_mode must have this syntax : 2358 // pref:PREFERENCE_NAME[_asc|_desc] 2359 // e.g. to sort on country : 2360 // pref:country OR pref:country_asc OR pref:country_desc 2361 2362 if ($ppos = strpos($sort_mode, ':')) { 2363 $sort_value = substr($sort_mode, $ppos + 1); 2364 $sort_way = 'asc'; 2365 2366 if (preg_match('/^(.+)_(asc|desc)$/i', $sort_value, $regs)) { 2367 $sort_value = $regs[1]; 2368 $sort_way = $regs[2]; 2369 unset($regs); 2370 } 2371 2372 if ($find_join != '' && $sort_value == 'realName') { 2373 // Avoid two joins if we can do only one 2374 $find_join = ''; 2375 $mid = 'where (`login` like ? or p.`value` like ?)'; 2376 } 2377 $sort_mode = "p.`value` $sort_way"; 2378 $pref_where = ( ( $mid == '' ) ? 'where' : $mid . ' and' ) . " p.`prefName` = '$sort_value'"; 2379 $pref_join = 'left join `tiki_user_preferences` p on (u.`login` = p.`user`)'; 2380 $pref_field = ', p.`value` as sf'; 2381 } else { 2382 $sort_mode = $this->convertSortMode($sort_mode); 2383 $pref_where = $mid; 2384 $pref_join = ''; 2385 $pref_field = ''; 2386 } 2387 2388 if ($sort_mode != '') { 2389 $sort_mode = 'order by ' . $sort_mode; 2390 } 2391 2392 $query = "select u.* $pref_field from `users_users` u $pref_join $find_join $pref_where $sort_mode"; 2393 2394 $query_cant = "select count(distinct u.`login`) from `users_users` u $find_join_cant $mid_cant"; 2395 $result = $this->fetchAll($query, $bindvars, $maxRecords, $offset); 2396 $cant = $this->getOne($query_cant, $bindvars2); 2397 2398 $ret = []; 2399 foreach ($result as $res) { 2400 if ($include_prefs) { 2401 $res['preferences'] = $userprefslib->get_userprefs($res['login']); 2402 } 2403 $ret[] = $res; 2404 } 2405 2406 return ['data' => $ret, 'cant' => $cant]; 2407 } 2408 2409 // CMS functions -ARTICLES- & -SUBMISSIONS- //// 2410 /*shared*/ 2411 /** 2412 * @param int $max 2413 * @return mixed 2414 */ 2415 function get_featured_links($max = 10) 2416 { 2417 $query = "select * from `tiki_featured_links` where `position` > ? order by " . $this->convertSortMode("position_asc"); 2418 return $this->fetchAll($query, [0], (int)$max, 0); 2419 } 2420 2421 /** 2422 * @param $sessionId 2423 */ 2424 function setSessionId($sessionId) 2425 { 2426 $this->sessionId = $sessionId; 2427 } 2428 2429 /** 2430 * @return null 2431 */ 2432 function getSessionId() 2433 { 2434 return $this->sessionId; 2435 } 2436 2437 /** 2438 * @return bool 2439 */ 2440 function update_session() 2441 { 2442 static $uptodate = false; 2443 if ($uptodate === true || $this->sessionId === null) { 2444 return true; 2445 } 2446 2447 global $user, $prefs; 2448 $logslib = TikiLib::lib('logs'); 2449 2450 if ($user === false) { 2451 $user = ''; 2452 } 2453 // If pref login_multiple_forbidden is set, length of tiki_sessions must match real session length to be up to date so we can detect concurrent logins of same user 2454 if ($prefs['login_multiple_forbidden'] == 'y') { 2455 $delay = ini_get('session.gc_maxlifetime'); 2456 } else { // Low value so as to guess who actually is in front of the computer 2457 $delay = 5 * 60; // 5 minutes 2458 } 2459 $oldy = $this->now - $delay; 2460 if ($user != '') { // was the user timeout? 2461 $query = "select count(*) from `tiki_sessions` where `sessionId`=?"; 2462 $cant = $this->getOne($query, [$this->sessionId]); 2463 if ($cant == 0) { 2464 if ($prefs['login_multiple_forbidden'] != 'y' || $user == 'admin') { 2465 // Recover after timeout 2466 $logslib->add_log("login", "back", $user, '', '', $this->now); 2467 } else { 2468 // Prevent multiple sessions for same user 2469 // Must check any user session, not only timed out ones 2470 $query = "SELECT count(*) FROM `tiki_sessions` WHERE user = ?"; 2471 $cant = $this->getOne($query, [$user]); 2472 if ($cant == 0) { 2473 // Recover after timeout (no other session) 2474 $logslib->add_log("login", "back", $user, '', '', $this->now); 2475 } else { 2476 // User has an active session on another browser 2477 $userlib = TikiLib::lib('user'); 2478 $userlib->user_logout($user, false, ''); 2479 } 2480 } 2481 } 2482 } 2483 $query = "select * from `tiki_sessions` where `timestamp`<?"; 2484 $result = $this->fetchAll($query, [$oldy]); 2485 foreach ($result as $res) { 2486 if ($res['user'] && $res['user'] != $user) { 2487 $logslib->add_log('login', 'timeout', $res['user'], ' ', ' ', $res['timestamp'] + $delay); 2488 } 2489 } 2490 2491 $sessions = $this->table('tiki_sessions'); 2492 2493 $sessions->delete(['sessionId' => $this->sessionId]); 2494 $sessions->deleteMultiple(['timestamp' => $sessions->lesserThan($oldy)]); 2495 2496 if ($user) { 2497 $sessions->delete(['user' => $user]); 2498 } 2499 2500 $sessions->insert( 2501 [ 2502 'sessionId' => $this->sessionId, 2503 'timestamp' => $this->now, 2504 'user' => $user, 2505 'tikihost' => isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : 'localhost', 2506 ] 2507 ); 2508 if ($prefs['session_storage'] == 'db') { 2509 // clean up adodb sessions as well in case adodb session garbage collection not working 2510 $sessions = $this->table('sessions'); 2511 2512 $sessions->deleteMultiple(['expiry' => $sessions->lesserThan($oldy)]); 2513 } 2514 2515 $uptodate = true; 2516 return true; 2517 } 2518 2519 // Returns the number of registered users which logged in or were active in the last 5 minutes. 2520 /** 2521 * @return mixed 2522 */ 2523 function count_sessions() 2524 { 2525 $this->update_session(); 2526 return $this->table('tiki_sessions')->fetchCount([]); 2527 } 2528 2529 // Returns a string-indexed array with all the hosts/servers active in the last 5 minutes. Keys are hostnames. Values represent the number of registered users which logged in or were active in the last 5 minutes on the host. 2530 /** 2531 * @return array 2532 */ 2533 function count_cluster_sessions() 2534 { 2535 $this->update_session(); 2536 $query = "select `tikihost`, count(`tikihost`) as cant from `tiki_sessions` group by `tikihost`"; 2537 return $this->fetchMap($query, []); 2538 } 2539 2540 /** 2541 * @param $links 2542 * @return bool 2543 */ 2544 function cache_links($links) 2545 { 2546 global $prefs; 2547 if ($prefs['cachepages'] != 'y') { 2548 return false; 2549 } 2550 foreach ($links as $link) { 2551 if (! $this->is_cached($link)) { 2552 $this->cache_url($link); 2553 } 2554 } 2555 } 2556 2557 /** 2558 * @param $data 2559 * @return array 2560 */ 2561 function get_links($data) 2562 { 2563 $links = []; 2564 2565 /// Prevent the substitution of link [] inside a <tag> ex: <input name="tracker[9]" ... > 2566 $data = preg_replace("/<[^>]*>/", "", $data); 2567 2568 /// Match things like [...], but ignore things like [[foo]. 2569 // -Robin 2570 if (preg_match_all("/(?<!\[)\[([^\[\|\]]+)(?:\|?[^\[\|\]]*){0,2}\]/", $data, $r1)) { 2571 $res = $r1[1]; 2572 $links = array_unique($res); 2573 } 2574 2575 return $links; 2576 } 2577 2578 /** 2579 * Convert internal links from absolute to relative 2580 * 2581 * @param string $data 2582 * @return string 2583 */ 2584 public function convertAbsoluteLinksToRelative($data) 2585 { 2586 global $prefs, $tikilib; 2587 2588 preg_match_all('/\[(([^|\]]+)(\|([^|\]]+))?)\]/', $data, $matches); 2589 2590 $counter = count($matches[0]); 2591 for ($i = 0; $i < $counter; $i++) { 2592 $label = ! empty($matches[3][$i]) ? ltrim($matches[3][$i], '|') : ''; 2593 if (! empty($label) && $matches[2][$i] == $label) { 2594 $data = str_replace($matches[0][$i], '[' . $matches[2][$i] . ']', $data); 2595 } 2596 2597 // Check if link part is valid url 2598 if (filter_var($matches[2][$i], FILTER_VALIDATE_URL) === false) { 2599 continue; 2600 } 2601 2602 // Check if url matches tiki instance links 2603 if ($url = $this->getMatchBaseUrlSchema($matches[2][$i]) && $matches[2][$i] == $matches[4][$i]) { 2604 $newLink = '[' . $matches[2][$i] . ']'; 2605 $data = str_replace($matches[0][$i], $newLink, $data); 2606 } 2607 } 2608 2609 preg_match_all('/\(\((([^|)]+)(\|([^|)]+))?)\)\)/', $data, $matches); 2610 2611 $counter = count($matches[0]); 2612 for ($i = 0; $i < $counter; $i++) { 2613 if ($matches[0][$i]) { 2614 $linkArray = explode('|', trim($matches[0][$i], '(())')); 2615 if (count($linkArray) == 2 && $linkArray[0] == $linkArray[1]) { 2616 $newLink = '((' . $linkArray[0] . '))'; 2617 $data = str_replace($matches[0][$i], $newLink, $data); 2618 } 2619 } 2620 } 2621 2622 if ($prefs['feature_absolute_to_relative_links'] != 'y') { 2623 return $data; 2624 } 2625 2626 $notification = false; 2627 2628 $from = 0; 2629 $to = strlen($data); 2630 $replace = []; 2631 foreach ($this->getWikiMarkers() as $marker) { 2632 while (false !== $open = $this->findText($data, $marker[0], $from, $to)) { 2633 // Wiki marker -+ begin should be proceeded by space or a newline 2634 if ($marker[0] == '-+' && $open != 0 && ! preg_match('/\s/', $data[$open - 1])) { 2635 $from = $open + 1; 2636 continue; 2637 } 2638 2639 if (false !== $close = $this->findText($data, $marker[1], $open, $to)) { 2640 $from = $close; 2641 $size = ($close - $open) + strlen($marker[1]); 2642 $markerBody = substr($data, $open, $size); 2643 $key = "§" . md5($tikilib->genPass()) . "§" ; 2644 $replace[$key] = $markerBody; 2645 $data = str_replace($markerBody, $key, $data); 2646 } else { 2647 break; 2648 } 2649 } 2650 } 2651 2652 // convert absolute to relative links 2653 $pluginMatches = WikiParser_PluginMatcher::match($data); 2654 foreach ($pluginMatches as $pluginMatch) { 2655 $pluginBody = $pluginMatch->getBody(); 2656 if (empty($pluginBody)) { 2657 $pluginBody = $pluginMatch->getArguments(); 2658 } 2659 2660 $key = "§" . md5($tikilib->genPass()) . "§" ; 2661 $replace[$key] = $pluginBody; 2662 $data = str_replace($pluginBody, $key, $data); 2663 } 2664 2665 // Detect tiki internal links 2666 preg_match_all('/\(\((([^|)]+)(\|([^|)]+))?)\)\)/', $data, $matches); 2667 2668 $counter = count($matches[0]); 2669 for ($i = 0; $i < $counter; $i++) { 2670 $linkArray = explode('|', trim($matches[0][$i], '(())')); 2671 if (count($linkArray) == 2 && $linkArray[0] == $linkArray[1]) { 2672 $newLink = '((' . $linkArray[0] . '))'; 2673 $data = str_replace($matches[0][$i], $newLink, $data); 2674 $notification = true; 2675 } 2676 2677 // Check if link part is valid url 2678 if (filter_var($matches[2][$i], FILTER_VALIDATE_URL) === false) { 2679 continue; 2680 } 2681 2682 // Check if url matches tiki instance links 2683 if ($url = $this->getMatchBaseUrlSchema($matches[2][$i])) { 2684 $newPath = str_replace($url, '', $matches[2][$i]); 2685 // In case of a tikibase instance point link to Homepage 2686 if (empty($newPath) || $newPath == '/') { 2687 $newPath = 'Homepage'; 2688 } 2689 $newLink = '((' . $newPath . $matches[3][$i] . '))'; 2690 $data = str_replace($matches[0][$i], $newLink, $data); 2691 $notification = true; 2692 } 2693 } 2694 2695 // Detect external links 2696 preg_match_all('/\[(([^|\]]+)(\|([^|\]]+))?)\]/', $data, $matches); 2697 2698 $counter = count($matches[0]); 2699 for ($i = 0; $i < $counter; $i++) { 2700 // Check if link part is valid url 2701 if (filter_var($matches[2][$i], FILTER_VALIDATE_URL) === false) { 2702 continue; 2703 } 2704 2705 // Check if url matches tiki instance links 2706 if ($url = $this->getMatchBaseUrlSchema($matches[2][$i])) { 2707 $newPath = str_replace($url, '', $matches[2][$i]); 2708 if (! empty($newPath)) { 2709 $newLink = '[' . $newPath . $matches[3][$i] . ']'; 2710 2711 $newLinkArray = explode('|', trim($newLink, '[]')); 2712 if (count($newLinkArray) === 2 && $newLinkArray[0] == str_replace($url, '', $newLinkArray[1])) { 2713 $newLink = '[' . $newLinkArray[0] . ']'; 2714 } 2715 2716 $data = str_replace($matches[0][$i], $newLink, $data); 2717 $notification = true; 2718 } 2719 } 2720 } 2721 2722 // Detect links outside wikiplugin or wiki markers 2723 preg_match_all('/(http|https):\/\/([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:\/~+#-]*[\w@?^=%&\/~+#-])?/', $data, $matches); 2724 2725 $counter = count($matches[0]); 2726 for ($i = 0; $i < $counter; $i++) { 2727 // Check if link part is valid url 2728 if (filter_var($matches[0][$i], FILTER_VALIDATE_URL) === false) { 2729 continue; 2730 } 2731 2732 // Check if url matches tiki instance links 2733 if ($url = $this->getMatchBaseUrlSchema($matches[0][$i])) { 2734 $newPath = str_replace($url, '', $matches[0][$i]); 2735 $objectLink = $this->getObjectRelativeLink($newPath); 2736 if (! empty($newPath) && ! empty($objectLink)) { 2737 $objStartPos = strpos($data, $matches[0][$i]); 2738 $objLength = strlen($matches[0][$i]); 2739 $data = substr_replace($data, $objectLink, $objStartPos, $objLength); 2740 $notification = true; 2741 } 2742 } 2743 } 2744 2745 foreach ($replace as $key => $body) { 2746 $data = str_replace($key, $body, $data); 2747 } 2748 2749 if ($notification) { 2750 Feedback::note(tr('Tiki links converted to relative links')); 2751 } 2752 2753 return $data; 2754 } 2755 2756 /** 2757 * Return the base url in the matched link protocol (http or https) 2758 * 2759 * @param string $link The link to check 2760 * 2761 * @return string The tiki base url with the matched schema (http or https) 2762 */ 2763 public function getMatchBaseUrlSchema($link) 2764 { 2765 global $base_url_http, $base_url_https; 2766 2767 if (strpos($link, $base_url_http) !== false) { 2768 return $base_url_http; 2769 } elseif (strpos($link, rtrim($base_url_http, '/')) !== false) { 2770 return rtrim($base_url_http, '/'); 2771 } elseif (strpos($link, $base_url_https) !== false) { 2772 return $base_url_https; 2773 } elseif (strpos($link, rtrim($base_url_https, '/')) !== false) { 2774 return rtrim($base_url_https, '/'); 2775 } else { 2776 return null; 2777 } 2778 } 2779 2780 /** 2781 * Returns the object internal link 2782 * 2783 * @param string $uri 2784 * @return string 2785 */ 2786 public function getObjectRelativeLink($uri) 2787 { 2788 global $prefs; 2789 $objectLink = ''; 2790 2791 if (! empty($prefs['feature_sefurl']) && $prefs['feature_sefurl'] === 'y') { 2792 $slug = explode('-', $uri); 2793 $slug = $slug[0]; 2794 2795 switch ($slug) { 2796 case (substr($slug, 0, 7) === 'article' || substr($slug, 0, 3) === 'art'): 2797 $articleId = substr($slug, 0, 7) === 'article' ? substr($slug, 7) : substr($slug, 3); 2798 $artlib = TikiLib::lib('art'); 2799 $article = $artlib->get_article($articleId); 2800 $objectLink = ! empty($article['title']) ? '[' . $uri . '|' . $article['title'] . ']' : ''; 2801 break; 2802 case substr($slug, 0, 8) === 'blogpost': 2803 $blogPostId = substr($slug, 8); 2804 $bloglib = TikiLib::lib('blog'); 2805 $blogPost = $bloglib->get_post($blogPostId); 2806 $objectLink = ! empty($blogPost['title']) ? '[' . $uri . '|' . $blogPost['title'] . ']' : ''; 2807 break; 2808 case substr($slug, 0, 4) === 'blog': 2809 $blogId = substr($slug, 4); 2810 $bloglib = TikiLib::lib('blog'); 2811 $blog = $bloglib->get_blog($blogId); 2812 $objectLink = ! empty($blog['title']) ? '[' . $uri . '|' . $blog['title'] . ']' : ''; 2813 break; 2814 case (substr($slug, 0, 11) === 'browseimage' || substr($slug, 0, 5) === 'image' || substr($slug, 0, 3) === 'img'): 2815 if (substr($slug, 0, 11) === 'browseimage') { 2816 $imageId = substr($slug, 11); 2817 } elseif (substr($slug, 0, 5) === 'image') { 2818 $imageId = substr($slug, 5); 2819 } else { 2820 $imageId = substr($slug, 3); 2821 } 2822 $imagegallib = TikiLib::lib('imagegal'); 2823 $image = $imagegallib->get_image_info($imageId); 2824 $objectLink = ! empty($image['name']) ? '[' . $uri . '|' . $image['name'] . ']' : ''; 2825 break; 2826 case substr($slug, 0, 8) === 'calevent': 2827 $eventId = substr($slug, 8); 2828 $calendarlib = TikiLib::lib('calendar'); 2829 $event = $calendarlib->get_item($eventId); 2830 $objectLink = ! empty($event['name']) ? '[' . $uri . '|' . $event['name'] . ']' : ''; 2831 break; 2832 case substr($slug, 0, 3) === 'cal': 2833 $calendarId = substr($slug, 3); 2834 $calendarlib = TikiLib::lib('calendar'); 2835 $calendar = $calendarlib->get_calendar($calendarId); 2836 $objectLink = ! empty($calendar['name']) ? '[' . $uri . '|' . $calendar['name'] . ']' : ''; 2837 break; 2838 case substr($slug, 0, 3) === 'cat': 2839 $catId = substr($slug, 3); 2840 $categlib = TikiLib::lib('categ'); 2841 $cat = $categlib->get_category($catId); 2842 $objectLink = ! empty($cat['name']) ? '[' . $uri . '|' . $cat['name'] . ']' : ''; 2843 break; 2844 case substr($slug, 0, 9) === 'directory': 2845 $directoryCatId = substr($slug, 9); 2846 if ($directoryCatId == 0) { 2847 $objectLink = '[' . $uri . '|Top]'; 2848 } else { 2849 global $dirlib; 2850 include_once('lib/directory/dirlib.php'); 2851 $directoryCat = $dirlib->dir_get_category($directoryCatId); 2852 $objectLink = ! empty($directoryCat['name']) ? '[' . $uri . '|' . $directoryCat['name'] . ']' : ''; 2853 } 2854 break; 2855 case substr($slug, 0, 7) === 'dirlink': 2856 $siteId = substr($slug, 7); 2857 global $dirlib; 2858 include_once('lib/directory/dirlib.php'); 2859 $site = $dirlib->dir_get_site($siteId); 2860 $objectLink = ! empty($site['name']) ? '[' . $uri . '|' . $site['name'] . ']' : ''; 2861 break; 2862 case substr($slug, 0, 5) === 'event': 2863 $eventId = substr($slug, 5); 2864 $calendarlib = TikiLib::lib('calendar'); 2865 $event = $calendarlib->get_item($eventId); 2866 $objectLink = ! empty($event['name']) ? '[' . $uri . '|' . $event['name'] . ']' : ''; 2867 break; 2868 case substr($slug, 0, 3) === 'faq': 2869 $faqId = substr($slug, 3); 2870 $faqlib = TikiLib::lib('faq'); 2871 $faq = $faqlib->get_faq($faqId); 2872 $objectLink = ! empty($faq['title']) ? '[' . $uri . '|' . $faq['title'] . ']' : ''; 2873 break; 2874 case substr($slug, 0, 4) === 'file': 2875 $fileGalleryId = substr($slug, 4); 2876 $filegallib = TikiLib::lib('filegal'); 2877 $gallery = $filegallib->get_file_gallery($fileGalleryId); 2878 $objectLink = ! empty($gallery['name']) ? '[' . $uri . '|' . $gallery['name'] . ']' : ''; 2879 break; 2880 case substr($slug, 0, 7) === 'gallery': 2881 $galleryId = substr($slug, 7); 2882 $filegallib = TikiLib::lib('filegal'); 2883 $gallery = $filegallib->get_file_gallery($galleryId); 2884 $objectLink = ! empty($gallery['name']) ? '[' . $uri . '|' . $gallery['name'] . ']' : ''; 2885 break; 2886 case (substr($slug, 0, 2) === 'dl' || substr($slug, 0, 9) === 'thumbnail' || substr($slug, 0, 7) === 'display' || substr($slug, 0, 7) === 'preview'): 2887 if (substr($slug, 0, 2) === 'dl') { 2888 $fileId = substr($slug, 2); 2889 } elseif (substr($slug, 0, 9) === 'thumbnail') { 2890 $fileId = substr($slug, 9); 2891 } else { 2892 $fileId = substr($slug, 7); 2893 } 2894 $filegallib = TikiLib::lib('filegal'); 2895 $file = $filegallib->get_file($fileId); 2896 $objectLink = ! empty($file['name']) ? '[' . $uri . '|' . $file['name'] . ']' : ''; 2897 break; 2898 case substr($slug, 0, 11) === 'forumthread': 2899 $forumCommentId = substr($slug, 11); 2900 $commentslib = TikiLib::lib('comments'); 2901 $forumComment = $commentslib->get_comment($forumCommentId); 2902 $objectLink = ! empty($forumComment['title']) ? '[' . $uri . '|' . $forumComment['title'] . ']' : ''; 2903 break; 2904 case substr($slug, 0, 5) === 'forum': 2905 $forumId = substr($slug, 5); 2906 $commentslib = TikiLib::lib('comments'); 2907 $forum = $commentslib->get_forum($forumId); 2908 $objectLink = ! empty($forum['name']) ? '[' . $uri . '|' . $forum['name'] . ']' : ''; 2909 break; 2910 case substr($slug, 0, 4) === 'item': 2911 $itemId = substr($slug, 4); 2912 $trklib = TikiLib::lib('trk'); 2913 $trackerItem = $trklib->get_tracker_item($itemId); 2914 $objectLink = ! empty($trackerItem) ? '[' . $uri . '|' . $slug . ']' : ''; 2915 break; 2916 case substr($slug, 0, 3) === 'int': 2917 $repID = substr($slug, 3); 2918 $integrator = new TikiIntegrator($dbTiki); 2919 $rep = $integrator->get_repository($repID); 2920 $objectLink = ! empty($rep['name']) ? '[' . $uri . '|' . $rep['name'] . ']' : ''; 2921 break; 2922 case (substr($slug, 0, 10) === 'newsletter' || substr($slug, 0, 2) === 'nl'): 2923 $newsletterId = substr($slug, 0, 10) === 'newsletter' ? substr($slug, 10) : substr($slug, 2); 2924 global $nllib; 2925 include_once('lib/newsletters/nllib.php'); 2926 $newsletter = $nllib->get_newsletter($newsletterId); 2927 $objectLink = ! empty($newsletter['name']) ? '[' . $uri . '|' . $newsletter['name'] . ']' : ''; 2928 break; 2929 case substr($slug, 0, 4) === 'poll': 2930 $pollId = substr($slug, 4); 2931 $polllib = TikiLib::lib('poll'); 2932 $poll = $polllib->get_poll($pollId); 2933 $objectLink = ! empty($poll['title']) ? '[' . $uri . '|' . $poll['title'] . ']' : ''; 2934 break; 2935 case substr($slug, 0, 4) === 'quiz': 2936 $quizId = substr($slug, 4); 2937 $quizlib = TikiLib::lib('quiz'); 2938 $quiz = $quizlib->get_quiz($quizId); 2939 $objectLink = ! empty($quiz['name']) ? '[' . $uri . '|' . $quiz['name'] . ']' : ''; 2940 break; 2941 case substr($slug, 0, 7) === 'tracker': 2942 $trackerId = substr($slug, 7); 2943 $trklib = TikiLib::lib('trk'); 2944 $tracker = $trklib->get_tracker($trackerId); 2945 $objectLink = ! empty($tracker['name']) ? '[' . $uri . '|' . $tracker['name'] . ']' : ''; 2946 break; 2947 case substr($slug, 0, 5) === 'sheet': 2948 $sheetId = substr($slug, 5); 2949 $sheetlib = TikiLib::lib("sheet"); 2950 $sheet = $sheetlib->get_sheet_info($sheetId); 2951 $objectLink = ! empty($sheet['title']) ? '[' . $uri . '|' . $sheet['title'] . ']' : ''; 2952 break; 2953 case substr($slug, 0, 6) === 'survey': 2954 include_once('lib/surveys/surveylib.php'); 2955 $surveyId = substr($slug, 6); 2956 $survey = $srvlib->get_survey($surveyId); 2957 $objectLink = ! empty($survey['name']) ? '[' . $uri . '|' . $survey['name'] . ']' : ''; 2958 break; 2959 case substr($slug, 0, 4) === 'user': 2960 $userId = substr($slug, 4); 2961 $user = $this->get_user_login($userId); 2962 $objectLink = ! empty($user) ? '[' . $uri . '|' . $user . ']' : ''; 2963 break; 2964 default: 2965 $pageName = $this->getPageBySlug($uri); 2966 $objectLink = ! empty($pageName) ? '((' . $pageName . '))' : ''; 2967 } 2968 } 2969 2970 $uriParams = explode('?', $uri); 2971 $param = ! empty($uriParams[1]) ? $uriParams[1] : ''; 2972 $clearParam = ! empty($param) ? explode('&', $param) : ''; 2973 $param = ! empty($clearParam[0]) ? $clearParam[0] : ''; 2974 if (! empty($param)) { 2975 switch ($param) { 2976 case substr($param, 0, 9) === 'articleId': 2977 $articleId = substr($param, 10); 2978 $artlib = TikiLib::lib('art'); 2979 $article = $artlib->get_article($articleId); 2980 $objectLink = ! empty($article['title']) ? '[' . $uri . '|' . $article['title'] . ']' : ''; 2981 break; 2982 case substr($param, 0, 6) === 'blogId': 2983 $blogId = substr($param, 7); 2984 $bloglib = TikiLib::lib('blog'); 2985 $blog = $bloglib->get_blog($blogId); 2986 $objectLink = ! empty($blog['title']) ? '[' . $uri . '|' . $blog['title'] . ']' : ''; 2987 break; 2988 case substr($param, 0, 6) === 'postId': 2989 $blogPostId = substr($param, 7); 2990 $bloglib = TikiLib::lib('blog'); 2991 $blogPost = $bloglib->get_post($blogPostId); 2992 $objectLink = ! empty($blogPost['title']) ? '[' . $uri . '|' . $blogPost['title'] . ']' : ''; 2993 break; 2994 case substr($param, 0, 10) === 'calendarId': 2995 $calendarId = substr($param, 11); 2996 $calendarlib = TikiLib::lib('calendar'); 2997 $calendar = $calendarlib->get_calendar($calendarId); 2998 $objectLink = ! empty($calendar['name']) ? '[' . $uri . '|' . $calendar['name'] . ']' : ''; 2999 break; 3000 case substr($param, 0, 17) === 'comments_parentId': 3001 $forumCommentId = substr($param, 18); 3002 $commentslib = TikiLib::lib('comments'); 3003 $forumComment = $commentslib->get_comment($forumCommentId); 3004 $objectLink = ! empty($forumComment['title']) ? '[' . $uri . '|' . $forumComment['title'] . ']' : ''; 3005 break; 3006 case substr($param, 0, 6) === 'parent': 3007 $directoryCatId = substr($param, 7); 3008 if ($directoryCatId == 0) { 3009 $objectLink = '[' . $uri . '|Top]'; 3010 } else { 3011 global $dirlib; 3012 include_once('lib/directory/dirlib.php'); 3013 $directoryCat = $dirlib->dir_get_category($directoryCatId); 3014 $objectLink = ! empty($directoryCat['name']) ? '[' . $uri . '|' . $directoryCat['name'] . ']' : ''; 3015 } 3016 break; 3017 case substr($param, 0, 9) === 'galleryId': 3018 $fileGalleryId = substr($param, 10); 3019 $filegallib = TikiLib::lib('filegal'); 3020 $gallery = $filegallib->get_file_gallery($fileGalleryId); 3021 $objectLink = ! empty($gallery['name']) ? '[' . $uri . '|' . $gallery['name'] . ']' : ''; 3022 break; 3023 case substr($param, 0, 5) === 'faqId': 3024 $faqId = substr($param, 6); 3025 $faqlib = TikiLib::lib('faq'); 3026 $faq = $faqlib->get_faq($faqId); 3027 $objectLink = ! empty($faq['title']) ? '[' . $uri . '|' . $faq['title'] . ']' : ''; 3028 break; 3029 case substr($param, 0, 6) === 'fileId': 3030 $fileId = substr($param, 7); 3031 $filegallib = TikiLib::lib('filegal'); 3032 $file = $filegallib->get_file($fileId); 3033 $objectLink = ! empty($file['name']) ? '[' . $uri . '|' . $file['name'] . ']' : ''; 3034 break; 3035 case substr($param, 0, 7) === 'forumId': 3036 $forumId = substr($param, 8); 3037 $commentslib = TikiLib::lib('comments'); 3038 $forum = $commentslib->get_forum($forumId); 3039 $objectLink = ! empty($forum['name']) ? '[' . $uri . '|' . $forum['name'] . ']' : ''; 3040 break; 3041 case (substr($param, 0, 7) === 'imageId' || substr($param, 0, 2) === 'id'): 3042 $imageId = (substr($param, 0, 7) === 'imageId') ? substr($param, 8) : substr($param, 3); 3043 $imagegallib = TikiLib::lib('imagegal'); 3044 $image = $imagegallib->get_image_info($imageId); 3045 $objectLink = ! empty($image['name']) ? '[' . $uri . '|' . $image['name'] . ']' : ''; 3046 break; 3047 case substr($param, 0, 4) === 'nlId': 3048 $newsletterId = substr($param, 5); 3049 global $nllib; 3050 include_once('lib/newsletters/nllib.php'); 3051 $newsletter = $nllib->get_newsletter($newsletterId); 3052 $objectLink = ! empty($newsletter['name']) ? '[' . $uri . '|' . $newsletter['name'] . ']' : ''; 3053 break; 3054 case substr($param, 0, 4) === 'page': 3055 $pageSlug = substr($param, 5); 3056 if ($uriParams[0] == 'tiki-index.php') { 3057 $pageName = $this->getPageBySlug($pageSlug); 3058 $objectLink = ! empty($pageName) ? '((' . $pageName . '))' : ''; 3059 } else { 3060 $objectLink = '[' . $uri . '|' . $uri . ']'; 3061 } 3062 break; 3063 case substr($param, 0, 8) === 'parentId': 3064 $catId = substr($param, 9); 3065 $categlib = TikiLib::lib('categ'); 3066 $cat = $categlib->get_category($catId); 3067 $objectLink = ! empty($cat['name']) ? '[' . $uri . '|' . $cat['name'] . ']' : ''; 3068 break; 3069 case substr($param, 0, 6) === 'pollId': 3070 $pollId = substr($param, 7); 3071 $polllib = TikiLib::lib('poll'); 3072 $poll = $polllib->get_poll($pollId); 3073 $objectLink = ! empty($poll['title']) ? '[' . $uri . '|' . $poll['title'] . ']' : ''; 3074 break; 3075 case substr($param, 0, 6) === 'quizId': 3076 $quizId = substr($param, 7); 3077 $quizlib = TikiLib::lib('quiz'); 3078 $quiz = $quizlib->get_quiz($quizId); 3079 $objectLink = ! empty($quiz['name']) ? '[' . $uri . '|' . $quiz['name'] . ']' : ''; 3080 break; 3081 case substr($param, 0, 5) === 'repID': 3082 $repID = substr($param, 6); 3083 $integrator = new TikiIntegrator($dbTiki); 3084 $rep = $integrator->get_repository($repID); 3085 $objectLink = ! empty($rep['name']) ? '[' . $uri . '|' . $rep['name'] . ']' : ''; 3086 break; 3087 case substr($param, 0, 6) === 'siteId': 3088 $siteId = substr($param, 7); 3089 global $dirlib; 3090 include_once('lib/directory/dirlib.php'); 3091 $site = $dirlib->dir_get_site($siteId); 3092 $objectLink = ! empty($site['name']) ? '[' . $uri . '|' . $site['name'] . ']' : ''; 3093 break; 3094 case substr($param, 0, 7) === 'sheetId': 3095 $sheetId = substr($param, 8); 3096 $sheetlib = TikiLib::lib("sheet"); 3097 $sheet = $sheetlib->get_sheet_info($sheetId); 3098 $objectLink = ! empty($sheet['title']) ? '[' . $uri . '|' . $sheet['title'] . ']' : ''; 3099 break; 3100 case substr($param, 0, 8) === 'surveyId': 3101 include_once('lib/surveys/surveylib.php'); 3102 $surveyId = substr($param, 9); 3103 $survey = $srvlib->get_survey($surveyId); 3104 $objectLink = ! empty($survey['name']) ? '[' . $uri . '|' . $survey['name'] . ']' : ''; 3105 break; 3106 case substr($param, 0, 9) === 'trackerId': 3107 $trackerId = substr($param, 10); 3108 $trklib = TikiLib::lib('trk'); 3109 $tracker = $trklib->get_tracker($trackerId); 3110 $objectLink = ! empty($tracker['name']) ? '[' . $uri . '|' . $tracker['name'] . ']' : ''; 3111 break; 3112 case substr($param, 0, 6) === 'userId': 3113 $userId = substr($param, 7); 3114 $user = $this->get_user_login($userId); 3115 $objectLink = ! empty($user) ? '[' . $uri . '|' . $user . ']' : ''; 3116 break; 3117 case substr($param, 0, 13) === 'viewcalitemId': 3118 $eventId = substr($param, 14); 3119 $calendarlib = TikiLib::lib('calendar'); 3120 $event = $calendarlib->get_item($eventId); 3121 $objectLink = ! empty($event['name']) ? '[' . $uri . '|' . $event['name'] . ']' : ''; 3122 break; 3123 } 3124 } 3125 3126 if (empty($objectLink)) { 3127 $pageName = $this->getPageBySlug($uri); 3128 if (in_array($uri, ['index.php', 'tiki-index.php'])) { 3129 $objectLink = '[' . $uri . '|' . $uri . ']'; 3130 } else { 3131 $objectLink = ! empty($pageName) ? '((' . $pageName . '))' : '[' . $uri . '|' . $uri . ']'; 3132 } 3133 } 3134 3135 return $objectLink; 3136 } 3137 3138 /** 3139 * Return wiki pages 3140 * 3141 * @param $slug 3142 * @return string 3143 */ 3144 public function getPageBySlug($slug) 3145 { 3146 global $prefs; 3147 3148 $pages = TikiDb::get()->table('tiki_pages'); 3149 $found = $pages->fetchOne('pageName', ['pageSlug' => $slug]); 3150 3151 return ! empty($found) ? $found : ''; 3152 } 3153 3154 /** 3155 * @param $data 3156 * @return array 3157 */ 3158 function get_links_nocache($data) 3159 { 3160 $links = []; 3161 3162 if (preg_match_all("/\[([^\]]+)/", $data, $r1)) { 3163 $res = []; 3164 3165 foreach ($r1[1] as $alink) { 3166 $parts = explode('|', $alink); 3167 3168 if (isset($parts[1]) && $parts[1] == 'nocache') { 3169 $res[] = $parts[0]; 3170 } elseif (isset($parts[2]) && $parts[2] == 'nocache') { 3171 $res[] = $parts[0]; 3172 } else { 3173 if (isset($parts[3]) && $parts[3] == 'nocache') { 3174 $res[] = $parts[0]; 3175 } 3176 } 3177 /// avoid caching URLs with common binary file extensions 3178 $extension = substr($parts[0], -4); 3179 $binary = [ 3180 '.arj', 3181 '.asf', 3182 '.avi', 3183 '.bz2', 3184 '.com', 3185 '.dat', 3186 '.doc', 3187 '.exe', 3188 '.hqx', 3189 '.mid', 3190 '.mov', 3191 '.mp3', 3192 '.mpg', 3193 '.ogg', 3194 '.pdf', 3195 '.ram', 3196 '.rar', 3197 '.rpm', 3198 '.rtf', 3199 '.sea', 3200 '.sit', 3201 '.tar', 3202 '.tgz', 3203 '.wav', 3204 '.wmv', 3205 '.xls', 3206 '.zip', 3207 'ar.Z', // .tar.Z 3208 'r.gz' // .tar.gz 3209 ]; 3210 if (in_array($extension, $binary)) { 3211 $res[] = $parts[0]; 3212 } 3213 } 3214 3215 $links = array_unique($res); 3216 } 3217 3218 return $links; 3219 } 3220 3221 /** 3222 * @param $url 3223 * @return bool 3224 */ 3225 function is_cacheable($url) 3226 { 3227 // simple implementation: future versions should analyse 3228 // if this is a link to the local machine 3229 if (strstr($url, 'tiki-')) { 3230 return false; 3231 } 3232 3233 if (strstr($url, 'messu-')) { 3234 return false; 3235 } 3236 3237 return true; 3238 } 3239 3240 /** 3241 * @param $url 3242 * @return mixed 3243 */ 3244 function is_cached($url) 3245 { 3246 return $this->table('tiki_link_cache')->fetchCount(['url' => $url]); 3247 } 3248 3249 /** 3250 * @param $offset 3251 * @param $maxRecords 3252 * @param $sort_mode 3253 * @param $find 3254 * @return array 3255 */ 3256 function list_cache($offset, $maxRecords, $sort_mode, $find) 3257 { 3258 3259 if ($find) { 3260 $findesc = '%' . $find . '%'; 3261 3262 $mid = " where (`url` like ?) "; 3263 $bindvars = [$findesc]; 3264 } else { 3265 $mid = ""; 3266 $bindvars = []; 3267 } 3268 3269 $query = "select `cacheId` ,`url`,`refresh` from `tiki_link_cache` $mid order by " . $this->convertSortMode($sort_mode); 3270 $query_cant = "select count(*) from `tiki_link_cache` $mid"; 3271 $ret = $this->fetchAll($query, $bindvars, $maxRecords, $offset); 3272 $cant = $this->getOne($query_cant, $bindvars); 3273 3274 $retval = []; 3275 $retval["data"] = $ret; 3276 $retval["cant"] = $cant; 3277 return $retval; 3278 } 3279 3280 /** 3281 * @param $cacheId 3282 * @return bool 3283 */ 3284 function refresh_cache($cacheId) 3285 { 3286 $linkCache = $this->table('tiki_link_cache'); 3287 3288 $url = $linkCache->fetchOne('url', ['cacheId' => $cacheId]); 3289 3290 $data = $this->httprequest($url); 3291 3292 $linkCache->update(['data' => $data, 'refresh' => $this->now], ['cacheId' => $cacheId]); 3293 return true; 3294 } 3295 3296 /** 3297 * @param $cacheId 3298 * @return bool 3299 */ 3300 function remove_cache($cacheId) 3301 { 3302 $linkCache = $this->table('tiki_link_cache'); 3303 $linkCache->delete(['cacheId' => $cacheId]); 3304 3305 return true; 3306 } 3307 3308 /** 3309 * @param $cacheId 3310 * @return mixed 3311 */ 3312 function get_cache($cacheId) 3313 { 3314 return $this->table('tiki_link_cache')->fetchFullRow(['cacheId' => $cacheId]); 3315 } 3316 3317 /** 3318 * @param $url 3319 * @return bool 3320 */ 3321 function get_cache_id($url) 3322 { 3323 $id = $this->table('tiki_link_cache')->fetchOne('cacheId', ['url' => $url]); 3324 return $id ? $id : false; 3325 } 3326 /* cachetime = 0 => no cache, otherwise duration cache is valid */ 3327 /** 3328 * @param $url 3329 * @param $isFresh 3330 * @param int $cachetime 3331 * @return mixed 3332 */ 3333 function get_cached_url($url, &$isFresh, $cachetime = 0) 3334 { 3335 $linkCache = $this->table('tiki_link_cache'); 3336 3337 $res = $linkCache->fetchFullRow(['url' => $url]); 3338 $now = $this->now; 3339 3340 if (empty($res) || ($now - $res['refresh']) > $cachetime) { // no cache or need to refresh 3341 $res['data'] = $this->httprequest($url); 3342 $isFresh = true; 3343 //echo '<br />Not cached:'.$url.'/'.strlen($res['data']); 3344 $res['refresh'] = $now; 3345 if ($cachetime > 0) { 3346 if (empty($res['cacheId'])) { 3347 $linkCache->insert(['url' => $url, 'data' => $res['data'], 'refresh' => $res['refresh']]); 3348 3349 $res = $linkCache->fetchFullRow(['url' => $url]); 3350 } else { 3351 $linkCache->update(['data' => $res['data'], 'refresh' => $res['refresh']], ['cacheId' => $res['cacheId']]); 3352 } 3353 } 3354 } else { 3355 //echo '<br />Cached:'.$url; 3356 $isFresh = false; 3357 } 3358 return $res; 3359 } 3360 3361 // This funcion return the $limit most accessed pages 3362 // it returns pageName and hits for each page 3363 /** 3364 * @param $limit 3365 * @return array 3366 */ 3367 function get_top_pages($limit) 3368 { 3369 $query = "select `pageName` , `hits` 3370 from `tiki_pages` 3371 order by `hits` desc"; 3372 3373 $result = $this->fetchAll($query, [], $limit); 3374 $ret = []; 3375 3376 foreach ($result as $res) { 3377 $aux["pageName"] = $res["pageName"]; 3378 3379 $aux["hits"] = $res["hits"]; 3380 $ret[] = $aux; 3381 } 3382 3383 return $ret; 3384 } 3385 3386 // Returns the name of all pages 3387 /** 3388 * @return mixed 3389 */ 3390 function get_all_pages() 3391 { 3392 return $this->table('tiki_pages')->fetchAll(['pageName'], []); 3393 } 3394 3395 /** 3396 * \brief Cache given url 3397 * If \c $data present (passed) it is just associated \c $url and \c $data. 3398 * Else it will request data for given URL and store it in DB. 3399 * Actualy (currently) data may be proviced by TIkiIntegrator only. 3400 */ 3401 function cache_url($url, $data = '') 3402 { 3403 // Avoid caching internal references... (only if $data not present) 3404 // (cdx) And avoid other protocols than http... 3405 // 03-Nov-2003, by zaufi 3406 // preg_match("_^(mailto:|ftp:|gopher:|file:|smb:|news:|telnet:|javascript:|nntp:|nfs:)_",$url) 3407 // was removed (replaced to explicit http[s]:// detection) bcouse 3408 // I now (and actualy use in my production Tiki) another bunch of protocols 3409 // available in my konqueror... (like ldap://, ldaps://, nfs://, fish://...) 3410 // ... seems like it is better to enum that allowed explicitly than all 3411 // noncacheable protocols. 3412 if (((strstr($url, 'tiki-') || strstr($url, 'messu-')) && $data == '') 3413 || (substr($url, 0, 7) != 'http://' && substr($url, 0, 8) != 'https://')) { 3414 return false; 3415 } 3416 // Request data for URL if nothing given in parameters 3417 // (reuse $data var) 3418 if ($data == '') { 3419 $data = $this->httprequest($url); 3420 } 3421 3422 // If stuff inside [] is *really* malformatted, $data 3423 // will be empty. -rlpowell 3424 if ($data) { 3425 $linkCache = $this->table('tiki_link_cache'); 3426 $linkCache->insert(['url' => $url, 'data' => $data, 'refresh' => $this->now]); 3427 return true; 3428 } else { 3429 return false; 3430 } 3431 } 3432 3433 // Removes all the versions of a page and the page itself 3434 /*shared*/ 3435 /** 3436 * @param $page 3437 * @param string $comment 3438 * @return bool 3439 */ 3440 function remove_all_versions($page, $comment = '') 3441 { 3442 $page_info = $this->get_page_info($page); 3443 if (! $page_info) { 3444 return false; 3445 } 3446 global $user, $prefs; 3447 if ($prefs['feature_actionlog'] == 'y' && isset($page_info['data'])) { 3448 $params = 'del=' . strlen($page_info['data']); 3449 } else { 3450 $params = ''; 3451 } 3452 // Deal with mail notifications. 3453 include_once(__DIR__ . '/notifications/notificationemaillib.php'); 3454 $foo = parse_url($_SERVER["REQUEST_URI"]); 3455 $machine = self::httpPrefix(true) . dirname($foo["path"]); 3456 sendWikiEmailNotification('wiki_page_deleted', $page, $user, $comment, 1, $page_info['data'], $machine); 3457 3458 //Remove the bibliography references for this page 3459 $this->removePageReference($page); 3460 3461 $wikilib = TikiLib::lib('wiki'); 3462 $multilinguallib = TikiLib::lib('multilingual'); 3463 $multilinguallib->detachTranslation('wiki page', $multilinguallib->get_page_id_from_name($page)); 3464 $this->invalidate_cache($page); 3465 //Delete structure references before we delete the page 3466 $query = "select `page_ref_id` "; 3467 $query .= "from `tiki_structures` ts, `tiki_pages` tp "; 3468 $query .= "where ts.`page_id`=tp.`page_id` and `pageName`=?"; 3469 $result = $this->fetchAll($query, [$page]); 3470 foreach ($result as $res) { 3471 $this->remove_from_structure($res["page_ref_id"]); 3472 } 3473 3474 $this->table('tiki_pages')->delete(['pageName' => $page]); 3475 if ($prefs['feature_contribution'] == 'y') { 3476 $contributionlib = TikiLib::lib('contribution'); 3477 $contributionlib->remove_page($page); 3478 } 3479 $this->table('tiki_history')->deleteMultiple(['pageName' => $page]); 3480 $this->table('tiki_links')->deleteMultiple(['fromPage' => $page]); 3481 $logslib = TikiLib::lib('logs'); 3482 $logslib->add_action('Removed', $page, 'wiki page', $params); 3483 //get_strings tra("Removed"); 3484 $this->table('users_groups')->updateMultiple(['groupHome' => null], ['groupHome' => $page]); 3485 3486 $this->table('tiki_theme_control_objects')->deleteMultiple(['name' => $page,'type' => 'wiki page']); 3487 $this->table('tiki_copyrights')->deleteMultiple(['page' => $page]); 3488 3489 $this->remove_object('wiki page', $page); 3490 3491 $this->table('tiki_user_watches')->deleteMultiple(['event' => 'wiki_page_changed', 'object' => $page]); 3492 $this->table('tiki_group_watches')->deleteMultiple(['event' => 'wiki_page_changed', 'object' => $page]); 3493 3494 $atts = $wikilib->list_wiki_attachments($page, 0, -1, 'created_desc', ''); 3495 foreach ($atts["data"] as $at) { 3496 $wikilib->remove_wiki_attachment($at["attId"]); 3497 } 3498 3499 $wikilib->remove_footnote('', $page); 3500 $this->refresh_index('wiki page', $page); 3501 3502 return true; 3503 } 3504 3505 /*shared*/ 3506 /** 3507 * @param $page_ref_id 3508 * @return bool 3509 */ 3510 function remove_from_structure($page_ref_id) 3511 { 3512 // Now recursively remove 3513 $query = "select `page_ref_id` "; 3514 $query .= "from `tiki_structures` as ts, `tiki_pages` as tp "; 3515 $query .= "where ts.`page_id`=tp.`page_id` and `parent_id`=?"; 3516 $result = $this->fetchAll($query, [$page_ref_id]); 3517 3518 foreach ($result as $res) { 3519 $this->remove_from_structure($res["page_ref_id"]); 3520 } 3521 3522 $structlib = TikiLib::lib('struct'); 3523 $page_info = $structlib->s_get_page_info($page_ref_id); 3524 3525 $structures = $this->table('tiki_structures'); 3526 3527 $structures->updateMultiple( 3528 ['pos' => $structures->decrement(1)], 3529 ['pos' => $structures->greaterThan((int) $page_info['pos']), 'parent_id' => (int) $page_info['parent_id'],] 3530 ); 3531 3532 $structures->delete(['page_ref_id' => $page_ref_id]); 3533 return true; 3534 } 3535 3536 /*shared*/ 3537 /** 3538 * @param int $offset 3539 * @param $maxRecords 3540 * @param string $sort_mode 3541 * @param string $user 3542 * @param null $find 3543 * @return array 3544 */ 3545 function list_galleries($offset = 0, $maxRecords = -1, $sort_mode = 'name_desc', $user = '', $find = null) 3546 { 3547 // If $user is admin then get ALL galleries, if not only user galleries are shown 3548 global $tiki_p_admin_galleries, $tiki_p_admin; 3549 3550 $old_sort_mode = ''; 3551 3552 if (in_array($sort_mode, ['images desc', 'images asc'])) { 3553 $old_offset = $offset; 3554 3555 $old_maxRecords = $maxRecords; 3556 $old_sort_mode = $sort_mode; 3557 $sort_mode = 'user desc'; 3558 $offset = 0; 3559 $maxRecords = -1; 3560 } 3561 3562 // If the user is not admin then select `it` 's own galleries or public galleries 3563 if ($tiki_p_admin_galleries === 'y' or $tiki_p_admin === 'y') { 3564 $whuser = ""; 3565 $bindvars = []; 3566 } else { 3567 $whuser = "where `user`=? or public=?"; 3568 $bindvars = [$user,'y']; 3569 } 3570 3571 if (! empty($find)) { 3572 $findesc = '%' . $find . '%'; 3573 3574 if (empty($whuser)) { 3575 $whuser = "where `name` like ? or `description` like ?"; 3576 $bindvars = [$findesc,$findesc]; 3577 } else { 3578 $whuser .= " and `name` like ? or `description` like ?"; 3579 $bindvars[] = $findesc; 3580 $bindvars[] = $findesc; 3581 } 3582 } 3583 3584 // If sort mode is versions then offset is 0, maxRecords is -1 (again) and sort_mode is nil 3585 // If sort mode is links then offset is 0, maxRecords is -1 (again) and sort_mode is nil 3586 // If sort mode is backlinks then offset is 0, maxRecords is -1 (again) and sort_mode is nil 3587 $query = "select * from `tiki_galleries` $whuser order by " . $this->convertSortMode($sort_mode); 3588 $query_cant = "select count(*) from `tiki_galleries` $whuser"; 3589 $result = $this->fetchAll($query, $bindvars, $maxRecords, $offset); 3590 $cant = $this->getOne($query_cant, $bindvars); 3591 $ret = []; 3592 3593 $images = $this->table('tiki_images'); 3594 foreach ($result as $res) { 3595 global $user; 3596 $add = $this->user_has_perm_on_object($user, $res['galleryId'], 'image gallery', 'tiki_p_view_image_gallery'); 3597 if ($add) { 3598 $aux = []; 3599 3600 $aux["name"] = $res["name"]; 3601 $gid = $res["galleryId"]; 3602 $aux["visible"] = $res["visible"]; 3603 $aux["id"] = $gid; 3604 $aux["galleryId"] = $res["galleryId"]; 3605 $aux["description"] = $res["description"]; 3606 $aux["created"] = $res["created"]; 3607 $aux["lastModif"] = $res["lastModif"]; 3608 $aux["user"] = $res["user"]; 3609 $aux["hits"] = $res["hits"]; 3610 $aux["public"] = $res["public"]; 3611 $aux["theme"] = $res["theme"]; 3612 $aux["geographic"] = $res["geographic"]; 3613 $aux["images"] = $images->fetchCount(['galleryId' => $gid]); 3614 $ret[] = $aux; 3615 } 3616 } 3617 3618 if ($old_sort_mode == 'images asc') { 3619 usort($ret, 'compare_images'); 3620 } 3621 3622 if ($old_sort_mode == 'images desc') { 3623 usort($ret, 'r_compare_images'); 3624 } 3625 3626 if (in_array($old_sort_mode, ['images desc', 'images asc'])) { 3627 $ret = array_slice($ret, $old_offset, $old_maxRecords); 3628 } 3629 3630 $retval = []; 3631 $retval["data"] = $ret; 3632 $retval["cant"] = $cant; 3633 return $retval; 3634 } 3635 3636 // Deprecated in favor of list_pages 3637 /** 3638 * @param $maxRecords 3639 * @param string $categories 3640 * @return array 3641 */ 3642 function last_pages($maxRecords = -1, $categories = '') 3643 { 3644 if (is_array($categories)) { 3645 $filter = ["categId" => $categories]; 3646 } else { 3647 $filter = []; 3648 } 3649 3650 return $this->list_pages(0, $maxRecords, "lastModif_desc", '', '', true, true, false, false, $filter); 3651 } 3652 3653 // Broken. Equivalent to last_pages($maxRecords) 3654 /** 3655 * @param $maxRecords 3656 * @return array 3657 */ 3658 function last_major_pages($maxRecords = -1) 3659 { 3660 return $this->list_pages(0, $maxRecords, "lastModif_desc"); 3661 } 3662 // use this function to speed up when pagename is only needed (the 3 getOne can killed tikiwith more that 3000 pages) 3663 /** 3664 * @param int $offset 3665 * @param $maxRecords 3666 * @param string $sort_mode 3667 * @param string $find 3668 * @return array 3669 */ 3670 function list_pageNames($offset = 0, $maxRecords = -1, $sort_mode = 'pageName_asc', $find = '') 3671 { 3672 return $this->list_pages($offset, $maxRecords, $sort_mode, $find, '', true, true); 3673 } 3674 3675 /** 3676 * @param int $offset 3677 * @param $maxRecords 3678 * @param string $sort_mode 3679 * @param string $find 3680 * @param string $initial 3681 * @param bool $exact_match 3682 * @param bool $onlyName 3683 * @param bool $forListPages 3684 * @param bool $only_orphan_pages 3685 * @param string $filter 3686 * @param bool $onlyCant 3687 * @param string $ref 3688 * @return array 3689 */ 3690 function list_pages($offset = 0, $maxRecords = -1, $sort_mode = 'pageName_desc', $find = '', $initial = '', $exact_match = true, $onlyName = false, $forListPages = false, $only_orphan_pages = false, $filter = '', $onlyCant = false, $ref = '', $exclude_pages = '') 3691 { 3692 global $prefs, $tiki_p_wiki_view_ratings; 3693 3694 $loadCategories = (isset($prefs['wiki_list_categories']) && $prefs['wiki_list_categories'] == 'y') || (isset($prefs['wiki_list_categories_path']) && $prefs['wiki_list_categories_path'] == 'y'); 3695 $loadCategories = $loadCategories && $forListPages; 3696 3697 $join_tables = ''; 3698 $join_bindvars = []; 3699 $old_sort_mode = ''; 3700 if ($sort_mode == 'size_desc') { 3701 $sort_mode = 'page_size_desc'; 3702 } 3703 if ($sort_mode == 'size_asc') { 3704 $sort_mode = 'page_size_asc'; 3705 } 3706 $select = ''; 3707 3708 // If sort mode is versions, links or backlinks then offset is 0, maxRecords is -1 (again) and sort_mode is nil 3709 $need_everything = false; 3710 if (in_array($sort_mode, ['versions_desc', 'versions_asc', 'links_asc', 'links_desc', 'backlinks_asc', 'backlinks_desc'])) { 3711 $old_sort_mode = $sort_mode; 3712 $sort_mode = 'user_desc'; 3713 $need_everything = true; 3714 } 3715 3716 if (is_array($find)) { // you can use an array of pages 3717 $mid = " where LOWER(`pageName`) IN (" . implode(',', array_fill(0, count($find), 'LOWER(?)')) . ")"; 3718 $bindvars = $find; 3719 } elseif (is_string($find) && ! empty($find)) { // or a string 3720 if (! $exact_match && $find) { 3721 $find = preg_replace("/([^\s]+)/", "%\\1%", $find); 3722 $f = preg_split("/[\s]+/", $find, -1, PREG_SPLIT_NO_EMPTY); 3723 if (empty($f)) {//look for space... 3724 $mid = " where LOWER(`pageName`) like LOWER('%$find%')"; 3725 } else { 3726 $findop = $forListPages ? ' AND' : ' OR'; 3727 $mid = " where LOWER(`pageName`) like " . implode($findop . ' LOWER(`pageName`) like ', array_fill(0, count($f), 'LOWER(?)')); 3728 $bindvars = $f; 3729 } 3730 } else { 3731 $mid = " where LOWER(`pageName`) like LOWER(?) "; 3732 $bindvars = [$find]; 3733 } 3734 } else { 3735 $bindvars = []; 3736 $mid = ''; 3737 } 3738 3739 //check if exclude page is array and then add its values in bindvars and 3740 if ($exclude_pages && is_array($exclude_pages)) { // you can use an array of pages 3741 if (! empty($mid)) { 3742 $mid .= " AND (LOWER(`pageName`) NOT IN (" . implode(',', array_fill(0, count($exclude_pages), 'LOWER(?)')) . "))"; 3743 } else { 3744 $mid = " where LOWER(`pageName`) NOT IN (" . implode(',', array_fill(0, count($exclude_pages), 'LOWER(?)')) . ")"; 3745 } 3746 3747 foreach ($exclude_pages as $epKey => $epVal) { 3748 $bindvars[] = $epVal; 3749 } 3750 } 3751 3752 $categlib = TikiLib::lib('categ'); 3753 $category_jails = $categlib->get_jail(); 3754 3755 if (! isset($filter['andCategId']) && ! isset($filter['categId']) && empty($filter['noCateg']) && ! empty($category_jails)) { 3756 $filter['categId'] = $category_jails; 3757 } 3758 3759 // If language is set to '', assume that no language filtering should be done. 3760 if (isset($filter['lang']) && $filter['lang'] == '') { 3761 unset($filter['lang']); 3762 } 3763 3764 $distinct = ''; 3765 if (! empty($filter)) { 3766 $tmp_mid = []; 3767 foreach ($filter as $type => $val) { 3768 if ($type == 'andCategId') { 3769 $categories = $categlib->get_jailed((array) $val); 3770 $join_tables .= " inner join `tiki_objects` as tob on (tob.`itemId`= tp.`pageName` and tob.`type`= ?) "; 3771 $join_bindvars[] = 'wiki page'; 3772 foreach ($categories as $i => $categId) { 3773 $join_tables .= " inner join `tiki_category_objects` as tc$i on (tc$i.`catObjectId`=tob.`objectId` and tc$i.`categId` =?) "; 3774 $join_bindvars[] = $categId; 3775 } 3776 } elseif ($type == 'categId') { 3777 $categories = $categlib->get_jailed((array) $val); 3778 $categories[] = -1; 3779 3780 $cat_count = count($categories); 3781 $join_tables .= " inner join `tiki_objects` as tob on (tob.`itemId`= tp.`pageName` and tob.`type`= ?) inner join `tiki_category_objects` as tc on (tc.`catObjectId`=tob.`objectId` and tc.`categId` IN(" . implode(', ', array_fill(0, $cat_count, '?')) . ")) "; 3782 3783 if ($cat_count > 1) { 3784 $distinct = ' DISTINCT '; 3785 } 3786 3787 $join_bindvars = array_merge(['wiki page'], $categories); 3788 } elseif ($type == 'noCateg') { 3789 $join_tables .= ' left join `tiki_objects` as tob on (tob.`itemId`= tp.`pageName` and tob.`type`= ?) left join `tiki_categorized_objects` as tcdo on (tcdo.`catObjectId`=tob.`objectId`) left join `tiki_category_objects` as tco on (tcdo.`catObjectId`=tco.`catObjectId`)'; 3790 $join_bindvars[] = 'wiki page'; 3791 $tmp_mid[] = '(tco.`categId` is null)'; 3792 } elseif ($type == 'notCategId') { 3793 foreach ($val as $v) { 3794 $tmp_mid[] = '(tp.`pageName` NOT IN(SELECT itemId FROM tiki_objects INNER JOIN tiki_category_objects ON catObjectId = objectId WHERE type = "wiki page" AND categId = ?))'; 3795 $bindvars[] = $v; 3796 } 3797 } elseif ($type == 'lang') { 3798 $tmp_mid[] = 'tp.`lang`=?'; 3799 $bindvars[] = $val; 3800 } elseif ($type == 'structHead') { 3801 $join_tables .= " inner join `tiki_structures` as ts on (ts.`page_id` = tp.`page_id` and ts.`parent_id` = 0) "; 3802 $select .= ',ts.`page_alias`'; 3803 } elseif ($type == 'langOrphan') { 3804 $join_tables .= " left join `tiki_translated_objects` tro on (tro.`type` = 'wiki page' AND tro.`objId` = tp.`page_id`) "; 3805 $tmp_mid[] = "( (tro.`traId` IS NULL AND tp.`lang` != ?) OR tro.`traId` NOT IN(SELECT `traId` FROM `tiki_translated_objects` WHERE `lang` = ?))"; 3806 $bindvars[] = $val; 3807 $bindvars[] = $val; 3808 } elseif ($type == 'structure_orphans') { 3809 $join_tables .= " left join `tiki_structures` as tss on (tss.`page_id` = tp.`page_id`) "; 3810 $tmp_mid[] = "(tss.`page_ref_id` is null)"; 3811 } elseif ($type == 'translationOrphan') { 3812 $multilinguallib = TikiLib::lib('multilingual'); 3813 $multilinguallib->sqlTranslationOrphan('wiki page', 'tp', 'page_id', $val, $join_tables, $midto, $bindvars); 3814 $tmp_mid[] = $midto; 3815 } 3816 } 3817 if (! empty($tmp_mid)) { 3818 $mid .= empty($mid) ? ' where (' : ' and ('; 3819 $mid .= implode(' and ', $tmp_mid) . ')'; 3820 } 3821 } 3822 if (! empty($initial)) { 3823 $mid .= empty($mid) ? ' where (' : ' and ('; 3824 $tmp_mid = ''; 3825 if (is_array($initial)) { 3826 foreach ($initial as $i) { 3827 if (! empty($tmp_mid)) { 3828 $tmp_mid .= ' or '; 3829 } 3830 $tmp_mid .= ' `pageName` like ? '; 3831 $bindvars[] = $i . '%'; 3832 } 3833 } else { 3834 $tmp_mid = " `pageName` like ? "; 3835 $bindvars[] = $initial . '%'; 3836 } 3837 $mid .= $tmp_mid . ')'; 3838 } 3839 3840 if ($only_orphan_pages) { 3841 $join_tables .= ' left join `tiki_links` as tl on tp.`pageName` = tl.`toPage` left join `tiki_structures` as tsoo on tp.`page_id` = tsoo.`page_id`'; 3842 $mid .= ( $mid == '' ) ? ' where ' : ' and '; 3843 $mid .= 'tl.`toPage` IS NULL and tsoo.`page_id` IS NULL'; 3844 } 3845 3846 if ($prefs['rating_advanced'] == 'y') { 3847 $ratinglib = TikiLib::lib('rating'); 3848 $join_tables .= $ratinglib->convert_rating_sort($sort_mode, 'wiki page', '`page_id`'); 3849 } 3850 3851 if ($tiki_p_wiki_view_ratings === 'y' && $prefs['feature_polls'] == 'y' && $prefs['feature_wiki_ratings'] == 'y') { 3852 $select .= ', (select sum(`tiki_poll_options`.`title`*`tiki_poll_options`.`votes`) as rating ' . // Multiply the option's label (title) by the number of votes for that option. Titles can be numbers but even then, this computation has doubtful relevance. A page with 5 ratings of "1" (on a scale of 5) would obtain a higher rating than a page with 1 rating of "4". This is most particular and apparently undocumented. Chealer 2017-05-22 3853 'from `tiki_objects` as tobt, `tiki_poll_objects` as tpo, `tiki_poll_options` where tobt.`itemId`= tp.`pageName` and tobt.`type`=\'wiki page\' and tobt.`objectId`=tpo.`catObjectId` and `tiki_poll_options`.`pollId`=tpo.`pollId` group by `tiki_poll_options`.`pollId`) as rating'; 3854 } 3855 3856 if (! empty($join_bindvars)) { 3857 $bindvars = empty($bindvars) ? $join_bindvars : array_merge($join_bindvars, $bindvars); 3858 } 3859 3860 $query = "select $distinct" 3861 . ( $onlyCant ? "tp.`pageName`" : "tp.* " . $select ) 3862 . " from `tiki_pages` as tp $join_tables $mid order by " . $this->convertSortMode($sort_mode); 3863 $countquery = "select count($distinct tp.`pageName`) from `tiki_pages` as tp $join_tables $mid"; 3864 $pageCount = $this->getOne($countquery, $bindvars); 3865 3866 3867 // HOTFIX (svn Rev. 22969 or near there) 3868 // Chunk loading. Because we cannot know what pages are visible, we load chunks of pages 3869 // and use Perms::filter to see what remains. Stop, if we have enough. 3870 $cant = 0; 3871 $n = -1; 3872 $ret = []; 3873 $raw = []; 3874 3875 $offset_tmp = 0; 3876 $haveEnough = false; 3877 $filterPerms = empty($ref) ? 'view' : ['view', 'wiki_view_ref']; 3878 while (! $haveEnough) { 3879 $rawTemp = $this->fetchAll($query, $bindvars, $maxRecords, $offset_tmp); 3880 $offset_tmp += $maxRecords; // next offset 3881 3882 if (count($rawTemp) == 0) { 3883 $haveEnough = true; // end of table 3884 } 3885 3886 $rawTemp = Perms::filter([ 'type' => 'wiki page' ], 'object', $rawTemp, ['object' => 'pageName', 'creator' => 'creator'], $filterPerms); 3887 3888 $raw = array_merge($raw, $rawTemp); 3889 if ((count($raw) >= $offset + $maxRecords) || $maxRecords == -1) { 3890 $haveEnough = true; // now we have enough records 3891 } 3892 } // prbably this brace has to include the next foreach??? I am unsure. 3893 // but if yes, the next lines have to be reviewed. 3894 3895 3896 $history = $this->table('tiki_history'); 3897 $links = $this->table('tiki_links'); 3898 3899 foreach ($raw as $res) { 3900 if ($initial) { 3901 $valid = false; 3902 $verified = self::take_away_accent($res['pageName']); 3903 foreach ((array) $initial as $candidate) { 3904 if (stripos($verified, $candidate) === 0) { 3905 $valid = true; 3906 break; 3907 } 3908 } 3909 3910 if (! $valid) { 3911 continue; 3912 } 3913 } 3914 //WYSIWYCA 3915 $res['perms'] = $this->get_perm_object($res['pageName'], 'wiki page', $res, false); 3916 3917 $n++; 3918 if (! $need_everything && $offset != -1 && $n < $offset) { 3919 continue; 3920 } 3921 3922 if (! $onlyCant && ( $need_everything || $maxRecords == -1 || $cant < $maxRecords )) { 3923 if ($onlyName) { 3924 $res = ['pageName' => $res['pageName']]; 3925 } else { 3926 $page = $res['pageName']; 3927 $res['len'] = $res['page_size']; 3928 unset($res['page_size']); 3929 $res['flag'] = $res['flag'] == 'L' ? 'locked' : 'unlocked'; 3930 if ($forListPages && $prefs['wiki_list_versions'] == 'y') { 3931 $res['versions'] = $history->fetchCount(['pageName' => $page]); 3932 } 3933 if ($forListPages && $prefs['wiki_list_links'] == 'y') { 3934 $res['links'] = $links->fetchCount(['fromPage' => $page]); 3935 } 3936 if ($forListPages && $prefs['wiki_list_backlinks'] == 'y') { 3937 $res['backlinks'] = $links->fetchCount(['toPage' => $page, 'fromPage' => $links->unlike('objectlink:%')]); 3938 } 3939 // backlinks do not include links from non-page objects TODO: full feature allowing this with options 3940 } 3941 3942 if ($loadCategories) { 3943 $cats = $categlib->get_object_categories('wiki page', $res['pageName']); 3944 $res['categpath'] = []; 3945 $res['categname'] = []; 3946 foreach ($cats as $cat) { 3947 $res['categpath'][] = $cp = $categlib->get_category_path_string($cat); 3948 if ($s = strrchr($cp, ':')) { 3949 $res['categname'][] = substr($s, 1); 3950 } else { 3951 $res['categname'][] = $cp; 3952 } 3953 } 3954 } 3955 $ret[] = $res; 3956 } 3957 $cant++; 3958 } 3959 if (! $need_everything) { 3960 $cant += $offset; 3961 } 3962 3963 // If sortmode is versions, links or backlinks sort using the ad-hoc function and reduce using old_offset and old_maxRecords 3964 if ($need_everything) { 3965 switch ($old_sort_mode) { 3966 case 'versions_asc': 3967 usort($ret, 'compare_versions'); 3968 break; 3969 case 'versions_desc': 3970 usort($ret, 'r_compare_versions'); 3971 break; 3972 case 'links_desc': 3973 usort($ret, 'compare_links'); 3974 break; 3975 case 'links_asc': 3976 usort($ret, 'r_compare_links'); 3977 break; 3978 case 'backlinks_desc': 3979 usort($ret, 'compare_backlinks'); 3980 break; 3981 case 'backlinks_asc': 3982 usort($ret, 'r_compare_backlinks'); 3983 break; 3984 } 3985 } 3986 3987 $retval = []; 3988 $retval['data'] = $ret; 3989 $retval['cant'] = $pageCount; // this is not exact. Workaround. 3990 return $retval; 3991 } 3992 3993 3994 // Function that checks for: 3995 // - tiki_p_admin 3996 // - the permission itself 3997 // - individual permission 3998 // - category permission 3999 // if O.K. this function shall replace similar constructs in list_pages and other functions above. 4000 // $categperm is the category permission that should grant $perm. if none, pass 0 4001 // If additional perm arguments are specified, the user must have all the perms to pass the test 4002 /** 4003 * @param $usertocheck 4004 * @param $object 4005 * @param $objtype 4006 * @param $perm1 4007 * @param null $perm2 4008 * @param null $perm3 4009 * @return bool 4010 */ 4011 function user_has_perm_on_object($usertocheck, $object, $objtype, $perm1, $perm2 = null, $perm3 = null) 4012 { 4013 $accessor = $this->get_user_permission_accessor($usertocheck, $objtype, $object); 4014 4015 $chk1 = $perm1 != null ? $accessor->$perm1 : true; 4016 $chk2 = $perm2 != null ? $accessor->$perm2 : true; 4017 $chk3 = $perm3 != null ? $accessor->$perm3 : true; 4018 4019 return $chk1 && $chk2 && $chk3; 4020 } 4021 4022 function get_user_permission_accessor($usertocheck, $type = null, $object = null) 4023 { 4024 global $user; 4025 if ($type && $object) { 4026 $context = [ 'type' => $type, 'object' => $object ]; 4027 $accessor = Perms::get($context); 4028 } else { 4029 $accessor = Perms::get(); 4030 } 4031 4032 // Do not override perms for current users otherwise security tokens won't work 4033 if ($usertocheck != $user) { 4034 $groups = $this->get_user_groups($usertocheck); 4035 $accessor->setGroups($groups); 4036 } 4037 4038 return $accessor; 4039 } 4040 4041 /* get all the perm of an object either in a table or global+smarty set 4042 * OPTIMISATION: better to test tiki_p_admin outside for global=false 4043 * TODO: all the objectTypes 4044 * TODO: replace switch with object 4045 * global = true set the global perm and smarty var, otherwise return an array of perms 4046 */ 4047 /** 4048 * @param $objectId 4049 * @param $objectType 4050 * @param string $info 4051 * @param bool $global 4052 * @return array|bool 4053 */ 4054 function get_perm_object($objectId, $objectType, $info = '', $global = true) 4055 { 4056 global $user; 4057 $smarty = TikiLib::lib('smarty'); 4058 $userlib = TikiLib::lib('user'); 4059 4060 $perms = Perms::get([ 'type' => $objectType, 'object' => $objectId ]); 4061 if (empty($perms->getGroups())) { 4062 $perms->setGroups($this->get_user_groups($user)); 4063 } 4064 $permNames = $userlib->get_permission_names_for($this->get_permGroup_from_objectType($objectType)); 4065 4066 $ret = []; 4067 foreach ($permNames as $perm) { 4068 $ret[$perm] = $perms->$perm ? 'y' : 'n'; 4069 4070 if ($global) { 4071 $smarty->assign($perm, $ret[$perm]); 4072 $GLOBALS[ $perm ] = $ret[$perm]; 4073 } 4074 } 4075 4076 // Skip those 'local' permissions for admin users and when global is not requested. 4077 if ($global && ! Perms::get()->admin) { 4078 $ret2 = $this->get_local_perms($user, $objectId, $objectType, $info, true); 4079 if ($ret2) { 4080 $ret = $ret2; 4081 } 4082 } 4083 4084 return $ret; 4085 } 4086 4087 /** 4088 * @param $objectType 4089 * @return string 4090 */ 4091 function get_permGroup_from_objectType($objectType) 4092 { 4093 4094 switch ($objectType) { 4095 case 'tracker': 4096 case 'trackeritem': 4097 return 'trackers'; 4098 case 'image gallery': 4099 case 'image': 4100 return 'image galleries'; 4101 case 'file gallery': 4102 case 'file': 4103 return 'file galleries'; 4104 case 'article': 4105 case 'submission': 4106 case 'topic': 4107 return 'cms'; 4108 case 'forum': 4109 case 'thread': 4110 return 'forums'; 4111 case 'blog': 4112 case 'blog post': 4113 return 'blogs'; 4114 case 'wiki page': 4115 case 'history': 4116 return 'wiki'; 4117 case 'faq': 4118 return 'faqs'; 4119 case 'survey': 4120 return 'surveys'; 4121 case 'newsletter': 4122 return 'newsletters'; 4123 /* TODO */ 4124 default: 4125 return $objectType; 4126 } 4127 } 4128 4129 /** 4130 * @param $objectType 4131 * @return string 4132 */ 4133 function get_adminPerm_from_objectType($objectType) 4134 { 4135 4136 switch ($objectType) { 4137 case 'tracker': 4138 return 'tiki_p_admin_trackers'; 4139 case 'image gallery': 4140 case 'image': 4141 return 'tiki_p_admin_galleries'; 4142 case 'file gallery': 4143 case 'file': 4144 return 'tiki_p_admin_file_galleries'; 4145 case 'article': 4146 case 'submission': 4147 return 'tiki_p_admin_cms'; 4148 case 'forum': 4149 return 'tiki_p_admin_forum'; 4150 case 'blog': 4151 case 'blog post': 4152 return 'tiki_p_blog_admin'; 4153 case 'wiki page': 4154 case 'history': 4155 return 'tiki_p_admin_wiki'; 4156 case 'faq': 4157 return 'tiki_p_admin_faqs'; 4158 case 'survey': 4159 return 'tiki_p_admin_surveys'; 4160 case 'newsletter': 4161 return 'tiki_p_admin_newsletters'; 4162 /* TODO */ 4163 default: 4164 return "tiki_p_admin_$objectType"; 4165 } 4166 } 4167 4168 /* deal all the special perm */ 4169 /** 4170 * @param $user 4171 * @param $objectId 4172 * @param $objectType 4173 * @param $info 4174 * @param $global 4175 * @return array|bool 4176 */ 4177 function get_local_perms($user, $objectId, $objectType, $info, $global) 4178 { 4179 global $prefs; 4180 $smarty = TikiLib::lib('smarty'); 4181 $userlib = TikiLib::lib('user'); 4182 $ret = []; 4183 switch ($objectType) { 4184 case 'wiki page': 4185 case 'wiki': 4186 if ($prefs['wiki_creator_admin'] == 'y' && ! empty($user) && ! empty($info) && $info['creator'] == $user) { //can admin his page 4187 $perms = $userlib->get_permission_names_for($this->get_permGroup_from_objectType($objectType)); 4188 foreach ($perms as $perm) { 4189 $ret[$perm] = 'y'; 4190 if ($global) { 4191 $GLOBALS[$perm] = 'y'; 4192 $smarty->assign($perm, 'y'); 4193 } 4194 } 4195 return $ret; 4196 } 4197 // Enabling userpage is not enough, the prefix must be present, otherwise, permissions will be messed-up on new page creation 4198 if ($prefs['feature_wiki_userpage'] == 'y' && ! empty($prefs['feature_wiki_userpage_prefix']) && ! empty($user) && strcasecmp($prefs['feature_wiki_userpage_prefix'], substr($objectId, 0, strlen($prefs['feature_wiki_userpage_prefix']))) == 0) { 4199 if (strcasecmp($objectId, $prefs['feature_wiki_userpage_prefix'] . $user) == 0) { //can edit his page 4200 if (! $global) { 4201 $perms = $userlib->get_permission_names_for($this->get_permGroup_from_objectType($objectType)); 4202 foreach ($perms as $perm) { 4203 if ($perm == 'tiki_p_view' || $perm == 'tiki_p_edit') { 4204 $ret[$perm] = 'y'; 4205 } else { 4206 $ret[$perm] = $GLOBALS[$perm]; 4207 } 4208 } 4209 } else { 4210 global $tiki_p_edit, $tiki_p_view; 4211 $tiki_p_view = 'y'; 4212 $smarty->assign('tiki_p_view', 'y'); 4213 $tiki_p_edit = 'y'; 4214 $smarty->assign('tiki_p_edit', 'y'); 4215 } 4216 } else { 4217 if (! $global) { 4218 $ret['tiki_p_edit'] = 'n'; 4219 } else { 4220 global $tiki_p_edit; 4221 $tiki_p_edit = 'n'; 4222 $smarty->assign('tiki_p_edit', 'n'); 4223 } 4224 } 4225 if (! $global) { 4226 $ret['tiki_p_rename'] = 'n'; 4227 $ret['tiki_p_rollback'] = 'n'; 4228 $ret['tiki_p_lock'] = 'n'; 4229 $ret['tiki_p_assign_perm_wiki_page'] = 'n'; 4230 } else { 4231 global $tiki_p_rename, $tiki_p_rollback, $tiki_p_lock, $tiki_p_assign_perm_wiki_page; 4232 $tiki_p_rename = $tiki_p_rollback = $tiki_p_lock = $tiki_p_assign_perm_wiki_page = 'n'; 4233 $smarty->assign('tiki_p_rename', 'n'); 4234 $smarty->assign('tiki_p_rollback', 'n'); 4235 $smarty->assign('tiki_p_lock', 'n'); 4236 $smarty->assign('tiki_p_assign_perm_wiki_page', 'n'); 4237 } 4238 } 4239 break; 4240 case 'file gallery': 4241 case 'file': 4242 global $tiki_p_userfiles; 4243 4244 if ($objectType === 'file') { 4245 $gal_info = TikiLib::lib('filegal')->get_file_gallery_info($info['galleryId']); 4246 if ($gal_info['user'] === $user) { 4247 $info['type'] = 'user'; // show my files as mine 4248 } else { 4249 $info['type'] = ''; 4250 } 4251 } 4252 if ($prefs['feature_use_fgal_for_user_files'] === 'y' && 4253 $info['type'] === 'user' && $info['user'] === $user && $tiki_p_userfiles === 'y') { 4254 foreach (['tiki_p_download_files', 4255 'tiki_p_upload_files', 4256 'tiki_p_view_file_gallery', 4257 'tiki_p_remove_files', 4258 'tiki_p_create_file_galleries', 4259 'tiki_p_edit_gallery_file', 4260 ] as $perm) { 4261 $GLOBALS[$perm] = 'y'; 4262 $smarty->assign($perm, 'y'); 4263 $ret[$perm] = 'y'; 4264 } 4265 return $ret; 4266 } 4267 break; 4268 default: 4269 break; 4270 } 4271 return false; 4272 } 4273 4274 // Returns a string-indexed array of modified preferences (those with a value other than the default). Keys are preference names. Values are preference values. 4275 // NOTE: prefslib contains a similar method now called getModifiedPrefsForExport 4276 /** 4277 * @return array 4278 */ 4279 function getModifiedPreferences() 4280 { 4281 $defaults = get_default_prefs(); 4282 $modified = []; 4283 4284 $results = $this->table('tiki_preferences')->fetchAll(['name', 'value'], []); 4285 4286 foreach ($results as $result) { 4287 $name = $result['name']; 4288 $value = $result['value']; 4289 4290 $strDef = ""; 4291 if (isset($defaults[$name]) && is_array($defaults[$name])) { 4292 $strDef = implode(" ", $defaults[$name]); 4293 } else { 4294 $strDef = isset($defaults[$name]) ? $defaults[$name] : ""; 4295 } 4296 if (empty($strDef) || ($strDef != (string) $value)) { 4297 $modified[$name] = $value; 4298 } 4299 } 4300 return $modified; 4301 } 4302 4303 /** 4304 * @param $names 4305 * @param bool $exact_match 4306 * @param bool $no_return 4307 * @return array|bool 4308 */ 4309 function get_preferences($names, $exact_match = false, $no_return = false) 4310 { 4311 global $prefs; 4312 4313 $preferences = []; 4314 if ($exact_match) { 4315 if (is_array($names)) { 4316 $this->_get_values('tiki_preferences', 'name', $names, $prefs); 4317 if (! $no_return) { 4318 foreach ($names as $name) { 4319 $preferences[$name] = $prefs[$name]; 4320 } 4321 } 4322 } else { 4323 $this->get_preference($names); 4324 if (! $no_return) { 4325 $preferences = [ $names => $prefs[$names] ]; 4326 } 4327 } 4328 } else { 4329 if (is_array($names)) { 4330 //Only handle $filtername as array with exact_matches 4331 return false; 4332 } else { 4333 $tikiPreferences = $this->table('tiki_preferences'); 4334 $preferences = $tikiPreferences->fetchMap('name', 'value', ['name' => $tikiPreferences->like($names)]); 4335 } 4336 } 4337 return $preferences; 4338 } 4339 4340 /** 4341 * @param $name 4342 * @param string $default 4343 * @param bool $expectArray 4344 * @return mixed|string 4345 */ 4346 function get_preference($name, $default = '', $expectArray = false) 4347 { 4348 global $prefs; 4349 4350 $value = isset($prefs[$name]) ? $prefs[$name] : $default; 4351 4352 if (empty($value)) { 4353 if ($expectArray) { 4354 return []; 4355 } else { 4356 return $value; 4357 } 4358 } 4359 4360 if ($expectArray && is_string($value)) { 4361 return unserialize($value); 4362 } else { 4363 return $value; 4364 } 4365 } 4366 4367 /** 4368 * @param $name 4369 */ 4370 function delete_preference($name) 4371 { 4372 global $user_overrider_prefs, $user_preferences, $user, $prefs; 4373 $prefslib = TikiLib::lib('prefs'); 4374 4375 $this->table('tiki_preferences')->delete(['name' => $name]); 4376 $cachelib = TikiLib::lib('cache'); 4377 $cachelib->invalidate('global_preferences'); 4378 4379 $definition = $prefslib->getPreference($name); 4380 $value = $definition['default']; 4381 if (isset($prefs)) { 4382 if (in_array($name, $user_overrider_prefs)) { 4383 $prefs['site_' . $name] = $value; 4384 } elseif (isset($user_preferences[$user][$name])) { 4385 $prefs[$name] = $user_preferences[$user][$name]; 4386 } else { 4387 $prefs[$name] = $value; 4388 } 4389 } 4390 } 4391 4392 /** 4393 * @param $name 4394 * @param $value 4395 * @return bool 4396 */ 4397 function set_preference($name, $value) 4398 { 4399 global $user_overrider_prefs, $user_preferences, $user, $prefs; 4400 4401 $prefslib = TikiLib::lib('prefs'); 4402 4403 $definition = $prefslib->getPreference($name); 4404 4405 if ($definition && ! $definition['available']) { 4406 return false; 4407 } 4408 4409 $preferences = $this->table('tiki_preferences'); 4410 $preferences->insertOrUpdate(['value' => is_array($value) ? serialize($value) : $value], ['name' => $name]); 4411 4412 if (isset($prefs)) { 4413 if (in_array($name, $user_overrider_prefs)) { 4414 $prefs['site_' . $name] = $value; 4415 } elseif (isset($user_preferences[$user][$name])) { 4416 $prefs[$name] = $user_preferences[$user][$name]; 4417 } else { 4418 $prefs[$name] = $value; 4419 } 4420 } 4421 4422 // Invalidate cache only after writing to DB to avoid other processes to cache the old info 4423 $menulib = TikiLib::lib('menu'); 4424 $menulib->empty_menu_cache(); 4425 4426 $cachelib = TikiLib::lib('cache'); 4427 $cachelib->invalidate('global_preferences'); 4428 4429 return true; 4430 } 4431 4432 /** 4433 * @param $table 4434 * @param $field_name 4435 * @param null $var_names 4436 * @param $global_ref 4437 * @param string $query_cond 4438 * @param null $bindvars 4439 * @return bool 4440 */ 4441 function _get_values($table, $field_name, $var_names = null, &$global_ref, $query_cond = '', $bindvars = null) 4442 { 4443 if (empty($table) || empty($field_name)) { 4444 return false; 4445 } 4446 4447 $needed = []; 4448 $defaults = null; 4449 4450 if (is_array($var_names)) { 4451 // Detect if var names are specified as keys (then values are considered as var defaults) 4452 // by looking at the type of the first key 4453 $defaults = ! is_integer(key($var_names)); 4454 4455 // Check if we need to get the value from DB by looking in the global $user_preferences array 4456 // (this is able to handle more than one var at a time) 4457 // ... and store the default values as well, just in case we needs them later 4458 if ($defaults) { 4459 foreach ($var_names as $var => $default) { 4460 if (! isset($global_ref[$var])) { 4461 $needed[$var] = $default; 4462 } 4463 } 4464 } else { 4465 foreach ($var_names as $var) { 4466 if (! isset($global_ref[$var])) { 4467 $needed[$var] = null; 4468 } 4469 } 4470 } 4471 } elseif ($var_names !== null) { 4472 return false; 4473 } 4474 4475 $where = $query_cond; 4476 if (empty($where)) { 4477 $where = '1'; 4478 } 4479 if (is_null($bindvars)) { 4480 $bindvars = []; 4481 } 4482 if (count($needed) > 0) { 4483 $where .= ' AND (0'; 4484 foreach ($needed as $var => $def) { 4485 $where .= " or `$field_name`=?"; 4486 $bindvars[] = $var; 4487 } 4488 $where .= ')'; 4489 } 4490 $query = "select `$field_name`, `value` from `$table` where $where"; 4491 $result = $this->fetchAll($query, $bindvars); 4492 4493 foreach ($result as $res) { 4494 // store the db value in the global array 4495 $global_ref[$res[$field_name]] = $res['value']; 4496 // remove vars that have a value in db from the $needed array to avoid affecting them a default value 4497 unset($needed[$res[$field_name]]); 4498 } 4499 4500 // set defaults values if needed and if there is no value in database and if it's default was not null 4501 if ($defaults) { 4502 foreach ($needed as $var => $def) { 4503 if (! is_null($def)) { 4504 $global_ref[$var] = $def; 4505 } 4506 } 4507 } 4508 return true; 4509 } 4510 4511 function clear_cache_user_preferences() 4512 { 4513 global $user_preferences; 4514 unset($user_preferences); 4515 } 4516 4517 /** 4518 * @param $my_user 4519 * @param null $names 4520 * @return bool 4521 */ 4522 function get_user_preferences($my_user, $names = null) 4523 { 4524 global $user_preferences; 4525 4526 // $my_user must be specified 4527 if (! is_string($my_user) || $my_user == '') { 4528 return false; 4529 } 4530 4531 global $user_preferences; 4532 if (! is_array($user_preferences) || ! array_key_exists($my_user, $user_preferences)) { 4533 $user_preferences[$my_user] = []; 4534 } 4535 $global_ref =& $user_preferences[$my_user]; 4536 $return = $this->_get_values('tiki_user_preferences', 'prefName', $names, $global_ref, '`user`=?', [$my_user]); 4537 4538 // Handle special display_timezone values 4539 if (isset($user_preferences[$my_user]['display_timezone']) && $user_preferences[$my_user]['display_timezone'] != 'Site' && $user_preferences[$my_user]['display_timezone'] != 'Local' 4540 && ! TikiDate::TimezoneIsValidId($user_preferences[$my_user]['display_timezone']) 4541 ) { 4542 unset($user_preferences[$my_user]['display_timezone']); 4543 } 4544 return $return; 4545 } 4546 4547 // Returns a boolean indicating whether the specified user (anonymous or not, the current user by default) has the specified preference set 4548 /** 4549 * @param $preference 4550 * @param bool $username 4551 * @return bool 4552 */ 4553 function userHasPreference($preference, $username = false) 4554 { 4555 global $user, $user_preferences; 4556 if ($username === false) { 4557 $username = $user; 4558 } 4559 if ($username) { 4560 if (! isset($user_preferences[$username])) { 4561 $this->get_user_preferences($username); 4562 } 4563 return isset($user_preferences[$username][$preference]); 4564 } else { // If $username is empty, we must be Anonymous looking up one of our own preferences 4565 return isset($_SESSION['preferences'][$preference]); 4566 } 4567 } 4568 4569 /** 4570 * @param $my_user 4571 * @param $name 4572 * @param null $default 4573 * @return null 4574 */ 4575 function get_user_preference($my_user, $name, $default = null) 4576 { 4577 global $user_preferences, $user; 4578 if ($my_user) { 4579 if ($user != $my_user && ! isset($user_preferences[$my_user])) { 4580 $this->get_user_preferences($my_user); 4581 } 4582 if (isset($user_preferences) && isset($user_preferences[$my_user]) && isset($user_preferences[$my_user][$name])) { 4583 return $user_preferences[$my_user][$name]; 4584 } 4585 } else { // If $my_user is empty, we must be Anonymous getting one of our own preferences 4586 if (isset($_SESSION['preferences'][$name])) { 4587 return $_SESSION['preferences'][$name]; 4588 } 4589 } 4590 return $default; 4591 } 4592 4593 /** 4594 * @param $my_user 4595 * @param $name 4596 * @param $value 4597 * 4598 * @return bool|TikiDb_Pdo_Result|TikiDb_Adodb_Result 4599 * @throws Exception 4600 */ 4601 function set_user_preference($my_user, $name, $value) 4602 { 4603 global $user_preferences, $prefs, $user, $user_overrider_prefs; 4604 4605 if ($my_user) { 4606 $cachelib = TikiLib::lib('cache'); 4607 $cachelib->invalidate('user_details_' . $my_user); 4608 4609 if ($name == "realName") { 4610 // attempt to invalidate userlink cache (does not cover all options - only the default) 4611 $cachelib->invalidate('userlink.' . $user . '.' . $my_user . '0'); 4612 $cachelib->invalidate('userlink.' . $my_user . '0'); 4613 } 4614 4615 $userPreferences = $this->table('tiki_user_preferences', false); 4616 $userPreferences->delete(['user' => $my_user, 'prefName' => $name]); 4617 $result = $userPreferences->insert(['user' => $my_user, 'prefName' => $name, 'value' => $value]); 4618 4619 $user_preferences[$my_user][$name] = $value; 4620 4621 if ($my_user == $user) { 4622 $prefs[$name] = $value; 4623 if ($name == 'theme' && $prefs['change_theme'] == 'y') { 4624 $prefs['users_prefs_theme'] = $value; 4625 if ($value == '') { 4626 $userPreferences->delete(['user' => $my_user, 'prefName' => $name]); 4627 } 4628 } elseif ($name == 'theme_option' && $prefs['change_theme'] == 'y') { 4629 $prefs['users_prefs_theme-option'] = $value; 4630 if ($value == '') { 4631 $userPreferences->delete(['user' => $my_user, 'prefName' => $name]); 4632 } 4633 } elseif ($value == '') { 4634 if (in_array($name, $user_overrider_prefs)) { 4635 $prefs[$name] = $prefs['site_' . $name]; 4636 $userPreferences->delete(['user' => $my_user, 'prefName' => $name]); 4637 } 4638 } 4639 return $result; 4640 } 4641 } else { // If $my_user is empty, we must be Anonymous updating one of our own preferences 4642 if ($name == 'theme' && $prefs['change_theme'] == 'y') { 4643 $prefs['theme'] = $value; 4644 $_SESSION['preferences']['theme'] = $value; 4645 if ($value == '') { 4646 unset($_SESSION['preferences']['theme']); 4647 unset($_SESSION['preferences']['theme_option']); 4648 } 4649 } elseif ($name == 'theme_option' && $prefs['change_theme'] == 'y' && ! empty($_SESSION['preferences']['theme'])) { 4650 $prefs['theme_option'] = $value; 4651 $_SESSION['preferences']['theme_option'] = $value; 4652 } elseif ($value == '') { 4653 if (in_array($name, $user_overrider_prefs)) { 4654 $prefs[$name] = $prefs['site_' . $name]; 4655 unset($_SESSION['preferences'][$name]); 4656 } 4657 } else { 4658 $prefs[$name] = $value; 4659 $_SESSION['preferences'][$name] = $value; 4660 } 4661 return true; 4662 } 4663 } 4664 4665 // similar to set_user_preference, but set all at once. 4666 /** 4667 * @param $my_user 4668 * @param $preferences 4669 * @return bool 4670 */ 4671 function set_user_preferences($my_user, &$preferences) 4672 { 4673 global $user_preferences, $prefs, $user; 4674 4675 $cachelib = TikiLib::lib('cache'); 4676 $cachelib->invalidate('user_details_' . $my_user); 4677 4678 $userPreferences = $this->table('tiki_user_preferences', false); 4679 $userPreferences->deleteMultiple(['user' => $my_user]); 4680 4681 foreach ($preferences as $prefName => $value) { 4682 $userPreferences->insert(['user' => $my_user, 'prefName' => $prefName, 'value' => $value]); 4683 } 4684 $user_preferences[$my_user] =& $preferences; 4685 4686 if ($my_user == $user) { 4687 $prefs = array_merge($prefs, $preferences); 4688 $_SESSION['s_prefs'] = array_merge($_SESSION['s_prefs'], $preferences); 4689 } 4690 return true; 4691 } 4692 4693 // This implements all the functions needed to use Tiki 4694 /*shared*/ 4695 // Returns whether a page named $pageName exists. Unless $casesensitive is set to true, the check is case-insensitive. 4696 /** 4697 * @param $pageName 4698 * @param bool $casesensitive 4699 * @return int 4700 */ 4701 function page_exists($pageName, $casesensitive = false) 4702 { 4703 $page_info = $this->get_page_info($pageName, false); 4704 return ( $page_info !== false && ( ! $casesensitive || $page_info['pageName'] == $pageName ) ) ? 1 : 0; 4705 } 4706 4707 /** 4708 * @param $pageName 4709 * @return mixed 4710 */ 4711 function page_exists_desc(&$pageName) 4712 { 4713 4714 $page_info = $this->get_page_info($pageName, false); 4715 4716 return empty($page_info['description']) ? $pageName : $page_info['description']; 4717 } 4718 4719 /** 4720 * @param $pageName 4721 * @return bool|int 4722 */ 4723 function page_exists_modtime($pageName) 4724 { 4725 $page_info = $this->get_page_info($pageName, false); 4726 if ($page_info === false) { 4727 return false; 4728 } 4729 return empty($page_info['lastModif']) ? 0 : $page_info['lastModif']; 4730 } 4731 4732 /** 4733 * @param $pageName 4734 * @return bool 4735 */ 4736 function add_hit($pageName) 4737 { 4738 global $prefs; 4739 if (StatsLib::is_stats_hit()) { 4740 $pages = $this->table('tiki_pages'); 4741 $pages->update(['hits' => $pages->increment(1)], ['pageName' => $pageName]); 4742 } 4743 return true; 4744 } 4745 4746 /** Create a wiki page 4747 @param array $hash- lock_it,contributions, contributors 4748 **/ 4749 function create_page($name, $hits, $data, $lastModif, $comment, $user = 'admin', $ip = '0.0.0.0', $description = '', $lang = '', $is_html = false, $hash = null, $wysiwyg = null, $wiki_authors_style = '', $minor = 0, $created = '') 4750 { 4751 global $prefs, $tracer; 4752 $parserlib = TikiLib::lib('parser'); 4753 4754 $tracer->trace('tikilib.create_page', "** invoked"); 4755 4756 if (! $is_html) { 4757 $data = str_replace('<x>', '', $data); 4758 } 4759 $name = trim($name); // to avoid pb with trailing space http://dev.mysql.com/doc/refman/5.1/en/char.html 4760 4761 if (! $user) { 4762 $user = 'anonymous'; 4763 } 4764 if (empty($wysiwyg)) { 4765 $wysiwyg = $prefs['wysiwyg_default']; 4766 if ($wysiwyg === 'y') { 4767 $is_html = $prefs['wysiwyg_htmltowiki'] !== 'y'; 4768 } 4769 } 4770 // Collect pages before modifying data 4771 $pointedPages = $parserlib->get_pages($data, true); 4772 4773 if (! isset($_SERVER["SERVER_NAME"])) { 4774 $_SERVER["SERVER_NAME"] = $_SERVER["HTTP_HOST"] ?? ''; 4775 } 4776 4777 if ($this->page_exists($name)) { 4778 Feedback::error(tr('TikiLib::create_page: Cannot create page "%0", it already exists.)', $name)); 4779 return false; 4780 } 4781 4782 $tracer->trace('tikilib.create_page', "** TikiLib::lib..."); 4783 $tracer->trace('tikilib.create_page', "** invoking process_save_plugins, \$parserlib=" . get_class($parserlib)); 4784 $data = $parserlib->process_save_plugins( 4785 $data, 4786 [ 4787 'type' => 'wiki page', 4788 'itemId' => $name, 4789 'user' => $user, 4790 ] 4791 ); 4792 4793 $html = $is_html ? 1 : 0; 4794 if ($html && $prefs['feature_purifier'] != 'n') { 4795 $noparsed = []; 4796 $parserlib->plugins_remove($data, $noparsed); 4797 4798 require_once('lib/htmlpurifier_tiki/HTMLPurifier.tiki.php'); 4799 $data = HTMLPurifier($data); 4800 4801 $parserlib->plugins_replace($data, $noparsed, true); 4802 } 4803 4804 $insertData = [ 4805 'pageName' => $name, 4806 'pageSlug' => TikiLib::lib('slugmanager')->generate($prefs['wiki_url_scheme'] ?: 'dash', $name, $prefs['url_only_ascii'] === 'y'), 4807 'hits' => (int) $hits, 4808 'data' => $data, 4809 'description' => $description, 4810 'lastModif' => (int) $lastModif, 4811 'comment' => $comment, 4812 'version' => 1, 4813 'version_minor' => $minor, 4814 'user' => $user, 4815 'ip' => $ip, 4816 'creator' => $user, 4817 'page_size' => strlen($data), 4818 'is_html' => $html, 4819 'created' => empty($created) ? $this->now : $created, 4820 'wysiwyg' => $wysiwyg, 4821 'wiki_authors_style' => $wiki_authors_style, 4822 ]; 4823 if ($lang) { 4824 $insertData['lang'] = $lang; 4825 } 4826 if (! empty($hash['lock_it']) && ($hash['lock_it'] == 'y' || $hash['lock_it'] == 'on')) { 4827 $insertData['flag'] = 'L'; 4828 $insertData['lockedby'] = $user; 4829 } elseif (empty($hash['lock_it']) || $hash['lock_it'] == 'n') { 4830 $insertData['flag'] = ''; 4831 $insertData['lockedby'] = ''; 4832 } 4833 if ($prefs['wiki_comments_allow_per_page'] != 'n') { 4834 if (! empty($hash['comments_enabled']) && $hash['comments_enabled'] == 'y') { 4835 $insertData['comments_enabled'] = 'y'; 4836 } elseif (empty($hash['comments_enabled']) || $hash['comments_enabled'] == 'n') { 4837 $insertData['comments_enabled'] = 'n'; 4838 } 4839 } 4840 if (empty($hash['contributions'])) { 4841 $hash['contributions'] = ''; 4842 } 4843 if (empty($hash['contributors'])) { 4844 $hash2 = ''; 4845 } else { 4846 foreach ($hash['contributors'] as $c) { 4847 $hash3['contributor'] = $c; 4848 $hash2[] = $hash3; 4849 } 4850 } 4851 $pages = $this->table('tiki_pages'); 4852 $page_id = $pages->insert($insertData); 4853 4854 //update status, page storage was updated in tiki 9 to be non html encoded 4855 $wikilib = TikiLib::lib('wiki'); 4856 $converter = new convertToTiki9(); 4857 $converter->saveObjectStatus($page_id, 'tiki_pages'); 4858 4859 $this->replicate_page_to_history($name); 4860 4861 $this->clear_links($name); 4862 4863 // Pages are collected before adding slashes 4864 foreach ($pointedPages as $pointedPage => $types) { 4865 $this->replace_link($name, $pointedPage, $types); 4866 } 4867 4868 $wikilib->update_wikicontent_relations($data, 'wiki page', $name); 4869 4870 // Update the log 4871 if (strtolower($name) != 'sandbox') { 4872 $logslib = TikiLib::lib('logs'); 4873 $logslib->add_action("Created", $name, 'wiki page', 'add=' . strlen($data), $user, '', '', $created, $hash['contributions'], $hash2); 4874 //get_strings tra("Created"); 4875 4876 // Need to categorize new pages before sending mail notifications to make sure category permissions are considered 4877 if (! empty($_REQUEST['cat_categories']) && ! empty($_REQUEST["page"])) { 4878 // these variables are used in categorize.php 4879 $cat_type = 'wiki page'; 4880 $cat_objid = $_REQUEST["page"]; 4881 include_once("categorize.php"); 4882 } 4883 4884 // Deal with mail notifications. 4885 include_once(__DIR__ . '/notifications/notificationemaillib.php'); 4886 4887 $foo = parse_url($_SERVER["REQUEST_URI"] ?? ''); 4888 $machine = self::httpPrefix(true) . dirname($foo["path"]); 4889 sendWikiEmailNotification('wiki_page_created', $name, $user, $comment, 1, $data, $machine, '', false, $hash['contributions']); 4890 if ($prefs['feature_contribution'] == 'y') { 4891 $contributionlib = TikiLib::lib('contribution'); 4892 $contributionlib->assign_contributions($hash['contributions'], $name, 'wiki page', $description, $name, "tiki-index.php?page=" . urlencode($name)); 4893 } 4894 } 4895 4896 //if there are links to this page, clear cache to avoid linking to edition 4897 $toInvalidate = $this->table('tiki_links')->fetchColumn('fromPage', ['toPage' => $name]); 4898 foreach ($toInvalidate as $res) { 4899 $this->invalidate_cache($res); 4900 } 4901 4902 TikiLib::events()->trigger( 4903 'tiki.wiki.create', 4904 [ 4905 'type' => 'wiki page', 4906 'object' => $name, 4907 'namespace' => $wikilib->get_namespace($name), 4908 'user' => $GLOBALS['user'], 4909 'page_id' => $page_id, 4910 'version' => 1, 4911 'data' => $data, 4912 'old_data' => '', 4913 ] 4914 ); 4915 4916 // Update links to the URL of the new page from HTML wiki pages (when wysiwyg is in use). 4917 // This is not an elegant fix but will do for now until the "use wiki syntax in WYSIWYG" feature is ready (if that ever replaces HTML-mode WYSIWYG completely). 4918 if ($prefs['feature_wysiwyg'] == 'y' && $prefs['wysiwyg_htmltowiki'] != 'y') { 4919 $wikilib = TikiLib::lib('wiki'); 4920 $temppage = md5($this->now . $name); 4921 $wikilib->wiki_rename_page($name, $temppage, false, $user); 4922 $wikilib->wiki_rename_page($temppage, $name, false, $user); 4923 } 4924 4925 $tracer->trace('tikilib.create_page', "** Returning"); 4926 4927 return true; 4928 } 4929 4930 /** 4931 * @param $pageName 4932 * @return bool|mixed 4933 */ 4934 protected function replicate_page_to_history($pageName) 4935 { 4936 if (strtolower($pageName) == 'sandbox') { 4937 return false; 4938 } 4939 4940 $query = "INSERT IGNORE INTO `tiki_history`(`pageName`, `version`, `version_minor`, `lastModif`, `user`, `ip`, `comment`, `data`, `description`,`is_html`) 4941 SELECT `pageName`, `version`, `version_minor`, `lastModif`, `user`, `ip`, `comment`, `data`, `description`,`is_html` 4942 FROM tiki_pages 4943 WHERE pageName = ? 4944 LIMIT 1"; 4945 4946 $this->query($query, [$pageName]); 4947 4948 $id = $this->lastInsertId(); 4949 4950 //update status, we don't want the page to be decoded later 4951 $wikilib = TikiLib::lib('wiki'); 4952 $converter = new convertToTiki9(); 4953 $converter->saveObjectStatus($id, 'tiki_history'); 4954 4955 return $id; 4956 } 4957 4958 /** 4959 * @param $pageName 4960 * @return bool|mixed 4961 */ 4962 public function restore_page_from_history($pageName, $version = null) 4963 { 4964 if (strtolower($pageName) == 'sandbox') { 4965 return false; 4966 } 4967 4968 $query = "SELECT `version`, `version_minor`, `lastModif`, `user`, `ip`, `comment`, `data`, `description`,`is_html` 4969 FROM tiki_history 4970 WHERE pageName = ? "; 4971 4972 $bindvars = [$pageName]; 4973 4974 if ($version === null) { 4975 $query .= "ORDER BY version DESC"; 4976 } else { 4977 $query .= "AND `version`=?"; 4978 $bindvars[] = $version; 4979 } 4980 4981 $result = $this->query($query, $bindvars, 1); 4982 if ($res = $result->fetchRow()) { 4983 $query = "UPDATE `tiki_pages` 4984 SET `version` = ?, `version_minor` = ?, `lastModif` = ?, `user` = ?, `ip` = ?, `comment` = ?, `data` = ?, `description` = ?,`is_html` = ? 4985 WHERE pageName = ?"; 4986 $bindvars = [$res['version'], $res['version_minor'], $res['lastModif'], $res['user'], $res['ip'], $res['comment'], $res['data'], $res['description'], $res['is_html'], $pageName]; 4987 $this->query($query, $bindvars); 4988 } 4989 4990 $bindvars = [$pageName]; 4991 $query = "SELECT `page_id` from `tiki_pages` WHERE pageName = ?"; 4992 $this->query($query, $bindvars); 4993 4994 if ($res = $result->fetchRow()) { 4995 $id = $res['version']; 4996 } 4997 4998 // FIXME: Are these lines necessary? If so, what is the proper status to use? 4999 //$converter = new convertToTiki9(); 5000 //$converter->saveObjectStatus($id, 'tiki_pages', 'conv9.0'); 5001 5002 return $id; 5003 } 5004 5005 /** 5006 * @param $user 5007 * @param $max 5008 * @param string $who 5009 * @return mixed 5010 */ 5011 function get_user_pages($user, $max, $who = 'user') 5012 { 5013 return $this->table('tiki_pages')->fetchAll(['pageName'], [$who => $user], $max); 5014 } 5015 5016 /** 5017 * @param $user 5018 * @param $max 5019 * @return array 5020 */ 5021 function get_user_galleries($user, $max) 5022 { 5023 $query = "select `name` ,`galleryId` from `tiki_galleries` where `user`=? order by `name` asc"; 5024 5025 $result = $this->fetchAll($query, [$user], $max); 5026 $ret = []; 5027 5028 foreach ($result as $res) { 5029 //FIXME Perm::filter ? 5030 if ($this->user_has_perm_on_object($user, $res['galleryId'], 'image gallery', 'tiki_p_view_image_gallery')) { 5031 $ret[] = $res; 5032 } 5033 } 5034 return $ret; 5035 } 5036 5037 /** 5038 * @param $pageName 5039 * @return bool 5040 */ 5041 function get_page_print_info($pageName) 5042 { 5043 $query = "SELECT `pageName`, `data` as `parsed`, `is_html` FROM `tiki_pages` WHERE `pageName`=?"; 5044 $result = $this->query($query, [$pageName]); 5045 if (! $result->numRows()) { 5046 return false; 5047 } else { 5048 $page_info = $result->fetchRow(); 5049 $page_info['parsed'] = TikiLib::lib('parser')->parse_data($page_info['parsed'], ['is_html' => $page_info['is_html'], 'print' => 'y', 'page' => $pageName]); 5050 $page_info['h'] = 1; 5051 } 5052 return $page_info; 5053 } 5054 5055 /** 5056 * @param $pageName 5057 * @param bool $retrieve_datas 5058 * @param bool $skipCache 5059 * @return bool 5060 */ 5061 function get_page_info($pageName, $retrieve_datas = true, $skipCache = false) 5062 { 5063 global $prefs; 5064 $pageNameEncode = urlencode($pageName); 5065 if (! $skipCache && isset($this->cache_page_info[$pageNameEncode]) 5066 && ( ! $retrieve_datas || isset($this->cache_page_info[$pageNameEncode]['data']) ) 5067 ) { 5068 return $this->cache_page_info[$pageNameEncode]; 5069 } 5070 5071 if ($retrieve_datas) { 5072 $query = "SELECT * FROM `tiki_pages` WHERE `pageName`=?"; 5073 } else { 5074 $query = "SELECT `page_id`, `pageName`, `hits`, `description`, `lastModif`, `comment`, `version`, `version_minor`, `user`, `ip`, `flag`, `points`, `votes`, `wiki_cache`, `cache_timestamp`, `pageRank`, `creator`, `page_size`, `lang`, `lockedby`, `is_html`, `created`, `wysiwyg`, `wiki_authors_style`, `comments_enabled` FROM `tiki_pages` WHERE `pageName`=?"; 5075 } 5076 $result = $this->query($query, [$pageName]); 5077 5078 if (! $result->numRows()) { 5079 return false; 5080 } else { 5081 $row = $result->fetchRow(); 5082 $row['baseName'] = TikiLib::lib('wiki')->get_without_namespace($row['pageName']); 5083 $row['prettyName'] = TikiLib::lib('wiki')->get_readable($row['pageName']); 5084 $row['namespace'] = TikiLib::lib('wiki')->get_namespace($row['pageName']); 5085 $row['namespace_parts'] = TikiLib::lib('wiki')->get_namespace_parts($row['pageName']); 5086 5087 // Be sure to have the correct character case (because DB is caseinsensitive) 5088 $pageNameEncode = urlencode($row['pageName']); 5089 5090 // Limit memory usage of the page cache. No 5091 // intelligence is attempted here whatsoever. This was 5092 // done because a few thousand ((page)) links would blow 5093 // up memory, even with the limit at 128MiB. 5094 // Information on 128 pages really should be plenty. 5095 while (count($this->cache_page_info) >= 128) { 5096 // Need to delete something; pick at random 5097 $keys = array_keys($this->cache_page_info); 5098 $num = rand(0, count($keys)); 5099 if (isset($keys[$num])) { 5100 unset($this->cache_page_info[$keys[$num]]); 5101 } 5102 } 5103 $row['outputType'] = ''; // TODO remove as redundant? 5104 5105 $this->cache_page_info[$pageNameEncode] = $row; 5106 return $this->cache_page_info[$pageNameEncode]; 5107 } 5108 } 5109 5110 /** 5111 * @param $page_id 5112 * @return mixed 5113 */ 5114 function get_page_info_from_id($page_id) 5115 { 5116 return $this->table('tiki_pages')->fetchFullRow(['page_id' => $page_id]); 5117 } 5118 5119 5120 /** 5121 * @param $page_id 5122 * @return mixed 5123 */ 5124 function get_page_name_from_id($page_id) 5125 { 5126 return $this->table('tiki_pages')->fetchOne('pageName', ['page_id' => $page_id]); 5127 } 5128 5129 /** 5130 * @param $page 5131 * @return mixed 5132 */ 5133 function get_page_id_from_name($page) 5134 { 5135 return $this->table('tiki_pages')->fetchOne('page_id', ['pageName' => $page]); 5136 } 5137 5138 /** 5139 * @param $str 5140 * @param $car 5141 * @return int 5142 */ 5143 static function how_many_at_start($str, $car) 5144 { 5145 $cant = 0; 5146 $i = 0; 5147 while (($i < strlen($str)) && (isset($str{$i})) && ($str{$i} == $car)) { 5148 $i++; 5149 $cant++; 5150 } 5151 return $cant; 5152 } 5153 5154 /** 5155 * @param $name 5156 * @param $domain 5157 * @param string $sep 5158 * @return string 5159 */ 5160 static function protect_email($name, $domain, $sep = '@') 5161 { 5162 TikiLib::lib('header')->add_jq_onready( 5163 '$(".convert-mailto").removeClass("convert-mailto").each(function () { 5164 var address = $(this).data("encode-name") + "@" + $(this).data("encode-domain"); 5165 $(this).attr("href", "mailto:" + address).text(address); 5166 });' 5167 ); 5168 return "<a class=\"convert-mailto\" href=\"mailto:nospam@example.com\" data-encode-name=\"$name\" data-encode-domain=\"$domain\">$name " . tra("at", "", true) . " $domain</a>"; 5169 } 5170 5171 //Updates a dynamic variable found in some object 5172 /*Shared*/ 5173 /** 5174 * @param $name 5175 * @param $value 5176 * @param null $lang 5177 * @return bool 5178 */ 5179 function update_dynamic_variable($name, $value, $lang = null) 5180 { 5181 $dynamicVariables = $this->table('tiki_dynamic_variables'); 5182 $dynamicVariables->delete(['name' => $name, 'lang' => $lang]); 5183 $dynamicVariables->insert(['name' => $name, 'data' => $value, 'lang' => $lang]); 5184 return true; 5185 } 5186 5187 /** 5188 * @param $page 5189 */ 5190 function clear_links($page) 5191 { 5192 $this->table('tiki_links')->deleteMultiple(['fromPage' => $page]); 5193 5194 $objectRelations = $this->table('tiki_object_relations'); 5195 $objectRelations->deleteMultiple( 5196 [ 5197 'source_type' => 'wiki page', 5198 'source_itemId' => $page, 5199 'target_type' => 'wiki page', 5200 'relation' => $objectRelations->like('tiki.link.%'), 5201 ] 5202 ); 5203 } 5204 5205 /** 5206 * @param $pageFrom 5207 * @param $pageTo 5208 * @param array $types 5209 */ 5210 function replace_link($pageFrom, $pageTo, $types = []) 5211 { 5212 global $prefs; 5213 if ($prefs['namespace_enabled'] == 'y' && $prefs['namespace_force_links'] == 'y' 5214 && TikiLib::lib('wiki')->get_namespace($pageFrom) 5215 && ! TikiLib::lib('wiki')->get_namespace($pageTo) ) { 5216 $namespace = TikiLib::lib('wiki')->get_namespace($pageFrom); 5217 $pageTo = $namespace . $prefs['namespace_separator'] . $pageTo; 5218 } 5219 // The max pagename length is 160 characters ( tiki_pages.pageName varchar(160) ). 5220 // However, wiki_rename_page stores a page in the format: $tmpName = "~".$newName."~"; 5221 // So, actual max page name length is 160 - 2 = 158 5222 // Strip excess characters (silently) and proceed. 5223 $pageTo = substr($pageTo, 0, 158); 5224 5225 $links = $this->table('tiki_links'); 5226 $links->insert(['fromPage' => $pageFrom, 'toPage' => $pageTo], true); 5227 5228 $relationlib = TikiLib::lib('relation'); 5229 foreach ($types as $type) { 5230 $relationlib->add_relation("tiki.link.$type", 'wiki page', $pageFrom, 'wiki page', $pageTo); 5231 } 5232 } 5233 5234 /** 5235 * @param $page 5236 */ 5237 function invalidate_cache($page) 5238 { 5239 unset($this->cache_page_info[urlencode($page)]); 5240 $this->table('tiki_pages')->update(['cache_timestamp' => 0], ['pageName' => $page]); 5241 5242 $pageCache = Tiki_PageCache::create() 5243 ->checkMeta('wiki-page-output-meta-timestamp', ['page' => $page ]) 5244 ->invalidate(); 5245 } 5246 5247 /** Update a wiki page 5248 @param array $hash- lock_it,contributions, contributors 5249 @param int $saveLastModif - modification time - pass null for now, unless importing a Wiki page 5250 **/ 5251 function update_page($pageName, $edit_data, $edit_comment, $edit_user, $edit_ip, $edit_description = null, $edit_minor = 0, $lang = '', $is_html = null, $hash = null, $saveLastModif = null, $wysiwyg = '', $wiki_authors_style = '') 5252 { 5253 global $prefs; 5254 $histlib = TikiLib::lib('hist'); 5255 $parserlib = TikiLib::lib('parser'); 5256 5257 if (! $edit_user) { 5258 $edit_user = 'anonymous'; 5259 } 5260 5261 $this->invalidate_cache($pageName); 5262 // Collect pages before modifying edit_data (see update of links below) 5263 $pages = $parserlib->get_pages($edit_data, true); 5264 5265 if (! $this->page_exists($pageName)) { 5266 return false; 5267 } 5268 5269 // Get this page information 5270 $info = $this->get_page_info($pageName); 5271 5272 if ($edit_description === null) { 5273 $edit_description = $info['description']; 5274 } 5275 5276 $user = $info["user"] ? $info["user"] : 'anonymous'; 5277 $data = $info["data"]; 5278 $willDoHistory = ($prefs['feature_wiki_history_full'] == 'y' || $data != $edit_data || $info['description'] != $edit_description || $info["comment"] != $edit_comment ); 5279 $version = $histlib->get_page_next_version($pageName, $willDoHistory); 5280 $old_version = $version - 1; // this doesn't really make sense but is needed to make diff links work properly - regression from r65651 5281 5282 if ($is_html === null) { 5283 $html = $info['is_html']; 5284 } else { 5285 $html = $is_html ? 1 : 0; 5286 } 5287 if ($wysiwyg == '') { 5288 $wysiwyg = $info['wysiwyg']; 5289 } 5290 5291 if ($wysiwyg == 'y' && $html != 1 && $prefs['wysiwyg_htmltowiki'] != 'y') { // correct for html only wysiwyg 5292 $html = 1; 5293 } 5294 5295 $edit_data = $parserlib->process_save_plugins( 5296 $edit_data, 5297 [ 5298 'type' => 'wiki page', 5299 'itemId' => $pageName, 5300 'user' => $user, 5301 ] 5302 ); 5303 5304 if ($html == 1 && $prefs['feature_purifier'] != 'n') { 5305 $noparsed = []; 5306 $parserlib->plugins_remove($edit_data, $noparsed); 5307 5308 require_once('lib/htmlpurifier_tiki/HTMLPurifier.tiki.php'); 5309 $edit_data = HTMLPurifier($edit_data); 5310 5311 $parserlib->plugins_replace($edit_data, $noparsed, true); 5312 } 5313 5314 if (is_null($saveLastModif)) { 5315 $saveLastModif = $this->now; 5316 } 5317 5318 if (empty($lang)) { 5319 $lang = $info['lang']; 5320 } 5321 5322 $queryData = [ 5323 'description' => $edit_description, 5324 'data' => $edit_data, 5325 'comment' => $edit_comment, 5326 'lastModif' => (int) $saveLastModif, 5327 'version' => $version, 5328 'version_minor' => $edit_minor, 5329 'user' => $edit_user, 5330 'ip' => $edit_ip, 5331 'page_size' => strlen($edit_data), 5332 'is_html' => $html, 5333 'wysiwyg' => $wysiwyg, 5334 'wiki_authors_style' => $wiki_authors_style, 5335 'lang' => $lang, 5336 ]; 5337 5338 if ($hash !== null) { 5339 if (! empty($hash['lock_it']) && ($hash['lock_it'] == 'y' || $hash['lock_it'] == 'on')) { 5340 $queryData['flag'] = 'L'; 5341 $queryData['lockedby'] = $user; 5342 } elseif (empty($hash['lock_it']) || $hash['lock_it'] == 'n') { 5343 $queryData['flag'] = ''; 5344 $queryData['lockedby'] = ''; 5345 } 5346 } 5347 if ($prefs['wiki_comments_allow_per_page'] != 'n') { 5348 if (! empty($hash['comments_enabled']) && $hash['comments_enabled'] == 'y') { 5349 $queryData['comments_enabled'] = 'y'; 5350 } elseif (empty($hash['comments_enabled']) || $hash['comments_enabled'] == 'n') { 5351 $queryData['comments_enabled'] = 'n'; 5352 } 5353 } 5354 if (empty($hash['contributions'])) { 5355 $hash['contributions'] = ''; 5356 } 5357 if (empty($hash['contributors'])) { 5358 $hash2 = ''; 5359 } else { 5360 foreach ($hash['contributors'] as $c) { 5361 $hash3['contributor'] = $c; 5362 $hash2[] = $hash3; 5363 } 5364 } 5365 5366 $this->table('tiki_pages')->update($queryData, ['pageName' => $pageName]); 5367 5368 // Synchronize object comment 5369 if ($prefs['feature_wiki_description'] == 'y') { 5370 $query = 'update `tiki_objects` set `description`=? where `itemId`=? and `type`=?'; 5371 $this->query($query, [ $edit_description, $pageName, 'wiki page']); 5372 } 5373 5374 //update status, page storage was updated in tiki 9 to be non html encoded 5375 $wikilib = TikiLib::lib('wiki'); 5376 $converter = new convertToTiki9(); 5377 $converter->saveObjectStatus($this->getOne("SELECT page_id FROM tiki_pages WHERE pageName = ?", [$pageName]), 'tiki_pages'); 5378 5379 // Parse edit_data updating the list of links from this page 5380 $this->clear_links($pageName); 5381 5382 // Pages collected above 5383 foreach ($pages as $page => $types) { 5384 $this->replace_link($pageName, $page, $types); 5385 } 5386 5387 $wikilib->update_wikicontent_relations($edit_data, 'wiki page', $pageName); 5388 5389 if (strtolower($pageName) != 'sandbox' && ! $edit_minor) { 5390 $maxversions = $prefs['maxVersions']; 5391 5392 if ($maxversions && ($nb = $histlib->get_nb_history($pageName)) > $maxversions) { 5393 // Select only versions older than keep_versions days 5394 $keep = $prefs['keep_versions']; 5395 5396 $oktodel = $saveLastModif - ($keep * 24 * 3600) + 1; 5397 5398 $history = $this->table('tiki_history'); 5399 $result = $history->fetchColumn( 5400 'version', 5401 ['pageName' => $pageName,'lastModif' => $history->lesserThan($oktodel)], 5402 $nb - $maxversions, 5403 0, 5404 ['lastModif' => 'ASC'] 5405 ); 5406 foreach ($result as $toRemove) { 5407 $histlib->remove_version($pageName, $toRemove); 5408 } 5409 } 5410 } 5411 5412 // This if no longer checks for minor-ness of the change; sendWikiEmailNotification does that. 5413 if ($willDoHistory) { 5414 $this->replicate_page_to_history($pageName); 5415 if (strtolower($pageName) != 'sandbox') { 5416 if ($prefs['feature_contribution'] == 'y') {// transfer page contributions to the history 5417 $contributionlib = TikiLib::lib('contribution'); 5418 $history = $this->table('tiki_history'); 5419 $historyId = $history->fetchOne($history->max('historyId'), ['pageName' => $pageName, 'version' => (int) $old_version]); 5420 $contributionlib->change_assigned_contributions($pageName, 'wiki page', $historyId, 'history', '', $pageName . '/' . $old_version, "tiki-pagehistory.php?page=$pageName&preview=$old_version"); 5421 } 5422 } 5423 include_once('lib/diff/difflib.php'); 5424 if (strtolower($pageName) != 'sandbox') { 5425 $logslib = TikiLib::lib('logs'); 5426 $bytes = diff2($data, $edit_data, 'bytes'); 5427 $logslib->add_action('Updated', $pageName, 'wiki page', $bytes, $edit_user, $edit_ip, '', $saveLastModif, $hash['contributions'], $hash2); 5428 if ($prefs['feature_contribution'] == 'y') { 5429 $contributionlib = TikiLib::lib('contribution'); 5430 $contributionlib->assign_contributions($hash['contributions'], $pageName, 'wiki page', $edit_description, $pageName, "tiki-index.php?page=" . urlencode($pageName)); 5431 } 5432 } 5433 5434 if ($prefs['feature_multilingual'] == 'y' && $lang) { 5435 // Need to update the translated objects table when an object's language changes. 5436 $this->table('tiki_translated_objects')->update(['lang' => $lang], ['type' => 'wiki page', 'objId' => $info['page_id']]); 5437 } 5438 5439 if ($prefs['wiki_watch_minor'] != 'n' || ! $edit_minor) { 5440 // Deal with mail notifications. 5441 include_once(__DIR__ . '/notifications/notificationemaillib.php'); 5442 $histlib = TikiLib::lib('hist'); 5443 $old = $histlib->get_version($pageName, $old_version); 5444 $foo = parse_url($_SERVER["REQUEST_URI"]); 5445 $machine = self::httpPrefix(true) . dirname($foo["path"]); 5446 $diff = diff2($old["data"], $edit_data, "unidiff"); // TODO: Only compute if we have at least one notification to send 5447 sendWikiEmailNotification('wiki_page_changed', $pageName, $edit_user, $edit_comment, $old_version, $edit_data, $machine, $diff, $edit_minor, $hash['contributions'], 0, 0, $lang); 5448 } 5449 } 5450 5451 $tx = $this->begin(); 5452 5453 TikiLib::events()->trigger( 5454 'tiki.wiki.update', 5455 [ 5456 'type' => 'wiki page', 5457 'object' => $pageName, 5458 'namespace' => $wikilib->get_namespace($pageName), 5459 'reply_action' => 'comment', 5460 'user' => $GLOBALS['user'], 5461 'page_id' => $info['page_id'], 5462 'version' => $version, 5463 'old_version' => $old_version, 5464 'data' => $edit_data, 5465 'old_data' => $info['data'], 5466 'edit_comment' => $edit_comment, 5467 ] 5468 ); 5469 5470 $tx->commit(); 5471 } 5472 5473 /** 5474 * @param $context 5475 * @param $data 5476 */ 5477 function object_post_save($context, $data) 5478 { 5479 global $prefs; 5480 5481 if (is_array($data)) { 5482 if (isset($data['content']) && $prefs['feature_file_galleries'] == 'y') { 5483 $filegallib = TikiLib::lib('filegal'); 5484 $filegallib->syncFileBacklinks($data['content'], $context); 5485 } 5486 5487 if (isset($data['content'])) { 5488 $this->plugin_post_save_actions($context, $data); 5489 } 5490 } else { 5491 if (isset($context['content']) && $prefs['feature_file_galleries'] == 'y') { 5492 $filegallib = TikiLib::lib('filegal'); 5493 $filegallib->syncFileBacklinks($context['content'], $context); 5494 } 5495 5496 $this->plugin_post_save_actions($context); 5497 } 5498 } 5499 5500 /** 5501 * Foreach plugin used in a object content call its save handler, 5502 * if one exist, and send email notifications when it has pending 5503 * status, if preference is enabled. 5504 * 5505 * A plugin save handler is a function defined on the plugin file 5506 * with the following format: wikiplugin_$pluginName_save() 5507 * 5508 * This function is called from object_post_save. Do not call directly. 5509 * 5510 * @param array $context object type and id 5511 * @param array $data 5512 * @return void 5513 */ 5514 private function plugin_post_save_actions($context, $data = null) 5515 { 5516 global $prefs; 5517 $parserlib = TikiLib::lib('parser'); 5518 5519 if (is_null($data)) { 5520 $content = []; 5521 if (isset($context['values'])) { 5522 $content = $context['values']; 5523 } 5524 if (isset($context['data'])) { 5525 $content[] = $context['data']; 5526 } 5527 $data['content'] = implode(' ', $content); 5528 } 5529 5530 $argumentParser = new WikiParser_PluginArgumentParser; 5531 5532 $matches = WikiParser_PluginMatcher::match($data['content']); 5533 5534 foreach ($matches as $match) { 5535 $plugin_name = $match->getName(); 5536 $body = $match->getBody(); 5537 $arguments = $argumentParser->parse($match->getArguments()); 5538 5539 $dummy_output = ''; 5540 if ($parserlib->plugin_enabled($plugin_name, $dummy_output)) { 5541 $status = $parserlib->plugin_can_execute($plugin_name, $body, $arguments, true); 5542 5543 // when plugin status is pending, $status equals plugin fingerprint 5544 if ($prefs['wikipluginprefs_pending_notification'] == 'y' && $status !== true && $status != 'rejected') { 5545 $this->plugin_pending_notification($plugin_name, $body, $arguments, $context); 5546 } 5547 5548 WikiPlugin_Negotiator_Wiki_Alias::findImplementation($plugin_name, $body, $arguments); 5549 5550 $func_name = 'wikiplugin_' . $plugin_name . '_save'; 5551 5552 if (function_exists($func_name)) { 5553 $func_name($context, $body, $arguments); 5554 } 5555 } 5556 } 5557 } 5558 5559 /** 5560 * Send notification by email that a plugin is waiting to be 5561 * approved to everyone with permission to approve it. 5562 * 5563 * @param string $plugin_name 5564 * @param string $body plugin body 5565 * @param array $arguments plugin arguments 5566 * @param array $context object type and id 5567 * @return void 5568 */ 5569 private function plugin_pending_notification($plugin_name, $body, $arguments, $context) 5570 { 5571 require_once('lib/webmail/tikimaillib.php'); 5572 global $prefs, $base_url; 5573 $mail = new TikiMail(null, $prefs['sender_email']); 5574 $mail->setSubject(tr("Plugin %0 pending approval", $plugin_name)); 5575 5576 $smarty = TikiLib::lib('smarty'); 5577 $smarty->assign('plugin_name', $plugin_name); 5578 $smarty->assign('type', $context['type']); 5579 $smarty->assign('objectId', $context['object']); 5580 $smarty->assign('arguments', $arguments); 5581 $smarty->assign('body', $body); 5582 5583 $mail->setHtml(nl2br($smarty->fetch('mail/plugin_pending_notification.tpl'))); 5584 5585 $recipients = $this->plugin_get_email_users_with_perm(); 5586 5587 $mail->setBcc($recipients); 5588 5589 if (! empty($prefs['sender_email'])) { 5590 $mail->send([$prefs['sender_email']]); 5591 } elseif ($admin_email = TikiLib::lib('user')->get_user_email('admin')) { 5592 $recipients = array_diff($recipients, [$admin_email]); 5593 $mail->setBcc($recipients); 5594 $mail->send([$admin_email]); 5595 } 5596 } 5597 5598 /** 5599 * Return a list of e-mails from the users with permission 5600 * to approve a plugin. 5601 * 5602 * @return array 5603 */ 5604 private function plugin_get_email_users_with_perm() 5605 { 5606 global $prefs; 5607 $userlib = TikiLib::lib('user'); 5608 5609 $allGroups = $userlib->get_groups(); 5610 $accessor = Perms::get(); 5611 5612 // list of groups with permission to approve plugin on this object 5613 $groups = []; 5614 5615 foreach ($allGroups['data'] as $group) { 5616 $accessor->setGroups([$group['groupName']]); 5617 if ($accessor->plugin_approve) { 5618 $groups[] = $group['groupName']; 5619 } 5620 } 5621 5622 $recipients = []; 5623 5624 foreach ($groups as $group) { 5625 $recipients = array_merge($recipients, $userlib->get_group_users($group, 0, -1, 'email')); 5626 } 5627 5628 $recipients = array_filter($recipients); 5629 $recipients = array_unique($recipients); 5630 5631 if ($prefs['user_plugin_approval_watch_editor'] === 'y') { 5632 # Do not self-notify, therefore, remove user's email from the list of recipients 5633 $useremail = TikiLib::lib('user')->get_user_email($user); 5634 $recipients = array_diff($recipients, array($useremail)); 5635 } 5636 5637 return $recipients; 5638 } 5639 5640 /** 5641 * @param bool $_user 5642 * @return null|string 5643 */ 5644 function get_display_timezone($_user = false) 5645 { 5646 global $prefs, $user; 5647 5648 if ($_user === false || $_user == $user) { 5649 // If the requested timezone is the current user timezone 5650 $tz = $prefs['display_timezone']; 5651 } elseif ($_user) { 5652 // ... else, get the user timezone preferences from DB 5653 $tz = $this->get_user_preference($_user, 'display_timezone'); 5654 } 5655 if (! TikiDate::TimezoneIsValidId($tz)) { 5656 $tz = $prefs['server_timezone']; 5657 } 5658 if (! TikiDate::TimezoneIsValidId($tz)) { 5659 $tz = 'UTC'; 5660 } 5661 return $tz; 5662 } 5663 5664 function set_display_timezone($user) 5665 { 5666 global $prefs, $user_preferences; 5667 5668 if ($prefs['users_prefs_display_timezone'] == 'Site' || 5669 (isset($user_preferences[$user]['display_timezone']) && $user_preferences[$user]['display_timezone'] == 'Site')) { 5670 // Stay in the time zone of the server 5671 $prefs['display_timezone'] = $prefs['server_timezone']; 5672 } elseif (empty($user_preferences[$user]['display_timezone']) || $user_preferences[$user]['display_timezone'] == 'Local') { 5673 // If the display timezone is not known ... 5674 if (isset($_COOKIE['local_tz'])) { 5675 // ... we try to use the timezone detected by javascript and stored in cookies 5676 if (TikiDate::TimezoneIsValidId($_COOKIE['local_tz'])) { 5677 $prefs['display_timezone'] = $_COOKIE['local_tz']; 5678 } elseif (in_array(strtolower($_COOKIE['local_tz']), TikiDate::getTimezoneAbbreviations())) { // abbreviation like BST or CEST 5679 // timezone_offset in seconds 5680 $prefs['timezone_offset'] = isset($_COOKIE['local_tzoffset']) ? (int) $_COOKIE['local_tzoffset'] * 60 * 60 : -1; 5681 $tzname = timezone_name_from_abbr($_COOKIE['local_tz'], $prefs['timezone_offset']); 5682 5683 if (TikiDate::TimezoneIsValidId($tzname)) { // double check 5684 $prefs['display_timezone'] = $tzname; 5685 } else { 5686 $prefs['display_timezone'] = $_COOKIE['local_tz']; 5687 } 5688 } elseif ($_COOKIE['local_tz'] == 'HAEC') { 5689 // HAEC, returned by Safari on Mac, is not recognized as a DST timezone (with daylightsavings) 5690 // ... So use one equivalent timezone name 5691 $prefs['display_timezone'] = 'Europe/Paris'; 5692 } else { 5693 $prefs['display_timezone'] = $prefs['server_timezone']; 5694 } 5695 } else { 5696 // ... and we fallback to the server timezone if the cookie value is not available 5697 $prefs['display_timezone'] = $prefs['server_timezone']; 5698 } 5699 } 5700 } 5701 5702 function get_long_date_format() 5703 { 5704 global $prefs; 5705 return $prefs['long_date_format']; 5706 } 5707 5708 function get_short_date_format() 5709 { 5710 global $prefs; 5711 return $prefs['short_date_format']; 5712 } 5713 5714 function get_long_time_format() 5715 { 5716 global $prefs; 5717 return $prefs['long_time_format']; 5718 } 5719 5720 function get_short_time_format() 5721 { 5722 global $prefs; 5723 return $prefs['short_time_format']; 5724 } 5725 5726 /** 5727 * @return string 5728 */ 5729 function get_long_datetime_format() 5730 { 5731 static $long_datetime_format = false; 5732 5733 if (! $long_datetime_format) { 5734 $t = trim($this->get_long_time_format()); 5735 if (! empty($t)) { 5736 $t = ' ' . $t; 5737 } 5738 $long_datetime_format = $this->get_long_date_format() . $t; 5739 } 5740 5741 return $long_datetime_format; 5742 } 5743 5744 /** 5745 * @return string 5746 */ 5747 function get_short_datetime_format() 5748 { 5749 static $short_datetime_format = false; 5750 5751 if (! $short_datetime_format) { 5752 $t = trim($this->get_short_time_format()); 5753 if (! empty($t)) { 5754 $t = ' ' . $t; 5755 } 5756 $short_datetime_format = $this->get_short_date_format() . $t; 5757 } 5758 5759 return $short_datetime_format; 5760 } 5761 5762 /** 5763 * @param $format 5764 * @param bool $timestamp 5765 * @param bool $_user 5766 * @param int $input_format 5767 * @return string 5768 */ 5769 static function date_format2($format, $timestamp = false, $_user = false, $input_format = 5/*DATE_FORMAT_UNIXTIME*/) 5770 { 5771 return TikiLib::date_format($format, $timestamp, $_user, $input_format, false); 5772 } 5773 5774 /** 5775 * @param $format 5776 * @param bool $timestamp 5777 * @param bool $_user 5778 * @param int $input_format 5779 * @param bool $is_strftime_format 5780 * @return string 5781 */ 5782 static function date_format($format, $timestamp = false, $_user = false, $input_format = 5/*DATE_FORMAT_UNIXTIME*/, $is_strftime_format = true) 5783 { 5784 $tikilib = TikiLib::lib('tiki'); 5785 static $currentUserDateByFormat = []; 5786 5787 if (! $timestamp) { 5788 $timestamp = $tikilib->now; 5789 } 5790 5791 if ($_user === false && $is_strftime_format && $timestamp == $tikilib->now && isset($currentUserDateByFormat[ $format . $timestamp ])) { 5792 return $currentUserDateByFormat[ $format . $timestamp ]; 5793 } 5794 5795 $tikidate = TikiLib::lib('tikidate'); 5796 try { 5797 $tikidate->setDate($timestamp, 'UTC'); 5798 } catch (Exception $e) { 5799 return $e->getMessage(); 5800 } 5801 5802 $tz = $tikilib->get_display_timezone($_user); 5803 5804 // If user timezone is not also in UTC, convert the date 5805 if ($tz != 'UTC') { 5806 $tikidate->setTZbyID($tz); 5807 } 5808 5809 $return = $tikidate->format($format, $is_strftime_format); 5810 if ($is_strftime_format) { 5811 $currentUserDateByFormat[ $format . $timestamp ] = $return; 5812 } 5813 return $return; 5814 } 5815 5816 /** 5817 * @param $hour 5818 * @param $minute 5819 * @param $second 5820 * @param $month 5821 * @param $day 5822 * @param $year 5823 * @return int 5824 */ 5825 function make_time($hour, $minute, $second, $month, $day, $year) 5826 { 5827 $tikilib = TikiLib::lib('tiki'); 5828 $tikidate = TikiLib::lib('tikidate'); 5829 $display_tz = $tikilib->get_display_timezone(); 5830 if ($display_tz == '') { 5831 $display_tz = 'UTC'; 5832 } 5833 $tikidate->setTZbyID($display_tz); 5834 $tikidate->setLocalTime($day, $month, $year, $hour, $minute, $second, 0); 5835 return $tikidate->getTime(); 5836 } 5837 5838 /** 5839 * @param $timestamp 5840 * @param bool $user 5841 * @return string 5842 */ 5843 function get_long_date($timestamp, $user = false) 5844 { 5845 return $this->date_format($this->get_long_date_format(), $timestamp, $user); 5846 } 5847 5848 /** 5849 * @param $timestamp 5850 * @param bool $user 5851 * @return string 5852 */ 5853 function get_short_date($timestamp, $user = false) 5854 { 5855 return $this->date_format($this->get_short_date_format(), (int) $timestamp, $user); 5856 } 5857 5858 /** 5859 * @param $timestamp 5860 * @param bool $user 5861 * @return string 5862 */ 5863 function get_long_time($timestamp, $user = false) 5864 { 5865 return $this->date_format($this->get_long_time_format(), $timestamp, $user); 5866 } 5867 5868 /** 5869 * @param $timestamp 5870 * @param bool $user 5871 * @return string 5872 */ 5873 function get_short_time($timestamp, $user = false) 5874 { 5875 return $this->date_format($this->get_short_time_format(), $timestamp, $user); 5876 } 5877 5878 /** 5879 * @param $timestamp 5880 * @param bool $user 5881 * @return string 5882 */ 5883 function get_long_datetime($timestamp, $user = false) 5884 { 5885 return $this->date_format($this->get_long_datetime_format(), $timestamp, $user); 5886 } 5887 5888 /** 5889 * @param $timestamp 5890 * @param bool $user 5891 * @return string 5892 */ 5893 function get_short_datetime($timestamp, $user = false) 5894 { 5895 return $this->date_format($this->get_short_datetime_format(), $timestamp, $user); 5896 } 5897 5898 /** 5899 Per http://www.w3.org/TR/NOTE-datetime 5900 */ 5901 function get_iso8601_datetime($timestamp, $user = false) 5902 { 5903 return $this->date_format('%Y-%m-%dT%H:%M:%S%O', $timestamp, $user); 5904 } 5905 5906 /** 5907 * @param $timestamp 5908 * @param bool $user 5909 * @return string 5910 */ 5911 function get_compact_iso8601_datetime($timestamp, $user = false) 5912 { 5913 // no dashes and no tz info - latter should be fixed 5914 return $this->date_format('%Y%m%dT%H%M%S', $timestamp, $user); 5915 } 5916 5917 /** 5918 * @return array of css files in the style dir 5919 */ 5920 function list_styles() 5921 { 5922 global $tikidomain; 5923 $csslib = TikiLib::lib('css'); 5924 5925 $sty = []; 5926 $style_base_path = $this->get_style_path(); // knows about $tikidomain 5927 5928 if ($style_base_path) { 5929 $sty = $csslib->list_css($style_base_path); 5930 } 5931 5932 if ($tikidomain) { 5933 $sty = array_unique(array_merge($sty, $csslib->list_css('styles'))); 5934 } 5935 foreach ($sty as &$s) { 5936 if (in_array($s, ['mobile', '960_gs'])) { 5937 $s = ''; 5938 } elseif (substr($s, -4) == '-rtl' || substr($s, -6) == '-print') { 5939 $s = ''; 5940 } else { 5941 $s .= '.css'; // add the .css back onto the end of the style names 5942 } 5943 } 5944 $sty = array_filter($sty); 5945 sort($sty); 5946 return $sty; 5947 5948 /* What is this $tikidomain section? 5949 * Some files that call this method used to list styles without considering 5950 * $tikidomain, now they do. They're listed below: 5951 * 5952 * tiki-theme_control.php 5953 * tiki-theme_control_objects.php 5954 * tiki-theme_control_sections.php 5955 * tiki-my_tiki.php 5956 * modules/mod-switch_theme.php 5957 * 5958 * lfagundes 5959 * 5960 * Tiki 3.0 - now handled by get_style_path() 5961 * jonnybradley 5962 */ 5963 } 5964 5965 /** 5966 * @param $a_style - main style (e.g. "thenews.css") 5967 * @return array of css files in the style options dir 5968 */ 5969 function list_style_options($a_style = '') 5970 { 5971 global $prefs; 5972 $csslib = TikiLib::lib('css'); 5973 5974 if (empty($a_style)) { 5975 $a_style = $prefs['style']; 5976 } 5977 5978 $sty = []; 5979 $option_base_path = $this->get_style_path($a_style) . 'options/'; 5980 5981 if (is_dir($option_base_path)) { 5982 $sty = $csslib->list_css($option_base_path); 5983 } 5984 5985 if (count($sty)) { 5986 foreach ($sty as &$s) { // add .css back as above 5987 $s .= '.css'; 5988 } 5989 sort($sty); 5990 return $sty; 5991 } else { 5992 return false; 5993 } 5994 } 5995 5996 /** 5997 * @param $stl - main style (e.g. "thenews.css") 5998 * @return string - style passed in up to - | or . char (e.g. "thenews") 5999 */ 6000 function get_style_base($stl) 6001 { 6002 $parts = preg_split('/[\-\.]/', $stl); 6003 if (count($parts) > 0) { 6004 return $parts[0]; 6005 } else { 6006 return ''; 6007 } 6008 } 6009 6010 /** 6011 * @param $stl - main style (e.g. "thenews.css" - can be empty to return main styles dir) 6012 * @param $opt - optional option file name (e.g. "purple.css") 6013 * @param $filename - optional filename to look for (e.g. "purple.png") 6014 * @return path to dir or file if found or empty if not - e.g. "styles/mydomain.tld/thenews/options/purple/" 6015 */ 6016 function get_style_path($stl = '', $opt = '', $filename = '') 6017 { 6018 global $tikidomain; 6019 6020 $path = ''; 6021 $dbase = ''; 6022 if ($tikidomain && is_dir("styles/$tikidomain")) { 6023 $dbase = $tikidomain . '/'; 6024 } 6025 6026 $sbase = ''; 6027 if (! empty($stl)) { 6028 $sbase = $this->get_style_base($stl) . '/'; 6029 } 6030 6031 $obase = ''; 6032 if (! empty($opt)) { 6033 $obase = 'options/'; 6034 if ($opt != $filename) { // exception for getting option.css as it doesn't live in it's own dir 6035 $obase .= substr($opt, 0, strlen($opt) - 4) . '/'; 6036 } 6037 } 6038 6039 if (empty($filename)) { 6040 if (is_dir('styles/' . $dbase . $sbase . $obase)) { 6041 $path = 'styles/' . $dbase . $sbase . $obase; 6042 } elseif (is_dir('styles/' . $dbase . $sbase)) { 6043 $path = 'styles/' . $dbase . $sbase; // try "parent" style dir if no option one 6044 } elseif (is_dir('styles/' . $sbase . $obase)) { 6045 $path = 'styles/' . $sbase . $obase; // try root style dir if no domain one 6046 } else { 6047 $path = 'styles/' . $sbase; // fall back to "parent" style dir if no option one 6048 } 6049 } else { 6050 if (is_file('styles/' . $dbase . $sbase . $obase . $filename)) { 6051 $path = 'styles/' . $dbase . $sbase . $obase . $filename; 6052 } elseif (is_file('styles/' . $dbase . $sbase . $filename)) { // try "parent" style dir if no option one 6053 $path = 'styles/' . $dbase . $sbase . $filename; 6054 } elseif (is_file('styles/' . $sbase . $obase . $filename)) { // try non-tikidomain dirs if not found 6055 $path = 'styles/' . $sbase . $obase . $filename; 6056 } elseif (is_file('styles/' . $sbase . $filename)) { 6057 $path = 'styles/' . $sbase . $filename; // fall back to "parent" style dir if no option 6058 } elseif (is_file('styles/' . $dbase . $filename)) { 6059 $path = 'styles/' . $dbase . $filename; // tikidomain root style dir? 6060 } elseif (is_file('styles/' . $dbase . $filename)) { 6061 $path = 'styles/' . $filename; // root style dir? 6062 } 6063 } 6064 6065 return $path; 6066 } 6067 6068 /** 6069 * @param bool $user 6070 * @return null 6071 */ 6072 function get_language($user = false) 6073 { 6074 global $prefs; 6075 static $language = false; 6076 6077 if (! $language) { 6078 if ($user) { 6079 $language = $this->get_user_preference($user, 'language', 'default'); 6080 if (! $language || $language == 'default') { 6081 $language = $prefs['language']; 6082 } 6083 } else { 6084 $language = $prefs['language']; 6085 } 6086 } 6087 return $language; 6088 } 6089 6090 /** 6091 * @param $text 6092 * @return string 6093 */ 6094 function read_raw($text, $preserve=false) 6095 { 6096 $file = explode("\n", $text); 6097 $back = []; 6098 // When the fieldID is not preserved, ensure uniqueness of the $var key even if the fieldID is duplicated in the input 6099 $i = 0; 6100 foreach ($file as $line) { 6101 $r = $s = ''; 6102 if (substr($line, 0, 1) != "#") { 6103 if (preg_match("/^\[([A-Z0-9]+)\]/", $line, $r)) { 6104 if ($preserve) { 6105 $var = strtolower($r[1]); 6106 } else { 6107 $i++; 6108 $var = 'id' . $i . strtolower($r[1]); 6109 } 6110 } 6111 if (isset($var) and (preg_match("/^([-_\/ a-zA-Z0-9]+)[ \t]+[:=][ \t]+(.*)/", $line, $s))) { 6112 $back[$var][trim($s[1])] = trim($s[2]); 6113 } 6114 } 6115 } 6116 return $back; 6117 } 6118 6119 6120 /** 6121 * Get URL Scheme (http / https) 6122 * Considers the use of a reverse proxy / ssl offloader. I.e If request is https -> ssl offloader -> http tiki, then it will correctly return https 6123 * @return string http | https 6124 */ 6125 static function httpScheme() 6126 { 6127 global $url_scheme; 6128 return $url_scheme; 6129 } 6130 6131 /** 6132 * @param bool $isUserSpecific 6133 * @return string 6134 */ 6135 static function httpPrefix($isUserSpecific = false) 6136 { 6137 global $url_scheme, $url_host, $url_port, $prefs; 6138 6139 if ($isUserSpecific && $prefs['https_login'] != 'disabled' && $prefs['https_external_links_for_users'] == 'y') { 6140 $scheme = 'https'; 6141 } else { 6142 $scheme = $url_scheme; 6143 } 6144 6145 return $scheme . '://' . $url_host . (($url_port != '') ? ":$url_port" : ''); 6146 } 6147 6148 /** 6149 * Includes the full tiki path in the links for external link generation. 6150 * @param string $relative 6151 * @param array $args 6152 * @return string 6153 */ 6154 static function tikiUrl($relative = "", $args = []) 6155 { 6156 global $tikiroot; 6157 6158 if (preg_match('/^http(s?):/', $relative)) { 6159 $base = $relative; 6160 } else { 6161 $base = self::httpPrefix() . $tikiroot . $relative; 6162 } 6163 6164 if (count($args)) { 6165 $base .= '?'; 6166 $base .= http_build_query($args, '', '&'); 6167 } 6168 6169 return $base; 6170 } 6171 6172 /** 6173 * Include the full tiki path if requested in an external context. 6174 * Otherwise, leave as-is. 6175 * 6176 * @param string $relative 6177 * @param array $args 6178 * @return string 6179 */ 6180 static function tikiUrlOpt($relative) 6181 { 6182 if (self::$isExternalContext) { 6183 return self::tikiUrl($relative); 6184 } else { 6185 return $relative; 6186 } 6187 } 6188 6189 static function setExternalContext($isExternal) 6190 { 6191 $oldValue = self::$isExternalContext; 6192 6193 self::$isExternalContext = (bool) $isExternal; 6194 6195 return $oldValue; 6196 } 6197 6198 static function contextualizeKey($key, $param1 = null, $param2 = null) 6199 { 6200 global $prefs; 6201 6202 $args = func_get_args(); 6203 array_shift($args); 6204 6205 foreach ($args as $arg) { 6206 if ($arg == 'language') { 6207 $language = isset($prefs['language']) ? $prefs['language'] : 'en'; 6208 $key .= "_{$language}"; 6209 } elseif ($arg == 'external') { 6210 $key .= (int) self::$isExternalContext; 6211 } 6212 } 6213 6214 return $key; 6215 } 6216 6217 /** 6218 * Removes the protocol, host and path from a URL if they match 6219 * 6220 * @param string $url URL to be converted 6221 * @return string relative URL if possible 6222 */ 6223 static function makeAbsoluteLinkRelative($url) 6224 { 6225 global $base_url; 6226 6227 if (strpos($url, $base_url) !== false) { 6228 $out = substr($url, strlen($base_url)); 6229 } else { 6230 $out = $url; 6231 } 6232 return $out; 6233 } 6234 6235 /** 6236 * @param $lat1 6237 * @param $lon1 6238 * @param $lat2 6239 * @param $lon2 6240 * @return int 6241 */ 6242 function distance($lat1, $lon1, $lat2, $lon2) 6243 { 6244 // This function uses a pure spherical model 6245 // it could be improved to use the WGS84 Datum 6246 // Franck Martin 6247 $lat1rad = deg2rad($lat1); 6248 $lon1rad = deg2rad($lon1); 6249 $lat2rad = deg2rad($lat2); 6250 $lon2rad = deg2rad($lon2); 6251 $distance = 6367 * acos(sin($lat1rad) * sin($lat2rad) + cos($lat1rad) * cos($lat2rad) * cos($lon1rad - $lon2rad)); 6252 return($distance); 6253 } 6254 6255 /** 6256 * returns a list of usergroups where the user is a member and the group has the right perm 6257 * sir-b 6258 **/ 6259 function get_groups_to_user_with_permissions($user, $perm) 6260 { 6261 $userid = $this->get_user_id($user); 6262 $query = "SELECT DISTINCT `users_usergroups`.`groupName` AS `groupName`"; 6263 $query .= "FROM `users_grouppermissions`, `users_usergroups` "; 6264 $query .= "WHERE `users_usergroups`.`userId` = ? AND "; 6265 $query .= "`users_grouppermissions`.`groupName` = `users_usergroups`.`groupName` AND "; 6266 $query .= "`users_grouppermissions`.`permName` = ? "; 6267 $query .= "ORDER BY `groupName`"; 6268 return $this->fetchAll($query, [(int)$userid, $perm]); 6269 } 6270 6271 /** 6272 * @param $tab 6273 * @param $valField1 6274 * @param $field1 6275 * @param $field2 6276 * @return mixed 6277 */ 6278 function other_value_in_tab_line($tab, $valField1, $field1, $field2) 6279 { 6280 foreach ($tab as $line) { 6281 if ($line[$field1] == $valField1) { 6282 return $line[$field2]; 6283 } 6284 } 6285 } 6286 6287 /** 6288 * @param $file_name 6289 * @return string 6290 */ 6291 function get_attach_hash_file_name($file_name) 6292 { 6293 global $prefs; 6294 do { 6295 $fhash = md5($file_name . date('U') . rand()); 6296 } while (file_exists($prefs['w_use_dir'] . $fhash)); 6297 return $fhash; 6298 } 6299 6300 /** 6301 * @param $file_name 6302 * @param $file_tmp_name 6303 * @param $store_type 6304 * @return array 6305 */ 6306 function attach_file($file_name, $file_tmp_name, $store_type) 6307 { 6308 global $prefs; 6309 $tmp_dest = $prefs['tmpDir'] . "/" . $file_name . ".tmp"; 6310 if (! move_uploaded_file($file_tmp_name, $tmp_dest)) { 6311 return ["ok" => false, "error" => tra('Errors detected')]; 6312 } 6313 try { 6314 $filegallib = TikiLib::lib('filegal'); 6315 $filegallib->assertUploadedFileIsSafe($tmp_dest, $file_name); 6316 } catch (Exception $e) { 6317 return ['ok' => false, 'error' => $e->getMessage()]; 6318 } 6319 $fp = fopen($tmp_dest, "rb"); 6320 $data = ''; 6321 $fhash = ''; 6322 $chunk = ''; 6323 if ($store_type == 'dir') { 6324 $fhash = $this->get_attach_hash_file_name($file_name); 6325 $fw = fopen($prefs['w_use_dir'] . $fhash, "wb"); 6326 if (! $fw) { 6327 return ["ok" => false, "error" => tra('Cannot write to this file:') . $prefs['w_use_dir'] . $fhash]; 6328 } 6329 } 6330 while (! feof($fp)) { 6331 $chunk = fread($fp, 8192 * 16); 6332 6333 if ($store_type == 'dir') { 6334 fwrite($fw, $chunk); 6335 } 6336 $data .= $chunk; 6337 } 6338 fclose($fp); 6339 unlink($tmp_dest); 6340 if ($store_type == 'dir') { 6341 fclose($fw); 6342 $data = ""; 6343 } 6344 return ["ok" => true, "data" => $data, "fhash" => $fhash]; 6345 } 6346 6347 /* to get the length of a data without the quoted part (very 6348 approximative) */ 6349 /** 6350 * @param $data 6351 * @return int 6352 */ 6353 function strlen_quoted($data) 6354 { 6355 global $prefs; 6356 if ($prefs['feature_use_quoteplugin'] != 'y') { 6357 $data = preg_replace('/^>.*\\n?/m', '', $data); 6358 } else { 6359 $data = preg_replace('/{QUOTE\([^\)]*\)}.*{QUOTE}/Ui', '', $data); 6360 } 6361 return strlen($data); 6362 } 6363 6364 /** 6365 * @param $id 6366 * @param int $offset 6367 * @param $maxRecords 6368 * @param string $sort_mode 6369 * @param string $find 6370 * @param string $table 6371 * @param string $column 6372 * @param string $from 6373 * @param string $to 6374 * @return array 6375 */ 6376 function list_votes($id, $offset = 0, $maxRecords = -1, $sort_mode = 'user_asc', $find = '', $table = '', $column = '', $from = '', $to = '') 6377 { 6378 $mid = 'where `id`=?'; 6379 $bindvars[] = $id; 6380 $select = ''; 6381 $join = ''; 6382 if (! empty($find)) { 6383 $mid .= ' and (`user` like ? or `title` like ? or `ip` like ?)'; 6384 $bindvars[] = '%' . $find . '%'; 6385 $bindvars[] = '%' . $find . '%'; 6386 $bindvars[] = '%' . $find . '%'; 6387 } 6388 if (! empty($from) && ! empty($to)) { 6389 $mid .= ' and ((time >= ? and time <= ?) or time = ?)'; 6390 $bindvars[] = $from; 6391 $bindvars[] = $to; 6392 $bindvars[] = 0; 6393 } 6394 if (! empty($table) && ! empty($column)) { 6395 $select = ", `$table`.`$column` as title"; 6396 $join = "left join `$table` on (`tiki_user_votings`.`optionId` = `$table`.`optionId`)"; 6397 } 6398 $query = "select * $select from `tiki_user_votings` $join $mid order by " . $this->convertSortMode($sort_mode); 6399 $query_cant = "select count(*) from `tiki_user_votings` $join $mid"; 6400 $ret = $this->fetchAll($query, $bindvars, $maxRecords, $offset); 6401 $cant = $this->getOne($query_cant, $bindvars); 6402 $retval = []; 6403 $retval["data"] = $ret; 6404 $retval["cant"] = $cant; 6405 return $retval; 6406 } 6407 6408 /** 6409 * Returns explicit message on upload problem 6410 * 6411 * @params: $iError: php status of the file uploading (documented in http://uk2.php.net/manual/en/features.file-upload.errors.php ) 6412 * 6413 */ 6414 function uploaded_file_error($iError) 6415 { 6416 switch ($iError) { 6417 case UPLOAD_ERR_OK: 6418 return tra('The file was successfully uploaded.'); 6419 case UPLOAD_ERR_INI_SIZE: 6420 return tra('The uploaded file exceeds the upload_max_filesize directive in php.ini.'); 6421 case UPLOAD_ERR_FORM_SIZE: 6422 return tra('The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form.'); 6423 case UPLOAD_ERR_PARTIAL: 6424 return tra('The file was only partially uploaded.'); 6425 case UPLOAD_ERR_NO_FILE: 6426 return tra('No file was uploaded. Was a file selected ?'); 6427 case UPLOAD_ERR_NO_TMP_DIR: 6428 return tra('A temporary folder is missing.'); 6429 case UPLOAD_ERR_CANT_WRITE: 6430 return tra('Failed to write file to disk.'); 6431 case UPLOAD_ERR_EXTENSION: 6432 return tra('File upload stopped by extension.'); 6433 6434 default: 6435 return tra('Unknown error.'); 6436 } 6437 } 6438 6439 // from PHP manual (ini-get function example) 6440 /** 6441 * @param string $val php.ini key returning memory string i.e. 32M 6442 * @return int size in bytes 6443 */ 6444 function return_bytes($val) 6445 { 6446 $val = trim($val); 6447 $bytes = (int) $val; 6448 $lastCharacter = strtolower($val{strlen($val) - 1}); 6449 $units = ['k' => 1, 'm' => 2, 'g' => 3]; 6450 if (array_key_exists($lastCharacter, $units)) { 6451 $bytes = $bytes * (1024 ** $units[$lastCharacter]); 6452 } 6453 return $bytes; 6454 } 6455 6456 /** 6457 * @return int bytes of memory available for PHP 6458 */ 6459 function get_memory_avail() 6460 { 6461 return $this->get_memory_limit() - memory_get_usage(true); 6462 } 6463 6464 /** 6465 * @return int 6466 */ 6467 function get_memory_limit() 6468 { 6469 return $this->return_bytes(ini_get('memory_limit')); 6470 } 6471 6472 /** 6473 * @param bool $with_names 6474 * @param bool $translate 6475 * @param bool $sort_names 6476 * @return array|mixed 6477 */ 6478 function get_flags($with_names = false, $translate = false, $sort_names = false, $langsort = false) 6479 { 6480 global $prefs; 6481 6482 $cachelib = TikiLib::lib('cache'); 6483 $args = func_get_args(); 6484 $cacheKey = serialize($args) . $prefs['language']; 6485 6486 if ($data = $cachelib->getSerialized($cacheKey, 'flags')) { 6487 return $data; 6488 } 6489 6490 $flags = []; 6491 $h = opendir("img/flags/"); 6492 while ($file = readdir($h)) { 6493 if (strstr($file, ".png")) { 6494 $parts = explode('.', $file); 6495 $flags[] = $parts[0]; 6496 } 6497 } 6498 closedir($h); 6499 if ($langsort) { 6500 foreach ($flags as $flagname => $flagtra) { 6501 unset($flags[$flagname]); 6502 $flags[$flagtra] = tra($flagtra); 6503 } 6504 } 6505 natcasesort($flags); 6506 6507 if ($with_names) { 6508 $ret = []; 6509 $names = []; 6510 foreach ($flags as $f) { 6511 $ret[$f] = strtr($f, '_', ' '); 6512 if ($translate) { 6513 $ret[$f] = tra($ret[$f]); 6514 } 6515 if ($sort_names) { 6516 $names[$f] = strtolower($this->take_away_accent($ret[$f])); 6517 } 6518 } 6519 if ($sort_names) { 6520 array_multisort($names, $ret); 6521 } 6522 6523 $flags = $ret; 6524 } 6525 6526 $cachelib->cacheItem($cacheKey, serialize($flags), 'flags'); 6527 6528 return $flags; 6529 } 6530 6531 6532 /** 6533 * @param {string} $data 6534 * @return string 6535 */ 6536 function strip_tags($data) 6537 { 6538 $result = preg_replace('/[<]style[^>]*?[>](.|\n|\r)*?[<][\/]style[>]/', '', $data); 6539 $result = strip_tags($result); 6540 return $result; 6541 } 6542 /** 6543 * @param $data 6544 * @param string $outputType 6545 * @param boolean $is_html 6546 * @param string $highlight 6547 * @param int $length 6548 * @param string $start 6549 * @param string $end 6550 * @return string 6551 */ 6552 function get_snippet($data, $outputType = '', $is_html = false, $highlight = '', $length = 240, $start = '', $end = '') 6553 { 6554 global $prefs; 6555 if ($prefs['search_parsed_snippet'] == 'y') { 6556 $data = preg_replace('/{(:?make)?toc[^}]*}/', '', $data); 6557 6558 $_REQUEST['redirectpage'] = 'y'; //do not interpret redirect 6559 $data = TikiLib::lib('parser')->parse_data($data, ['is_html' => $is_html, 'stripplugins' => true, 'parsetoc' => true]); 6560 } 6561 6562 6563 $data = strip_tags($data); 6564 if ($length > 0) { 6565 if (function_exists('mb_substr')) { 6566 return mb_substr($data, 0, $length); 6567 } else { 6568 return substr($data, 0, $length); 6569 } 6570 } 6571 if (! empty($start) && ($i = strpos($data, $start))) { 6572 $data = substr($data, $i + strlen($start)); 6573 } 6574 if (! empty($end) && ($i = strpos($data, $end))) { 6575 $data = substr($data, 0, $i); 6576 } 6577 return $data; 6578 } 6579 6580 /** 6581 * @param $string 6582 * @param int $quote_style 6583 * @param int $translation_table 6584 * @return string 6585 */ 6586 static function htmldecode($string, $quote_style = ENT_COMPAT, $translation_table = HTML_ENTITIES) 6587 { 6588 if ($translation_table == HTML_ENTITIES) { 6589 $string = html_entity_decode($string, $quote_style, 'utf-8'); 6590 } elseif ($translation_table === HTML_SPECIALCHARS) { 6591 $string = htmlspecialchars_decode($string, $quote_style); 6592 } 6593 6594 return $string; 6595 } 6596 6597 /** 6598 * * Unaccent the input string string. An example string like `ÀØėÿᾜὨζὅБю` 6599 * will be translated to `AOeyIOzoBY` 6600 * @param $str 6601 * @return string unaccented string 6602 */ 6603 static function take_away_accent($str) 6604 { 6605 $transliteration = array( 6606 'IJ' => 'I', 'Ö' => 'O','Œ' => 'O','Ü' => 'U','ä' => 'a','æ' => 'a', 6607 'ij' => 'i','ö' => 'o','œ' => 'o','ü' => 'u','ß' => 's','ſ' => 's', 6608 'À' => 'A','Á' => 'A','Â' => 'A','Ã' => 'A','Ä' => 'A','Å' => 'A', 6609 'Æ' => 'A','Ā' => 'A','Ą' => 'A','Ă' => 'A','Ç' => 'C','Ć' => 'C', 6610 'Č' => 'C','Ĉ' => 'C','Ċ' => 'C','Ď' => 'D','Đ' => 'D','È' => 'E', 6611 'É' => 'E','Ê' => 'E','Ë' => 'E','Ē' => 'E','Ę' => 'E','Ě' => 'E', 6612 'Ĕ' => 'E','Ė' => 'E','Ĝ' => 'G','Ğ' => 'G','Ġ' => 'G','Ģ' => 'G', 6613 'Ĥ' => 'H','Ħ' => 'H','Ì' => 'I','Í' => 'I','Î' => 'I','Ï' => 'I', 6614 'Ī' => 'I','Ĩ' => 'I','Ĭ' => 'I','Į' => 'I','İ' => 'I','Ĵ' => 'J', 6615 'Ķ' => 'K','Ľ' => 'K','Ĺ' => 'K','Ļ' => 'K','Ŀ' => 'K','Ł' => 'L', 6616 'Ñ' => 'N','Ń' => 'N','Ň' => 'N','Ņ' => 'N','Ŋ' => 'N','Ò' => 'O', 6617 'Ó' => 'O','Ô' => 'O','Õ' => 'O','Ø' => 'O','Ō' => 'O','Ő' => 'O', 6618 'Ŏ' => 'O','Ŕ' => 'R','Ř' => 'R','Ŗ' => 'R','Ś' => 'S','Ş' => 'S', 6619 'Ŝ' => 'S','Ș' => 'S','Š' => 'S','Ť' => 'T','Ţ' => 'T','Ŧ' => 'T', 6620 'Ț' => 'T','Ù' => 'U','Ú' => 'U','Û' => 'U','Ū' => 'U','Ů' => 'U', 6621 'Ű' => 'U','Ŭ' => 'U','Ũ' => 'U','Ų' => 'U','Ŵ' => 'W','Ŷ' => 'Y', 6622 'Ÿ' => 'Y','Ý' => 'Y','Ź' => 'Z','Ż' => 'Z','Ž' => 'Z','à' => 'a', 6623 'á' => 'a','â' => 'a','ã' => 'a','ā' => 'a','ą' => 'a','ă' => 'a', 6624 'å' => 'a','ç' => 'c','ć' => 'c','č' => 'c','ĉ' => 'c','ċ' => 'c', 6625 'ď' => 'd','đ' => 'd','è' => 'e','é' => 'e','ê' => 'e','ë' => 'e', 6626 'ē' => 'e','ę' => 'e','ě' => 'e','ĕ' => 'e','ė' => 'e','ƒ' => 'f', 6627 'ĝ' => 'g','ğ' => 'g','ġ' => 'g','ģ' => 'g','ĥ' => 'h','ħ' => 'h', 6628 'ì' => 'i','í' => 'i','î' => 'i','ï' => 'i','ī' => 'i','ĩ' => 'i', 6629 'ĭ' => 'i','į' => 'i','ı' => 'i','ĵ' => 'j','ķ' => 'k','ĸ' => 'k', 6630 'ł' => 'l','ľ' => 'l','ĺ' => 'l','ļ' => 'l','ŀ' => 'l','ñ' => 'n', 6631 'ń' => 'n','ň' => 'n','ņ' => 'n','ʼn' => 'n','ŋ' => 'n','ò' => 'o', 6632 'ó' => 'o','ô' => 'o','õ' => 'o','ø' => 'o','ō' => 'o','ő' => 'o', 6633 'ŏ' => 'o','ŕ' => 'r','ř' => 'r','ŗ' => 'r','ś' => 's','š' => 's', 6634 'ť' => 't','ù' => 'u','ú' => 'u','û' => 'u','ū' => 'u','ů' => 'u', 6635 'ű' => 'u','ŭ' => 'u','ũ' => 'u','ų' => 'u','ŵ' => 'w','ÿ' => 'y', 6636 'ý' => 'y','ŷ' => 'y','ż' => 'z','ź' => 'z','ž' => 'z','Α' => 'A', 6637 'Ά' => 'A','Ἀ' => 'A','Ἁ' => 'A','Ἂ' => 'A','Ἃ' => 'A','Ἄ' => 'A', 6638 'Ἅ' => 'A','Ἆ' => 'A','Ἇ' => 'A','ᾈ' => 'A','ᾉ' => 'A','ᾊ' => 'A', 6639 'ᾋ' => 'A','ᾌ' => 'A','ᾍ' => 'A','ᾎ' => 'A','ᾏ' => 'A','Ᾰ' => 'A', 6640 'Ᾱ' => 'A','Ὰ' => 'A','ᾼ' => 'A','Β' => 'B','Γ' => 'G','Δ' => 'D', 6641 'Ε' => 'E','Έ' => 'E','Ἐ' => 'E','Ἑ' => 'E','Ἒ' => 'E','Ἓ' => 'E', 6642 'Ἔ' => 'E','Ἕ' => 'E','Ὲ' => 'E','Ζ' => 'Z','Η' => 'I','Ή' => 'I', 6643 'Ἠ' => 'I','Ἡ' => 'I','Ἢ' => 'I','Ἣ' => 'I','Ἤ' => 'I','Ἥ' => 'I', 6644 'Ἦ' => 'I','Ἧ' => 'I','ᾘ' => 'I','ᾙ' => 'I','ᾚ' => 'I','ᾛ' => 'I', 6645 'ᾜ' => 'I','ᾝ' => 'I','ᾞ' => 'I','ᾟ' => 'I','Ὴ' => 'I','ῌ' => 'I', 6646 'Θ' => 'T','Ι' => 'I','Ί' => 'I','Ϊ' => 'I','Ἰ' => 'I','Ἱ' => 'I', 6647 'Ἲ' => 'I','Ἳ' => 'I','Ἴ' => 'I','Ἵ' => 'I','Ἶ' => 'I','Ἷ' => 'I', 6648 'Ῐ' => 'I','Ῑ' => 'I','Ὶ' => 'I','Κ' => 'K','Λ' => 'L','Μ' => 'M', 6649 'Ν' => 'N','Ξ' => 'K','Ο' => 'O','Ό' => 'O','Ὀ' => 'O','Ὁ' => 'O', 6650 'Ὂ' => 'O','Ὃ' => 'O','Ὄ' => 'O','Ὅ' => 'O','Ὸ' => 'O','Π' => 'P', 6651 'Ρ' => 'R','Ῥ' => 'R','Σ' => 'S','Τ' => 'T','Υ' => 'Y','Ύ' => 'Y', 6652 'Ϋ' => 'Y','Ὑ' => 'Y','Ὓ' => 'Y','Ὕ' => 'Y','Ὗ' => 'Y','Ῠ' => 'Y', 6653 'Ῡ' => 'Y','Ὺ' => 'Y','Φ' => 'F','Χ' => 'X','Ψ' => 'P','Ω' => 'O', 6654 'Ώ' => 'O','Ὠ' => 'O','Ὡ' => 'O','Ὢ' => 'O','Ὣ' => 'O','Ὤ' => 'O', 6655 'Ὥ' => 'O','Ὦ' => 'O','Ὧ' => 'O','ᾨ' => 'O','ᾩ' => 'O','ᾪ' => 'O', 6656 'ᾫ' => 'O','ᾬ' => 'O','ᾭ' => 'O','ᾮ' => 'O','ᾯ' => 'O','Ὼ' => 'O', 6657 'ῼ' => 'O','α' => 'a','ά' => 'a','ἀ' => 'a','ἁ' => 'a','ἂ' => 'a', 6658 'ἃ' => 'a','ἄ' => 'a','ἅ' => 'a','ἆ' => 'a','ἇ' => 'a','ᾀ' => 'a', 6659 'ᾁ' => 'a','ᾂ' => 'a','ᾃ' => 'a','ᾄ' => 'a','ᾅ' => 'a','ᾆ' => 'a', 6660 'ᾇ' => 'a','ὰ' => 'a','ᾰ' => 'a','ᾱ' => 'a','ᾲ' => 'a','ᾳ' => 'a', 6661 'ᾴ' => 'a','ᾶ' => 'a','ᾷ' => 'a','β' => 'b','γ' => 'g','δ' => 'd', 6662 'ε' => 'e','έ' => 'e','ἐ' => 'e','ἑ' => 'e','ἒ' => 'e','ἓ' => 'e', 6663 'ἔ' => 'e','ἕ' => 'e','ὲ' => 'e','ζ' => 'z','η' => 'i','ή' => 'i', 6664 'ἠ' => 'i','ἡ' => 'i','ἢ' => 'i','ἣ' => 'i','ἤ' => 'i','ἥ' => 'i', 6665 'ἦ' => 'i','ἧ' => 'i','ᾐ' => 'i','ᾑ' => 'i','ᾒ' => 'i','ᾓ' => 'i', 6666 'ᾔ' => 'i','ᾕ' => 'i','ᾖ' => 'i','ᾗ' => 'i','ὴ' => 'i','ῂ' => 'i', 6667 'ῃ' => 'i','ῄ' => 'i','ῆ' => 'i','ῇ' => 'i','θ' => 't','ι' => 'i', 6668 'ί' => 'i','ϊ' => 'i','ΐ' => 'i','ἰ' => 'i','ἱ' => 'i','ἲ' => 'i', 6669 'ἳ' => 'i','ἴ' => 'i','ἵ' => 'i','ἶ' => 'i','ἷ' => 'i','ὶ' => 'i', 6670 'ῐ' => 'i','ῑ' => 'i','ῒ' => 'i','ῖ' => 'i','ῗ' => 'i','κ' => 'k', 6671 'λ' => 'l','μ' => 'm','ν' => 'n','ξ' => 'k','ο' => 'o','ό' => 'o', 6672 'ὀ' => 'o','ὁ' => 'o','ὂ' => 'o','ὃ' => 'o','ὄ' => 'o','ὅ' => 'o', 6673 'ὸ' => 'o','π' => 'p','ρ' => 'r','ῤ' => 'r','ῥ' => 'r','σ' => 's', 6674 'ς' => 's','τ' => 't','υ' => 'y','ύ' => 'y','ϋ' => 'y','ΰ' => 'y', 6675 'ὐ' => 'y','ὑ' => 'y','ὒ' => 'y','ὓ' => 'y','ὔ' => 'y','ὕ' => 'y', 6676 'ὖ' => 'y','ὗ' => 'y','ὺ' => 'y','ῠ' => 'y','ῡ' => 'y','ῢ' => 'y', 6677 'ῦ' => 'y','ῧ' => 'y','φ' => 'f','χ' => 'x','ψ' => 'p','ω' => 'o', 6678 'ώ' => 'o','ὠ' => 'o','ὡ' => 'o','ὢ' => 'o','ὣ' => 'o','ὤ' => 'o', 6679 'ὥ' => 'o','ὦ' => 'o','ὧ' => 'o','ᾠ' => 'o','ᾡ' => 'o','ᾢ' => 'o', 6680 'ᾣ' => 'o','ᾤ' => 'o','ᾥ' => 'o','ᾦ' => 'o','ᾧ' => 'o','ὼ' => 'o', 6681 'ῲ' => 'o','ῳ' => 'o','ῴ' => 'o','ῶ' => 'o','ῷ' => 'o','А' => 'A', 6682 'Б' => 'B','В' => 'V','Г' => 'G','Д' => 'D','Е' => 'E','Ё' => 'E', 6683 'Ж' => 'Z','З' => 'Z','И' => 'I','Й' => 'I','К' => 'K','Л' => 'L', 6684 'М' => 'M','Н' => 'N','О' => 'O','П' => 'P','Р' => 'R','С' => 'S', 6685 'Т' => 'T','У' => 'U','Ф' => 'F','Х' => 'K','Ц' => 'T','Ч' => 'C', 6686 'Ш' => 'S','Щ' => 'S','Ы' => 'Y','Э' => 'E','Ю' => 'Y','Я' => 'Y', 6687 'а' => 'A','б' => 'B','в' => 'V','г' => 'G','д' => 'D','е' => 'E', 6688 'ё' => 'E','ж' => 'Z','з' => 'Z','и' => 'I','й' => 'I','к' => 'K', 6689 'л' => 'L','м' => 'M','н' => 'N','о' => 'O','п' => 'P','р' => 'R', 6690 'с' => 'S','т' => 'T','у' => 'U','ф' => 'F','х' => 'K','ц' => 'T', 6691 'ч' => 'C','ш' => 'S','щ' => 'S','ы' => 'Y','э' => 'E','ю' => 'Y', 6692 'я' => 'Y','ð' => 'd','Ð' => 'D','þ' => 't','Þ' => 'T','ა' => 'a', 6693 'ბ' => 'b','გ' => 'g','დ' => 'd','ე' => 'e','ვ' => 'v','ზ' => 'z', 6694 'თ' => 't','ი' => 'i','კ' => 'k','ლ' => 'l','მ' => 'm','ნ' => 'n', 6695 'ო' => 'o','პ' => 'p','ჟ' => 'z','რ' => 'r','ს' => 's','ტ' => 't', 6696 'უ' => 'u','ფ' => 'p','ქ' => 'k','ღ' => 'g','ყ' => 'q','შ' => 's', 6697 'ჩ' => 'c','ც' => 't','ძ' => 'd','წ' => 't','ჭ' => 'c','ხ' => 'k', 6698 'ჯ' => 'j','ჰ' => 'h' 6699 ); 6700 $str = str_replace( array_keys( $transliteration ),array_values( $transliteration ),$str); 6701 return $str; 6702 } 6703 6704 /** 6705 * @param $str 6706 * @return mixed 6707 */ 6708 static function substituteSeparators($str) 6709 { 6710 $subst = explode(' ', '+ \' : ;'); 6711 $convs = explode(' ', '_ _ _ _'); 6712 $ret = str_replace($subst, $convs, $str); 6713 $ret = str_replace(' ', '_', $ret); 6714 return $ret; 6715 } 6716 6717 /** 6718 * @param $str 6719 * @return mixed 6720 */ 6721 function urlencode_accent($str) 6722 { 6723 $convs = []; 6724 preg_match_all('/[\x80-\xFF| ]/', $str, $matches); 6725 $accents = $matches[0]; 6726 foreach ($accents as $a) { 6727 $convs[] = rawurlencode($a); 6728 } 6729 return str_replace($accents, $convs, $str); 6730 } 6731 6732 /** 6733 * Remove all "non-word" characters and accents from a string 6734 * Can be used for DOM elements and preferences etc 6735 * 6736 * @static 6737 * @param string $str 6738 * @return string cleaned 6739 */ 6740 6741 static function remove_non_word_characters_and_accents($str) 6742 { 6743 return preg_replace('/\W+/', '_', TikiLib::take_away_accent($str)); 6744 } 6745 6746 /* return the positions in data where the hdr-nth header is find 6747 */ 6748 /** 6749 * @param $data 6750 * @param $hdr 6751 * @return array 6752 */ 6753 function get_wiki_section($data, $hdr) 6754 { 6755 $start = 0; 6756 $end = strlen($data); 6757 $lines = explode("\n", $data); 6758 $header = 0; 6759 $pp_level = 0; 6760 $np_level = 0; 6761 for ($i = 0, $count_lines = count($lines); $i < $count_lines; ++$i) { 6762 $pp_level += preg_match('/~pp~/', $lines[$i]); 6763 $pp_level -= preg_match('/~\/pp~/', $lines[$i]); 6764 $np_level += preg_match('/~np~/', $lines[$i]); 6765 $np_level -= preg_match('/~\/np~/', $lines[$i]); 6766 // We test if we are inside nonparsed or pre section to ignore !* 6767 if ($pp_level % 2 == 0 and $np_level % 2 == 0) { 6768 if (substr($lines[$i], 0, 1) == '!') { 6769 ++$header; 6770 if ($header == $hdr) { // we are on it - now find the next header at same or lower level 6771 $level = $this->how_many_at_start($lines[$i], '!'); 6772 $end = strlen($lines[$i]) + 1; 6773 for (++$i; $i < $count_lines; ++$i) { 6774 if (substr($lines[$i], 0, 1) == '!' && $level >= $this->how_many_at_start($lines[$i], '!')) { 6775 return ([$start, $end]); 6776 } 6777 $end += strlen($lines[$i]) + 1; 6778 } 6779 break; 6780 } 6781 } 6782 } 6783 $start += strlen($lines[$i]) + 1; 6784 } 6785 return ([$start, $end]); 6786 } 6787 6788 /** 6789 * \brief Function to embed a flash object (using JS method by default when JS in user's browser is detected) 6790 * 6791 * So far it's being called from wikiplugin_flash.php and tiki-edit_banner.php 6792 * 6793 * @param javascript = y or n to force to generate a version with javascript or not, ='' user prefs 6794 */ 6795 function embed_flash($params, $javascript = '', $flashvars = false) 6796 { 6797 global $prefs; 6798 $headerlib = TikiLib::lib('header'); 6799 if (! isset($params['movie'])) { 6800 return false; 6801 } 6802 $defaults = [ 6803 'width' => 425, 6804 'height' => 350, 6805 'quality' => 'high', 6806 'version' => '9.0.0', 6807 'wmode' => 'transparent', 6808 ]; 6809 $params = array_merge($defaults, $params); 6810 if (preg_match('/^(\/|https?:)/', $params['movie'])) { 6811 $params['allowscriptaccess'] = 'always'; 6812 } 6813 6814 if (((empty($javascript) && $prefs['javascript_enabled'] == 'y') || $javascript == 'y')) { 6815 $myId = (! empty($params['id'])) ? ($params['id']) : 'wp-flash-' . uniqid(); 6816 $movie = '"' . $params['movie'] . '"'; 6817 $div = json_encode($myId); 6818 $width = (int) $params['width']; 6819 $height = (int) $params['height']; 6820 $version = json_encode($params['version']); 6821 if (! empty($params['altimg'])) { 6822 $alt = '<img src="' . $params['altimg'] . '" width="' . $width . '" height="' . $height . '" alt=\"\" />'; 6823 } else { 6824 $alt = ''; // Must be blank otherwise for a split second before Flash loads you can see any text that is set 6825 } 6826 unset($params['movie'], $params['width'], $params['height'], $params['version'], $params['altimg']); 6827 $params = json_encode($params); 6828 6829 if (! $flashvars) { 6830 $flashvars = '{}'; 6831 } else { 6832 $flashvars = json_encode($flashvars); 6833 $flashvars = str_replace('\\/', '/', $flashvars); 6834 } 6835 $js = <<<JS 6836swfobject.embedSWF( $movie, $div, $width, $height, $version, 'vendor_bundled/vendor/bower-asset/swfobject/swfobject/expressInstall.swf', $flashvars, $params, {} ); 6837JS; 6838 $headerlib->add_js($js); 6839 return "<div id=\"$myId\">" . $alt . "</div>"; 6840 } else { // link on the movie will not work with IE6 6841 $asetup = "<object classid=\"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000\" codebase=\"http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,29,0\" width=\"{$params['width']}\" height=\"{$params['height']}\">"; 6842 $asetup .= "<param name=\"movie\" value=\"{$params['movie']}\" />"; 6843 $asetup .= "<param name=\"quality\" value=\"{$params['quality']}\" />"; 6844 $asetup .= "<param name=\"wmode\" value=\"transparent\" />"; 6845 if (! empty($params['allowscriptaccess'])) { 6846 $asetup .= "<param name=\"allowscriptaccess\" value=\"always\" />"; 6847 } 6848 if (! empty($params['allowFullScreen'])) { 6849 $asetup .= '<param name="allowFullScreen" value="' . $params['allowFullScreen'] . '"></param>'; 6850 } 6851 if (! empty($params['altimg'])) { 6852 $asetup .= '<img src="' . $params['altimg'] . '" width="' . $params['width'] . '" height="' . $params['height'] . '" alt=\"\" />'; 6853 } 6854 $asetup .= "<embed src=\"{$params['movie']}\" quality=\"{$params['quality']}\" pluginspage=\"http://www.macromedia.com/go/getflashplayer\"" . 6855 " type=\"application/x-shockwave-flash\" width=\"{$params['width']}\" height=\"{$params['height']}\" wmode=\"transparent\"></embed></object>"; 6856 return $asetup; 6857 } 6858 } 6859 6860 /** 6861 * @param bool $descendants The default is to get all descendents of the jailed categories, but for unified search 6862 * we only need the "root" jailed categories as the search does a deep_categories search on them 6863 * @return array 6864 */ 6865 function get_jail($descendants = true) 6866 { 6867 global $prefs; 6868 // if jail is zero, we should allow non-categorized objects to be seen as well, i.e. consider as no jail 6869 if (! empty($prefs['feature_categories']) && $prefs['feature_categories'] == 'y' && 6870 ! empty($prefs['category_jail']) && $prefs['category_jail'] != [0 => 0] ) { 6871 $expanded = []; 6872 if ($descendants) { 6873 $categlib = TikiLib::lib('categ'); 6874 foreach ($prefs['category_jail'] as $categId) { 6875 $expanded = array_merge($expanded, $categlib->get_category_descendants($categId)); 6876 } 6877 } else { 6878 $expanded = $prefs['category_jail']; 6879 } 6880 return $expanded; 6881 } else { 6882 return []; 6883 } 6884 } 6885 6886 /** 6887 * @param $type 6888 * @param $old 6889 * @param $new 6890 */ 6891 protected function rename_object($type, $old, $new, $user = '') 6892 { 6893 global $prefs; 6894 6895 // comments 6896 $this->table('tiki_comments')->updateMultiple(['object' => $new], ['object' => $old, 'objectType' => $type]); 6897 6898 // Move email notifications 6899 $oldId = str_replace($type, ' ', '') . $old; 6900 $newId = str_replace($type, ' ', '') . $new; 6901 $this->table('tiki_user_watches')->updateMultiple(['object' => $newId], ['object' => $oldId]); 6902 $this->table('tiki_group_watches')->updateMultiple(['object' => $newId], ['object' => $oldId]); 6903 6904 // theme_control_objects(objId,name) 6905 $oldId = md5($type . $old); 6906 $newId = md5($type . $new); 6907 $this->table('tiki_theme_control_objects')->updateMultiple(['objId' => $newId, 'name' => $new], ['objId' => $oldId]); 6908 6909 // polls 6910 if ($prefs['feature_polls'] == 'y') { 6911 $query = "update `tiki_polls` tp inner join `tiki_poll_objects` tpo on tp.`pollId` = tpo.`pollId` inner join `tiki_objects` tob on tpo.`catObjectId` = tob.`objectId` set tp.`title`=? where tp.`title`=? and tob.`type` = ?"; 6912 $this->query($query, [ $new, $old, $type ]); 6913 } 6914 6915 // Move custom permissions 6916 $oldId = md5($type . TikiLib::strtolower($old)); 6917 $newId = md5($type . TikiLib::strtolower($new)); 6918 $this->table('users_objectpermissions')->updateMultiple(['objectId' => $newId], ['objectId' => $oldId, 'objectType' => $type]); 6919 6920 // Logs 6921 if ($prefs['feature_actionlog'] == 'y') { 6922 $logslib = TikiLib::lib('logs'); 6923 $logslib->add_action('Renamed', $new, 'wiki page', 'old=' . $old . '&new=' . $new, $user, '', '', '', '', [['rename' => $old]]); 6924 $logslib->rename($type, $old, $new); 6925 } 6926 6927 // Attributes 6928 $this->table('tiki_object_attributes')->updateMultiple(['itemId' => $new], ['itemId' => $old, 'type' => $type]); 6929 $this->table('tiki_object_relations')->updateMultiple(['source_itemId' => $new], ['source_itemId' => $old, 'source_type' => $type]); 6930 $this->table('tiki_object_relations')->updateMultiple(['target_itemId' => $new], ['target_itemId' => $old, 'target_type' => $type]); 6931 6932 $menulib = TikiLib::lib('menu'); 6933 $menulib->rename_wiki_page($old, $new); 6934 } 6935 6936 /** 6937 * @param $delimiters 6938 * @param $string 6939 * @return array 6940 */ 6941 function multi_explode($delimiters, $string) 6942 { 6943 global $prefs; 6944 6945 if (is_array($delimiters) == false) { 6946 $delimiters = [$delimiters]; 6947 } 6948 6949 $delimiter = array_shift($delimiters); 6950 $temp = explode($delimiter, $string); 6951 6952 $array = []; 6953 $keep = false; 6954 6955 $ignore_chars = array_unique(str_split($prefs['namespace_separator'])); 6956 6957 foreach ($temp as $v) { 6958 $filtered = str_replace($ignore_chars, '', $v); 6959 if ($filtered == '' && $v != '') { 6960 if (! $keep) { 6961 $array[count($array) - 1] .= $delimiter; 6962 } 6963 6964 $array[count($array) - 1] .= $v . $delimiter; 6965 $keep = true; 6966 } elseif ($keep) { 6967 $array[count($array) - 1] .= $v; 6968 $keep = false; 6969 } else { 6970 $array[] = $v; 6971 } 6972 } 6973 6974 if ($delimiters != null) { 6975 foreach ($array as $key => $val) { 6976 $array[$key] = $this->multi_explode($delimiters, $val); 6977 } 6978 } 6979 6980 return $array; 6981 } 6982 6983 /** 6984 * @param $delimiters 6985 * @param $string 6986 * @return string 6987 */ 6988 function multi_implode($delimiters, $array) 6989 { 6990 $delimiters = (array) $delimiters; 6991 $delimiter = array_shift($delimiters); 6992 6993 if (count($delimiters)) { 6994 $self = $this; 6995 $array = array_map( 6996 function ($value) use ($delimiters, $self) { 6997 return $self->multi_implode($delimiters, $value); 6998 }, 6999 $array 7000 ); 7001 } 7002 7003 return implode($delimiter, $array); 7004 } 7005 7006 /** 7007 * @param $vals 7008 * @param $filter 7009 * @return string 7010 */ 7011 function array_apply_filter($vals, $filter) 7012 { 7013 if (is_array($vals) == true) { 7014 foreach ($vals as $key => $val) { 7015 $vals[$key] = $this->array_apply_filter($val, $filter); 7016 } 7017 return $vals; 7018 } else { 7019 return trim($filter->filter($vals)); 7020 } 7021 } 7022 7023 /** 7024 * @param $type 7025 * @param $object 7026 * @param bool $process 7027 * @return bool 7028 */ 7029 function refresh_index($type, $object, $process = true) 7030 { 7031 require_once __DIR__ . '/search/refresh-functions.php'; 7032 return refresh_index($type, $object, $process); 7033 } 7034 7035 /** 7036 * Possibly enhanced version of strtolower(), using multi-byte if mbstring is available 7037 * 7038 * Since Tiki 17, mb_strtolower() can be used directly instead since Tiki indirectly depends on the symfony/polyfill-mbstring compatibility library. 7039 * 7040 * @param $string 7041 * @return string 7042 */ 7043 public static function strtolower($string) 7044 { 7045 if (function_exists('mb_strtolower')) { 7046 return mb_strtolower($string, 'UTF-8'); 7047 } else { 7048 return strtolower($string); 7049 } 7050 } 7051 7052 /** 7053 * Possibly enhanced version of strtoupper(), using multi-byte if mbstring is available 7054 * 7055 * Since Tiki 17, mb_strtoupper() can be used directly instead since Tiki indirectly depends on the symfony/polyfill-mbstring compatibility library. 7056 * 7057 * @param $string 7058 * @return string 7059 */ 7060 public static function strtoupper($string) 7061 { 7062 if (function_exists('mb_strtoupper')) { 7063 return mb_strtoupper($string, 'UTF-8'); 7064 } else { 7065 return strtoupper($string); 7066 } 7067 } 7068 7069 /** 7070 * @param $string 7071 * @return string UTF-8 7072 */ 7073 public static function urldecode($string) 7074 { 7075 return TikiInit::to_utf8(urldecode($string)); 7076 } 7077 7078 /** 7079 * @param $string 7080 * @return string UTF-8 7081 */ 7082 public static function rawurldecode($string) 7083 { 7084 return TikiInit::to_utf8(rawurldecode($string)); 7085 } 7086 7087 /** 7088 * Unparse an array of url parts, e.g. the result of parse_url() 7089 * Thanks to http://php.net/manual/en/function.parse-url.php#106731 7090 * 7091 * @param $parsed_url 7092 * @return string 7093 */ 7094 public static function unparse_url($parsed_url) 7095 { 7096 $scheme = isset($parsed_url['scheme']) ? $parsed_url['scheme'] . '://' : '//'; 7097 $host = isset($parsed_url['host']) ? $parsed_url['host'] : ''; 7098 $port = isset($parsed_url['port']) ? ':' . $parsed_url['port'] : ''; 7099 $user = isset($parsed_url['user']) ? $parsed_url['user'] : ''; 7100 $pass = isset($parsed_url['pass']) ? ':' . $parsed_url['pass'] : ''; 7101 $pass = ($user || $pass) ? "$pass@" : ''; 7102 $path = isset($parsed_url['path']) ? $parsed_url['path'] : ''; 7103 $query = isset($parsed_url['query']) ? '?' . $parsed_url['query'] : ''; 7104 $fragment = isset($parsed_url['fragment']) ? '#' . $parsed_url['fragment'] : ''; 7105 7106 return "$scheme$user$pass$host$port$path$query$fragment"; 7107 } 7108 7109 7110 7111 /** 7112 * Return the request URI. 7113 * Assumes http or https is used. Non-standard ports are taken into account 7114 * @return Full URL to the current page 7115 * \static 7116 */ 7117 // Note: this is unused as of r37658, but quite generic. 7118 static function curPageURL() 7119 { 7120 $pageURL = 'http'; 7121 if (isset($_SERVER["HTTPS"]) && ($_SERVER["HTTPS"] == "on")) { 7122 $pageURL .= 's'; 7123 } 7124 $pageURL .= '://'; 7125 if ($_SERVER['SERVER_PORT'] != '80') { 7126 $pageURL .= $_SERVER['SERVER_NAME'] . ":" . $_SERVER['SERVER_PORT'] . $_SERVER['REQUEST_URI']; 7127 } else { 7128 $pageURL .= $_SERVER['SERVER_NAME'] . $_SERVER['REQUEST_URI']; 7129 } 7130 return $pageURL; 7131 } 7132 7133 /** 7134 * @param array $data 7135 * @return array 7136 */ 7137 public static function array_flat(array $data) 7138 { 7139 $out = []; 7140 foreach ($data as $entry) { 7141 if (is_array($entry)) { 7142 $out = array_merge($out, self::array_flat($entry)); 7143 } else { 7144 $out[] = $entry; 7145 } 7146 } 7147 return $out; 7148 } 7149 7150 /** 7151 * This checks the modifier array and scans the template directory for templates 7152 * that match the modifiers. 7153 * Example: if we are looking at modifier "blog" for the articles.tpl, this function 7154 * looks for the existence of articles--blog.tpl to use before using the standard articles.tpl 7155 * 7156 * @param $basetpl 7157 * @param $modifier_arr 7158 * @return string 7159 * @throws Exception 7160 */ 7161 public static function custom_template($basetpl, $modifier_arr) 7162 { 7163 //if it's an item passed and not an array, put the item in an array 7164 if (! is_array($modifier_arr)) { 7165 $modifier_arr = [$modifier_arr]; 7166 } 7167 //strip the .tpl 7168 $temp = explode('.', $basetpl); 7169 $ext = array_pop($temp); 7170 $base = implode('.', $temp); 7171 7172 $smarty = TikiLib::lib('smarty'); 7173 foreach ($modifier_arr as $modifier) { 7174 if ($smarty->templateExists("$base--$modifier.tpl")) { 7175 return "$base--$modifier.tpl"; 7176 } 7177 } 7178 return "$base.tpl"; 7179 } 7180 7181 /** 7182 * @param $page 7183 * @return mixed 7184 */ 7185 public function removePageReference($page) 7186 { 7187 $page_id = $this->get_page_id_from_name($page); 7188 $query = "DELETE FROM `tiki_page_references` WHERE `page_id`=?"; 7189 $result = $this->query($query, [$page_id]); 7190 return $result; 7191 } 7192 7193 /** 7194 * @param array $new_toolbars 7195 * @param string $section 7196 * @param string $action 7197 */ 7198 public function saveEditorToolbars($new_toolbars = [], $section = 'global', $action = 'add') 7199 { 7200 global $prefs; 7201 $prefName = 'toolbar_' . $section; 7202 $toolbars = explode(',', $prefs[$prefName]); 7203 if ($action == 'add') { 7204 foreach ($new_toolbars as $key => $value) { 7205 if (! in_array($value, $toolbars)) { 7206 $toolbars[] = $value; 7207 } 7208 } 7209 } else {//remove the toolbars 7210 $toolbars = array_diff($toolbars, $new_toolbars); 7211 } 7212 $toolbars = implode(',', $toolbars); 7213 $this->set_preference($prefName, $toolbars); 7214 } 7215 7216 /** 7217 * @param $haystack 7218 * @param $needle 7219 * @return bool 7220 */ 7221 static function startsWith($haystack, $needle) 7222 { 7223 $length = strlen($needle); 7224 return (substr($haystack, 0, $length) === $needle); 7225 } 7226 7227 /** 7228 * @param $haystack 7229 * @param $needle 7230 * @return bool 7231 */ 7232 static function endsWith($haystack, $needle) 7233 { 7234 $length = strlen($needle); 7235 if ($length == 0) { 7236 return true; 7237 } 7238 7239 $start = $length * -1; //negative 7240 return (substr($haystack, $start) === $needle); 7241 } 7242 7243 /** 7244 * Checks if all link aliases contained in a page are valid, it automatically flashes the error in case there are invalid aliases 7245 * @param String $edit Contains page edit content 7246 * @param String $page Page name 7247 * @return bool returns false if there is at least one invalid alias 7248 * @throws Exception 7249 */ 7250 function check_duplicate_alias($edit, $page) 7251 { 7252 $errors = []; 7253 7254 $parserlib = TikiLib::lib('parser'); 7255 $table = $this->table('tiki_object_relations'); 7256 7257 $smarty = TikiLib::lib('smarty'); 7258 $smarty->loadPlugin('smarty_modifier_sefurl'); 7259 7260 foreach ($parserlib->get_pages($edit, true) as $pointedPage => $types) { 7261 if (empty($types[0]) || $types[0] != 'alias') { 7262 continue; 7263 } 7264 7265 $conflictPages = $table->fetchColumn('source_itemId', [ 7266 'target_itemId' => $pointedPage, 7267 'source_itemId' => $table->not($page), 7268 'relation' => $table->like('%alias%') 7269 ]); 7270 7271 if (empty($conflictPages)) { 7272 continue; 7273 } 7274 7275 $url = []; 7276 foreach ($conflictPages as $pageName) { 7277 $url[] = sprintf('<a href="%s">%s</a>', smarty_modifier_sefurl($pageName, 'wiki'), $pageName); 7278 } 7279 7280 $errors[] = tr('Alias <b>%0</b> link already present in %1 page(s)', $pointedPage, implode(', ', $url)); 7281 } 7282 7283 if (! empty($errors)) { 7284 Feedback::error(implode('<br>', $errors)); 7285 } 7286 7287 return empty($errors); 7288 } 7289 7290 /** 7291 * @param $arr - array of data to convert to csv 7292 * @return string - csv formatted string 7293 */ 7294 function str_putcsv($arr) 7295 { 7296 $fh = fopen('php://temp', 'rw'); 7297 fputcsv($fh, $arr); 7298 rewind($fh); 7299 $csv = stream_get_contents($fh); 7300 fclose($fh); 7301 return trim($csv); 7302 } 7303 7304 /** 7305 * Find a text inside string range 7306 * 7307 * @param string $text 7308 * @param string $string 7309 * @param int $from 7310 * @param int $to 7311 * @return mixed 7312 */ 7313 public function findText($text, $string, $from, $to) 7314 { 7315 if ($from >= strlen($text)) { 7316 return false; 7317 } 7318 7319 $pos = strpos($text, $string, $from); 7320 7321 if ($pos === false || $pos + strlen($string) > $to) { 7322 return false; 7323 } 7324 7325 return $pos; 7326 } 7327 7328 /** 7329 * Return wiki markers 7330 * 7331 * @return array 7332 */ 7333 public function getWikiMarkers() 7334 { 7335 $listMarkers = [ 7336 ['~np~', '~/np~'], 7337 ['-+', '+-'], 7338 ['~pp~', '~/pp~'], 7339 ['~pre~', '~/pre~'], 7340 ['-=', '=-'], 7341 ]; 7342 7343 return $listMarkers; 7344 } 7345} 7346// end of class ------------------------------------------------------ 7347 7348// function to check if a file or directory is in the path 7349// returns FALSE if incorrect 7350// returns the canonicalized absolute pathname otherwise 7351/** 7352 * @param $file 7353 * @param $dir 7354 * @return bool|string 7355 */ 7356function inpath($file, $dir) 7357{ 7358 $realfile = realpath($file); 7359 $realdir = realpath($dir); 7360 if (! $realfile) { 7361 return (false); 7362 } 7363 if (! $realdir) { 7364 return (false); 7365 } 7366 if (substr($realfile, 0, strlen($realdir)) != $realdir) { 7367 return(false); 7368 } else { 7369 return($realfile); 7370 } 7371} 7372 7373/** 7374 * @param $ar1 7375 * @param $ar2 7376 * @return mixed 7377 */ 7378function compare_links($ar1, $ar2) 7379{ 7380 return $ar1["links"] - $ar2["links"]; 7381} 7382 7383/** 7384 * @param $ar1 7385 * @param $ar2 7386 * @return mixed 7387 */ 7388function compare_backlinks($ar1, $ar2) 7389{ 7390 return $ar1["backlinks"] - $ar2["backlinks"]; 7391} 7392 7393/** 7394 * @param $ar1 7395 * @param $ar2 7396 * @return mixed 7397 */ 7398function r_compare_links($ar1, $ar2) 7399{ 7400 return $ar2["links"] - $ar1["links"]; 7401} 7402 7403/** 7404 * @param $ar1 7405 * @param $ar2 7406 * @return mixed 7407 */ 7408function r_compare_backlinks($ar1, $ar2) 7409{ 7410 return $ar2["backlinks"] - $ar1["backlinks"]; 7411} 7412 7413/** 7414 * @param $ar1 7415 * @param $ar2 7416 * @return mixed 7417 */ 7418function compare_images($ar1, $ar2) 7419{ 7420 return $ar1["images"] - $ar2["images"]; 7421} 7422 7423/** 7424 * @param $ar1 7425 * @param $ar2 7426 * @return mixed 7427 */ 7428function r_compare_images($ar1, $ar2) 7429{ 7430 return $ar2["images"] - $ar1["images"]; 7431} 7432 7433/** 7434 * @param $ar1 7435 * @param $ar2 7436 * @return mixed 7437 */ 7438function compare_versions($ar1, $ar2) 7439{ 7440 return $ar1["versions"] - $ar2["versions"]; 7441} 7442 7443/** 7444 * @param $ar1 7445 * @param $ar2 7446 * @return mixed 7447 */ 7448function r_compare_versions($ar1, $ar2) 7449{ 7450 return $ar2["versions"] - $ar1["versions"]; 7451} 7452 7453/** 7454 * @param $ar1 7455 * @param $ar2 7456 * @return mixed 7457 */ 7458function compare_changed($ar1, $ar2) 7459{ 7460 return $ar1["lastChanged"] - $ar2["lastChanged"]; 7461} 7462 7463/** 7464 * @param $ar1 7465 * @param $ar2 7466 * @return mixed 7467 */ 7468function r_compare_changed($ar1, $ar2) 7469{ 7470 return $ar2["lastChanged"] - $ar1["lastChanged"]; 7471} 7472 7473/** 7474 * @param $ar1 7475 * @param $ar2 7476 * @return int 7477 */ 7478function compare_names($ar1, $ar2) 7479{ 7480 return strcasecmp(tra($ar1["name"]), tra($ar2["name"])); 7481} 7482 7483function chkgd2() 7484{ 7485 return function_exists('imagecreatetruecolor'); 7486} 7487 7488 7489/** 7490 * @return string 7491 */ 7492function detect_browser_language() 7493{ 7494 global $prefs; 7495 // Get supported languages 7496 if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) { 7497 $supported = preg_split('/\s*,\s*/', preg_replace('/;q=[0-9.]+/', '', $_SERVER['HTTP_ACCEPT_LANGUAGE'])); 7498 } else { 7499 return ''; 7500 } 7501 7502 // Get available languages 7503 $available = []; 7504 $available_aprox = []; 7505 7506 if (is_dir("lang")) { 7507 $dh = opendir("lang"); 7508 while ($lang = readdir($dh)) { 7509 if (! strpos($lang, '.') and is_dir("lang/$lang") and file_exists("lang/$lang/language.php") and ($prefs['restrict_language'] === 'n' || empty($prefs['available_languages']) || in_array($lang, $prefs['available_languages']))) { 7510 $available[strtolower($lang)] = $lang; 7511 $available_aprox[substr(strtolower($lang), 0, 2)] = $lang; 7512 } 7513 } 7514 } 7515 7516 // Check better language 7517 // Priority has been changed in 2.0 to that defined in RFC 4647 7518 $aproximate_lang = ''; 7519 foreach ($supported as $supported_lang) { 7520 $lang = strtolower($supported_lang); 7521 if (in_array($lang, array_keys($available))) { 7522 // exact match is always good 7523 return $available[$lang]; 7524 } elseif (in_array($lang, array_keys($available_aprox))) { 7525 // otherwise if supported language matches any available dialect, ok also 7526 return $available_aprox[$lang]; 7527 } elseif ($aproximate_lang == '') { 7528 // otherwise if supported dialect matches language, store as possible fallback 7529 $lang = substr($lang, 0, 2); 7530 if (in_array($lang, array_keys($available_aprox))) { 7531 $aproximate_lang = $available_aprox[$lang]; 7532 } 7533 } 7534 } 7535 7536 return $aproximate_lang; 7537} 7538 7539/** 7540 * Validates an email address, using a domain check if $validate == 'y' 7541 * 7542 * @param string $email email to validate 7543 * @param string $validate n|y|d (d = deep) defaults to pref validateEmail 7544 * @return bool 7545 */ 7546function validate_email($email, $validate = null) 7547{ 7548 global $prefs; 7549 7550 if (empty($validate)) { 7551 $validate = $prefs['validateEmail']; 7552 } 7553 7554 $options = ['allow' => Zend\Validator\Hostname::ALLOW_ALL,]; 7555 7556 if ($validate === 'n') { 7557 return true; 7558 } else { 7559 $options['useDomainCheck'] = true; // both y and d 7560 } 7561 7562 if ($validate === 'd') { // deep mx check 7563 $options['useMxCheck'] = true; 7564 $options['useDeepMxCheck'] = true; 7565 } 7566 $validator = new Zend\Validator\EmailAddress($options); 7567 return $validator->isValid($email); 7568} 7569 7570/** 7571 * @param $val 7572 * @param $default 7573 * @return string 7574 */ 7575function makeBool($val, $default) 7576{ 7577 // Warning: This function is meant to return a string 'true' or 'false' to be used in JS, not a real boolean value 7578 if (isset($val) && ! empty($val)) { 7579 $val = ($val == 'y' ? true : false); 7580 } else { 7581 $val = $default; 7582 } 7583 return ($val ? 'true' : 'false'); 7584} 7585/* Editor configuration 7586 Local Variables: 7587 tab-width: 4 7588 c-basic-offset: 4 7589End: 7590 * vim: fdm=marker tabstop=4 shiftwidth=4 noet: 7591 */ 7592 7593 7594/** 7595 * 7596 * Writes a temporary directory and/or file in a cryptographically secure way. 7597 * 7598 * @param string|null $data Data to be written to file, null if we are creating directories only. 7599 * @param string $directory Directory for the file to be created in. using the string 'random' will generate a random directory. Sending NULL will create a directory only. 7600 * @param bool $system If files should be stored in the system directory (outside the web root), will fall back to tiki /temp directory upon failure. 7601 * @param string $prefix A string to add to the beginning of the file name. 7602 * @param string $append A string to append the file name, such as an extension. 7603 * 7604 * @return string The path and filename of the file written. 7605 * @throws exception If a file can not be created, an exception will be thrown. 7606 */ 7607 7608function writeTempFile(?string $data, string $directory = '', bool $system = true, string $prefix = '', string $append = ''): ?string { 7609 global $prefs; 7610 $fileName = ''; 7611 7612 if ($directory === 'random') { 7613 if (is_callable('random_bytes')) { 7614 $directory = bin2hex(random_bytes(16)) . '/'; 7615 } else { 7616 $directory = dechex(rand(0, 2 ** 62)) . dechex(rand(0, 2 ** 62)) . '/'; 7617 } 7618 } 7619 7620 if (strlen($prefix) + strlen($append) > 223) { 7621 throw new Exception('File name must be under 255 characters.'); 7622 } 7623 7624 if ($system) { 7625 $tmpDir = $prefs['tmpDir']; 7626 if (substr($tmpDir, -1) !== '/'){ 7627 $tmpDir = $tmpDir . '/'; 7628 } 7629 if (file_exists($tmpDir . $directory)) { 7630 $dirName = $tmpDir . $directory; 7631 } elseif (@mkdir($tmpDir . $directory)) { 7632 $dirName = $tmpDir . $directory; 7633 } 7634 // if the system directory is not writable, then fall back to Tiki tmp directory. 7635 if (!is_writable($tmpDir . $directory)){ 7636 unset($dirName); 7637 } 7638 } 7639 7640 if (! isset($dirName)) { 7641 if (file_exists('temp/' . $directory)) { 7642 $dirName = 'temp/' . $directory; 7643 } elseif (@mkdir('temp/' . $directory)) { 7644 $dirName = 'temp/' . $directory; 7645 @file_put_contents('temp/' . $directory . 'index.php', ''); 7646 } else { 7647 throw new Exception ("Can not create temp/$directory directory."); 7648 } 7649 } 7650 7651 if (! is_null($data)) { 7652 7653 do { 7654 if (is_callable('random_bytes')) { 7655 $fileName = $prefix . bin2hex(random_bytes(16)) . $append; 7656 } else { 7657 $fileName = $prefix . dechex(rand(0, 2 ** 62)) . dechex(rand(0, 2 ** 62)) . $append; 7658 } 7659 } while (file_exists($dirName . $fileName)); 7660 7661 7662 if (@file_put_contents($dirName . $fileName, $data) === false) { 7663 throw new exception ("Can not write to $dirName$fileName file."); 7664 } 7665 } 7666 return $dirName . $fileName; 7667} 7668