The version of vichan running on lainchan.org
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

13 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
13 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
11 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
10 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
11 年之前
12 年之前
12 年之前
10 年之前
12 年之前
12 年之前
11 年之前
12 年之前
12 年之前
12 年之前
12 年之前
11 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
13 年之前
12 年之前
13 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
13 年之前
12 年之前
13 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
10 年之前
12 年之前
12 年之前
12 年之前
12 年之前
10 年之前
12 年之前
10 年之前
12 年之前
12 年之前
12 年之前
12 年之前
10 年之前
12 年之前
10 年之前
12 年之前
12 年之前
12 年之前
12 年之前
11 年之前
12 年之前
10 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
9 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
10 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
10 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
9 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
10 年之前
10 年之前
12 年之前
12 年之前
12 年之前
12 年之前
13 年之前
13 年之前
13 年之前
12 年之前
13 年之前
12 年之前
12 年之前
10 年之前
12 年之前
12 年之前
13 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
10 年之前
12 年之前
12 年之前
12 年之前
10 年之前
12 年之前
10 年之前
10 年之前
12 年之前
13 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
13 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
13 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
11 年之前
12 年之前
12 年之前
12 年之前
10 年之前
12 年之前
11 年之前
11 年之前
11 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
10 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
10 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
8 年之前
13 年之前
12 年之前
12 年之前
13 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
13 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
13 年之前
12 年之前
12 年之前
12 年之前
13 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
11 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
13 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
13 年之前
9 年之前
12 年之前
13 年之前
12 年之前
12 年之前
12 年之前
12 年之前
9 年之前
9 年之前
13 年之前
13 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
13 年之前
13 年之前
12 年之前
11 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
13 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
10 年之前
12 年之前
12 年之前
10 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
12 年之前
9 年之前
9 年之前
9 年之前
9 年之前
9 年之前
9 年之前
9 年之前
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866
  1. <?php
  2. /*
  3. * Copyright (c) 2010-2014 Tinyboard Development Group
  4. */
  5. if (realpath($_SERVER['SCRIPT_FILENAME']) == str_replace('\\', '/', __FILE__)) {
  6. // You cannot request this file directly.
  7. exit;
  8. }
  9. define('TINYBOARD', null);
  10. $microtime_start = microtime(true);
  11. require_once 'inc/display.php';
  12. require_once 'inc/template.php';
  13. require_once 'inc/database.php';
  14. require_once 'inc/events.php';
  15. require_once 'inc/api.php';
  16. require_once 'inc/mod/auth.php';
  17. require_once 'inc/lock.php';
  18. require_once 'inc/queue.php';
  19. require_once 'inc/polyfill.php';
  20. @include_once 'inc/lib/parsedown/Parsedown.php'; // fail silently, this isn't a critical piece of code
  21. if (!extension_loaded('gettext')) {
  22. require_once 'inc/lib/gettext/gettext.inc';
  23. }
  24. // the user is not currently logged in as a moderator
  25. $mod = false;
  26. register_shutdown_function('fatal_error_handler');
  27. mb_internal_encoding('UTF-8');
  28. loadConfig();
  29. function init_locale($locale, $error='error') {
  30. if (extension_loaded('gettext')) {
  31. if (setlocale(LC_ALL, $locale) === false) {
  32. //$error('The specified locale (' . $locale . ') does not exist on your platform!');
  33. }
  34. bindtextdomain('tinyboard', './inc/locale');
  35. bind_textdomain_codeset('tinyboard', 'UTF-8');
  36. textdomain('tinyboard');
  37. } else {
  38. if (_setlocale(LC_ALL, $locale) === false) {
  39. $error('The specified locale (' . $locale . ') does not exist on your platform!');
  40. }
  41. _bindtextdomain('tinyboard', './inc/locale');
  42. _bind_textdomain_codeset('tinyboard', 'UTF-8');
  43. _textdomain('tinyboard');
  44. }
  45. }
  46. $current_locale = 'en';
  47. function loadConfig() {
  48. global $board, $config, $__ip, $debug, $__version, $microtime_start, $current_locale, $events;
  49. $error = function_exists('error') ? 'error' : 'basic_error_function_because_the_other_isnt_loaded_yet';
  50. $boardsuffix = isset($board['uri']) ? $board['uri'] : '';
  51. if (!isset($_SERVER['REMOTE_ADDR']))
  52. $_SERVER['REMOTE_ADDR'] = '0.0.0.0';
  53. if (file_exists('tmp/cache/cache_config.php')) {
  54. require_once('tmp/cache/cache_config.php');
  55. }
  56. if (isset($config['cache_config']) &&
  57. $config['cache_config'] &&
  58. $config = Cache::get('config_' . $boardsuffix ) ) {
  59. $events = Cache::get('events_' . $boardsuffix );
  60. define_groups();
  61. if (file_exists('inc/instance-functions.php')) {
  62. require_once('inc/instance-functions.php');
  63. }
  64. if ($config['locale'] != $current_locale) {
  65. $current_locale = $config['locale'];
  66. init_locale($config['locale'], $error);
  67. }
  68. }
  69. else {
  70. $config = array();
  71. reset_events();
  72. $arrays = array(
  73. 'db',
  74. 'api',
  75. 'cache',
  76. 'lock',
  77. 'queue',
  78. 'cookies',
  79. 'error',
  80. 'dir',
  81. 'mod',
  82. 'spam',
  83. 'filters',
  84. 'wordfilters',
  85. 'custom_capcode',
  86. 'custom_tripcode',
  87. 'dnsbl',
  88. 'dnsbl_exceptions',
  89. 'remote',
  90. 'allowed_ext',
  91. 'allowed_ext_files',
  92. 'file_icons',
  93. 'footer',
  94. 'stylesheets',
  95. 'additional_javascript',
  96. 'markup',
  97. 'custom_pages',
  98. 'dashboard_links'
  99. );
  100. foreach ($arrays as $key) {
  101. $config[$key] = array();
  102. }
  103. if (!file_exists('inc/instance-config.php'))
  104. $error('Tinyboard is not configured! Create inc/instance-config.php.');
  105. // Initialize locale as early as possible
  106. // Those calls are expensive. Unfortunately, our cache system is not initialized at this point.
  107. // So, we may store the locale in a tmp/ filesystem.
  108. if (file_exists($fn = 'tmp/cache/locale_' . $boardsuffix ) ) {
  109. $config['locale'] = @file_get_contents($fn);
  110. }
  111. else {
  112. $config['locale'] = 'en';
  113. $configstr = file_get_contents('inc/instance-config.php');
  114. if (isset($board['dir']) && file_exists($board['dir'] . '/config.php')) {
  115. $configstr .= file_get_contents($board['dir'] . '/config.php');
  116. }
  117. $matches = array();
  118. preg_match_all('/[^\/#*]\$config\s*\[\s*[\'"]locale[\'"]\s*\]\s*=\s*([\'"])(.*?)\1/', $configstr, $matches);
  119. if ($matches && isset ($matches[2]) && $matches[2]) {
  120. $matches = $matches[2];
  121. $config['locale'] = $matches[count($matches)-1];
  122. }
  123. @file_put_contents($fn, $config['locale']);
  124. }
  125. if ($config['locale'] != $current_locale) {
  126. $current_locale = $config['locale'];
  127. init_locale($config['locale'], $error);
  128. }
  129. require 'inc/config.php';
  130. require 'inc/instance-config.php';
  131. if (isset($board['dir']) && file_exists($board['dir'] . '/config.php')) {
  132. require $board['dir'] . '/config.php';
  133. }
  134. if ($config['locale'] != $current_locale) {
  135. $current_locale = $config['locale'];
  136. init_locale($config['locale'], $error);
  137. }
  138. if (!isset($config['global_message']))
  139. $config['global_message'] = false;
  140. if (!isset($config['post_url']))
  141. $config['post_url'] = $config['root'] . $config['file_post'];
  142. if (!isset($config['referer_match']))
  143. if (isset($_SERVER['HTTP_HOST'])) {
  144. $config['referer_match'] = '/^' .
  145. (preg_match('@^https?://@', $config['root']) ? '' :
  146. 'https?:\/\/' . $_SERVER['HTTP_HOST']) .
  147. preg_quote($config['root'], '/') .
  148. '(' .
  149. str_replace('%s', $config['board_regex'], preg_quote($config['board_path'], '/')) .
  150. '(' .
  151. preg_quote($config['file_index'], '/') . '|' .
  152. str_replace('%d', '\d+', preg_quote($config['file_page'])) .
  153. ')?' .
  154. '|' .
  155. str_replace('%s', $config['board_regex'], preg_quote($config['board_path'], '/')) .
  156. preg_quote($config['dir']['res'], '/') .
  157. '(' .
  158. str_replace('%d', '\d+', preg_quote($config['file_page'], '/')) . '|' .
  159. str_replace('%d', '\d+', preg_quote($config['file_page50'], '/')) . '|' .
  160. str_replace(array('%d', '%s'), array('\d+', '[a-z0-9-]+'), preg_quote($config['file_page_slug'], '/')) . '|' .
  161. str_replace(array('%d', '%s'), array('\d+', '[a-z0-9-]+'), preg_quote($config['file_page50_slug'], '/')) .
  162. ')' .
  163. '|' .
  164. preg_quote($config['file_mod'], '/') . '\?\/.+' .
  165. ')([#?](.+)?)?$/ui';
  166. } else {
  167. // CLI mode
  168. $config['referer_match'] = '//';
  169. }
  170. if (!isset($config['cookies']['path']))
  171. $config['cookies']['path'] = &$config['root'];
  172. if (!isset($config['dir']['static']))
  173. $config['dir']['static'] = $config['root'] . 'static/';
  174. if (!isset($config['image_blank']))
  175. $config['image_blank'] = $config['dir']['static'] . 'blank.gif';
  176. if (!isset($config['image_sticky']))
  177. $config['image_sticky'] = $config['dir']['static'] . 'sticky.gif';
  178. if (!isset($config['image_locked']))
  179. $config['image_locked'] = $config['dir']['static'] . 'locked.gif';
  180. if (!isset($config['image_bumplocked']))
  181. $config['image_bumplocked'] = $config['dir']['static'] . 'sage.gif';
  182. if (!isset($config['image_deleted']))
  183. $config['image_deleted'] = $config['dir']['static'] . 'deleted.png';
  184. if (!isset($config['uri_thumb']))
  185. $config['uri_thumb'] = $config['root'] . $board['dir'] . $config['dir']['thumb'];
  186. elseif (isset($board['dir']))
  187. $config['uri_thumb'] = sprintf($config['uri_thumb'], $board['dir']);
  188. if (!isset($config['uri_img']))
  189. $config['uri_img'] = $config['root'] . $board['dir'] . $config['dir']['img'];
  190. elseif (isset($board['dir']))
  191. $config['uri_img'] = sprintf($config['uri_img'], $board['dir']);
  192. if (!isset($config['uri_stylesheets']))
  193. $config['uri_stylesheets'] = $config['root'] . 'stylesheets/';
  194. if (!isset($config['url_stylesheet']))
  195. $config['url_stylesheet'] = $config['uri_stylesheets'] . 'style.css';
  196. if (!isset($config['url_javascript']))
  197. $config['url_javascript'] = $config['root'] . $config['file_script'];
  198. if (!isset($config['additional_javascript_url']))
  199. $config['additional_javascript_url'] = $config['root'];
  200. if (!isset($config['uri_flags']))
  201. $config['uri_flags'] = $config['root'] . 'static/flags/%s.png';
  202. if (!isset($config['user_flag']))
  203. $config['user_flag'] = false;
  204. if (!isset($config['user_flags']))
  205. $config['user_flags'] = array();
  206. if (!isset($__version))
  207. $__version = file_exists('.installed') ? trim(file_get_contents('.installed')) : false;
  208. $config['version'] = $__version;
  209. if ($config['allow_roll'])
  210. event_handler('post', 'diceRoller');
  211. if (in_array('webm', $config['allowed_ext_files']) ||
  212. in_array('mp4', $config['allowed_ext_files']))
  213. event_handler('post', 'postHandler');
  214. }
  215. // Effectful config processing below:
  216. date_default_timezone_set($config['timezone']);
  217. if ($config['root_file']) {
  218. chdir($config['root_file']);
  219. }
  220. // Keep the original address to properly comply with other board configurations
  221. if (!isset($__ip))
  222. $__ip = $_SERVER['REMOTE_ADDR'];
  223. // ::ffff:0.0.0.0
  224. if (preg_match('/^\:\:(ffff\:)?(\d+\.\d+\.\d+\.\d+)$/', $__ip, $m))
  225. $_SERVER['REMOTE_ADDR'] = $m[2];
  226. if ($config['verbose_errors']) {
  227. set_error_handler('verbose_error_handler');
  228. error_reporting(E_ALL);
  229. ini_set('display_errors', true);
  230. ini_set('html_errors', false);
  231. } else {
  232. ini_set('display_errors', false);
  233. }
  234. if ($config['syslog'])
  235. openlog('tinyboard', LOG_ODELAY, LOG_SYSLOG); // open a connection to sysem logger
  236. if ($config['recaptcha'])
  237. require_once 'inc/lib/recaptcha/recaptchalib.php';
  238. if ($config['cache']['enabled'])
  239. require_once 'inc/cache.php';
  240. if (in_array('webm', $config['allowed_ext_files']) ||
  241. in_array('mp4', $config['allowed_ext_files']))
  242. require_once 'inc/lib/webm/posthandler.php';
  243. event('load-config');
  244. if ($config['cache_config'] && !isset ($config['cache_config_loaded'])) {
  245. file_put_contents('tmp/cache/cache_config.php', '<?php '.
  246. '$config = array();'.
  247. '$config[\'cache\'] = '.var_export($config['cache'], true).';'.
  248. '$config[\'cache_config\'] = true;'.
  249. '$config[\'debug\'] = '.var_export($config['debug'], true).';'.
  250. 'require_once(\'inc/cache.php\');'
  251. );
  252. $config['cache_config_loaded'] = true;
  253. Cache::set('config_'.$boardsuffix, $config);
  254. Cache::set('events_'.$boardsuffix, $events);
  255. }
  256. if (is_array($config['anonymous']))
  257. $config['anonymous'] = $config['anonymous'][array_rand($config['anonymous'])];
  258. if ($config['debug']) {
  259. if (!isset($debug)) {
  260. $debug = array(
  261. 'sql' => array(),
  262. 'exec' => array(),
  263. 'purge' => array(),
  264. 'cached' => array(),
  265. 'write' => array(),
  266. 'time' => array(
  267. 'db_queries' => 0,
  268. 'exec' => 0,
  269. ),
  270. 'start' => $microtime_start,
  271. 'start_debug' => microtime(true)
  272. );
  273. $debug['start'] = $microtime_start;
  274. }
  275. }
  276. }
  277. function basic_error_function_because_the_other_isnt_loaded_yet($message, $priority = true) {
  278. global $config;
  279. if ($config['syslog'] && $priority !== false) {
  280. // Use LOG_NOTICE instead of LOG_ERR or LOG_WARNING because most error message are not significant.
  281. _syslog($priority !== true ? $priority : LOG_NOTICE, $message);
  282. }
  283. // Yes, this is horrible.
  284. die('<!DOCTYPE html><html><head><title>Error</title>' .
  285. '<style type="text/css">' .
  286. 'body{text-align:center;font-family:arial, helvetica, sans-serif;font-size:10pt;}' .
  287. 'p{padding:0;margin:20px 0;}' .
  288. 'p.c{font-size:11px;}' .
  289. '</style></head>' .
  290. '<body><h2>Error</h2>' . $message . '<hr/>' .
  291. '<p class="c">This alternative error page is being displayed because the other couldn\'t be found or hasn\'t loaded yet.</p></body></html>');
  292. }
  293. function fatal_error_handler() {
  294. if ($error = error_get_last()) {
  295. if ($error['type'] == E_ERROR) {
  296. if (function_exists('error')) {
  297. error('Caught fatal error: ' . $error['message'] . ' in <strong>' . $error['file'] . '</strong> on line ' . $error['line'], LOG_ERR);
  298. } else {
  299. basic_error_function_because_the_other_isnt_loaded_yet('Caught fatal error: ' . $error['message'] . ' in ' . $error['file'] . ' on line ' . $error['line'], LOG_ERR);
  300. }
  301. }
  302. }
  303. }
  304. function _syslog($priority, $message) {
  305. if (isset($_SERVER['REMOTE_ADDR'], $_SERVER['REQUEST_METHOD'], $_SERVER['REQUEST_URI'])) {
  306. // CGI
  307. syslog($priority, $message . ' - client: ' . $_SERVER['REMOTE_ADDR'] . ', request: "' . $_SERVER['REQUEST_METHOD'] . ' ' . $_SERVER['REQUEST_URI'] . '"');
  308. } else {
  309. syslog($priority, $message);
  310. }
  311. }
  312. function verbose_error_handler($errno, $errstr, $errfile, $errline) {
  313. if (error_reporting() == 0)
  314. return false; // Looks like this warning was suppressed by the @ operator.
  315. error(utf8tohtml($errstr), true, array(
  316. 'file' => $errfile . ':' . $errline,
  317. 'errno' => $errno,
  318. 'error' => $errstr,
  319. 'backtrace' => array_slice(debug_backtrace(), 1)
  320. ));
  321. }
  322. function define_groups() {
  323. global $config;
  324. foreach ($config['mod']['groups'] as $group_value => $group_name) {
  325. $group_name = strtoupper($group_name);
  326. if(!defined($group_name)) {
  327. define($group_name, $group_value, true);
  328. }
  329. }
  330. ksort($config['mod']['groups']);
  331. }
  332. function create_antibot($board, $thread = null) {
  333. require_once dirname(__FILE__) . '/anti-bot.php';
  334. return _create_antibot($board, $thread);
  335. }
  336. function rebuildThemes($action, $boardname = false) {
  337. global $config, $board, $current_locale;
  338. // Save the global variables
  339. $_config = $config;
  340. $_board = $board;
  341. // List themes
  342. if ($themes = Cache::get("themes")) {
  343. // OK, we already have themes loaded
  344. }
  345. else {
  346. $query = query("SELECT `theme` FROM ``theme_settings`` WHERE `name` IS NULL AND `value` IS NULL") or error(db_error());
  347. $themes = array();
  348. while ($theme = $query->fetch(PDO::FETCH_ASSOC)) {
  349. $themes[] = $theme;
  350. }
  351. Cache::set("themes", $themes);
  352. }
  353. foreach ($themes as $theme) {
  354. // Restore them
  355. $config = $_config;
  356. $board = $_board;
  357. // Reload the locale
  358. if ($config['locale'] != $current_locale) {
  359. $current_locale = $config['locale'];
  360. init_locale($config['locale']);
  361. }
  362. if (PHP_SAPI === 'cli') {
  363. echo "Rebuilding theme ".$theme['theme']."... ";
  364. }
  365. rebuildTheme($theme['theme'], $action, $boardname);
  366. if (PHP_SAPI === 'cli') {
  367. echo "done\n";
  368. }
  369. }
  370. // Restore them again
  371. $config = $_config;
  372. $board = $_board;
  373. // Reload the locale
  374. if ($config['locale'] != $current_locale) {
  375. $current_locale = $config['locale'];
  376. init_locale($config['locale']);
  377. }
  378. }
  379. function loadThemeConfig($_theme) {
  380. global $config;
  381. if (!file_exists($config['dir']['themes'] . '/' . $_theme . '/info.php'))
  382. return false;
  383. // Load theme information into $theme
  384. include $config['dir']['themes'] . '/' . $_theme . '/info.php';
  385. return $theme;
  386. }
  387. function rebuildTheme($theme, $action, $board = false) {
  388. global $config, $_theme;
  389. $_theme = $theme;
  390. $theme = loadThemeConfig($_theme);
  391. if (file_exists($config['dir']['themes'] . '/' . $_theme . '/theme.php')) {
  392. require_once $config['dir']['themes'] . '/' . $_theme . '/theme.php';
  393. $theme['build_function']($action, themeSettings($_theme), $board);
  394. }
  395. }
  396. function themeSettings($theme) {
  397. if ($settings = Cache::get("theme_settings_".$theme)) {
  398. return $settings;
  399. }
  400. $query = prepare("SELECT `name`, `value` FROM ``theme_settings`` WHERE `theme` = :theme AND `name` IS NOT NULL");
  401. $query->bindValue(':theme', $theme);
  402. $query->execute() or error(db_error($query));
  403. $settings = array();
  404. while ($s = $query->fetch(PDO::FETCH_ASSOC)) {
  405. $settings[$s['name']] = $s['value'];
  406. }
  407. Cache::set("theme_settings_".$theme, $settings);
  408. return $settings;
  409. }
  410. function sprintf3($str, $vars, $delim = '%') {
  411. $replaces = array();
  412. foreach ($vars as $k => $v) {
  413. $replaces[$delim . $k . $delim] = $v;
  414. }
  415. return str_replace(array_keys($replaces),
  416. array_values($replaces), $str);
  417. }
  418. function mb_substr_replace($string, $replacement, $start, $length) {
  419. return mb_substr($string, 0, $start) . $replacement . mb_substr($string, $start + $length);
  420. }
  421. function setupBoard($array) {
  422. global $board, $config;
  423. $board = array(
  424. 'uri' => $array['uri'],
  425. 'title' => $array['title'],
  426. 'subtitle' => $array['subtitle'],
  427. #'indexed' => $array['indexed'],
  428. );
  429. // older versions
  430. $board['name'] = &$board['title'];
  431. $board['dir'] = sprintf($config['board_path'], $board['uri']);
  432. $board['url'] = sprintf($config['board_abbreviation'], $board['uri']);
  433. loadConfig();
  434. if (!file_exists($board['dir']))
  435. @mkdir($board['dir'], 0777) or error("Couldn't create " . $board['dir'] . ". Check permissions.", true);
  436. if (!file_exists($board['dir'] . $config['dir']['img']))
  437. @mkdir($board['dir'] . $config['dir']['img'], 0777)
  438. or error("Couldn't create " . $board['dir'] . $config['dir']['img'] . ". Check permissions.", true);
  439. if (!file_exists($board['dir'] . $config['dir']['thumb']))
  440. @mkdir($board['dir'] . $config['dir']['thumb'], 0777)
  441. or error("Couldn't create " . $board['dir'] . $config['dir']['img'] . ". Check permissions.", true);
  442. if (!file_exists($board['dir'] . $config['dir']['res']))
  443. @mkdir($board['dir'] . $config['dir']['res'], 0777)
  444. or error("Couldn't create " . $board['dir'] . $config['dir']['img'] . ". Check permissions.", true);
  445. }
  446. function openBoard($uri) {
  447. global $config, $build_pages;
  448. if ($config['try_smarter'])
  449. $build_pages = array();
  450. $board = getBoardInfo($uri);
  451. if ($board) {
  452. setupBoard($board);
  453. if (function_exists('after_open_board')) {
  454. after_open_board();
  455. }
  456. return true;
  457. }
  458. return false;
  459. }
  460. function getBoardInfo($uri) {
  461. global $config;
  462. if ($config['cache']['enabled'] && ($board = cache::get('board_' . $uri))) {
  463. return $board;
  464. }
  465. $query = prepare("SELECT * FROM ``boards`` WHERE `uri` = :uri LIMIT 1");
  466. $query->bindValue(':uri', $uri);
  467. $query->execute() or error(db_error($query));
  468. if ($board = $query->fetch(PDO::FETCH_ASSOC)) {
  469. if ($config['cache']['enabled'])
  470. cache::set('board_' . $uri, $board);
  471. return $board;
  472. }
  473. return false;
  474. }
  475. function boardTitle($uri) {
  476. $board = getBoardInfo($uri);
  477. if ($board)
  478. return $board['title'];
  479. return false;
  480. }
  481. function purge($uri) {
  482. global $config, $debug;
  483. // Fix for Unicode
  484. $uri = rawurlencode($uri);
  485. $noescape = "/!~*()+:";
  486. $noescape = preg_split('//', $noescape);
  487. $noescape_url = array_map("rawurlencode", $noescape);
  488. $uri = str_replace($noescape_url, $noescape, $uri);
  489. if (preg_match($config['referer_match'], $config['root']) && isset($_SERVER['REQUEST_URI'])) {
  490. $uri = (str_replace('\\', '/', dirname($_SERVER['REQUEST_URI'])) == '/' ? '/' : str_replace('\\', '/', dirname($_SERVER['REQUEST_URI'])) . '/') . $uri;
  491. } else {
  492. $uri = $config['root'] . $uri;
  493. }
  494. if ($config['debug']) {
  495. $debug['purge'][] = $uri;
  496. }
  497. foreach ($config['purge'] as &$purge) {
  498. $host = &$purge[0];
  499. $port = &$purge[1];
  500. $http_host = isset($purge[2]) ? $purge[2] : (isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : 'localhost');
  501. $request = "PURGE {$uri} HTTP/1.1\r\nHost: {$http_host}\r\nUser-Agent: Tinyboard\r\nConnection: Close\r\n\r\n";
  502. if ($fp = fsockopen($host, $port, $errno, $errstr, $config['purge_timeout'])) {
  503. fwrite($fp, $request);
  504. fclose($fp);
  505. } else {
  506. // Cannot connect?
  507. error('Could not PURGE for ' . $host);
  508. }
  509. }
  510. }
  511. function file_write($path, $data, $simple = false, $skip_purge = false) {
  512. global $config, $debug;
  513. if (preg_match('/^remote:\/\/(.+)\:(.+)$/', $path, $m)) {
  514. if (isset($config['remote'][$m[1]])) {
  515. require_once 'inc/remote.php';
  516. $remote = new Remote($config['remote'][$m[1]]);
  517. $remote->write($data, $m[2]);
  518. return;
  519. } else {
  520. error('Invalid remote server: ' . $m[1]);
  521. }
  522. }
  523. if (!$fp = fopen($path, $simple ? 'w' : 'c'))
  524. error('Unable to open file for writing: ' . $path);
  525. // File locking
  526. if (!$simple && !flock($fp, LOCK_EX)) {
  527. error('Unable to lock file: ' . $path);
  528. }
  529. // Truncate file
  530. if (!$simple && !ftruncate($fp, 0))
  531. error('Unable to truncate file: ' . $path);
  532. // Write data
  533. if (($bytes = fwrite($fp, $data)) === false)
  534. error('Unable to write to file: ' . $path);
  535. // Unlock
  536. if (!$simple)
  537. flock($fp, LOCK_UN);
  538. // Close
  539. if (!fclose($fp))
  540. error('Unable to close file: ' . $path);
  541. /**
  542. * Create gzipped file.
  543. *
  544. * When writing into a file foo.bar and the size is larger or equal to 1
  545. * KiB, this also produces the gzipped version foo.bar.gz
  546. *
  547. * This is useful with nginx with gzip_static on.
  548. */
  549. if ($config['gzip_static']) {
  550. $gzpath = "$path.gz";
  551. if ($bytes & ~0x3ff) { // if ($bytes >= 1024)
  552. if (file_put_contents($gzpath, gzencode($data), $simple ? 0 : LOCK_EX) === false)
  553. error("Unable to write to file: $gzpath");
  554. //if (!touch($gzpath, filemtime($path), fileatime($path)))
  555. // error("Unable to touch file: $gzpath");
  556. }
  557. else {
  558. @unlink($gzpath);
  559. }
  560. }
  561. if (!$skip_purge && isset($config['purge'])) {
  562. // Purge cache
  563. if (basename($path) == $config['file_index']) {
  564. // Index file (/index.html); purge "/" as well
  565. $uri = dirname($path);
  566. // root
  567. if ($uri == '.')
  568. $uri = '';
  569. else
  570. $uri .= '/';
  571. purge($uri);
  572. }
  573. purge($path);
  574. }
  575. if ($config['debug']) {
  576. $debug['write'][] = $path . ': ' . $bytes . ' bytes';
  577. }
  578. event('write', $path);
  579. }
  580. function file_unlink($path) {
  581. global $config, $debug;
  582. if ($config['debug']) {
  583. if (!isset($debug['unlink']))
  584. $debug['unlink'] = array();
  585. $debug['unlink'][] = $path;
  586. }
  587. $ret = @unlink($path);
  588. if ($config['gzip_static']) {
  589. $gzpath = "$path.gz";
  590. @unlink($gzpath);
  591. }
  592. if (isset($config['purge']) && $path[0] != '/' && isset($_SERVER['HTTP_HOST'])) {
  593. // Purge cache
  594. if (basename($path) == $config['file_index']) {
  595. // Index file (/index.html); purge "/" as well
  596. $uri = dirname($path);
  597. // root
  598. if ($uri == '.')
  599. $uri = '';
  600. else
  601. $uri .= '/';
  602. purge($uri);
  603. }
  604. purge($path);
  605. }
  606. event('unlink', $path);
  607. return $ret;
  608. }
  609. function hasPermission($action = null, $board = null, $_mod = null) {
  610. global $config;
  611. if (isset($_mod))
  612. $mod = &$_mod;
  613. else
  614. global $mod;
  615. if (!is_array($mod))
  616. return false;
  617. if (isset($action) && $mod['type'] < $action)
  618. return false;
  619. if (!isset($board) || $config['mod']['skip_per_board'])
  620. return true;
  621. if (!isset($mod['boards']))
  622. return false;
  623. if (!in_array('*', $mod['boards']) && !in_array($board, $mod['boards']))
  624. return false;
  625. return true;
  626. }
  627. function listBoards($just_uri = false) {
  628. global $config;
  629. $just_uri ? $cache_name = 'all_boards_uri' : $cache_name = 'all_boards';
  630. if ($config['cache']['enabled'] && ($boards = cache::get($cache_name)))
  631. return $boards;
  632. if (!$just_uri) {
  633. $query = query("SELECT * FROM ``boards`` ORDER BY `uri`") or error(db_error());
  634. $boards = $query->fetchAll();
  635. } else {
  636. $boards = array();
  637. $query = query("SELECT `uri` FROM ``boards``") or error(db_error());
  638. while ($board = $query->fetchColumn()) {
  639. $boards[] = $board;
  640. }
  641. }
  642. if ($config['cache']['enabled'])
  643. cache::set($cache_name, $boards);
  644. return $boards;
  645. }
  646. function until($timestamp) {
  647. $difference = $timestamp - time();
  648. switch(TRUE){
  649. case ($difference < 60):
  650. return $difference . ' ' . ngettext('second', 'seconds', $difference);
  651. case ($difference < 3600): //60*60 = 3600
  652. return ($num = round($difference/(60))) . ' ' . ngettext('minute', 'minutes', $num);
  653. case ($difference < 86400): //60*60*24 = 86400
  654. return ($num = round($difference/(3600))) . ' ' . ngettext('hour', 'hours', $num);
  655. case ($difference < 604800): //60*60*24*7 = 604800
  656. return ($num = round($difference/(86400))) . ' ' . ngettext('day', 'days', $num);
  657. case ($difference < 31536000): //60*60*24*365 = 31536000
  658. return ($num = round($difference/(604800))) . ' ' . ngettext('week', 'weeks', $num);
  659. default:
  660. return ($num = round($difference/(31536000))) . ' ' . ngettext('year', 'years', $num);
  661. }
  662. }
  663. function ago($timestamp) {
  664. $difference = time() - $timestamp;
  665. switch(TRUE){
  666. case ($difference < 60) :
  667. return $difference . ' ' . ngettext('second', 'seconds', $difference);
  668. case ($difference < 3600): //60*60 = 3600
  669. return ($num = round($difference/(60))) . ' ' . ngettext('minute', 'minutes', $num);
  670. case ($difference < 86400): //60*60*24 = 86400
  671. return ($num = round($difference/(3600))) . ' ' . ngettext('hour', 'hours', $num);
  672. case ($difference < 604800): //60*60*24*7 = 604800
  673. return ($num = round($difference/(86400))) . ' ' . ngettext('day', 'days', $num);
  674. case ($difference < 31536000): //60*60*24*365 = 31536000
  675. return ($num = round($difference/(604800))) . ' ' . ngettext('week', 'weeks', $num);
  676. default:
  677. return ($num = round($difference/(31536000))) . ' ' . ngettext('year', 'years', $num);
  678. }
  679. }
  680. function displayBan($ban) {
  681. global $config, $board;
  682. if (!$ban['seen']) {
  683. Bans::seen($ban['id']);
  684. }
  685. $ban['ip'] = $_SERVER['REMOTE_ADDR'];
  686. if ($ban['post'] && isset($ban['post']['board'], $ban['post']['id'])) {
  687. if (openBoard($ban['post']['board'])) {
  688. $query = query(sprintf("SELECT `files` FROM ``posts_%s`` WHERE `id` = " .
  689. (int)$ban['post']['id'], $board['uri']));
  690. if ($_post = $query->fetch(PDO::FETCH_ASSOC)) {
  691. $ban['post'] = array_merge($ban['post'], $_post);
  692. }
  693. }
  694. if ($ban['post']['thread']) {
  695. $post = new Post($ban['post']);
  696. } else {
  697. $post = new Thread($ban['post'], null, false, false);
  698. }
  699. }
  700. $denied_appeals = array();
  701. $pending_appeal = false;
  702. if ($config['ban_appeals']) {
  703. $query = query("SELECT `time`, `denied` FROM ``ban_appeals`` WHERE `ban_id` = " . (int)$ban['id']) or error(db_error());
  704. while ($ban_appeal = $query->fetch(PDO::FETCH_ASSOC)) {
  705. if ($ban_appeal['denied']) {
  706. $denied_appeals[] = $ban_appeal['time'];
  707. } else {
  708. $pending_appeal = $ban_appeal['time'];
  709. }
  710. }
  711. }
  712. // Show banned page and exit
  713. die(
  714. Element('page.html', array(
  715. 'title' => _('Banned!'),
  716. 'config' => $config,
  717. 'boardlist' => createBoardlist($mod),
  718. 'body' => Element('banned.html', array(
  719. 'config' => $config,
  720. 'ban' => $ban,
  721. 'board' => $board,
  722. 'post' => isset($post) ? $post->build(true) : false,
  723. 'denied_appeals' => $denied_appeals,
  724. 'pending_appeal' => $pending_appeal
  725. )
  726. ))
  727. ));
  728. }
  729. function checkBan($board = false) {
  730. global $config;
  731. if (!isset($_SERVER['REMOTE_ADDR'])) {
  732. // Server misconfiguration
  733. return;
  734. }
  735. if (event('check-ban', $board))
  736. return true;
  737. $ips = array();
  738. $ips[] = $_SERVER['REMOTE_ADDR'];
  739. if ($config['proxy_check'] && isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
  740. $ips = array_merge($ips, explode(", ", $_SERVER['HTTP_X_FORWARDED_FOR']));
  741. }
  742. foreach ($ips as $ip) {
  743. $bans = Bans::find($_SERVER['REMOTE_ADDR'], $board, $config['show_modname']);
  744. foreach ($bans as &$ban) {
  745. if ($ban['expires'] && $ban['expires'] < time()) {
  746. Bans::delete($ban['id']);
  747. if ($config['require_ban_view'] && !$ban['seen']) {
  748. if (!isset($_POST['json_response'])) {
  749. displayBan($ban);
  750. } else {
  751. header('Content-Type: text/json');
  752. die(json_encode(array('error' => true, 'banned' => true)));
  753. }
  754. }
  755. } else {
  756. if (!isset($_POST['json_response'])) {
  757. displayBan($ban);
  758. } else {
  759. header('Content-Type: text/json');
  760. die(json_encode(array('error' => true, 'banned' => true)));
  761. }
  762. }
  763. }
  764. }
  765. // I'm not sure where else to put this. It doesn't really matter where; it just needs to be called every
  766. // now and then to keep the ban list tidy.
  767. if ($config['cache']['enabled'] && $last_time_purged = cache::get('purged_bans_last')) {
  768. if (time() - $last_time_purged < $config['purge_bans'] )
  769. return;
  770. }
  771. Bans::purge();
  772. if ($config['cache']['enabled'])
  773. cache::set('purged_bans_last', time());
  774. }
  775. function threadLocked($id) {
  776. global $board;
  777. if (event('check-locked', $id))
  778. return true;
  779. $query = prepare(sprintf("SELECT `locked` FROM ``posts_%s`` WHERE `id` = :id AND `thread` IS NULL LIMIT 1", $board['uri']));
  780. $query->bindValue(':id', $id, PDO::PARAM_INT);
  781. $query->execute() or error(db_error());
  782. if (($locked = $query->fetchColumn()) === false) {
  783. // Non-existant, so it can't be locked...
  784. return false;
  785. }
  786. return (bool)$locked;
  787. }
  788. function threadSageLocked($id) {
  789. global $board;
  790. if (event('check-sage-locked', $id))
  791. return true;
  792. $query = prepare(sprintf("SELECT `sage` FROM ``posts_%s`` WHERE `id` = :id AND `thread` IS NULL LIMIT 1", $board['uri']));
  793. $query->bindValue(':id', $id, PDO::PARAM_INT);
  794. $query->execute() or error(db_error());
  795. if (($sagelocked = $query->fetchColumn()) === false) {
  796. // Non-existant, so it can't be locked...
  797. return false;
  798. }
  799. return (bool)$sagelocked;
  800. }
  801. function threadExists($id) {
  802. global $board;
  803. $query = prepare(sprintf("SELECT 1 FROM ``posts_%s`` WHERE `id` = :id AND `thread` IS NULL LIMIT 1", $board['uri']));
  804. $query->bindValue(':id', $id, PDO::PARAM_INT);
  805. $query->execute() or error(db_error());
  806. if ($query->rowCount()) {
  807. return true;
  808. }
  809. return false;
  810. }
  811. function insertFloodPost(array $post) {
  812. global $board;
  813. $query = prepare("INSERT INTO ``flood`` VALUES (NULL, :ip, :board, :time, :posthash, :filehash, :isreply)");
  814. $query->bindValue(':ip', $_SERVER['REMOTE_ADDR']);
  815. $query->bindValue(':board', $board['uri']);
  816. $query->bindValue(':time', time());
  817. $query->bindValue(':posthash', make_comment_hex($post['body_nomarkup']));
  818. if ($post['has_file'])
  819. $query->bindValue(':filehash', $post['filehash']);
  820. else
  821. $query->bindValue(':filehash', null, PDO::PARAM_NULL);
  822. $query->bindValue(':isreply', !$post['op'], PDO::PARAM_INT);
  823. $query->execute() or error(db_error($query));
  824. }
  825. function post(array $post) {
  826. global $pdo, $board;
  827. $query = prepare(sprintf("INSERT INTO ``posts_%s`` VALUES ( NULL, :thread, :subject, :email, :name, :trip, :capcode, :body, :body_nomarkup, :time, :time, :files, :num_files, :filehash, :password, :ip, :sticky, :locked, :cycle, 0, :embed, :slug)", $board['uri']));
  828. // Basic stuff
  829. if (!empty($post['subject'])) {
  830. $query->bindValue(':subject', $post['subject']);
  831. } else {
  832. $query->bindValue(':subject', null, PDO::PARAM_NULL);
  833. }
  834. if (!empty($post['email'])) {
  835. $query->bindValue(':email', $post['email']);
  836. } else {
  837. $query->bindValue(':email', null, PDO::PARAM_NULL);
  838. }
  839. if (!empty($post['trip'])) {
  840. $query->bindValue(':trip', $post['trip']);
  841. } else {
  842. $query->bindValue(':trip', null, PDO::PARAM_NULL);
  843. }
  844. $query->bindValue(':name', $post['name']);
  845. $query->bindValue(':body', $post['body']);
  846. $query->bindValue(':body_nomarkup', $post['body_nomarkup']);
  847. $query->bindValue(':time', isset($post['time']) ? $post['time'] : time(), PDO::PARAM_INT);
  848. $query->bindValue(':password', $post['password']);
  849. $query->bindValue(':ip', isset($post['ip']) ? $post['ip'] : $_SERVER['REMOTE_ADDR']);
  850. if ($post['op'] && $post['mod'] && isset($post['sticky']) && $post['sticky']) {
  851. $query->bindValue(':sticky', true, PDO::PARAM_INT);
  852. } else {
  853. $query->bindValue(':sticky', false, PDO::PARAM_INT);
  854. }
  855. if ($post['op'] && $post['mod'] && isset($post['locked']) && $post['locked']) {
  856. $query->bindValue(':locked', true, PDO::PARAM_INT);
  857. } else {
  858. $query->bindValue(':locked', false, PDO::PARAM_INT);
  859. }
  860. if ($post['op'] && $post['mod'] && isset($post['cycle']) && $post['cycle']) {
  861. $query->bindValue(':cycle', true, PDO::PARAM_INT);
  862. } else {
  863. $query->bindValue(':cycle', false, PDO::PARAM_INT);
  864. }
  865. if ($post['mod'] && isset($post['capcode']) && $post['capcode']) {
  866. $query->bindValue(':capcode', $post['capcode'], PDO::PARAM_INT);
  867. } else {
  868. $query->bindValue(':capcode', null, PDO::PARAM_NULL);
  869. }
  870. if (!empty($post['embed'])) {
  871. $query->bindValue(':embed', $post['embed']);
  872. } else {
  873. $query->bindValue(':embed', null, PDO::PARAM_NULL);
  874. }
  875. if ($post['op']) {
  876. // No parent thread, image
  877. $query->bindValue(':thread', null, PDO::PARAM_NULL);
  878. } else {
  879. $query->bindValue(':thread', $post['thread'], PDO::PARAM_INT);
  880. }
  881. if ($post['has_file']) {
  882. $query->bindValue(':files', json_encode($post['files']));
  883. $query->bindValue(':num_files', $post['num_files']);
  884. $query->bindValue(':filehash', $post['filehash']);
  885. } else {
  886. $query->bindValue(':files', null, PDO::PARAM_NULL);
  887. $query->bindValue(':num_files', 0);
  888. $query->bindValue(':filehash', null, PDO::PARAM_NULL);
  889. }
  890. if ($post['op']) {
  891. $query->bindValue(':slug', slugify($post));
  892. }
  893. else {
  894. $query->bindValue(':slug', NULL);
  895. }
  896. if (!$query->execute()) {
  897. undoImage($post);
  898. error(db_error($query));
  899. }
  900. return $pdo->lastInsertId();
  901. }
  902. function bumpThread($id) {
  903. global $config, $board, $build_pages;
  904. if (event('bump', $id))
  905. return true;
  906. if ($config['try_smarter']) {
  907. $build_pages = array_merge(range(1, thread_find_page($id)), $build_pages);
  908. }
  909. $query = prepare(sprintf("UPDATE ``posts_%s`` SET `bump` = :time WHERE `id` = :id AND `thread` IS NULL", $board['uri']));
  910. $query->bindValue(':time', time(), PDO::PARAM_INT);
  911. $query->bindValue(':id', $id, PDO::PARAM_INT);
  912. $query->execute() or error(db_error($query));
  913. }
  914. // Remove file from post
  915. function deleteFile($id, $remove_entirely_if_already=true, $file=null) {
  916. global $board, $config;
  917. $query = prepare(sprintf("SELECT `thread`, `files`, `num_files` FROM ``posts_%s`` WHERE `id` = :id LIMIT 1", $board['uri']));
  918. $query->bindValue(':id', $id, PDO::PARAM_INT);
  919. $query->execute() or error(db_error($query));
  920. if (!$post = $query->fetch(PDO::FETCH_ASSOC))
  921. error($config['error']['invalidpost']);
  922. $files = json_decode($post['files']);
  923. $file_to_delete = $file !== false ? $files[(int)$file] : (object)array('file' => false);
  924. if (!$files[0]) error(_('That post has no files.'));
  925. if ($files[0]->file == 'deleted' && $post['num_files'] == 1 && !$post['thread'])
  926. return; // Can't delete OP's image completely.
  927. $query = prepare(sprintf("UPDATE ``posts_%s`` SET `files` = :file WHERE `id` = :id", $board['uri']));
  928. if (($file && $file_to_delete->file == 'deleted') && $remove_entirely_if_already) {
  929. // Already deleted; remove file fully
  930. $files[$file] = null;
  931. } else {
  932. foreach ($files as $i => $f) {
  933. if (($file !== false && $i == $file) || $file === null) {
  934. // Delete thumbnail
  935. file_unlink($board['dir'] . $config['dir']['thumb'] . $f->thumb);
  936. unset($files[$i]->thumb);
  937. // Delete file
  938. file_unlink($board['dir'] . $config['dir']['img'] . $f->file);
  939. $files[$i]->file = 'deleted';
  940. }
  941. }
  942. }
  943. $query->bindValue(':file', json_encode($files), PDO::PARAM_STR);
  944. $query->bindValue(':id', $id, PDO::PARAM_INT);
  945. $query->execute() or error(db_error($query));
  946. if ($post['thread'])
  947. buildThread($post['thread']);
  948. else
  949. buildThread($id);
  950. }
  951. // rebuild post (markup)
  952. function rebuildPost($id) {
  953. global $board, $mod;
  954. $query = prepare(sprintf("SELECT * FROM ``posts_%s`` WHERE `id` = :id", $board['uri']));
  955. $query->bindValue(':id', $id, PDO::PARAM_INT);
  956. $query->execute() or error(db_error($query));
  957. if ((!$post = $query->fetch(PDO::FETCH_ASSOC)) || !$post['body_nomarkup'])
  958. return false;
  959. markup($post['body'] = &$post['body_nomarkup']);
  960. $post = (object)$post;
  961. event('rebuildpost', $post);
  962. $post = (array)$post;
  963. $query = prepare(sprintf("UPDATE ``posts_%s`` SET `body` = :body WHERE `id` = :id", $board['uri']));
  964. $query->bindValue(':body', $post['body']);
  965. $query->bindValue(':id', $id, PDO::PARAM_INT);
  966. $query->execute() or error(db_error($query));
  967. buildThread($post['thread'] ? $post['thread'] : $id);
  968. return true;
  969. }
  970. // Delete a post (reply or thread)
  971. function deletePost($id, $error_if_doesnt_exist=true, $rebuild_after=true) {
  972. global $board, $config;
  973. // Select post and replies (if thread) in one query
  974. $query = prepare(sprintf("SELECT `id`,`thread`,`files`,`slug` FROM ``posts_%s`` WHERE `id` = :id OR `thread` = :id", $board['uri']));
  975. $query->bindValue(':id', $id, PDO::PARAM_INT);
  976. $query->execute() or error(db_error($query));
  977. if ($query->rowCount() < 1) {
  978. if ($error_if_doesnt_exist)
  979. error($config['error']['invalidpost']);
  980. else return false;
  981. }
  982. $ids = array();
  983. // Delete posts and maybe replies
  984. while ($post = $query->fetch(PDO::FETCH_ASSOC)) {
  985. event('delete', $post);
  986. if (!$post['thread']) {
  987. // Delete thread HTML page
  988. file_unlink($board['dir'] . $config['dir']['res'] . link_for($post) );
  989. file_unlink($board['dir'] . $config['dir']['res'] . link_for($post, true) ); // noko50
  990. file_unlink($board['dir'] . $config['dir']['res'] . sprintf('%d.json', $post['id']));
  991. $antispam_query = prepare('DELETE FROM ``antispam`` WHERE `board` = :board AND `thread` = :thread');
  992. $antispam_query->bindValue(':board', $board['uri']);
  993. $antispam_query->bindValue(':thread', $post['id']);
  994. $antispam_query->execute() or error(db_error($antispam_query));
  995. } elseif ($query->rowCount() == 1) {
  996. // Rebuild thread
  997. $rebuild = &$post['thread'];
  998. }
  999. if ($post['files']) {
  1000. // Delete file
  1001. foreach (json_decode($post['files']) as $i => $f) {
  1002. if ($f->file !== 'deleted') {
  1003. file_unlink($board['dir'] . $config['dir']['img'] . $f->file);
  1004. file_unlink($board['dir'] . $config['dir']['thumb'] . $f->thumb);
  1005. }
  1006. }
  1007. }
  1008. $ids[] = (int)$post['id'];
  1009. }
  1010. $query = prepare(sprintf("DELETE FROM ``posts_%s`` WHERE `id` = :id OR `thread` = :id", $board['uri']));
  1011. $query->bindValue(':id', $id, PDO::PARAM_INT);
  1012. $query->execute() or error(db_error($query));
  1013. $query = prepare("SELECT `board`, `post` FROM ``cites`` WHERE `target_board` = :board AND (`target` = " . implode(' OR `target` = ', $ids) . ") ORDER BY `board`");
  1014. $query->bindValue(':board', $board['uri']);
  1015. $query->execute() or error(db_error($query));
  1016. while ($cite = $query->fetch(PDO::FETCH_ASSOC)) {
  1017. if ($board['uri'] != $cite['board']) {
  1018. if (!isset($tmp_board))
  1019. $tmp_board = $board['uri'];
  1020. openBoard($cite['board']);
  1021. }
  1022. rebuildPost($cite['post']);
  1023. }
  1024. if (isset($tmp_board))
  1025. openBoard($tmp_board);
  1026. $query = prepare("DELETE FROM ``cites`` WHERE (`target_board` = :board AND (`target` = " . implode(' OR `target` = ', $ids) . ")) OR (`board` = :board AND (`post` = " . implode(' OR `post` = ', $ids) . "))");
  1027. $query->bindValue(':board', $board['uri']);
  1028. $query->execute() or error(db_error($query));
  1029. if (isset($rebuild) && $rebuild_after) {
  1030. buildThread($rebuild);
  1031. buildIndex();
  1032. }
  1033. return true;
  1034. }
  1035. function clean($pid = false) {
  1036. global $board, $config;
  1037. $offset = round($config['max_pages']*$config['threads_per_page']);
  1038. // I too wish there was an easier way of doing this...
  1039. $query = prepare(sprintf("SELECT `id` FROM ``posts_%s`` WHERE `thread` IS NULL ORDER BY `sticky` DESC, `bump` DESC LIMIT :offset, 9001", $board['uri']));
  1040. $query->bindValue(':offset', $offset, PDO::PARAM_INT);
  1041. $query->execute() or error(db_error($query));
  1042. while ($post = $query->fetch(PDO::FETCH_ASSOC)) {
  1043. deletePost($post['id'], false, false);
  1044. if ($pid) modLog("Automatically deleting thread #{$post['id']} due to new thread #{$pid}");
  1045. }
  1046. // Bump off threads with X replies earlier, spam prevention method
  1047. if ($config['early_404']) {
  1048. $offset = round($config['early_404_page']*$config['threads_per_page']);
  1049. $query = prepare(sprintf("SELECT `id` AS `thread_id`, (SELECT COUNT(`id`) FROM ``posts_%s`` WHERE `thread` = `thread_id`) AS `reply_count` FROM ``posts_%s`` WHERE `thread` IS NULL ORDER BY `sticky` DESC, `bump` DESC LIMIT :offset, 9001", $board['uri'], $board['uri']));
  1050. $query->bindValue(':offset', $offset, PDO::PARAM_INT);
  1051. $query->execute() or error(db_error($query));
  1052. while ($post = $query->fetch(PDO::FETCH_ASSOC)) {
  1053. if ($post['reply_count'] < $config['early_404_replies']) {
  1054. deletePost($post['thread_id'], false, false);
  1055. if ($pid) modLog("Automatically deleting thread #{$post['thread_id']} due to new thread #{$pid} (early 404 is set, #{$post['thread_id']} had {$post['reply_count']} replies)");
  1056. }
  1057. }
  1058. }
  1059. }
  1060. function thread_find_page($thread) {
  1061. global $config, $board;
  1062. $query = query(sprintf("SELECT `id` FROM ``posts_%s`` WHERE `thread` IS NULL ORDER BY `sticky` DESC, `bump` DESC", $board['uri'])) or error(db_error($query));
  1063. $threads = $query->fetchAll(PDO::FETCH_COLUMN);
  1064. if (($index = array_search($thread, $threads)) === false)
  1065. return false;
  1066. return floor(($config['threads_per_page'] + $index) / $config['threads_per_page']);
  1067. }
  1068. // $brief means that we won't need to generate anything yet
  1069. function index($page, $mod=false, $brief = false) {
  1070. global $board, $config, $debug;
  1071. $body = '';
  1072. $offset = round($page*$config['threads_per_page']-$config['threads_per_page']);
  1073. $query = prepare(sprintf("SELECT * FROM ``posts_%s`` WHERE `thread` IS NULL ORDER BY `sticky` DESC, `bump` DESC LIMIT :offset,:threads_per_page", $board['uri']));
  1074. $query->bindValue(':offset', $offset, PDO::PARAM_INT);
  1075. $query->bindValue(':threads_per_page', $config['threads_per_page'], PDO::PARAM_INT);
  1076. $query->execute() or error(db_error($query));
  1077. if ($page == 1 && $query->rowCount() < $config['threads_per_page'])
  1078. $board['thread_count'] = $query->rowCount();
  1079. if ($query->rowCount() < 1 && $page > 1)
  1080. return false;
  1081. $threads = array();
  1082. while ($th = $query->fetch(PDO::FETCH_ASSOC)) {
  1083. $thread = new Thread($th, $mod ? '?/' : $config['root'], $mod);
  1084. if ($config['cache']['enabled']) {
  1085. $cached = cache::get("thread_index_{$board['uri']}_{$th['id']}");
  1086. if (isset($cached['replies'], $cached['omitted'])) {
  1087. $replies = $cached['replies'];
  1088. $omitted = $cached['omitted'];
  1089. } else {
  1090. unset($cached);
  1091. }
  1092. }
  1093. if (!isset($cached)) {
  1094. $posts = prepare(sprintf("SELECT * FROM ``posts_%s`` WHERE `thread` = :id ORDER BY `id` DESC LIMIT :limit", $board['uri']));
  1095. $posts->bindValue(':id', $th['id']);
  1096. $posts->bindValue(':limit', ($th['sticky'] ? $config['threads_preview_sticky'] : $config['threads_preview']), PDO::PARAM_INT);
  1097. $posts->execute() or error(db_error($posts));
  1098. $replies = array_reverse($posts->fetchAll(PDO::FETCH_ASSOC));
  1099. if (count($replies) == ($th['sticky'] ? $config['threads_preview_sticky'] : $config['threads_preview'])) {
  1100. $count = numPosts($th['id']);
  1101. $omitted = array('post_count' => $count['replies'], 'image_count' => $count['images']);
  1102. } else {
  1103. $omitted = false;
  1104. }
  1105. if ($config['cache']['enabled'])
  1106. cache::set("thread_index_{$board['uri']}_{$th['id']}", array(
  1107. 'replies' => $replies,
  1108. 'omitted' => $omitted,
  1109. ));
  1110. }
  1111. $num_images = 0;
  1112. foreach ($replies as $po) {
  1113. if ($po['num_files'])
  1114. $num_images+=$po['num_files'];
  1115. $thread->add(new Post($po, $mod ? '?/' : $config['root'], $mod));
  1116. }
  1117. $thread->images = $num_images;
  1118. $thread->replies = isset($omitted['post_count']) ? $omitted['post_count'] : count($replies);
  1119. if ($omitted) {
  1120. $thread->omitted = $omitted['post_count'] - ($th['sticky'] ? $config['threads_preview_sticky'] : $config['threads_preview']);
  1121. $thread->omitted_images = $omitted['image_count'] - $num_images;
  1122. }
  1123. $threads[] = $thread;
  1124. if (!$brief) {
  1125. $body .= $thread->build(true);
  1126. }
  1127. }
  1128. if ($config['file_board']) {
  1129. $body = Element('fileboard.html', array('body' => $body, 'mod' => $mod));
  1130. }
  1131. return array(
  1132. 'board' => $board,
  1133. 'body' => $body,
  1134. 'post_url' => $config['post_url'],
  1135. 'config' => $config,
  1136. 'boardlist' => createBoardlist($mod),
  1137. 'threads' => $threads,
  1138. );
  1139. }
  1140. function getPageButtons($pages, $mod=false) {
  1141. global $config, $board;
  1142. $btn = array();
  1143. $root = ($mod ? '?/' : $config['root']) . $board['dir'];
  1144. foreach ($pages as $num => $page) {
  1145. if (isset($page['selected'])) {
  1146. // Previous button
  1147. if ($num == 0) {
  1148. // There is no previous page.
  1149. $btn['prev'] = _('Previous');
  1150. } else {
  1151. $loc = ($mod ? '?/' . $board['uri'] . '/' : '') .
  1152. ($num == 1 ?
  1153. $config['file_index']
  1154. :
  1155. sprintf($config['file_page'], $num)
  1156. );
  1157. $btn['prev'] = '<form action="' . ($mod ? '' : $root . $loc) . '" method="get">' .
  1158. ($mod ?
  1159. '<input type="hidden" name="status" value="301" />' .
  1160. '<input type="hidden" name="r" value="' . htmlentities($loc) . '" />'
  1161. :'') .
  1162. '<input type="submit" value="' . _('Previous') . '" /></form>';
  1163. }
  1164. if ($num == count($pages) - 1) {
  1165. // There is no next page.
  1166. $btn['next'] = _('Next');
  1167. } else {
  1168. $loc = ($mod ? '?/' . $board['uri'] . '/' : '') . sprintf($config['file_page'], $num + 2);
  1169. $btn['next'] = '<form action="' . ($mod ? '' : $root . $loc) . '" method="get">' .
  1170. ($mod ?
  1171. '<input type="hidden" name="status" value="301" />' .
  1172. '<input type="hidden" name="r" value="' . htmlentities($loc) . '" />'
  1173. :'') .
  1174. '<input type="submit" value="' . _('Next') . '" /></form>';
  1175. }
  1176. }
  1177. }
  1178. return $btn;
  1179. }
  1180. function getPages($mod=false) {
  1181. global $board, $config;
  1182. if (isset($board['thread_count'])) {
  1183. $count = $board['thread_count'];
  1184. } else {
  1185. // Count threads
  1186. $query = query(sprintf("SELECT COUNT(*) FROM ``posts_%s`` WHERE `thread` IS NULL", $board['uri'])) or error(db_error());
  1187. $count = $query->fetchColumn();
  1188. }
  1189. $count = floor(($config['threads_per_page'] + $count - 1) / $config['threads_per_page']);
  1190. if ($count < 1) $count = 1;
  1191. $pages = array();
  1192. for ($x=0;$x<$count && $x<$config['max_pages'];$x++) {
  1193. $pages[] = array(
  1194. 'num' => $x+1,
  1195. 'link' => $x==0 ? ($mod ? '?/' : $config['root']) . $board['dir'] . $config['file_index'] : ($mod ? '?/' : $config['root']) . $board['dir'] . sprintf($config['file_page'], $x+1)
  1196. );
  1197. }
  1198. return $pages;
  1199. }
  1200. // Stolen with permission from PlainIB (by Frank Usrs)
  1201. function make_comment_hex($str) {
  1202. // remove cross-board citations
  1203. // the numbers don't matter
  1204. $str = preg_replace('!>>>/[A-Za-z0-9]+/!', '', $str);
  1205. if (function_exists('iconv')) {
  1206. // remove diacritics and other noise
  1207. // FIXME: this removes cyrillic entirely
  1208. $oldstr = $str;
  1209. $str = @iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $str);
  1210. if (!$str) $str = $oldstr;
  1211. }
  1212. $str = strtolower($str);
  1213. // strip all non-alphabet characters
  1214. $str = preg_replace('/[^a-z]/', '', $str);
  1215. return md5($str);
  1216. }
  1217. function makerobot($body) {
  1218. global $config;
  1219. $body = strtolower($body);
  1220. // Leave only letters
  1221. $body = preg_replace('/[^a-z]/i', '', $body);
  1222. // Remove repeating characters
  1223. if ($config['robot_strip_repeating'])
  1224. $body = preg_replace('/(.)\\1+/', '$1', $body);
  1225. return sha1($body);
  1226. }
  1227. function checkRobot($body) {
  1228. if (empty($body) || event('check-robot', $body))
  1229. return true;
  1230. $body = makerobot($body);
  1231. $query = prepare("SELECT 1 FROM ``robot`` WHERE `hash` = :hash LIMIT 1");
  1232. $query->bindValue(':hash', $body);
  1233. $query->execute() or error(db_error($query));
  1234. if ($query->fetchColumn()) {
  1235. return true;
  1236. }
  1237. // Insert new hash
  1238. $query = prepare("INSERT INTO ``robot`` VALUES (:hash)");
  1239. $query->bindValue(':hash', $body);
  1240. $query->execute() or error(db_error($query));
  1241. return false;
  1242. }
  1243. // Returns an associative array with 'replies' and 'images' keys
  1244. function numPosts($id) {
  1245. global $board;
  1246. $query = prepare(sprintf("SELECT COUNT(*) AS `replies`, SUM(`num_files`) AS `images` FROM ``posts_%s`` WHERE `thread` = :thread", $board['uri'], $board['uri']));
  1247. $query->bindValue(':thread', $id, PDO::PARAM_INT);
  1248. $query->execute() or error(db_error($query));
  1249. return $query->fetch(PDO::FETCH_ASSOC);
  1250. }
  1251. function muteTime() {
  1252. global $config;
  1253. if ($time = event('mute-time'))
  1254. return $time;
  1255. // Find number of mutes in the past X hours
  1256. $query = prepare("SELECT COUNT(*) FROM ``mutes`` WHERE `time` >= :time AND `ip` = :ip");
  1257. $query->bindValue(':time', time()-($config['robot_mute_hour']*3600), PDO::PARAM_INT);
  1258. $query->bindValue(':ip', $_SERVER['REMOTE_ADDR']);
  1259. $query->execute() or error(db_error($query));
  1260. if (!$result = $query->fetchColumn())
  1261. return 0;
  1262. return pow($config['robot_mute_multiplier'], $result);
  1263. }
  1264. function mute() {
  1265. // Insert mute
  1266. $query = prepare("INSERT INTO ``mutes`` VALUES (:ip, :time)");
  1267. $query->bindValue(':time', time(), PDO::PARAM_INT);
  1268. $query->bindValue(':ip', $_SERVER['REMOTE_ADDR']);
  1269. $query->execute() or error(db_error($query));
  1270. return muteTime();
  1271. }
  1272. function checkMute() {
  1273. global $config, $debug;
  1274. if ($config['cache']['enabled']) {
  1275. // Cached mute?
  1276. if (($mute = cache::get("mute_${_SERVER['REMOTE_ADDR']}")) && ($mutetime = cache::get("mutetime_${_SERVER['REMOTE_ADDR']}"))) {
  1277. error(sprintf($config['error']['youaremuted'], $mute['time'] + $mutetime - time()));
  1278. }
  1279. }
  1280. $mutetime = muteTime();
  1281. if ($mutetime > 0) {
  1282. // Find last mute time
  1283. $query = prepare("SELECT `time` FROM ``mutes`` WHERE `ip` = :ip ORDER BY `time` DESC LIMIT 1");
  1284. $query->bindValue(':ip', $_SERVER['REMOTE_ADDR']);
  1285. $query->execute() or error(db_error($query));
  1286. if (!$mute = $query->fetch(PDO::FETCH_ASSOC)) {
  1287. // What!? He's muted but he's not muted...
  1288. return;
  1289. }
  1290. if ($mute['time'] + $mutetime > time()) {
  1291. if ($config['cache']['enabled']) {
  1292. cache::set("mute_${_SERVER['REMOTE_ADDR']}", $mute, $mute['time'] + $mutetime - time());
  1293. cache::set("mutetime_${_SERVER['REMOTE_ADDR']}", $mutetime, $mute['time'] + $mutetime - time());
  1294. }
  1295. // Not expired yet
  1296. error(sprintf($config['error']['youaremuted'], $mute['time'] + $mutetime - time()));
  1297. } else {
  1298. // Already expired
  1299. return;
  1300. }
  1301. }
  1302. }
  1303. function buildIndex($global_api = "yes") {
  1304. global $board, $config, $build_pages;
  1305. $catalog_api_action = generation_strategy('sb_api', array($board['uri']));
  1306. $pages = null;
  1307. $antibot = null;
  1308. if ($config['api']['enabled']) {
  1309. $api = new Api();
  1310. $catalog = array();
  1311. }
  1312. for ($page = 1; $page <= $config['max_pages']; $page++) {
  1313. $filename = $board['dir'] . ($page == 1 ? $config['file_index'] : sprintf($config['file_page'], $page));
  1314. $jsonFilename = $board['dir'] . ($page - 1) . '.json'; // pages should start from 0
  1315. $wont_build_this_page = $config['try_smarter'] && isset($build_pages) && !empty($build_pages) && !in_array($page, $build_pages);
  1316. if ((!$config['api']['enabled'] || $global_api == "skip") && $wont_build_this_page)
  1317. continue;
  1318. $action = generation_strategy('sb_board', array($board['uri'], $page));
  1319. if ($action == 'rebuild' || $catalog_api_action == 'rebuild') {
  1320. $content = index($page, false, $wont_build_this_page);
  1321. if (!$content)
  1322. break;
  1323. // json api
  1324. if ($config['api']['enabled']) {
  1325. $threads = $content['threads'];
  1326. $json = json_encode($api->translatePage($threads));
  1327. file_write($jsonFilename, $json);
  1328. $catalog[$page-1] = $threads;
  1329. if ($wont_build_this_page) continue;
  1330. }
  1331. if ($config['try_smarter']) {
  1332. $antibot = create_antibot($board['uri'], 0 - $page);
  1333. $content['current_page'] = $page;
  1334. }
  1335. elseif (!$antibot) {
  1336. $antibot = create_antibot($board['uri']);
  1337. }
  1338. $antibot->reset();
  1339. if (!$pages) {
  1340. $pages = getPages();
  1341. }
  1342. $content['pages'] = $pages;
  1343. $content['pages'][$page-1]['selected'] = true;
  1344. $content['btn'] = getPageButtons($content['pages']);
  1345. $content['antibot'] = $antibot;
  1346. file_write($filename, Element('index.html', $content));
  1347. }
  1348. elseif ($action == 'delete' || $catalog_api_action == 'delete') {
  1349. file_unlink($filename);
  1350. file_unlink($jsonFilename);
  1351. }
  1352. }
  1353. // $action is an action for our last page
  1354. if (($catalog_api_action == 'rebuild' || $action == 'rebuild' || $action == 'delete') && $page < $config['max_pages']) {
  1355. for (;$page<=$config['max_pages'];$page++) {
  1356. $filename = $board['dir'] . ($page==1 ? $config['file_index'] : sprintf($config['file_page'], $page));
  1357. file_unlink($filename);
  1358. if ($config['api']['enabled']) {
  1359. $jsonFilename = $board['dir'] . ($page - 1) . '.json';
  1360. file_unlink($jsonFilename);
  1361. }
  1362. }
  1363. }
  1364. // json api catalog
  1365. if ($config['api']['enabled'] && $global_api != "skip") {
  1366. if ($catalog_api_action == 'delete') {
  1367. $jsonFilename = $board['dir'] . 'catalog.json';
  1368. file_unlink($jsonFilename);
  1369. $jsonFilename = $board['dir'] . 'threads.json';
  1370. file_unlink($jsonFilename);
  1371. }
  1372. elseif ($catalog_api_action == 'rebuild') {
  1373. $json = json_encode($api->translateCatalog($catalog));
  1374. $jsonFilename = $board['dir'] . 'catalog.json';
  1375. file_write($jsonFilename, $json);
  1376. $json = json_encode($api->translateCatalog($catalog, true));
  1377. $jsonFilename = $board['dir'] . 'threads.json';
  1378. file_write($jsonFilename, $json);
  1379. }
  1380. }
  1381. if ($config['try_smarter'])
  1382. $build_pages = array();
  1383. }
  1384. function buildJavascript() {
  1385. global $config;
  1386. $stylesheets = array();
  1387. foreach ($config['stylesheets'] as $name => $uri) {
  1388. $stylesheets[] = array(
  1389. 'name' => addslashes($name),
  1390. 'uri' => addslashes((!empty($uri) ? $config['uri_stylesheets'] : '') . $uri));
  1391. }
  1392. $script = Element('main.js', array(
  1393. 'config' => $config,
  1394. 'stylesheets' => $stylesheets
  1395. ));
  1396. // Check if we have translation for the javascripts; if yes, we add it to additional javascripts
  1397. list($pure_locale) = explode(".", $config['locale']);
  1398. if (file_exists ($jsloc = "inc/locale/$pure_locale/LC_MESSAGES/javascript.js")) {
  1399. $script = file_get_contents($jsloc) . "\n\n" . $script;
  1400. }
  1401. if ($config['additional_javascript_compile']) {
  1402. foreach ($config['additional_javascript'] as $file) {
  1403. $script .= file_get_contents($file);
  1404. }
  1405. }
  1406. if ($config['minify_js']) {
  1407. require_once 'inc/lib/minify/JSMin.php';
  1408. $script = JSMin::minify($script);
  1409. }
  1410. file_write($config['file_script'], $script);
  1411. }
  1412. function checkDNSBL() {
  1413. global $config;
  1414. if (isIPv6())
  1415. return; // No IPv6 support yet.
  1416. if (!isset($_SERVER['REMOTE_ADDR']))
  1417. return; // Fix your web server configuration
  1418. if (in_array($_SERVER['REMOTE_ADDR'], $config['dnsbl_exceptions']))
  1419. return;
  1420. $ipaddr = ReverseIPOctets($_SERVER['REMOTE_ADDR']);
  1421. foreach ($config['dnsbl'] as $blacklist) {
  1422. if (!is_array($blacklist))
  1423. $blacklist = array($blacklist);
  1424. if (($lookup = str_replace('%', $ipaddr, $blacklist[0])) == $blacklist[0])
  1425. $lookup = $ipaddr . '.' . $blacklist[0];
  1426. if (!$ip = DNS($lookup))
  1427. continue; // not in list
  1428. $blacklist_name = isset($blacklist[2]) ? $blacklist[2] : $blacklist[0];
  1429. if (!isset($blacklist[1])) {
  1430. // If you're listed at all, you're blocked.
  1431. error(sprintf($config['error']['dnsbl'], $blacklist_name));
  1432. } elseif (is_array($blacklist[1])) {
  1433. foreach ($blacklist[1] as $octet) {
  1434. if ($ip == $octet || $ip == '127.0.0.' . $octet)
  1435. error(sprintf($config['error']['dnsbl'], $blacklist_name));
  1436. }
  1437. } elseif (is_callable($blacklist[1])) {
  1438. if ($blacklist[1]($ip))
  1439. error(sprintf($config['error']['dnsbl'], $blacklist_name));
  1440. } else {
  1441. if ($ip == $blacklist[1] || $ip == '127.0.0.' . $blacklist[1])
  1442. error(sprintf($config['error']['dnsbl'], $blacklist_name));
  1443. }
  1444. }
  1445. }
  1446. function isIPv6() {
  1447. return strstr($_SERVER['REMOTE_ADDR'], ':') !== false;
  1448. }
  1449. function ReverseIPOctets($ip) {
  1450. return implode('.', array_reverse(explode('.', $ip)));
  1451. }
  1452. function wordfilters(&$body) {
  1453. global $config;
  1454. foreach ($config['wordfilters'] as $filter) {
  1455. if (isset($filter[2]) && $filter[2]) {
  1456. if (is_callable($filter[1]))
  1457. $body = preg_replace_callback($filter[0], $filter[1], $body);
  1458. else
  1459. $body = preg_replace($filter[0], $filter[1], $body);
  1460. } else {
  1461. $body = str_ireplace($filter[0], $filter[1], $body);
  1462. }
  1463. }
  1464. }
  1465. function quote($body, $quote=true) {
  1466. global $config;
  1467. $body = str_replace('<br/>', "\n", $body);
  1468. $body = strip_tags($body);
  1469. $body = preg_replace("/(^|\n)/", '$1&gt;', $body);
  1470. $body .= "\n";
  1471. if ($config['minify_html'])
  1472. $body = str_replace("\n", '&#010;', $body);
  1473. return $body;
  1474. }
  1475. function markup_url($matches) {
  1476. global $config, $markup_urls;
  1477. $url = $matches[1];
  1478. $after = $matches[2];
  1479. $markup_urls[] = $url;
  1480. $link = (object) array(
  1481. 'href' => $config['link_prefix'] . $url,
  1482. 'text' => $url,
  1483. 'rel' => 'nofollow',
  1484. 'target' => '_blank',
  1485. );
  1486. event('markup-url', $link);
  1487. $link = (array)$link;
  1488. $parts = array();
  1489. foreach ($link as $attr => $value) {
  1490. if ($attr == 'text' || $attr == 'after')
  1491. continue;
  1492. $parts[] = $attr . '="' . $value . '"';
  1493. }
  1494. if (isset($link['after']))
  1495. $after = $link['after'] . $after;
  1496. return '<a ' . implode(' ', $parts) . '>' . $link['text'] . '</a>' . $after;
  1497. }
  1498. function unicodify($body) {
  1499. $body = str_replace('...', '&hellip;', $body);
  1500. $body = str_replace('&lt;--', '&larr;', $body);
  1501. $body = str_replace('--&gt;', '&rarr;', $body);
  1502. // En and em- dashes are rendered exactly the same in
  1503. // most monospace fonts (they look the same in code
  1504. // editors).
  1505. $body = str_replace('---', '&mdash;', $body); // em dash
  1506. $body = str_replace('--', '&ndash;', $body); // en dash
  1507. return $body;
  1508. }
  1509. function extract_modifiers($body) {
  1510. $modifiers = array();
  1511. if (preg_match_all('@<tinyboard ([\w\s]+)>(.*?)</tinyboard>@us', $body, $matches, PREG_SET_ORDER)) {
  1512. foreach ($matches as $match) {
  1513. if (preg_match('/^escape /', $match[1]))
  1514. continue;
  1515. $modifiers[$match[1]] = html_entity_decode($match[2]);
  1516. }
  1517. }
  1518. return $modifiers;
  1519. }
  1520. function remove_modifiers($body) {
  1521. return preg_replace('@<tinyboard ([\w\s]+)>(.+?)</tinyboard>@usm', '', $body);
  1522. }
  1523. function markup(&$body, $track_cites = false, $op = false) {
  1524. global $board, $config, $markup_urls;
  1525. $modifiers = extract_modifiers($body);
  1526. $body = preg_replace('@<tinyboard (?!escape )([\w\s]+)>(.+?)</tinyboard>@us', '', $body);
  1527. $body = preg_replace('@<(tinyboard) escape ([\w\s]+)>@i', '<$1 $2>', $body);
  1528. if (isset($modifiers['raw html']) && $modifiers['raw html'] == '1') {
  1529. return array();
  1530. }
  1531. $body = str_replace("\r", '', $body);
  1532. $body = utf8tohtml($body);
  1533. if (mysql_version() < 50503)
  1534. $body = mb_encode_numericentity($body, array(0x010000, 0xffffff, 0, 0xffffff), 'UTF-8');
  1535. if ($config['markup_code']) {
  1536. $code_markup = array();
  1537. $body = preg_replace_callback($config['markup_code'], function($matches) use (&$code_markup) {
  1538. $d = count($code_markup);
  1539. $code_markup[] = $matches;
  1540. return "<code $d>";
  1541. }, $body);
  1542. }
  1543. foreach ($config['markup'] as $markup) {
  1544. if (is_string($markup[1])) {
  1545. $body = preg_replace($markup[0], $markup[1], $body);
  1546. } elseif (is_callable($markup[1])) {
  1547. $body = preg_replace_callback($markup[0], $markup[1], $body);
  1548. }
  1549. }
  1550. if ($config['markup_urls']) {
  1551. $markup_urls = array();
  1552. $body = preg_replace_callback(
  1553. '/((?:https?:\/\/|ftp:\/\/|irc:\/\/)[^\s<>()"]+?(?:\([^\s<>()"]*?\)[^\s<>()"]*?)*)((?:\s|<|>|"|\.||\]|!|\?|,|&#44;|&quot;)*(?:[\s<>()"]|$))/',
  1554. 'markup_url',
  1555. $body,
  1556. -1,
  1557. $num_links);
  1558. if ($num_links > $config['max_links'])
  1559. error($config['error']['toomanylinks']);
  1560. }
  1561. if ($config['markup_repair_tidy'])
  1562. $body = str_replace(' ', ' &nbsp;', $body);
  1563. if ($config['auto_unicode']) {
  1564. $body = unicodify($body);
  1565. if ($config['markup_urls']) {
  1566. foreach ($markup_urls as &$url) {
  1567. $body = str_replace(unicodify($url), $url, $body);
  1568. }
  1569. }
  1570. }
  1571. $tracked_cites = array();
  1572. // Cites
  1573. if (isset($board) && preg_match_all('/(^|\s)&gt;&gt;(\d+?)([\s,.)?]|$)/m', $body, $cites, PREG_SET_ORDER | PREG_OFFSET_CAPTURE)) {
  1574. if (count($cites[0]) > $config['max_cites']) {
  1575. error($config['error']['toomanycites']);
  1576. }
  1577. $skip_chars = 0;
  1578. $body_tmp = $body;
  1579. $search_cites = array();
  1580. foreach ($cites as $matches) {
  1581. $search_cites[] = '`id` = ' . $matches[2][0];
  1582. }
  1583. $search_cites = array_unique($search_cites);
  1584. $query = query(sprintf('SELECT `thread`, `id` FROM ``posts_%s`` WHERE ' .
  1585. implode(' OR ', $search_cites), $board['uri'])) or error(db_error());
  1586. $cited_posts = array();
  1587. while ($cited = $query->fetch(PDO::FETCH_ASSOC)) {
  1588. $cited_posts[$cited['id']] = $cited['thread'] ? $cited['thread'] : false;
  1589. }
  1590. foreach ($cites as $matches) {
  1591. $cite = $matches[2][0];
  1592. // preg_match_all is not multibyte-safe
  1593. foreach ($matches as &$match) {
  1594. $match[1] = mb_strlen(substr($body_tmp, 0, $match[1]));
  1595. }
  1596. if (isset($cited_posts[$cite])) {
  1597. $replacement = '<a onclick="highlightReply(\''.$cite.'\', event);" href="' .
  1598. $config['root'] . $board['dir'] . $config['dir']['res'] .
  1599. link_for(array('id' => $cite, 'thread' => $cited_posts[$cite])) . '#' . $cite . '">' .
  1600. '&gt;&gt;' . $cite .
  1601. '</a>';
  1602. $body = mb_substr_replace($body, $matches[1][0] . $replacement . $matches[3][0], $matches[0][1] + $skip_chars, mb_strlen($matches[0][0]));
  1603. $skip_chars += mb_strlen($matches[1][0] . $replacement . $matches[3][0]) - mb_strlen($matches[0][0]);
  1604. if ($track_cites && $config['track_cites'])
  1605. $tracked_cites[] = array($board['uri'], $cite);
  1606. }
  1607. }
  1608. }
  1609. // Cross-board linking
  1610. if (preg_match_all('/(^|\s)&gt;&gt;&gt;\/(' . $config['board_regex'] . 'f?)\/(\d+)?([\s,.)?]|$)/um', $body, $cites, PREG_SET_ORDER | PREG_OFFSET_CAPTURE)) {
  1611. if (count($cites[0]) > $config['max_cites']) {
  1612. error($config['error']['toomanycross']);
  1613. }
  1614. $skip_chars = 0;
  1615. $body_tmp = $body;
  1616. if (isset($cited_posts)) {
  1617. // Carry found posts from local board >>X links
  1618. foreach ($cited_posts as $cite => $thread) {
  1619. $cited_posts[$cite] = $config['root'] . $board['dir'] . $config['dir']['res'] .
  1620. ($thread ? $thread : $cite) . '.html#' . $cite;
  1621. }
  1622. $cited_posts = array(
  1623. $board['uri'] => $cited_posts
  1624. );
  1625. } else
  1626. $cited_posts = array();
  1627. $crossboard_indexes = array();
  1628. $search_cites_boards = array();
  1629. foreach ($cites as $matches) {
  1630. $_board = $matches[2][0];
  1631. $cite = @$matches[3][0];
  1632. if (!isset($search_cites_boards[$_board]))
  1633. $search_cites_boards[$_board] = array();
  1634. $search_cites_boards[$_board][] = $cite;
  1635. }
  1636. $tmp_board = $board['uri'];
  1637. foreach ($search_cites_boards as $_board => $search_cites) {
  1638. $clauses = array();
  1639. foreach ($search_cites as $cite) {
  1640. if (!$cite || isset($cited_posts[$_board][$cite]))
  1641. continue;
  1642. $clauses[] = '`id` = ' . $cite;
  1643. }
  1644. $clauses = array_unique($clauses);
  1645. if ($board['uri'] != $_board) {
  1646. if (!openBoard($_board))
  1647. continue; // Unknown board
  1648. }
  1649. if (!empty($clauses)) {
  1650. $cited_posts[$_board] = array();
  1651. $query = query(sprintf('SELECT `thread`, `id`, `slug` FROM ``posts_%s`` WHERE ' .
  1652. implode(' OR ', $clauses), $board['uri'])) or error(db_error());
  1653. while ($cite = $query->fetch(PDO::FETCH_ASSOC)) {
  1654. $cited_posts[$_board][$cite['id']] = $config['root'] . $board['dir'] . $config['dir']['res'] .
  1655. link_for($cite) . '#' . $cite['id'];
  1656. }
  1657. }
  1658. $crossboard_indexes[$_board] = $config['root'] . $board['dir'] . $config['file_index'];
  1659. }
  1660. // Restore old board
  1661. if ($board['uri'] != $tmp_board)
  1662. openBoard($tmp_board);
  1663. foreach ($cites as $matches) {
  1664. $_board = $matches[2][0];
  1665. $cite = @$matches[3][0];
  1666. // preg_match_all is not multibyte-safe
  1667. foreach ($matches as &$match) {
  1668. $match[1] = mb_strlen(substr($body_tmp, 0, $match[1]));
  1669. }
  1670. if ($cite) {
  1671. if (isset($cited_posts[$_board][$cite])) {
  1672. $link = $cited_posts[$_board][$cite];
  1673. $replacement = '<a ' .
  1674. ($_board == $board['uri'] ?
  1675. 'onclick="highlightReply(\''.$cite.'\', event);" '
  1676. : '') . 'href="' . $link . '">' .
  1677. '&gt;&gt;&gt;/' . $_board . '/' . $cite .
  1678. '</a>';
  1679. $body = mb_substr_replace($body, $matches[1][0] . $replacement . $matches[4][0], $matches[0][1] + $skip_chars, mb_strlen($matches[0][0]));
  1680. $skip_chars += mb_strlen($matches[1][0] . $replacement . $matches[4][0]) - mb_strlen($matches[0][0]);
  1681. if ($track_cites && $config['track_cites'])
  1682. $tracked_cites[] = array($_board, $cite);
  1683. }
  1684. } elseif(isset($crossboard_indexes[$_board])) {
  1685. $replacement = '<a href="' . $crossboard_indexes[$_board] . '">' .
  1686. '&gt;&gt;&gt;/' . $_board . '/' .
  1687. '</a>';
  1688. $body = mb_substr_replace($body, $matches[1][0] . $replacement . $matches[4][0], $matches[0][1] + $skip_chars, mb_strlen($matches[0][0]));
  1689. $skip_chars += mb_strlen($matches[1][0] . $replacement . $matches[4][0]) - mb_strlen($matches[0][0]);
  1690. }
  1691. }
  1692. }
  1693. $tracked_cites = array_unique($tracked_cites, SORT_REGULAR);
  1694. $body = preg_replace("/^\s*&gt;.*$/m", '<span class="quote">$0</span>', $body);
  1695. if ($config['strip_superfluous_returns'])
  1696. $body = preg_replace('/\s+$/', '', $body);
  1697. $body = preg_replace("/\n/", '<br/>', $body);
  1698. // Fix code markup
  1699. if ($config['markup_code']) {
  1700. foreach ($code_markup as $id => $val) {
  1701. $code = isset($val[2]) ? $val[2] : $val[1];
  1702. $code_lang = isset($val[2]) ? $val[1] : "";
  1703. $code = "<pre class='code lang-$code_lang'>".str_replace(array("\n","\t"), array("&#10;","&#9;"), htmlspecialchars($code))."</pre>";
  1704. $body = str_replace("<code $id>", $code, $body);
  1705. }
  1706. }
  1707. if ($config['markup_repair_tidy']) {
  1708. $tidy = new tidy();
  1709. $body = str_replace("\t", '&#09;', $body);
  1710. $body = $tidy->repairString($body, array(
  1711. 'doctype' => 'omit',
  1712. 'bare' => true,
  1713. 'literal-attributes' => true,
  1714. 'indent' => false,
  1715. 'show-body-only' => true,
  1716. 'wrap' => 0,
  1717. 'output-bom' => false,
  1718. 'output-html' => true,
  1719. 'newline' => 'LF',
  1720. 'quiet' => true,
  1721. ), 'utf8');
  1722. $body = str_replace("\n", '', $body);
  1723. }
  1724. // replace tabs with 8 spaces
  1725. $body = str_replace("\t", ' ', $body);
  1726. return $tracked_cites;
  1727. }
  1728. function escape_markup_modifiers($string) {
  1729. return preg_replace('@<(tinyboard) ([\w\s]+)>@mi', '<$1 escape $2>', $string);
  1730. }
  1731. function utf8tohtml($utf8) {
  1732. return htmlspecialchars($utf8, ENT_NOQUOTES, 'UTF-8');
  1733. }
  1734. function ordutf8($string, &$offset) {
  1735. $code = ord(substr($string, $offset,1));
  1736. if ($code >= 128) { // otherwise 0xxxxxxx
  1737. if ($code < 224)
  1738. $bytesnumber = 2; // 110xxxxx
  1739. else if ($code < 240)
  1740. $bytesnumber = 3; // 1110xxxx
  1741. else if ($code < 248)
  1742. $bytesnumber = 4; // 11110xxx
  1743. $codetemp = $code - 192 - ($bytesnumber > 2 ? 32 : 0) - ($bytesnumber > 3 ? 16 : 0);
  1744. for ($i = 2; $i <= $bytesnumber; $i++) {
  1745. $offset ++;
  1746. $code2 = ord(substr($string, $offset, 1)) - 128; //10xxxxxx
  1747. $codetemp = $codetemp*64 + $code2;
  1748. }
  1749. $code = $codetemp;
  1750. }
  1751. $offset += 1;
  1752. if ($offset >= strlen($string))
  1753. $offset = -1;
  1754. return $code;
  1755. }
  1756. function strip_combining_chars($str) {
  1757. $chars = preg_split('//u', $str, -1, PREG_SPLIT_NO_EMPTY);
  1758. $str = '';
  1759. foreach ($chars as $char) {
  1760. $o = 0;
  1761. $ord = ordutf8($char, $o);
  1762. if ( ($ord >= 768 && $ord <= 879) || ($ord >= 1536 && $ord <= 1791) || ($ord >= 3655 && $ord <= 3659) || ($ord >= 7616 && $ord <= 7679) || ($ord >= 8400 && $ord <= 8447) || ($ord >= 65056 && $ord <= 65071))
  1763. continue;
  1764. $str .= $char;
  1765. }
  1766. return $str;
  1767. }
  1768. function buildThread($id, $return = false, $mod = false) {
  1769. global $board, $config, $build_pages;
  1770. $id = round($id);
  1771. if (event('build-thread', $id))
  1772. return;
  1773. if ($config['cache']['enabled'] && !$mod) {
  1774. // Clear cache
  1775. cache::delete("thread_index_{$board['uri']}_{$id}");
  1776. cache::delete("thread_{$board['uri']}_{$id}");
  1777. }
  1778. if ($config['try_smarter'] && !$mod)
  1779. $build_pages[] = thread_find_page($id);
  1780. $action = generation_strategy('sb_thread', array($board['uri'], $id));
  1781. if ($action == 'rebuild' || $return || $mod) {
  1782. $query = prepare(sprintf("SELECT * FROM ``posts_%s`` WHERE (`thread` IS NULL AND `id` = :id) OR `thread` = :id ORDER BY `thread`,`id`", $board['uri']));
  1783. $query->bindValue(':id', $id, PDO::PARAM_INT);
  1784. $query->execute() or error(db_error($query));
  1785. while ($post = $query->fetch(PDO::FETCH_ASSOC)) {
  1786. if (!isset($thread)) {
  1787. $thread = new Thread($post, $mod ? '?/' : $config['root'], $mod);
  1788. } else {
  1789. $thread->add(new Post($post, $mod ? '?/' : $config['root'], $mod));
  1790. }
  1791. }
  1792. // Check if any posts were found
  1793. if (!isset($thread))
  1794. error($config['error']['nonexistant']);
  1795. $hasnoko50 = $thread->postCount() >= $config['noko50_min'];
  1796. $antibot = $mod || $return ? false : create_antibot($board['uri'], $id);
  1797. $body = Element('thread.html', array(
  1798. 'board' => $board,
  1799. 'thread' => $thread,
  1800. 'body' => $thread->build(),
  1801. 'config' => $config,
  1802. 'id' => $id,
  1803. 'mod' => $mod,
  1804. 'hasnoko50' => $hasnoko50,
  1805. 'isnoko50' => false,
  1806. 'antibot' => $antibot,
  1807. 'boardlist' => createBoardlist($mod),
  1808. 'return' => ($mod ? '?' . $board['url'] . $config['file_index'] : $config['root'] . $board['dir'] . $config['file_index'])
  1809. ));
  1810. // json api
  1811. if ($config['api']['enabled'] && !$mod) {
  1812. $api = new Api();
  1813. $json = json_encode($api->translateThread($thread));
  1814. $jsonFilename = $board['dir'] . $config['dir']['res'] . $id . '.json';
  1815. file_write($jsonFilename, $json);
  1816. }
  1817. }
  1818. elseif($action == 'delete') {
  1819. $jsonFilename = $board['dir'] . $config['dir']['res'] . $id . '.json';
  1820. file_unlink($jsonFilename);
  1821. }
  1822. if ($action == 'delete' && !$return && !$mod) {
  1823. $noko50fn = $board['dir'] . $config['dir']['res'] . link_for(array('id' => $id), true);
  1824. file_unlink($noko50fn);
  1825. file_unlink($board['dir'] . $config['dir']['res'] . link_for(array('id' => $id)));
  1826. } elseif ($return) {
  1827. return $body;
  1828. } elseif ($action == 'rebuild') {
  1829. $noko50fn = $board['dir'] . $config['dir']['res'] . link_for($thread, true);
  1830. if ($hasnoko50 || file_exists($noko50fn)) {
  1831. buildThread50($id, $return, $mod, $thread, $antibot);
  1832. }
  1833. file_write($board['dir'] . $config['dir']['res'] . link_for($thread), $body);
  1834. }
  1835. }
  1836. function buildThread50($id, $return = false, $mod = false, $thread = null, $antibot = false) {
  1837. global $board, $config, $build_pages;
  1838. $id = round($id);
  1839. if ($antibot)
  1840. $antibot->reset();
  1841. if (!$thread) {
  1842. $query = prepare(sprintf("SELECT * FROM ``posts_%s`` WHERE (`thread` IS NULL AND `id` = :id) OR `thread` = :id ORDER BY `thread`,`id` DESC LIMIT :limit", $board['uri']));
  1843. $query->bindValue(':id', $id, PDO::PARAM_INT);
  1844. $query->bindValue(':limit', $config['noko50_count']+1, PDO::PARAM_INT);
  1845. $query->execute() or error(db_error($query));
  1846. $num_images = 0;
  1847. while ($post = $query->fetch(PDO::FETCH_ASSOC)) {
  1848. if (!isset($thread)) {
  1849. $thread = new Thread($post, $mod ? '?/' : $config['root'], $mod);
  1850. } else {
  1851. if ($post['files'])
  1852. $num_images += $post['num_files'];
  1853. $thread->add(new Post($post, $mod ? '?/' : $config['root'], $mod));
  1854. }
  1855. }
  1856. // Check if any posts were found
  1857. if (!isset($thread))
  1858. error($config['error']['nonexistant']);
  1859. if ($query->rowCount() == $config['noko50_count']+1) {
  1860. $count = prepare(sprintf("SELECT COUNT(`id`) as `num` FROM ``posts_%s`` WHERE `thread` = :thread UNION ALL
  1861. SELECT SUM(`num_files`) FROM ``posts_%s`` WHERE `files` IS NOT NULL AND `thread` = :thread", $board['uri'], $board['uri']));
  1862. $count->bindValue(':thread', $id, PDO::PARAM_INT);
  1863. $count->execute() or error(db_error($count));
  1864. $c = $count->fetch();
  1865. $thread->omitted = $c['num'] - $config['noko50_count'];
  1866. $c = $count->fetch();
  1867. $thread->omitted_images = $c['num'] - $num_images;
  1868. }
  1869. $thread->posts = array_reverse($thread->posts);
  1870. } else {
  1871. $allPosts = $thread->posts;
  1872. $thread->posts = array_slice($allPosts, -$config['noko50_count']);
  1873. $thread->omitted += count($allPosts) - count($thread->posts);
  1874. foreach ($allPosts as $index => $post) {
  1875. if ($index == count($allPosts)-count($thread->posts))
  1876. break;
  1877. if ($post->files)
  1878. $thread->omitted_images += $post->num_files;
  1879. }
  1880. }
  1881. $hasnoko50 = $thread->postCount() >= $config['noko50_min'];
  1882. $body = Element('thread.html', array(
  1883. 'board' => $board,
  1884. 'thread' => $thread,
  1885. 'body' => $thread->build(false, true),
  1886. 'config' => $config,
  1887. 'id' => $id,
  1888. 'mod' => $mod,
  1889. 'hasnoko50' => $hasnoko50,
  1890. 'isnoko50' => true,
  1891. 'antibot' => $mod ? false : ($antibot ? $antibot : create_antibot($board['uri'], $id)),
  1892. 'boardlist' => createBoardlist($mod),
  1893. 'return' => ($mod ? '?' . $board['url'] . $config['file_index'] : $config['root'] . $board['dir'] . $config['file_index'])
  1894. ));
  1895. if ($return) {
  1896. return $body;
  1897. } else {
  1898. file_write($board['dir'] . $config['dir']['res'] . link_for($thread, true), $body);
  1899. }
  1900. }
  1901. function rrmdir($dir) {
  1902. if (is_dir($dir)) {
  1903. $objects = scandir($dir);
  1904. foreach ($objects as $object) {
  1905. if ($object != "." && $object != "..") {
  1906. if (filetype($dir."/".$object) == "dir")
  1907. rrmdir($dir."/".$object);
  1908. else
  1909. file_unlink($dir."/".$object);
  1910. }
  1911. }
  1912. reset($objects);
  1913. rmdir($dir);
  1914. }
  1915. }
  1916. function poster_id($ip, $thread) {
  1917. global $config;
  1918. if ($id = event('poster-id', $ip, $thread))
  1919. return $id;
  1920. // Confusing, hard to brute-force, but simple algorithm
  1921. return substr(sha1(sha1($ip . $config['secure_trip_salt'] . $thread) . $config['secure_trip_salt']), 0, $config['poster_id_length']);
  1922. }
  1923. function generate_tripcode($name) {
  1924. global $config;
  1925. if ($trip = event('tripcode', $name))
  1926. return $trip;
  1927. if (!preg_match('/^([^#]+)?(##|#)(.+)$/', $name, $match))
  1928. return array($name);
  1929. $name = $match[1];
  1930. $secure = $match[2] == '##';
  1931. $trip = $match[3];
  1932. // convert to SHIT_JIS encoding
  1933. $trip = mb_convert_encoding($trip, 'Shift_JIS', 'UTF-8');
  1934. // generate salt
  1935. $salt = substr($trip . 'H..', 1, 2);
  1936. $salt = preg_replace('/[^.-z]/', '.', $salt);
  1937. $salt = strtr($salt, ':;<=>?@[\]^_`', 'ABCDEFGabcdef');
  1938. if ($secure) {
  1939. if (isset($config['custom_tripcode']["##{$trip}"]))
  1940. $trip = $config['custom_tripcode']["##{$trip}"];
  1941. else
  1942. $trip = '!!' . substr(crypt($trip, str_replace('+', '.', '_..A.' . substr(base64_encode(sha1($trip . $config['secure_trip_salt'], true)), 0, 4))), -10);
  1943. } else {
  1944. if (isset($config['custom_tripcode']["#{$trip}"]))
  1945. $trip = $config['custom_tripcode']["#{$trip}"];
  1946. else
  1947. $trip = '!' . substr(crypt($trip, $salt), -10);
  1948. }
  1949. return array($name, $trip);
  1950. }
  1951. // Highest common factor
  1952. function hcf($a, $b){
  1953. $gcd = 1;
  1954. if ($a>$b) {
  1955. $a = $a+$b;
  1956. $b = $a-$b;
  1957. $a = $a-$b;
  1958. }
  1959. if ($b==(round($b/$a))*$a)
  1960. $gcd=$a;
  1961. else {
  1962. for ($i=round($a/2);$i;$i--) {
  1963. if ($a == round($a/$i)*$i && $b == round($b/$i)*$i) {
  1964. $gcd = $i;
  1965. $i = false;
  1966. }
  1967. }
  1968. }
  1969. return $gcd;
  1970. }
  1971. function fraction($numerator, $denominator, $sep) {
  1972. $gcf = hcf($numerator, $denominator);
  1973. $numerator = $numerator / $gcf;
  1974. $denominator = $denominator / $gcf;
  1975. return "{$numerator}{$sep}{$denominator}";
  1976. }
  1977. function getPostByHash($hash) {
  1978. global $board;
  1979. $query = prepare(sprintf("SELECT `id`,`thread` FROM ``posts_%s`` WHERE `filehash` = :hash", $board['uri']));
  1980. $query->bindValue(':hash', $hash, PDO::PARAM_STR);
  1981. $query->execute() or error(db_error($query));
  1982. if ($post = $query->fetch(PDO::FETCH_ASSOC)) {
  1983. return $post;
  1984. }
  1985. return false;
  1986. }
  1987. function getPostByHashInThread($hash, $thread) {
  1988. global $board;
  1989. $query = prepare(sprintf("SELECT `id`,`thread` FROM ``posts_%s`` WHERE `filehash` = :hash AND ( `thread` = :thread OR `id` = :thread )", $board['uri']));
  1990. $query->bindValue(':hash', $hash, PDO::PARAM_STR);
  1991. $query->bindValue(':thread', $thread, PDO::PARAM_INT);
  1992. $query->execute() or error(db_error($query));
  1993. if ($post = $query->fetch(PDO::FETCH_ASSOC)) {
  1994. return $post;
  1995. }
  1996. return false;
  1997. }
  1998. function getPostByEmbed($embed) {
  1999. global $board, $config;
  2000. $matches = array();
  2001. foreach ($config['embedding'] as &$e) {
  2002. if (preg_match($e[0], $embed, $matches) && isset($matches[1]) && !empty($matches[1])) {
  2003. $embed = '%'.$matches[1].'%';
  2004. break;
  2005. }
  2006. }
  2007. if (!isset($embed)) return false;
  2008. $query = prepare(sprintf("SELECT `id`,`thread` FROM ``posts_%s`` WHERE `embed` LIKE :embed", $board['uri']));
  2009. $query->bindValue(':embed', $embed, PDO::PARAM_STR);
  2010. $query->execute() or error(db_error($query));
  2011. if ($post = $query->fetch(PDO::FETCH_ASSOC)) {
  2012. return $post;
  2013. }
  2014. return false;
  2015. }
  2016. function getPostByEmbedInThread($embed, $thread) {
  2017. global $board, $config;
  2018. $matches = array();
  2019. foreach ($config['embedding'] as &$e) {
  2020. if (preg_match($e[0], $embed, $matches) && isset($matches[1]) && !empty($matches[1])) {
  2021. $embed = '%'.$matches[1].'%';
  2022. break;
  2023. }
  2024. }
  2025. if (!isset($embed)) return false;
  2026. $query = prepare(sprintf("SELECT `id`,`thread` FROM ``posts_%s`` WHERE `embed` = :embed AND ( `thread` = :thread OR `id` = :thread )", $board['uri']));
  2027. $query->bindValue(':embed', $embed, PDO::PARAM_STR);
  2028. $query->bindValue(':thread', $thread, PDO::PARAM_INT);
  2029. $query->execute() or error(db_error($query));
  2030. if ($post = $query->fetch(PDO::FETCH_ASSOC)) {
  2031. return $post;
  2032. }
  2033. return false;
  2034. }
  2035. function undoImage(array $post) {
  2036. if (!$post['has_file'] || !isset($post['files']))
  2037. return;
  2038. foreach ($post['files'] as $key => $file) {
  2039. if (isset($file['file_path']))
  2040. file_unlink($file['file_path']);
  2041. if (isset($file['thumb_path']))
  2042. file_unlink($file['thumb_path']);
  2043. }
  2044. }
  2045. function rDNS($ip_addr) {
  2046. global $config;
  2047. if ($config['cache']['enabled'] && ($host = cache::get('rdns_' . $ip_addr))) {
  2048. return $host;
  2049. }
  2050. if (!$config['dns_system']) {
  2051. $host = gethostbyaddr($ip_addr);
  2052. } else {
  2053. $resp = shell_exec_error('host -W 1 ' . $ip_addr);
  2054. if (preg_match('/domain name pointer ([^\s]+)$/', $resp, $m))
  2055. $host = $m[1];
  2056. else
  2057. $host = $ip_addr;
  2058. }
  2059. $isip = filter_var($host, FILTER_VALIDATE_IP);
  2060. if ($config['fcrdns'] && !$isip && DNS($host) != $ip_addr) {
  2061. $host = $ip_addr;
  2062. }
  2063. if ($config['cache']['enabled'])
  2064. cache::set('rdns_' . $ip_addr, $host);
  2065. return $host;
  2066. }
  2067. function DNS($host) {
  2068. global $config;
  2069. if ($config['cache']['enabled'] && ($ip_addr = cache::get('dns_' . $host))) {
  2070. return $ip_addr != '?' ? $ip_addr : false;
  2071. }
  2072. if (!$config['dns_system']) {
  2073. $ip_addr = gethostbyname($host);
  2074. if ($ip_addr == $host)
  2075. $ip_addr = false;
  2076. } else {
  2077. $resp = shell_exec_error('host -W 1 ' . $host);
  2078. if (preg_match('/has address ([^\s]+)$/', $resp, $m))
  2079. $ip_addr = $m[1];
  2080. else
  2081. $ip_addr = false;
  2082. }
  2083. if ($config['cache']['enabled'])
  2084. cache::set('dns_' . $host, $ip_addr !== false ? $ip_addr : '?');
  2085. return $ip_addr;
  2086. }
  2087. function shell_exec_error($command, $suppress_stdout = false) {
  2088. global $config, $debug;
  2089. if ($config['debug'])
  2090. $start = microtime(true);
  2091. $return = trim(shell_exec('PATH="' . escapeshellcmd($config['shell_path']) . ':$PATH";' .
  2092. $command . ' 2>&1 ' . ($suppress_stdout ? '> /dev/null ' : '') . '&& echo "TB_SUCCESS"'));
  2093. $return = preg_replace('/TB_SUCCESS$/', '', $return);
  2094. if ($config['debug']) {
  2095. $time = microtime(true) - $start;
  2096. $debug['exec'][] = array(
  2097. 'command' => $command,
  2098. 'time' => '~' . round($time * 1000, 2) . 'ms',
  2099. 'response' => $return ? $return : null
  2100. );
  2101. $debug['time']['exec'] += $time;
  2102. }
  2103. return $return === 'TB_SUCCESS' ? false : $return;
  2104. }
  2105. /* Die rolling:
  2106. * If "dice XdY+/-Z" is in the email field (where X or +/-Z may be
  2107. * missing), X Y-sided dice are rolled and summed, with the modifier Z
  2108. * added on. The result is displayed at the top of the post.
  2109. */
  2110. function diceRoller($post) {
  2111. global $config;
  2112. if(strpos(strtolower($post->email), 'dice%20') === 0) {
  2113. $dicestr = str_split(substr($post->email, strlen('dice%20')));
  2114. // Get params
  2115. $diceX = '';
  2116. $diceY = '';
  2117. $diceZ = '';
  2118. $curd = 'diceX';
  2119. for($i = 0; $i < count($dicestr); $i ++) {
  2120. if(is_numeric($dicestr[$i])) {
  2121. $$curd .= $dicestr[$i];
  2122. } else if($dicestr[$i] == 'd') {
  2123. $curd = 'diceY';
  2124. } else if($dicestr[$i] == '-' || $dicestr[$i] == '+') {
  2125. $curd = 'diceZ';
  2126. $$curd = $dicestr[$i];
  2127. }
  2128. }
  2129. // Default values for X and Z
  2130. if($diceX == '') {
  2131. $diceX = '1';
  2132. }
  2133. if($diceZ == '') {
  2134. $diceZ = '+0';
  2135. }
  2136. // Intify them
  2137. $diceX = intval($diceX);
  2138. $diceY = intval($diceY);
  2139. $diceZ = intval($diceZ);
  2140. // Continue only if we have valid values
  2141. if($diceX > 0 && $diceY > 0) {
  2142. $dicerolls = array();
  2143. $dicesum = $diceZ;
  2144. for($i = 0; $i < $diceX; $i++) {
  2145. $roll = rand(1, $diceY);
  2146. $dicerolls[] = $roll;
  2147. $dicesum += $roll;
  2148. }
  2149. // Prepend the result to the post body
  2150. $modifier = ($diceZ != 0) ? ((($diceZ < 0) ? ' - ' : ' + ') . abs($diceZ)) : '';
  2151. $dicesum = ($diceX > 1) ? ' = ' . $dicesum : '';
  2152. $post->body = '<table class="diceroll"><tr><td><img src="'.$config['dir']['static'].'d10.svg" alt="Dice roll" width="24"></td><td>Rolled ' . implode(', ', $dicerolls) . $modifier . $dicesum . '</td></tr></table><br/>' . $post->body;
  2153. }
  2154. }
  2155. }
  2156. function slugify($post) {
  2157. global $config;
  2158. $slug = "";
  2159. if (isset($post['subject']) && $post['subject'])
  2160. $slug = $post['subject'];
  2161. elseif (isset ($post['body_nomarkup']) && $post['body_nomarkup'])
  2162. $slug = $post['body_nomarkup'];
  2163. elseif (isset ($post['body']) && $post['body'])
  2164. $slug = strip_html($post['body']);
  2165. // Fix UTF-8 first
  2166. $slug = mb_convert_encoding($slug, "UTF-8", "UTF-8");
  2167. // Transliterate local characters like ü, I wonder how would it work for weird alphabets :^)
  2168. $slug = iconv("UTF-8", "ASCII//TRANSLIT//IGNORE", $slug);
  2169. // Remove Tinyboard custom markup
  2170. $slug = preg_replace("/<tinyboard [^>]+>.*?<\/tinyboard>/s", '', $slug);
  2171. // Downcase everything
  2172. $slug = strtolower($slug);
  2173. // Strip bad characters, alphanumerics should suffice
  2174. $slug = preg_replace('/[^a-zA-Z0-9]/', '-', $slug);
  2175. // Replace multiple dashes with single ones
  2176. $slug = preg_replace('/-+/', '-', $slug);
  2177. // Strip dashes at the beginning and at the end
  2178. $slug = preg_replace('/^-|-$/', '', $slug);
  2179. // Slug should be X characters long, at max (80?)
  2180. $slug = substr($slug, 0, $config['slug_max_size']);
  2181. // Slug is now ready
  2182. return $slug;
  2183. }
  2184. function link_for($post, $page50 = false, $foreignlink = false, $thread = false) {
  2185. global $config, $board;
  2186. $post = (array)$post;
  2187. // Where do we need to look for OP?
  2188. $b = $foreignlink ? $foreignlink : (isset($post['board']) ? array('uri' => $post['board']) : $board);
  2189. $id = (isset($post['thread']) && $post['thread']) ? $post['thread'] : $post['id'];
  2190. $slug = false;
  2191. if ($config['slugify'] && ( (isset($post['thread']) && $post['thread']) || !isset ($post['slug']) ) ) {
  2192. $cvar = "slug_".$b['uri']."_".$id;
  2193. if (!$thread) {
  2194. $slug = Cache::get($cvar);
  2195. if ($slug === false) {
  2196. $query = prepare(sprintf("SELECT `slug` FROM ``posts_%s`` WHERE `id` = :id", $b['uri']));
  2197. $query->bindValue(':id', $id, PDO::PARAM_INT);
  2198. $query->execute() or error(db_error($query));
  2199. $thread = $query->fetch(PDO::FETCH_ASSOC);
  2200. $slug = $thread['slug'];
  2201. Cache::set($cvar, $slug);
  2202. }
  2203. }
  2204. else {
  2205. $slug = $thread['slug'];
  2206. }
  2207. }
  2208. elseif ($config['slugify']) {
  2209. $slug = $post['slug'];
  2210. }
  2211. if ( $page50 && $slug) $tpl = $config['file_page50_slug'];
  2212. else if (!$page50 && $slug) $tpl = $config['file_page_slug'];
  2213. else if ( $page50 && !$slug) $tpl = $config['file_page50'];
  2214. else if (!$page50 && !$slug) $tpl = $config['file_page'];
  2215. return sprintf($tpl, $id, $slug);
  2216. }
  2217. function prettify_textarea($s){
  2218. return str_replace("\t", '&#09;', str_replace("\n", '&#13;&#10;', htmlentities($s)));
  2219. }
  2220. /*class HTMLPurifier_URIFilter_NoExternalImages extends HTMLPurifier_URIFilter {
  2221. public $name = 'NoExternalImages';
  2222. public function filter(&$uri, $c, $context) {
  2223. global $config;
  2224. $ct = $context->get('CurrentToken');
  2225. if (!$ct || $ct->name !== 'img') return true;
  2226. if (!isset($uri->host) && !isset($uri->scheme)) return true;
  2227. if (!in_array($uri->scheme . '://' . $uri->host . '/', $config['allowed_offsite_urls'])) {
  2228. error('No off-site links in board announcement images.');
  2229. }
  2230. return true;
  2231. }
  2232. }*/
  2233. function purify_html($s) {
  2234. global $config;
  2235. $c = HTMLPurifier_Config::createDefault();
  2236. $c->set('HTML.Allowed', $config['allowed_html']);
  2237. $uri = $c->getDefinition('URI');
  2238. $uri->addFilter(new HTMLPurifier_URIFilter_NoExternalImages(), $c);
  2239. $purifier = new HTMLPurifier($c);
  2240. $clean_html = $purifier->purify($s);
  2241. return $clean_html;
  2242. }
  2243. function markdown($s) {
  2244. $pd = new Parsedown();
  2245. $pd->setMarkupEscaped(true);
  2246. $pd->setimagesEnabled(false);
  2247. return $pd->text($s);
  2248. }
  2249. function generation_strategy($fun, $array=array()) { global $config;
  2250. $action = false;
  2251. foreach ($config['generation_strategies'] as $s) {
  2252. if ($action = $s($fun, $array)) {
  2253. break;
  2254. }
  2255. }
  2256. switch ($action[0]) {
  2257. case 'immediate':
  2258. return 'rebuild';
  2259. case 'defer':
  2260. // Ok, it gets interesting here :)
  2261. get_queue('generate')->push(serialize(array('build', $fun, $array, $action)));
  2262. return 'ignore';
  2263. case 'build_on_load':
  2264. return 'delete';
  2265. }
  2266. }
  2267. function strategy_immediate($fun, $array) {
  2268. return array('immediate');
  2269. }
  2270. function strategy_smart_build($fun, $array) {
  2271. return array('build_on_load');
  2272. }
  2273. function strategy_sane($fun, $array) { global $config;
  2274. // Well, ideally a sane strategy would involve a more stringent checking,
  2275. // but let's at least have something to get the ball rolling :^)
  2276. if (php_sapi_name() == 'cli') return false;
  2277. else if (isset($_POST['mod']) || isset($_POST['json_response'])) return false;
  2278. else if ($fun == 'sb_thread' || ($fun == 'sb_board' && $array[1] == 1)) return array('immediate');
  2279. else return false;
  2280. }
  2281. // My first, test strategy.
  2282. function strategy_first($fun, $array) {
  2283. switch ($fun) {
  2284. case 'sb_thread':
  2285. return array('defer');
  2286. case 'sb_board':
  2287. if ($array[1] > 8) return array('build_on_load');
  2288. else return array('defer');
  2289. case 'sb_api':
  2290. return array('defer');
  2291. case 'sb_catalog':
  2292. return array('defer');
  2293. case 'sb_recent':
  2294. return array('build_on_load');
  2295. case 'sb_sitemap':
  2296. return array('build_on_load');
  2297. case 'sb_ukko':
  2298. return array('defer');
  2299. }
  2300. }