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.

functions.php 83KB

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