Textpattern | PHP Cross Reference | Content Management Systems |
Description: Site hostname.
1 <?php 2 3 /* 4 * Textpattern Content Management System 5 * https://textpattern.com/ 6 * 7 * Copyright (C) 2020 The Textpattern Development Team 8 * 9 * This file is part of Textpattern. 10 * 11 * Textpattern is free software; you can redistribute it and/or 12 * modify it under the terms of the GNU General Public License 13 * as published by the Free Software Foundation, version 2. 14 * 15 * Textpattern is distributed in the hope that it will be useful, 16 * but WITHOUT ANY WARRANTY; without even the implied warranty of 17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 * GNU General Public License for more details. 19 * 20 * You should have received a copy of the GNU General Public License 21 * along with Textpattern. If not, see <https://www.gnu.org/licenses/>. 22 */ 23 24 if (!defined('txpath')) { 25 define("txpath", dirname(__FILE__)); 26 } 27 28 if (!defined("txpinterface")) { 29 die('If you just updated and expect to see your site here, please also update the files in your main installation directory.'. 30 ' (Otherwise note that publish.php cannot be called directly.)'); 31 } 32 33 global $trace; 34 35 $trace->start('[PHP includes, stage 2]'); 36 include_once txpath.'/vendors/Textpattern/Loader.php'; 37 38 $loader = new \Textpattern\Loader(txpath.'/vendors'); 39 $loader->register(); 40 41 $loader = new \Textpattern\Loader(txpath.'/lib'); 42 $loader->register(); 43 44 include_once txpath.'/lib/txplib_db.php'; 45 include_once txpath.'/lib/admin_config.php'; 46 include_once txpath.'/publish/log.php'; 47 48 $trace->stop(); 49 50 set_error_handler('publicErrorHandler', error_reporting()); 51 52 ob_start(); 53 54 // Get logged user. 55 $userInfo = is_logged_in(); 56 57 // Initialise the current user. 58 $txp_user = empty($userInfo) ? null : $userInfo['name']; 59 60 // Get all prefs as an array. 61 $prefs = get_prefs(empty($userInfo['name']) ? '' : array('', $userInfo['name'])); 62 plug_privs(null, $userInfo); 63 64 // Add prefs to globals. 65 extract($prefs); 66 67 // Check the size of the URL request. 68 bombShelter(); 69 70 $txp_sections = array(); 71 $txp_current_tag = ''; 72 $txp_parsed = $txp_else = $txp_item = $txp_context = $txp_yield = $yield = array(); 73 $txp_atts = null; 74 75 isset($pretext) or $pretext = array(); 76 77 // Set a higher error level during initialisation. 78 set_error_level(@$production_status == 'live' ? 'testing' : @$production_status); 79 80 // disable tracing in live environment. 81 if ($production_status == 'live') { 82 Trace::setQuiet(true); 83 } 84 85 // Use the current URL path if $siteurl is unknown. 86 if (empty($siteurl)) { 87 $httphost = preg_replace('/[^-_a-zA-Z0-9.:]/', '', $_SERVER['HTTP_HOST']); 88 $prefs['siteurl'] = $siteurl = $httphost.rtrim(dirname($_SERVER['SCRIPT_NAME']), DS); 89 } 90 91 if (empty($path_to_site)) { 92 updateSitePath(dirname(dirname(__FILE__))); 93 } 94 95 if (!defined('PROTOCOL')) { 96 switch (serverSet('HTTPS')) { 97 case '': 98 case 'off': // ISAPI with IIS. 99 define('PROTOCOL', 'http://'); 100 break; 101 default: 102 define('PROTOCOL', 'https://'); 103 break; 104 } 105 } 106 107 // Definitive HTTP address of the site. 108 if (!defined('hu')) { 109 define('hu', PROTOCOL.$siteurl.'/'); 110 } 111 112 // Relative URL global. 113 if (!defined('rhu')) { 114 define('rhu', preg_replace('|^https?://[^/]+|', '', hu)); 115 } 116 117 // HTTP address of the site serving images. 118 if (!defined('ihu')) { 119 define('ihu', hu); 120 } 121 122 // HTTP address of Textpattern admin URL. 123 if (!defined('ahu')) { 124 if (empty($txpcfg['admin_url'])) { 125 $adminurl = hu.'textpattern/'; 126 } else { 127 $adminurl = PROTOCOL.rtrim(preg_replace('|^https?://|', '', $txpcfg['admin_url']), '/').'/'; 128 } 129 130 define('ahu', $adminurl); 131 } 132 133 // Shared admin and public cookie_domain when using multisite admin URL. 134 if (!defined('cookie_domain')) { 135 if (!isset($txpcfg['cookie_domain'])) { 136 $txpcfg['cookie_domain'] = ''; 137 } 138 139 define('cookie_domain', $txpcfg['cookie_domain']); 140 } 141 142 if (!defined('SITE_HOST')) { 143 /** 144 * Site hostname. 145 * 146 * @package Network 147 * @since 4.6.0 148 */ 149 150 define('SITE_HOST', (string) @parse_url(hu, PHP_URL_HOST)); 151 } 152 153 if (!defined('IMPATH')) { 154 /** 155 * Path to image directory. 156 * 157 * @package Image 158 */ 159 160 define('IMPATH', $path_to_site.DS.$img_dir.DS); 161 } 162 163 // 1.0: a new $here variable in the top-level index.php should let us know the 164 // server path to the live site let's save it to prefs. 165 if (isset($here) and $path_to_site != $here) { 166 updateSitePath($here); 167 } 168 169 if (!defined('LANG')) { 170 /** 171 * Currently active language. 172 * 173 * @package L10n 174 */ 175 176 define('LANG', $language); 177 } 178 179 if (!defined('TXP_PATTERN')) { 180 define('TXP_PATTERN', get_pref('enable_short_tags', false) ? 'txp|[a-z]+:' : 'txp:?'); 181 } 182 183 if (!empty($locale)) { 184 setlocale(LC_ALL, $locale); 185 } 186 187 // For backwards-compatibility (sort of) with plugins that expect the 188 // $textarray global to be present. 189 // Will remove in future. 190 $textarray = array(); 191 192 // Here come the early plugins. 193 if ($use_plugins) { 194 load_plugins(false, 5); 195 } 196 197 // Request URI rewrite, anyone? 198 callback_event('pretext', '', 1); 199 $pretext = preText($pretext, null) + array('secondpass' => 0, '_txp_atts' => false); 200 201 // Send 304 Not Modified if appropriate. 202 203 if (empty($pretext['feed'])) { 204 handle_lastmod(); 205 } 206 207 if (txpinterface === 'css') { 208 output_css(gps('s'), gps('n'), gps('t')); 209 210 exit; 211 } 212 213 $txp_sections = safe_column(array('name'), 'txp_section'); 214 215 $trace->start('[PHP includes, stage 3]'); 216 217 include_once txpath.'/lib/txplib_publish.php'; 218 include_once txpath.'/lib/txplib_html.php'; 219 include_once txpath.'/lib/txplib_forms.php'; 220 include_once txpath.'/publish/comment.php'; 221 include_once txpath.'/publish/taghandlers.php'; 222 223 $trace->stop(); 224 225 // i18n. 226 //load_lang(LANG); 227 228 // Tidy up the site. 229 janitor(); 230 231 // Here come the regular plugins. 232 if ($use_plugins) { 233 load_plugins(); 234 } 235 236 callback_event('pretext'); 237 $pretext = preText($pretext, $prefs); 238 callback_event('pretext_end'); 239 extract($pretext); 240 241 // Now that everything is initialised, we can crank down error reporting. 242 set_error_level($production_status); 243 244 if (!empty($feed) && in_array($feed, array('atom', 'rss'), true)) { 245 include txpath."/publish/{$feed}.php"; 246 echo $feed(); 247 248 if ($production_status !== 'live') { 249 echo $trace->summary(); 250 251 if ($production_status === 'debug') { 252 echo $trace->result(); 253 } 254 } 255 256 exit; 257 } 258 259 if (gps('parentid')) { 260 if (ps('submit')) { 261 saveComment(); 262 } elseif (ps('preview')) { 263 checkCommentRequired(getComment()); 264 } elseif ($comments_mode == 1) { 265 // Popup comments? 266 header("Content-Type: text/html; charset=utf-8"); 267 exit(parse_form('popup_comments')); 268 } 269 } 270 271 // We are dealing with a download. 272 if ($s == 'file_download') { 273 empty($filename) or output_file_download($filename); 274 exit(0); 275 } 276 277 // Log the page view. 278 log_hit($status); 279 280 // ------------------------------------------------------------- 281 282 function preText($store, $prefs = null) 283 { 284 global $thisarticle, $txp_sections; 285 static $url = array(), $out = null; 286 287 if (empty($url)) { 288 // Some useful vars for taghandlers, plugins. 289 $out['request_uri'] = preg_replace("|^https?://[^/]+|i", "", serverSet('REQUEST_URI')); 290 $out['qs'] = serverSet('QUERY_STRING'); 291 292 // IIS fix. 293 if (!$out['request_uri'] and serverSet('SCRIPT_NAME')) { 294 $out['request_uri'] = serverSet('SCRIPT_NAME').((serverSet('QUERY_STRING')) ? '?'.serverSet('QUERY_STRING') : ''); 295 } 296 297 // Another IIS fix. 298 if (!$out['request_uri'] and serverSet('argv')) { 299 $argv = serverSet('argv'); 300 $out['request_uri'] = @substr($argv[0], strpos($argv[0], ';') + 1); 301 } 302 303 // Define the usable url, minus any subdirectories. 304 // This is pretty ugly, if anyone wants to have a go at it. 305 $out['subpath'] = $subpath = preg_quote(preg_replace("/https?:\/\/.*(\/.*)/Ui", "$1", hu), "/"); 306 $out['req'] = $req = preg_replace("/^$subpath/i", "/", $out['request_uri']); 307 308 $url = chopUrl($req, 4); 309 310 for ($out[0] = 0; isset($url['u'.($out[0]+1)]); $out[++$out[0]] = $url['u'.$out[0]]); 311 312 if ($url['u1'] == 'rss' || gps('rss')) { 313 $out['feed'] = 'rss'; 314 } elseif ($url['u1'] == 'atom' || gps('atom')) { 315 $out['feed'] = 'atom'; 316 } 317 } 318 319 if (is_array($store)) { 320 $out = $store + $out; 321 } 322 323 if (!isset($prefs)) { 324 return $out; 325 } 326 327 extract($prefs); 328 329 // Set messy variables. 330 $out += makeOut('id', 's', 'c', 'context', 'q', 'm', 'pg', 'p', 'month', 'author', 'f'); 331 $out['skin'] = $out['page'] = $out['css'] = ''; 332 333 $is_404 = ($out['status'] == '404'); 334 $title = null; 335 336 // If messy vars exist, bypass URL parsing. 337 if (!$is_404 && !$out['id'] && !$out['s'] && txpinterface != 'css' && txpinterface != 'admin') { 338 // Return clean URL test results for diagnostics. 339 if (gps('txpcleantest')) { 340 exit(show_clean_test($out)); 341 } 342 343 // First we sniff out some of the preset URL schemes. 344 extract($url); 345 346 if (strlen($u1)) { 347 $n = $out[0]; 348 $un = $out[$n]; 349 350 switch ($u1) { 351 case 'atom': 352 $out['feed'] = 'atom'; 353 break; 354 355 case 'rss': 356 $out['feed'] = 'rss'; 357 break; 358 359 // urldecode(strtolower(urlencode())) looks ugly but is the 360 // only way to make it multibyte-safe without breaking 361 // backwards-compatibility. 362 case 'section': 363 case urldecode(strtolower(urlencode(gTxt('section')))): 364 $out['s'] = $u2; 365 break; 366 367 case 'category': 368 case urldecode(strtolower(urlencode(gTxt('category')))): 369 $out['context'] = $u3 ? validContext($u2) : 'article'; 370 if ($permlink_mode == 'breadcrumb_title') { 371 $n < 2 or $out['c'] = $un ? $un : $out[$n-1]; 372 } else { 373 $out['c'] = $u3 ? $u3 : $u2; 374 } 375 break; 376 377 case 'author': 378 case urldecode(strtolower(urlencode(gTxt('author')))): 379 if ($u3) { 380 $out['context'] = validContext($u2); 381 $out['author'] = $u3; 382 } else { 383 $out['context'] = 'article'; 384 $out['author'] = $u2; 385 } 386 387 $out['author'] = (!empty($out['author'])) ? $out['author'] : ''; 388 break; 389 // AuthorID gets resolved from Name further down. 390 391 case 'file_download': 392 case urldecode(strtolower(urlencode(gTxt('file_download')))): 393 $out['s'] = 'file_download'; 394 $out['id'] = (!empty($u2)) ? $u2 : ''; 395 $out['filename'] = (!empty($u3)) ? $u3 : ''; 396 break; 397 398 default: 399 $permlink_modes = array('default' => $permlink_mode) + array_column($txp_sections, 'permlink_mode', 'name'); 400 $custom_modes = array_filter($permlink_modes, function ($v) use ($permlink_mode) { 401 return $v && $v !== $permlink_mode; 402 }); 403 404 if (empty($custom_modes)) { 405 $permlink_guess = $permlink_mode; 406 } elseif (!empty($un) && empty($no_trailing_slash)) {// ID or url_title 407 $safe_un = doSlash($un); 408 409 $guessarticles = safe_rows( 410 '*, UNIX_TIMESTAMP(Posted) AS uPosted, UNIX_TIMESTAMP(Expires) AS uExpires, UNIX_TIMESTAMP(LastMod) AS uLastMod', 411 'textpattern', 412 "url_title='$safe_un'".($n < 3 && is_numeric($un) ? " OR ID='$safe_un'" : '') 413 ); 414 415 foreach ($guessarticles as $a) { 416 populateArticleData($a); 417 418 if (permlinkurl($thisarticle, '/') === $u0) { 419 $permlink_guess = $permlink_modes[$a['Section']]; 420 break; 421 } 422 } 423 424 if (!isset($permlink_guess)) { 425 unset($thisarticle); 426 $is_404 = true; 427 } else { 428 $out['id'] = $thisarticle['thisid']; 429 $out['s'] = $thisarticle['section']; 430 $title = $thisarticle['url_title']; 431 $month = explode('-', strftime('%Y-%m-%d', $thisarticle['posted'])); 432 } 433 } 434 435 if (empty($un) && is_numeric($u1) && strlen($u1) === 4 && !isset($permlink_modes[$u1])) { 436 // Could be a year. 437 $permlink_guess = 'year_month_day_title'; 438 } elseif (!isset($permlink_guess) && isset($permlink_modes[$u1]) && ($n > 1 || !empty($no_trailing_slash))) { 439 $permlink_guess = $permlink_modes[$u1]; 440 } 441 442 if (!$is_404 && empty($out['id'])) { 443 // Then see if the prefs-defined permlink scheme is usable. 444 switch (empty($permlink_guess) ? $permlink_mode : $permlink_guess) { 445 case 'section_id_title': 446 $out['s'] = $u1; 447 448 if (is_numeric($u2)) { 449 $out['id'] = $u2; 450 } else { 451 $title = empty($u2) ? null : $u2; 452 } 453 454 break; 455 456 case 'section_category_title': 457 case 'breadcrumb_title': 458 $out['s'] = $u1; 459 $title = $n < 2 || empty($un) ? null : $un; 460 isset($title) || $n <= 2 or $out['c'] = $out[$n-1]; 461 462 break; 463 464 case 'year_month_day_title': 465 if (@checkdate(!empty($u2) ? $u2 : 1, !empty($u3) ? $u3 : 1, $u1)) { 466 $title = empty($u4) ? null : $u4; 467 $month = array($u1); 468 469 if (!empty($u2)) { 470 $month[] = str_pad(ltrim($u2), 2, '0', STR_PAD_LEFT); 471 empty($u3) or $month[] = str_pad(ltrim($u3), 2, '0', STR_PAD_LEFT); 472 } 473 } elseif (@checkdate(!empty($u3) ? $u3 : 1, !empty($u4) ? $u4 : 1, $u2)) { 474 $title = empty($u5) ? null : $u5; 475 $out['s'] = $u1; 476 $month = array($u2); 477 478 if (!empty($u3)) { 479 $month[] = str_pad(ltrim($u3), 2, '0', STR_PAD_LEFT); 480 empty($u4) or $month[] = str_pad(ltrim($u4), 2, '0', STR_PAD_LEFT); 481 } 482 } elseif (empty($u3)) { 483 $out['s'] = $u1; 484 $title = empty($u2) ? null : $u2; 485 } else { 486 $is_404 = true; 487 } 488 489 break; 490 491 case 'section_title': 492 $out['s'] = $u1; 493 $title = empty($u2) ? null : $u2; 494 495 break; 496 497 case 'id_title': 498 if (is_numeric($u1)) { 499 $out['id'] = $u1; 500 } else { 501 // We don't want to miss the /section/ pages. 502 $out['s'] = $u1; 503 $title = empty($u2) ? null : $u2; 504 } 505 506 break; 507 508 default: 509 if (isset($u2)) { 510 $out['s'] = $u1; 511 $title = empty($u2) ? null : $u2; 512 } else { 513 $title = $u1; 514 } 515 } 516 } 517 } 518 } else { 519 $out['s'] = 'default'; 520 } 521 } 522 523 $out['context'] = validContext($out['context']); 524 525 // Validate dates 526 if ($out['month']) { 527 $date = empty($month) ? '' : implode('-', $month); 528 $month = explode('-', $out['month'], 3) + (!empty($month) ? $month : array()); 529 530 if (!$date || strpos($date, $out['month']) === 0 || strpos($out['month'], $date) === 0) { 531 $month = implode('-', $month); 532 } else { 533 $out['month'] = $month = ''; 534 $is_404 = true; 535 } 536 } elseif (isset($month)) { 537 $month = implode('-', $month); 538 !empty($title) or $out['month'] = $month; 539 } 540 541 // Resolve AuthorID from Authorname. 542 if ($out['author']) { 543 $name = safe_field('name', 'txp_users', "RealName LIKE '".doSlash($out['author'])."'"); 544 545 if ($name) { 546 $out['realname'] = $out['author']; 547 $out['author'] = $name; 548 } else { 549 $out['author'] = $out['realname'] = ''; 550 $is_404 = true; 551 } 552 } else { 553 $out['realname'] = ''; 554 } 555 556 // Prevent to get the id for file_downloads. 557 if ($out['s'] == 'file_download') { 558 if (is_numeric($out['id'])) { 559 global $thisfile; 560 561 // Undo the double-encoding workaround for .gz files; 562 // @see filedownloadurl(). 563 if (!empty($out['filename'])) { 564 $out['filename'] = preg_replace('/gz&$/i', 'gz', $out['filename']); 565 } 566 567 $fn = empty($out['filename']) ? '' : " AND filename = '".doSlash($out['filename'])."'"; 568 $rs = safe_row('*', 'txp_file', "id = ".intval($out['id'])." AND status = ".STATUS_LIVE." AND created <= ".now('created').$fn); 569 570 $thisfile = $rs ? file_download_format_info($rs) : null; 571 } 572 573 $is_404 = $is_404 || empty($rs); 574 $out = array_merge($out, $is_404 ? array('id' => '', 'file_error' => 404, 'status' => 404) : $rs); 575 } 576 577 // Allow article preview. 578 elseif (gps('txpreview')) { 579 doAuth(); 580 581 if (!has_privs('article.preview')) { 582 txp_status_header('401 Unauthorized'); 583 exit(hed('401 Unauthorized', 1).graf(gTxt('restricted_area'))); 584 } 585 586 global $nolog; 587 588 $nolog = true; 589 header('Cache-Control: no-cache, no-store, max-age=0'); 590 $rs = safe_row("ID AS id, Section AS s", 'textpattern', "ID = ".intval(gps('txpreview'))." LIMIT 1"); 591 592 if ($rs) { 593 $is_404 = false; 594 $out = array_merge($out, $rs); 595 } 596 } elseif ($out['context'] == 'article') { 597 if (!$is_404 && empty($thisarticle) && (!empty($out['id']) || !empty($title))) { 598 if (empty($out['s']) || $out['s'] === 'default') { 599 $rs = !empty($out['id']) ? 600 lookupByID($out['id']) : 601 lookupByDateTitle(isset($month) ? $month : '', $title); 602 } else { 603 $rs = !empty($out['id']) ? 604 lookupByIDSection($out['id'], $out['s']) : 605 lookupByTitleSection($title, $out['s']); 606 } 607 608 $out['id'] = (!empty($rs['ID'])) ? $rs['ID'] : ''; 609 $out['s'] = (!empty($rs['Section'])) ? $rs['Section'] : ''; 610 $is_404 = $is_404 || (empty($out['s']) || empty($out['id'])); 611 } 612 613 if (!empty($out['s']) && $out['s'] !== 'default') { 614 if (!isset($txp_sections[$out['s']])) { 615 $out['s'] = ''; 616 $is_404 = true; 617 } 618 } 619 } 620 621 // Existing category in messy or clean URL? 622 if (!empty($out['c'])) { 623 global $thiscategory; 624 625 if (!($thiscategory = ckCat($out['context'], $out['c']))) { 626 $is_404 = true; 627 $out['c'] = ''; 628 $thiscategory = null; 629 } else { 630 $thiscategory += array('is_first' => true, 'is_last' => true, 'section' => $out['s']); 631 } 632 } 633 634 // Stats: found or not. 635 $out['status'] = ($is_404 ? '404' : '200'); 636 $out['pg'] = is_numeric($out['pg']) ? intval($out['pg']) : ''; 637 $out['id'] = is_numeric($out['id']) ? intval($out['id']) : ''; 638 $id = $out['id']; 639 640 if (!$is_404) { 641 $out['s'] = empty($out['s']) ? 'default' : $out['s']; 642 } 643 644 // Hackish. 645 global $is_article_list; 646 647 if (empty($id)) { 648 $is_article_list = true; 649 } 650 651 if (!$is_404 && $id && $out['s'] !== 'file_download') { 652 if (empty($thisarticle)) { 653 $a = safe_row( 654 "*, UNIX_TIMESTAMP(Posted) AS uPosted, UNIX_TIMESTAMP(Expires) AS uExpires, UNIX_TIMESTAMP(LastMod) AS uLastMod", 655 'textpattern', 656 "ID = $id".(gps('txpreview') ? '' : " AND Status IN (".STATUS_LIVE.",".STATUS_STICKY.")") 657 ); 658 659 if ($a) { 660 populateArticleData($a); 661 } 662 } elseif (!gps('txpreview') && !in_array($thisarticle['status'], array(STATUS_LIVE, STATUS_STICKY))) { 663 unset($thisarticle); 664 } 665 666 if (!empty($thisarticle)) { 667 unset($thiscategory); 668 $uExpires = $thisarticle['expires']; 669 $out['id_keywords'] = $thisarticle['keywords']; 670 $out['id_author'] = $thisarticle['authorid']; 671 672 if (!$publish_expired_articles && $uExpires && time() > $uExpires) { 673 $out['status'] = '410'; 674 } 675 } else { 676 $is_404 = true; 677 } 678 } 679 680 // By this point we should know the section, so grab its page and CSS. 681 // Logged-in users with enough privs use the skin they're currently editing. 682 if (txpinterface != 'css') { 683 $userInfo = is_logged_in(); 684 685 if ($userInfo && has_privs('skin.preview', $userInfo)) { 686 foreach ($txp_sections as &$rs) { 687 empty($rs['dev_skin']) or $rs['skin'] = $rs['dev_skin']; 688 empty($rs['dev_page']) or $rs['page'] = $rs['dev_page']; 689 empty($rs['dev_css']) or $rs['css'] = $rs['dev_css']; 690 } 691 692 unset($rs); 693 } 694 695 $s = empty($out['s']) || $is_404 || !isset($txp_sections[$out['s']]) ? 'default' : $out['s']; 696 $rs = $txp_sections[$s]; 697 698 $out['skin'] = isset($rs['skin']) ? $rs['skin'] : ''; 699 $out['page'] = isset($rs['page']) ? $rs['page'] : ''; 700 $out['css'] = isset($rs['css']) ? $rs['css'] : ''; 701 } 702 703 // These are deprecated as of Textpattern v1.0 - leaving them here for 704 // plugin compatibility. 705 $out['path_from_root'] = rhu; 706 $out['pfr'] = rhu; 707 708 $out['path_to_site'] = $path_to_site; 709 $out['permlink_mode'] = $permlink_mode; 710 $out['sitename'] = $sitename; 711 712 return $out; 713 } 714 715 // textpattern() is the function that assembles a page, based on 716 // the variables passed to it by pretext(); 717 718 // ------------------------------------------------------------- 719 720 function textpattern() 721 { 722 global $pretext, $production_status, $has_article_tag; 723 724 $has_article_tag = false; 725 726 callback_event('textpattern'); 727 728 if ($pretext['status'] == '404') { 729 txp_die(gTxt('404_not_found'), '404'); 730 } 731 732 if ($pretext['status'] == '410') { 733 txp_die(gTxt('410_gone'), '410'); 734 } 735 736 // Useful for clean URLs with error-handlers. 737 txp_status_header('200 OK'); 738 739 set_error_handler('tagErrorHandler'); 740 $html = parse_page($pretext['page'], $pretext['skin']); 741 742 if ($html === false) { 743 txp_die(gTxt('unknown_section'), '404'); 744 } 745 746 // Make sure the page has an article tag if necessary. 747 if (!$has_article_tag && $production_status != 'live' && $pretext['context'] == 'article' && (!empty($pretext['id']) || !empty($pretext['c']) || !empty($pretext['q']) || !empty($pretext['pg']))) { 748 trigger_error(gTxt('missing_article_tag', array('{page}' => $pretext['page']))); 749 } 750 751 restore_error_handler(); 752 set_headers(); 753 echo ltrim($html); 754 755 callback_event('textpattern_end'); 756 } 757 758 // ------------------------------------------------------------- 759 function output_component($n = '') 760 { 761 global $pretext; 762 static $mimetypes = null, $typequery = null; 763 764 if (!isset($mimetypes)) { 765 $null = null; 766 $mimetypes = Txp::get('Textpattern\Skin\Form')->getMimeTypes(); 767 $typequery = " AND type IN ('".implode("','", doSlash(array_keys($mimetypes)))."')"; 768 } 769 770 if (!$n || !is_scalar($n) || empty($mimetypes)) { 771 return; 772 } 773 774 $t = $pretext['skin']; 775 $skinquery = $t ? " AND skin='".doSlash($t)."'" : ''; 776 777 $n = do_list_unique(doSlash($n)); 778 $name = join("','", $n); 779 $order = count($n) > 1 ? " ORDER BY FIELD(name, '$name')" : ''; 780 $mimetype = null; 781 $assets = array(); 782 783 if (!empty($name) && $rs = safe_rows('Form, type', 'txp_form', "name IN ('$name')".$typequery.$skinquery.$order)) { 784 foreach ($rs as $row) { 785 if (!isset($mimetype) || $mimetypes[$row['type']] == $mimetype) { 786 $assets[] = $row['Form']; 787 $mimetype = $mimetypes[$row['type']]; 788 } 789 } 790 791 set_error_handler('tagErrorHandler'); 792 @header('Content-Type: '.$mimetype.'; charset=utf-8'); 793 echo ltrim(parse_page(null, null, implode(n, $assets))); 794 restore_error_handler(); 795 } 796 } 797 798 // ------------------------------------------------------------- 799 function output_css($s = '', $n = '', $t = '') 800 { 801 $order = ''; 802 803 if ($n) { 804 if (!is_array($n)) { 805 $n = do_list_unique($n); 806 } 807 808 $cssname = join("','", doSlash($n)); 809 810 if (count($n) > 1) { 811 $order = " ORDER BY FIELD(name, '$cssname')"; 812 } 813 } elseif ($s && $res = safe_row('css, skin', 'txp_section', "name='".doSlash($s)."'")) { 814 $cssname = $res['css']; 815 $t or $t = $res['skin']; 816 } 817 818 if (!empty($cssname)) { 819 $skinquery = $t ? " AND skin='".doSlash($t)."'" : ''; 820 $css = join(n, safe_column_num('css', 'txp_css', "name IN ('$cssname')".$skinquery.$order)); 821 set_error_handler('tagErrorHandler'); 822 @header('Content-Type: text/css; charset=utf-8'); 823 echo $css; 824 restore_error_handler(); 825 } 826 } 827 828 // ------------------------------------------------------------- 829 function output_file_download($filename) 830 { 831 global $file_error, $file_base_path, $pretext; 832 833 set_headers(array( 834 'last-modified' => false, 835 'etag' => false 836 ), true); 837 838 callback_event('file_download'); 839 840 if (!isset($file_error)) { 841 $filename = sanitizeForFile($filename); 842 $fullpath = build_file_path($file_base_path, $filename); 843 844 if (is_file($fullpath)) { 845 // Discard any error PHP messages. 846 ob_clean(); 847 $filesize = filesize($fullpath); 848 $sent = 0; 849 850 set_headers(array( 851 'content-type' => 'application/octet-stream', 852 'content-disposition' => 'attachment; filename="'.$filename.'"', 853 'content-length' => $filesize, 854 // Fix for IE6 PDF bug on servers configured to send cache headers. 855 'cache-control' => 'private' 856 )); 857 858 @ini_set("zlib.output_compression", "Off"); 859 @set_time_limit(0); 860 @ignore_user_abort(true); 861 862 if ($file = fopen($fullpath, 'rb')) { 863 while (!feof($file) and (connection_status() == 0)) { 864 echo fread($file, 1024 * 64); 865 $sent += (1024 * 64); 866 ob_flush(); 867 flush(); 868 } 869 870 fclose($file); 871 872 // Record download. 873 if ((connection_status() == 0) and !connection_aborted()) { 874 safe_update('txp_file', "downloads = downloads + 1", "id = ".intval($pretext['id'])); 875 } else { 876 $pretext['request_uri'] .= ($sent >= $filesize) 877 ? '#aborted' 878 : "#aborted-at-".floor($sent * 100 / $filesize)."%"; 879 } 880 881 log_hit('200'); 882 } 883 } else { 884 $file_error = 404; 885 } 886 } 887 888 // Deal with error. 889 if (isset($file_error)) { 890 switch ($file_error) { 891 case 403: 892 txp_die(gTxt('403_forbidden'), '403'); 893 break; 894 case 404: 895 txp_die(gTxt('404_not_found'), '404'); 896 break; 897 default: 898 txp_die(gTxt('500_internal_server_error'), '500'); 899 break; 900 } 901 } 902 } 903 904 // article() is called when parse() finds a <txp:article /> tag. 905 // If an $id has been established, we output a single article, 906 // otherwise, output a list. 907 908 // ------------------------------------------------------------- 909 function article($atts, $thing = null) 910 { 911 global $is_article_body, $has_article_tag; 912 913 if ($is_article_body) { 914 trigger_error(gTxt('article_tag_illegal_body')); 915 916 return ''; 917 } 918 919 $has_article_tag = true; 920 921 return parseArticles($atts, '0', $thing); 922 } 923 924 // ------------------------------------------------------------- 925 926 function doArticles($atts, $iscustom, $thing = null) 927 { 928 global $pretext, $thispage, $trace, $txp_item, $txp_sections; 929 static $date_fields = array('posted' => 'Posted', 'modified' => 'LastMod', 'expires' => 'Expires'); 930 931 extract($pretext); 932 933 if ($iscustom) { 934 // Custom articles must not render search results. 935 $q = ''; 936 } 937 938 // Getting attributes. 939 if (isset($thing) && !isset($atts['form'])) { 940 $atts['form'] = ''; 941 } 942 943 $theAtts = filterAtts($atts, $iscustom); 944 extract($theAtts); 945 $issticky = $theAtts['status'] == STATUS_STICKY; 946 947 $pg or $pg = 1; 948 $custom_pg = $pgonly && $pgonly !== true && !is_numeric($pgonly); 949 $pgby = intval(empty($pageby) || $pageby === true ? ($custom_pg ? 1 : $limit) : $pageby); 950 951 if ($offset === true || !$iscustom && !$issticky) { 952 $offset = $offset === true ? 0 : intval($offset); 953 $pgoffset = ($pg - 1) * $pgby + $offset; 954 } else { 955 $pgoffset = $offset = intval($offset); 956 } 957 958 if (isset($fields)) { 959 $what = $groupby = $sortby = array(); 960 $column_map = $date_fields + article_column_map(); 961 $reg_fields = implode('|', array_keys($column_map)); 962 963 foreach (do_list_unique(strtolower($fields)) as $field) { 964 if (preg_match("/^(?:(avg|max|min|sum)\s*\(\s*)?($reg_fields)(?:\s*\))?$/", $field, $matches)) { 965 $field = $matches[2]; 966 $column = $column_map[$field]; 967 $sortby[$field] = $column; 968 969 if (!empty($matches[1])) { 970 $alias = ' AS '.$column; 971 $what[$field] = strtoupper($matches[1]).'('.$column.')'; 972 } else { 973 $alias = ''; 974 $what[$field] = $column; 975 !is_array($groupby) or $groupby[$field] = $column; 976 } 977 978 if (isset($date_fields[$field])) { 979 $what[$field] .= $alias.', UNIX_TIMESTAMP('.$what[$field].') AS u'.$column; 980 } elseif ($alias) { 981 $what[$field] .= $alias; 982 } elseif ($field === 'thisid') { 983 $groupby = false; 984 } 985 } 986 } 987 988 $fields = implode(', ', $what); 989 $groupby = $groupby ? implode(', ', $groupby) : ''; 990 991 if ($groupby && !$sort) { 992 $sort = implode(', ', $sortby); 993 } 994 } elseif ($custom_pg) { 995 $groupby = trim($pgonly); 996 } 997 998 // Give control to search, if necessary. 999 if ($q && !$issticky) { 1000 $s_filter = $searchall ? filterFrontPage('Section', 'searchable') : (empty($s) || $s == 'default' ? filterFrontPage() : ''); 1001 $q = trim($q); 1002 $quoted = ($q[0] === '"') && ($q[strlen($q) - 1] === '"'); 1003 $q = doSlash($quoted ? trim(trim($q, '"')) : $q); 1004 1005 // Searchable article fields are limited to the columns of the 1006 // textpattern table and a matching fulltext index must exist. 1007 $cols = do_list_unique(get_pref('searchable_article_fields')); 1008 1009 if (empty($cols) or $cols[0] == '') { 1010 $cols = array('Title', 'Body'); 1011 } 1012 1013 $search_terms = preg_replace('/\s+/', ' ', str_replace(array('\\', '%', '_', '\''), array('\\\\', '\\%', '\\_', '\\\''), $q)); 1014 $score = ", MATCH (`".join("`, `", $cols)."`) AGAINST ('$q') AS score"; 1015 1016 if ($quoted || empty($m) || $m === 'exact') { 1017 for ($i = 0; $i < count($cols); $i++) { 1018 $cols[$i] = "`$cols[$i]` LIKE '%$search_terms%'"; 1019 } 1020 } else { 1021 $colJoin = ($m === 'any') ? "OR" : "AND"; 1022 $search_terms = explode(' ', $search_terms); 1023 for ($i = 0; $i < count($cols); $i++) { 1024 $like = array(); 1025 foreach ($search_terms as $search_term) { 1026 $like[] = "`$cols[$i]` LIKE '%$search_term%'"; 1027 } 1028 $cols[$i] = "(".join(" $colJoin ", $like).")"; 1029 } 1030 } 1031 1032 $cols = join(" OR ", $cols); 1033 $search = " AND ($cols) $s_filter"; 1034 $fname = $searchform ? $searchform : (isset($thing) ? '' : 'search_results'); 1035 1036 if (!$sort) { 1037 $sort = 'score DESC'; 1038 } 1039 } else { 1040 $search = $score = ''; 1041 $fname = (!empty($listform) ? $listform : $form); 1042 1043 if (!$sort) { 1044 $sort = "Posted DESC"; 1045 } 1046 } 1047 1048 $where = $theAtts['*'].$search; 1049 !empty($fields) or $fields = '*'; 1050 1051 // Do not paginate if we are on a custom list. 1052 if (!$iscustom && !$issticky) { 1053 if ($pageby === true || empty($thispage) && (!isset($pageby) || $pageby)) { 1054 $grand_total = getCount(array('textpattern', !empty($groupby) ? "DISTINCT $groupby" : '*'), $where); 1055 $total = $grand_total - $offset; 1056 $numPages = $pgby ? ceil($total / $pgby) : 1; 1057 $trace->log("[Found: $total articles, $numPages pages]"); 1058 1059 // Send paging info to txp:newer and txp:older. 1060 $thispage = array( 1061 'pg' => $pg, 1062 'numPages' => $numPages, 1063 's' => $s, 1064 'c' => $c, 1065 'context' => 'article', 1066 'grand_total' => $grand_total, 1067 'total' => $total 1068 ); 1069 } 1070 1071 if ($pgonly) { 1072 return; 1073 } 1074 } elseif ($pgonly) { 1075 $total = getCount(array('textpattern', !empty($groupby) ? "DISTINCT $groupby" : '*'), $where); 1076 $total -= $offset; 1077 1078 return $pgby ? ceil($total / $pgby) : $total; 1079 } 1080 1081 // Preserve order of custom article ids unless 'sort' attribute is set. 1082 if (!empty($id) && empty($atts['sort']) && empty($groupby)) { 1083 $safe_sort = "FIELD(ID, ".$id."), ".$sort; 1084 } else { 1085 $safe_sort = $sort; 1086 } 1087 1088 $fields !== '*' or $fields = null; 1089 1090 if ($fields && !empty($groupby)) { 1091 $where .= " GROUP BY $groupby"; 1092 $fields .= ', COUNT(*) AS count'; 1093 $score = ''; 1094 } 1095 1096 $rs = safe_rows_start( 1097 ($fields ? $fields : "*, UNIX_TIMESTAMP(Posted) AS uPosted, UNIX_TIMESTAMP(Expires) AS uExpires, UNIX_TIMESTAMP(LastMod) AS uLastMod").$score, 1098 'textpattern', 1099 "$where ORDER BY $safe_sort LIMIT ".intval($pgoffset).", ".intval($limit) 1100 ); 1101 1102 if ($rs && $last = numRows($rs)) { 1103 $count = 0; 1104 $articles = array(); 1105 $chunk = false; 1106 $old_item = $txp_item; 1107 $txp_item['total'] = $last; 1108 unset($txp_item['breakby']); 1109 $groupby = !$breakby || is_numeric(strtr($breakby, ' ,', '00')) ? 1110 false : 1111 (preg_match('@<(?:'.TXP_PATTERN.'):@', $breakby) ? 1 : 2); 1112 1113 while ($count++ <= $last) { 1114 global $thisarticle; 1115 1116 if ($a = nextRow($rs)) { 1117 populateArticleData($a); 1118 $thisarticle['is_first'] = ($count == 1); 1119 $thisarticle['is_last'] = ($count == $last); 1120 $txp_item['count'] = isset($a['count']) ? $a['count'] : $count; 1121 1122 $newbreak = !$groupby ? $count : 1123 ($groupby === 1 ? 1124 parse($breakby, true, false) : 1125 parse_form($breakby) 1126 ); 1127 } else { 1128 $newbreak = null; 1129 } 1130 1131 if (isset($txp_item['breakby']) && $newbreak !== $txp_item['breakby']) { 1132 if ($breakform) { 1133 $tmparticle = $thisarticle; 1134 $thisarticle = $oldarticle; 1135 $newform = parse_form($breakform); 1136 $chunk = str_replace('<+>', $chunk, $newform); 1137 $thisarticle = $tmparticle; 1138 } 1139 1140 $chunk === false or $articles[] = $chunk; 1141 $chunk = false; 1142 } 1143 1144 if ($count <= $last) { 1145 $item = false; 1146 1147 if ($allowoverride && !empty($a['override_form'])) { 1148 $item = parse_form($a['override_form'], $txp_sections[$a['Section']]['skin']); 1149 } elseif ($fname) { 1150 $item = parse_form($fname); 1151 } 1152 1153 if ($item !== false) { 1154 $item = txp_sandbox(array(), $item, false); 1155 } elseif (isset($thing)) { 1156 $item = txp_sandbox(array(), $thing); 1157 } 1158 1159 $item === false or $chunk .= $item; 1160 } 1161 1162 $oldarticle = $thisarticle; 1163 $txp_item['breakby'] = $newbreak; 1164 unset($GLOBALS['thisarticle']); 1165 } 1166 1167 if ($groupby) { 1168 $breakby = ''; 1169 } 1170 1171 $txp_item = $old_item; 1172 } 1173 1174 return !empty($articles) ? 1175 doLabel($label, $labeltag).doWrap($articles, $wraptag, compact('break', 'breakby', 'breakclass', 'class')) : 1176 ($thing ? parse($thing, false) : ''); 1177 } 1178 1179 // ------------------------------------------------------------- 1180 1181 function doArticle($atts, $thing = null) 1182 { 1183 global $pretext, $thisarticle; 1184 1185 if (isset($thing) && !isset($atts['form'])) { 1186 $atts['form'] = ''; 1187 } 1188 1189 $oldAtts = filterAtts(); 1190 $atts = filterAtts($atts); 1191 extract($atts); 1192 1193 // No output required, only setting atts. 1194 if ($pgonly) { 1195 return ''; 1196 } 1197 1198 if (empty($thisarticle) || $thisarticle['thisid'] != $pretext['id']) { 1199 $id = assert_int($pretext['id']); 1200 $thisarticle = null; 1201 $where = $atts['*']; 1202 1203 $rs = safe_row( 1204 "*, UNIX_TIMESTAMP(Posted) AS uPosted, UNIX_TIMESTAMP(Expires) AS uExpires, UNIX_TIMESTAMP(LastMod) AS uLastMod", 1205 'textpattern', 1206 "ID = $id AND $where LIMIT 1" 1207 ); 1208 1209 if ($rs) { 1210 populateArticleData($rs); 1211 } 1212 } 1213 1214 if (!empty($thisarticle) && (in_list($thisarticle['status'], $status) || gps('txpreview'))) { 1215 extract($thisarticle); 1216 $thisarticle['is_first'] = $thisarticle['is_last'] = 1; 1217 $article = false; 1218 1219 if ($allowoverride && $override_form) { 1220 $article = parse_form($override_form); 1221 } elseif ($form) { 1222 $article = parse_form($form); 1223 } 1224 1225 if (isset($thing) && $article === false) { 1226 $article = parse($thing); 1227 } 1228 1229 if ($article !== false && get_pref('use_comments') && get_pref('comments_auto_append')) { 1230 $article .= parse_form('comments_display'); 1231 } 1232 1233 unset($GLOBALS['thisarticle']); 1234 } else { 1235 // Restore atts to the previous article filter criteria. 1236 filterAtts($oldAtts ? $oldAtts : false); 1237 } 1238 1239 return $article !== false ? $article : ($thing ? parse($thing, false) : ''); 1240 } 1241 1242 // ------------------------------------------------------------- 1243 1244 function article_custom($atts, $thing = null) 1245 { 1246 return parseArticles($atts, '1', $thing); 1247 } 1248 1249 // ------------------------------------------------------------- 1250 1251 function parseArticles($atts, $iscustom = 0, $thing = null) 1252 { 1253 global $pretext, $is_article_list; 1254 $old_ial = $is_article_list; 1255 $is_article_list = empty($pretext['id']) || $iscustom; 1256 article_push(); 1257 $r = ($is_article_list) ? doArticles($atts, $iscustom, $thing) : doArticle($atts, $thing); 1258 article_pop(); 1259 $is_article_list = $old_ial; 1260 1261 return $r; 1262 } 1263 1264 // ------------------------------------------------------------- 1265 1266 function makeOut() 1267 { 1268 $array['status'] = '200'; 1269 1270 foreach (func_get_args() as $a) { 1271 $in = gps($a); 1272 1273 if (is_scalar($in)) { 1274 $array[$a] = strval($in); 1275 } else { 1276 $array[$a] = ''; 1277 $array['status'] = '404'; 1278 } 1279 } 1280 1281 return $array; 1282 } 1283 1284 // ------------------------------------------------------------- 1285 1286 function validContext($context) 1287 { 1288 static $valid = null; 1289 1290 if (empty($valid)) { 1291 foreach (array('article', 'image', 'file', 'link') as $type) { 1292 $valid[gTxt($type.'_context')] = $type; 1293 $valid[$type] = $type; 1294 } 1295 } 1296 1297 return isset($valid[$context]) ? $valid[$context] : 'article'; 1298 } 1299 1300 /** 1301 * Chops a request string into URL-decoded path parts. 1302 * 1303 * @param string $req Request string 1304 * @return array 1305 * @package URL 1306 */ 1307 1308 function chopUrl($req, $min = 4) 1309 { 1310 $req = strtok($req, '?'); 1311 $req = preg_replace('/index\.php$/i', '', $req); 1312 $r = array_map('urldecode', explode('/', strtolower($req))); 1313 $n = isset($min) ? max($min, count($r)) : count($r); 1314 $o = array('u0' => $req); 1315 1316 for ($i = 1; $i < $n; $i++) { 1317 $o['u'.$i] = (isset($r[$i])) ? $r[$i] : null; 1318 } 1319 1320 return $o; 1321 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
title