/ catalog.php
catalog.php
1 <?php 2 // haha i named this file catalog.php now everyone but me will find it 3 // awkward to type 4 // nvm moot is a nerd 5 function generate_catalog() 6 { 7 global $log; 8 9 if( !STATIC_REBUILD ) log_cache(); 10 $catalogjson = array(); 11 12 $i = 0; 13 14 foreach( $log['THREADS'] as $thread ) { 15 catalog_thread($log[$thread], $catalogjson, $i); 16 ++$i; 17 } 18 19 $catalogjson = array( 20 'threads' => $catalogjson, 21 22 'count' => count( $log['THREADS'] ), 23 'slug' => BOARD_DIR, 24 'anon' => S_ANONAME, 25 'mtime' => time(), 26 'pagesize' => DEF_PAGES 27 ); 28 29 if (!REPLIES_SHOWN && IS_REBUILDD) { 30 $catalogjson['no_lr'] = true; 31 } 32 33 if (SHOW_COUNTRY_FLAGS) { 34 $catalogjson['flags'] = true; 35 } 36 37 if( SPOILERS ) $catalogjson['custom_spoiler'] = (int)SPOILER_NUM; 38 39 $catalogjson = json_encode( $catalogjson ); 40 41 $catalog = catalog( $catalogjson ); 42 43 print_page( INDEX_DIR . 'catalog.html', $catalog ); 44 45 return true; 46 } 47 48 function catalog_thread($res, &$json, $pos) 49 { 50 global $log; 51 52 $reps = $res['replycount']; 53 $sub = $res['sub']; 54 55 $imgs = $res['imgreplycount']; 56 $last_reply_id = null; 57 $capcodelist = array(); 58 59 if (TEXT_ONLY) { 60 $time_prop = 'now'; 61 } 62 else { 63 $time_prop = 'time'; 64 } 65 66 foreach( $res['children'] as $reply => $unused ) { 67 $last_reply_id = $reply; 68 69 if( META_BOARD && $log[$reply]['capcode'] != 'none' ) { 70 $tCapcode = $log[$reply]['capcode']; 71 if( $tCapcode == 'admin_highlight' ) $tCapcode = 'admin'; 72 73 if( $tCapcode != 'none' ) { 74 $capcodelist[$tCapcode] = 1; 75 } 76 } 77 } 78 79 if ($last_reply_id === null) { 80 $last_reply = array( 'id' => $res['no'] ); 81 } 82 else { 83 $lr_data = $log[$last_reply_id]; 84 85 $last_reply = array( 86 'id' => $last_reply_id, 87 'date' => $lr_data['time'] 88 ); 89 90 if( $lr_data['capcode'] != 'none' ) $last_reply['capcode'] = $lr_data['capcode']; 91 92 $force_anon = ( ( FORCED_ANON || META_BOARD ) && $lr_data['capcode'] != 'admin' && $lr_data['capcode'] != 'admin_highlight' ); 93 94 if( !$force_anon ) { 95 if( strpos( $lr_data['name'], '</span> <span class="postertrip">' ) !== false ) { 96 list( $last_reply['author'], $last_reply['trip'] ) = explode( '</span> <span class="postertrip">', $lr_data['name'] ); 97 } else { 98 $last_reply['author'] = $lr_data['name']; 99 } 100 } else { 101 $last_reply['author'] = S_ANONAME; 102 } 103 } 104 105 $json[$res['no']] = array( 106 'date' => $res[$time_prop], 107 'file' => mb_convert_encoding($res['filename'], 'UTF-8', 'UTF-8') . $res['ext'], 108 'r' => $reps, 109 'i' => $imgs, 110 'lr' => $last_reply, 111 'b' => $pos 112 ); 113 /* 114 if( META_BOARD && $capcodelist ) { 115 $json[$res['no']]['capcodereps'] = implode(',', array_keys($capcodelist)); 116 } 117 */ 118 if ($res['capcode'] == 'none') { 119 if (SHOW_COUNTRY_FLAGS && (!ENABLE_BOARD_FLAGS || $res['board_flag'] == '')) { 120 $json[$res['no']]['country'] = $res['country']; 121 } 122 } 123 124 $com = $res['com']; 125 126 if( strpos( $com, 'class="abbr"' ) !== false ) { 127 $com = preg_replace( '#(<br>)+<span class="abbr">(.+)$#s', '', $com ); 128 } 129 130 if (!TEXT_ONLY) { 131 $com = preg_replace( '#(<br>)+#', ' ', $com ); 132 } 133 else { 134 $com = preg_replace( '#(<br>)+#', "\n", $com ); 135 } 136 137 if (BOARD_DIR === 'b') { // fixme, hardcoded for now 138 $com = truncate_comment($com, 300, true); 139 } 140 else { 141 if (SJIS_TAGS) { 142 $com = preg_replace('/<span class="sjis".+?<\/span>/', '[SJIS]', $com); 143 } 144 $com = strip_tags($com, '<s>'); 145 } 146 147 $has_spoilers = (bool)SPOILERS; 148 149 if (!$res['permaage'] && !$res['sticky']) { 150 if( $reps >= MAX_RES ) $json[$res['no']]['bumplimit'] = 1; 151 if( $imgs >= MAX_IMGRES ) $json[$res['no']]['imagelimit'] = 1; 152 } 153 154 if( $res['sticky'] ) $json[$res['no']]['sticky'] = 1; 155 if( $res['closed'] ) $json[$res['no']]['closed'] = 1; 156 157 if( $res['capcode'] != 'none' ) $json[$res['no']]['capcode'] = $res['capcode']; 158 159 $force_anon = ( ( FORCED_ANON || META_BOARD ) && $res['capcode'] != 'admin' && $res['capcode'] != 'admin_highlight' ); 160 161 if( !$force_anon ) { 162 if( strpos( $res['name'], '</span> <span class="postertrip">' ) !== false ) { 163 list( $json[$res['no']]['author'], $json[$res['no']]['trip'] ) = explode( '</span> <span class="postertrip">', $res['name'] ); 164 } else { 165 $json[$res['no']]['author'] = $res['name']; 166 } 167 } else { 168 $json[$res['no']]['author'] = S_ANONAME; 169 } 170 171 if( $res['fsize'] != 0 && $res['filedeleted'] != 1 ) { 172 $json[$res['no']]['imgurl'] = $res['tim']; 173 $json[$res['no']]['tn_w'] = $res['tn_w']; 174 $json[$res['no']]['tn_h'] = $res['tn_h']; 175 } 176 177 if( $res['filedeleted'] == 1 ) $json[$res['no']]['imgdel'] = true; 178 179 if( strpos( $res['sub'], 'SPOILER<>' ) !== false ) { 180 $json[$res['no']]['imgspoiler'] = true; 181 $sub = substr( $res['sub'], 9 ); 182 } 183 184 $json[$res['no']]['sub'] = $sub; 185 $json[$res['no']]['teaser'] = $com; 186 } 187 188 function catalog($catjson) { 189 $nav = file_get_contents_cached( NAV_TXT ); 190 $foot = file_get_contents_cached( NAV2_TXT ); 191 192 $nav = preg_replace( '/href="(\/\/boards.(?:4chan|4channel).org)?\/([a-z0-9]+)\/"/', 'href="$1/$2/catalog"', $nav ); 193 $nav = preg_replace( '/href="(\/\/boards.(?:4chan|4channel).org)?\/f\/catalog"/', 'href="$1/f/"', $nav ); 194 195 $title = strip_tags( TITLE ); 196 197 $js = ''; 198 199 // danbo ads start 200 if (defined('ADS_DANBO') && ADS_DANBO) { 201 $js .= '<script>'; 202 203 if (DEFAULT_BURICHAN) { 204 $js .= "var danbo_rating = '__SFW__';"; 205 } 206 else { 207 $js .= "var danbo_rating = '__NSFW__';"; 208 } 209 210 $_danbo_fallbacks = []; 211 212 if (defined('AD_BIDGEAR_TOP') && AD_BIDGEAR_TOP) { 213 $_danbo_fallbacks['t_bg'] = AD_BIDGEAR_TOP; 214 } 215 else { 216 if (defined('AD_ABC_TOP_DESKTOP') && AD_ABC_TOP_DESKTOP) { 217 $_danbo_fallbacks['t_abc_d'] = AD_ABC_TOP_DESKTOP; 218 } 219 220 if (defined('AD_ABC_TOP_MOBILE') && AD_ABC_TOP_MOBILE) { 221 $_danbo_fallbacks['t_abc_m'] = AD_ABC_TOP_MOBILE; 222 } 223 } 224 225 if (defined('AD_BIDGEAR_BOTTOM') && AD_BIDGEAR_BOTTOM) { 226 $_danbo_fallbacks['b_bg'] = AD_BIDGEAR_BOTTOM; 227 } 228 else if (defined('AD_ABC_BOTTOM_MOBILE') && AD_ABC_BOTTOM_MOBILE) { 229 $_danbo_fallbacks['b_abc_m'] = AD_ABC_BOTTOM_MOBILE; 230 } 231 232 if (!$_danbo_fallbacks) { 233 $_danbo_fallbacks = 'null'; 234 } 235 else { 236 $_danbo_fallbacks = json_encode($_danbo_fallbacks); 237 } 238 239 $js .= 'var danbo_fb = ' . $_danbo_fallbacks . ';'; 240 241 $js .= '</script>'; 242 243 $js .= '<script src="https://static.danbo.org/publisher/q2g345hq2g534-4chan/js/preload.4chan.js" defer></script>'; 244 } 245 // danbo ads end 246 247 // PubFuture 248 if (DEFAULT_BURICHAN) { 249 $js .= '<script async data-cfasync="false" src="https://cdn.pubfuture-ad.com/v2/unit/pt.js"></script>'; 250 } 251 252 if (TEST_BOARD) { 253 // Main catalog JS 254 $js .= '<script type="text/javascript" src="' . STATIC_SERVER . 'js/test/catalog-8psvqAqszI.' . JS_VERSION_TEST . '.js"></script>'; 255 256 // Painter JS + CSS 257 if (ENABLE_PAINTERJS) { 258 $js .= '<script type="text/javascript" src="' . STATIC_SERVER . 'js/test/tegaki-8psvqAqszI.' . JS_VERSION_TEST . '.js"></script>' 259 . '<link rel="stylesheet" href="' . STATIC_SERVER . 'css/tegaki-8psvqAqszI.' . CSS_VERSION_TEST . '.css">'; 260 } 261 262 // Core JS 263 $js .= '<script type="text/javascript" src="' . STATIC_SERVER . 'js/test/core-8psvqAqszI.' . JS_VERSION_TEST . '.js"></script>'; 264 } 265 else { 266 // Main catalog JS 267 $js .= '<script type="text/javascript" src="' . STATIC_SERVER . 'js/catalog.min.' . JS_VERSION_CATALOG . '.js"></script>'; 268 269 // Painter JS + CSS 270 if (ENABLE_PAINTERJS) { 271 $js .= '<script type="text/javascript" src="' . STATIC_SERVER . 'js/tegaki.min.' . JS_VERSION_PAINTER . '.js"></script>' 272 . '<link rel="stylesheet" href="' . STATIC_SERVER . 'css/tegaki.' . CSS_VERSION_PAINTER . '.css">'; 273 } 274 275 // Core JS 276 $js .= '<script type="text/javascript" src="' . STATIC_SERVER . 'js/core.min.' . JS_VERSION_CORE . '.js"></script>'; 277 } 278 279 $css = STATIC_SERVER . 'css'; 280 $cssv = TEST_BOARD ? CSS_VERSION_TEST : CSS_VERSION_CATALOG; 281 $style_group = style_group(); 282 283 $flags = SHOW_COUNTRY_FLAGS ? '<link rel="stylesheet" type="text/css" href="' . $css . '/flags.' . CSS_VERSION_FLAGS . '.css">' : ''; 284 285 $titlepart = $subtitle = ''; 286 287 if( TITLE_IMAGE_TYPE == 1 ) { 288 $titleimg = rand_from_flatfile( YOTSUBA_DIR, 'title_banners.txt' ); 289 $titlepart .= '<div id="bannerCnt" class="title desktop" data-src="' . $titleimg . '"></div>'; 290 } elseif( TITLE_IMAGE_TYPE == 2 ) { 291 $titlepart .= '<img class="title" src="' . TITLEIMG . '" onclick="this.src = this.src;">'; 292 } 293 294 if( defined( 'SUBTITLE' ) ) { 295 $subtitle = '<div class="boardSubtitle">' . SUBTITLE . '</div>'; 296 } 297 298 /** 299 * ADS 300 */ 301 $topad = ''; 302 303 $bottomad = ''; 304 305 if (defined('AD_CUSTOM_BOTTOM') && AD_CUSTOM_BOTTOM) { 306 $bottomad .= '<div>' . AD_CUSTOM_BOTTOM . '<hr></div>'; 307 }/* 308 else if (defined('AD_ABC_BOTTOM_MOBILE') && AD_ABC_BOTTOM_MOBILE) { 309 $bottomad .= '<div class="adg-rects mobile"><div class="adg-m adp-250" data-abc="' . AD_ABC_BOTTOM_MOBILE . '"></div><hr></div>'; 310 } 311 else if (defined('AD_BIDGEAR_BOTTOM') && AD_BIDGEAR_BOTTOM) { 312 $bottomad .= '<div class="adc-resp-bg" data-ad-bg="' . AD_BIDGEAR_BOTTOM . '"></div>'; 313 }*/ 314 else if (defined('ADS_DANBO') && ADS_DANBO) { 315 $bottomad .= '<div id="danbo-s-b" class="danbo-slot"></div><div class="adl">[<a target="_blank" href="https://www.4channel.org/advertise">Advertise on 4chan</a>]</div><hr>'; 316 } 317 318 $favicon = FAVICON; 319 $meta_robots = META_ROBOTS; 320 $meta_description = META_DESCRIPTION; 321 $meta_keywords = META_KEYWORDS; 322 323 $body_class = explode('_', $style_group); 324 $body_class = $body_class[0]; 325 $body_class .= ' is_catalog board_' . BOARD_DIR; 326 327 $canonical = '<link rel="canonical" href="https://boards.4chan.org/'.BOARD_DIR.'/catalog">'; 328 329 $embedearly = EMBEDEARLY; 330 $embedlate = EMBEDLATE; 331 $adembedearly = AD_EMBEDEARLY; 332 333 $start_thread = S_FORM_THREAD; 334 335 $jsVersion = TEST_BOARD ? JS_VERSION_TEST : JS_VERSION; 336 $comlen = MAX_COM_CHARS; 337 $maxfs = MAX_KB * 1024; 338 $jsCooldowns = json_encode(array( 339 'thread' => RENZOKU3, 340 'reply' => RENZOKU, 341 'image' => RENZOKU2 342 )); 343 344 if (defined('CSS_EVENT_NAME') && CSS_EVENT_NAME) { 345 $event_css_html = '<option value="_special">Special</option>'; 346 347 // Christmas 2021 348 if (CSS_EVENT_NAME === 'tomorrow') { 349 $js .= <<<JJS 350 <script src='//s.4cdn.org/js/snow.js'></script> 351 <script> 352 function fc_tomorrow_init() { 353 if (window.matchMedia && window.matchMedia('(min-width: 481px)').matches) { 354 fc_spawn_snow(Math.floor(Math.random() * 50) + 50); 355 } 356 } 357 function fc_tomorrow_cleanup() { 358 fc_remove_snow(); 359 } 360 </script> 361 <style> 362 .boardBanner, #delform, .navLinksBot.desktop { 363 border-image-slice: 50 0 50 0; 364 border-image-width: 40px 0px 0px 0px; 365 border-image-outset: 0px 0px 0px 0px; 366 border-image-repeat: repeat repeat; 367 border-image-source: url('https://s.4cdn.org/image/temp/garland.png'); 368 border-style: solid; 369 padding-top: 50px; 370 } 371 </style> 372 JJS; 373 } 374 375 $js .= '<script>var css_event = "' . CSS_EVENT_NAME . '";'; 376 377 if (defined('CSS_EVENT_VERSION')) { 378 $_css_event_version = (int)CSS_EVENT_VERSION; 379 } 380 else { 381 $_css_event_version = 1; 382 } 383 384 $js .= 'var css_event_v = ' . $_css_event_version . ';'; 385 386 $js .= '</script>'; 387 } 388 else { 389 $event_css_html = ''; 390 } 391 392 if (PARTY) { 393 $partyHats = 'var partyHats = "' . PARTY_IMAGE . '";'; 394 } 395 else { 396 $partyHats = ''; 397 } 398 399 if (ENABLE_ARCHIVE) { 400 $archive_link = ' <span class="btn-wrap"><a href="./archive" class="button">' . S_ARCHIVE . '</a></span>'; 401 } 402 else { 403 $archive_link = ''; 404 } 405 406 if (TEXT_ONLY) { 407 $text_only = 'var text_only = true;'; 408 $body_text_css = ' text_only'; 409 $ctrl_css = ' hidden'; 410 } 411 else { 412 $text_only = $body_text_css = ''; 413 $ctrl_css = ''; 414 } 415 416 $adg_js = 'var _adg = 1;'; 417 418 $postform = ''; 419 form( $postform, 0, '', false, true ); 420 421 $cat = <<<HTML 422 <!DOCTYPE html> 423 <html> 424 <head> 425 <meta charset="utf-8"> 426 <title>$title - Catalog - 4chan</title> 427 <meta name="robots" content="$meta_robots"> 428 <meta name="description" content="$meta_description"> 429 <meta name="keywords" content="$meta_keywords"> 430 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 431 $canonical 432 <link id="mobile-css" rel="stylesheet" href="$css/catalog_mobile.$cssv.css" /> 433 $js 434 $flags 435 <script type="text/javascript">$partyHats 436 $text_only 437 $adg_js 438 var jsVersion = $jsVersion; 439 var comlen = $comlen; 440 var maxFilesize = $maxfs; 441 var cooldowns = $jsCooldowns; 442 var catalog = $catjson; 443 var style_group = "$style_group"; 444 var check_for_block = true; 445 var fourcat = new FC(); 446 fourcat.applyCSS(null, "$style_group", $cssv); 447 </script> 448 <link rel="shortcut icon" href="$favicon" type="image/x-icon"> 449 $embedearly 450 $adembedearly 451 </head> 452 <body class="$body_class$body_text_css"> 453 <div id="topnav" class="boardnav">$nav</div> 454 <div class="boardBanner"> 455 $titlepart 456 <div class="boardTitle">$title</div> 457 $subtitle 458 </div> 459 <hr class="abovePostForm"> 460 $topad 461 <div id="togglePostForm" class="mobilebtn mobile"><span class="btn-wrap"><span id="togglePostFormLinkMobile" class="button">$start_thread</span></span></div> 462 $postform 463 <hr> 464 <div id="content"> 465 <div id="ctrl"> 466 <div id="info"> 467 <span class="navLinks mobilebtn"><span class="btn-wrap"><a href="./" class="button">Return</a></span>$archive_link <span id="tobottom" class="btn-wrap"><span class="button">Bottom</span></span> <span class="btn-wrap"><a id="refresh-btn" href="./catalog" class="button">Refresh</a></span></span><span id="filtered-label"> — Filtered threads: <span id="filtered-count"></span></span><span id="hidden-label"> — Hidden threads: <span id="hidden-count"></span> <span class="btn-wrap"><a id="filters-clear-hidden" href="">Show</a></span></span><span id="search-label"> — Search results for: <span id="search-term"></span></span> 468 </div> 469 <hr class="mobile"> 470 <div id="settings" class="mobilebtn"> 471 <span class="ctrl-wrap">Sort By: 472 <select id="order-ctrl" size="1"> 473 <option value="alt">Bump order</option> 474 <option value="absdate">Last reply</option> 475 <option value="date">Creation date</option> 476 <option value="r">Reply count</option> 477 </select></span> 478 <span class="ctrl-wrap$ctrl_css">Image Size: 479 <select id="size-ctrl" size="1"> 480 <option value="small">Small</option> 481 <option value="large">Large</option> 482 </select></span> 483 <span class="ctrl-wrap$ctrl_css">Show OP Comment: 484 <select id="teaser-ctrl" size="1"> 485 <option value="off">Off</option> 486 <option value="on">On</option> 487 </select></span> 488 <span class="btn-wrap"><span id="filters-ctrl" class="button">Filters</span></span> 489 <span class="btn-wrap"><span id="qf-ctrl" class="button">Search</span></span> 490 <span style="display:none" id="qf-cnt"> 491 <input type="text" id="qf-box" name="qf-box"><span id="qf-clear" class="button">✖</span> 492 </span> 493 </div> 494 <div class="clear"></div> 495 </div><hr> 496 <div id="threads"></div> 497 <hr> 498 <span class="navLinks navLinksBottom mobilebtn"><span class="btn-wrap"><a href="./" class="button">Return</a></span>$archive_link <span id="totop" class="btn-wrap"><span class="button">Top</span></span> <span class="btn-wrap"><a href="./catalog" class="button">Refresh</a></span></span><span id="filtered-label-bottom"> — Filtered threads: <span id="filtered-count-bottom"></span></span><span id="hidden-label-bottom"> — Hidden threads: <span id="hidden-count-bottom"></span> <span class="btn-wrap"><a id="filters-clear-hidden-bottom" href="">Show</a></span></span><span id="search-label-bottom"> — Search results for: <span id="search-term-bottom"></span></span> 499 <hr> 500 $bottomad 501 <div id="styleSwitcher">Style: <select id="styleSelector" size="1"> 502 <option value="Yotsuba New">Yotsuba</option> 503 <option value="Yotsuba B New">Yotsuba‌ B</option> 504 <option value="Futaba New">Futaba</option> 505 <option value="Burichan New">Burichan</option> 506 <option value="Tomorrow">Tomorrow</option> 507 <option value="Photon">Photon</option>$event_css_html 508 </select></div> 509 </div> 510 $foot 511 <div id="backdrop" class="hidden"></div> 512 <noscript> 513 <style scoped type="text/css"> 514 #nojs { 515 background-color: #000; 516 text-align: center; 517 position: fixed; 518 top: 0px; 519 left: 0px; 520 width: 100%; 521 height: 100%; 522 } 523 #nojs > span { 524 color: #000; 525 background-color: #8C92AC; 526 padding: 5px; 527 position: relative; 528 top: 35%; 529 font-size: 22px; 530 } 531 </style> 532 <div id="nojs"> 533 <span>Your web browser must have JavaScript enabled in order for this site to display correctly.</span> 534 </div> 535 </noscript> 536 <menu type="context" id="ctxmenu-main"></menu> 537 <menu type="context" id="ctxmenu-thread"></menu> 538 <script type="text/javascript"> 539 document.addEventListener('DOMContentLoaded', function() { 540 initAnalytics(); 541 fourcat.init(); 542 fourcat.loadCatalog(catalog); 543 $.on($.id('tobottom'), 'click', function() { window.scrollTo(0, document.documentElement.scrollHeight); }); 544 $.on($.id('totop'), 'click', function() { window.scrollTo(0, 0); }); 545 }, false); 546 </script> 547 $embedlate</body> 548 </html> 549 HTML; 550 551 return $cat; 552 553 }