The version of vichan running on lainchan.org
Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.

pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 11 gadiem
pirms 10 gadiem
pirms 10 gadiem
pirms 10 gadiem
pirms 11 gadiem
pirms 11 gadiem
pirms 11 gadiem
pirms 11 gadiem
pirms 10 gadiem
pirms 11 gadiem
pirms 11 gadiem
pirms 11 gadiem
pirms 11 gadiem
pirms 11 gadiem
pirms 11 gadiem
pirms 11 gadiem
pirms 11 gadiem
pirms 11 gadiem
pirms 11 gadiem
pirms 11 gadiem
pirms 11 gadiem
pirms 11 gadiem
pirms 11 gadiem
pirms 11 gadiem
pirms 11 gadiem
pirms 11 gadiem
pirms 11 gadiem
pirms 11 gadiem
pirms 11 gadiem
pirms 11 gadiem
pirms 11 gadiem
pirms 12 gadiem
pirms 10 gadiem
pirms 12 gadiem
pirms 10 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 10 gadiem
pirms 10 gadiem
pirms 10 gadiem
pirms 10 gadiem
pirms 10 gadiem
pirms 10 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 10 gadiem
pirms 12 gadiem
pirms 11 gadiem
pirms 11 gadiem
pirms 11 gadiem
pirms 10 gadiem
pirms 11 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 10 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 10 gadiem
pirms 12 gadiem
pirms 10 gadiem
pirms 12 gadiem
pirms 10 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 10 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 10 gadiem
pirms 12 gadiem
pirms 10 gadiem
pirms 12 gadiem
pirms 10 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 10 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 11 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 10 gadiem
pirms 10 gadiem
pirms 12 gadiem
pirms 10 gadiem
pirms 10 gadiem
pirms 10 gadiem
pirms 10 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 10 gadiem
pirms 10 gadiem
pirms 12 gadiem
pirms 10 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 9 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 10 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 10 gadiem
pirms 10 gadiem
pirms 12 gadiem
pirms 11 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 9 gadiem
pirms 12 gadiem
pirms 9 gadiem
pirms 12 gadiem
pirms 11 gadiem
pirms 12 gadiem
pirms 11 gadiem
pirms 11 gadiem
pirms 11 gadiem
pirms 11 gadiem
pirms 11 gadiem
pirms 11 gadiem
pirms 11 gadiem
pirms 11 gadiem
pirms 11 gadiem
pirms 11 gadiem
pirms 11 gadiem
pirms 11 gadiem
pirms 11 gadiem
pirms 11 gadiem
pirms 11 gadiem
pirms 11 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 11 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 10 gadiem
pirms 10 gadiem
pirms 10 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 10 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 10 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 10 gadiem
pirms 12 gadiem
pirms 10 gadiem
pirms 10 gadiem
pirms 12 gadiem
pirms 10 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 10 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 10 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 10 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 10 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 10 gadiem
pirms 10 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 10 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 10 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 10 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 10 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 10 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 10 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 12 gadiem
pirms 10 gadiem
pirms 12 gadiem
pirms 10 gadiem
pirms 10 gadiem
pirms 12 gadiem
pirms 11 gadiem
pirms 11 gadiem
pirms 11 gadiem
pirms 11 gadiem
pirms 10 gadiem
pirms 11 gadiem
pirms 11 gadiem
pirms 11 gadiem
pirms 11 gadiem
pirms 11 gadiem
pirms 11 gadiem
pirms 11 gadiem
pirms 11 gadiem
pirms 11 gadiem
pirms 11 gadiem
pirms 11 gadiem
pirms 11 gadiem
pirms 11 gadiem
pirms 10 gadiem
pirms 11 gadiem
pirms 11 gadiem
pirms 10 gadiem
pirms 11 gadiem
pirms 11 gadiem
pirms 11 gadiem
pirms 10 gadiem
pirms 10 gadiem
pirms 11 gadiem
pirms 10 gadiem
pirms 10 gadiem
pirms 10 gadiem
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908
  1. <?php
  2. /*
  3. * Copyright (c) 2010-2013 Tinyboard Development Group
  4. */
  5. defined('TINYBOARD') or exit;
  6. function mod_page($title, $template, $args, $subtitle = false) {
  7. global $config, $mod;
  8. echo Element('page.html', array(
  9. 'config' => $config,
  10. 'mod' => $mod,
  11. 'hide_dashboard_link' => $template == 'mod/dashboard.html',
  12. 'title' => $title,
  13. 'subtitle' => $subtitle,
  14. 'boardlist' => createBoardlist($mod),
  15. 'body' => Element($template,
  16. array_merge(
  17. array('config' => $config, 'mod' => $mod),
  18. $args
  19. )
  20. )
  21. )
  22. );
  23. }
  24. function mod_login($redirect = false) {
  25. global $config;
  26. $args = array();
  27. if (isset($_POST['login'])) {
  28. // Check if inputs are set and not empty
  29. if (!isset($_POST['username'], $_POST['password']) || $_POST['username'] == '' || $_POST['password'] == '') {
  30. $args['error'] = $config['error']['invalid'];
  31. } elseif (!login($_POST['username'], $_POST['password'])) {
  32. if ($config['syslog'])
  33. _syslog(LOG_WARNING, 'Unauthorized login attempt!');
  34. $args['error'] = $config['error']['invalid'];
  35. } else {
  36. modLog('Logged in');
  37. // Login successful
  38. // Set cookies
  39. setCookies();
  40. if ($redirect)
  41. header('Location: ?' . $redirect, true, $config['redirect_http']);
  42. else
  43. header('Location: ?/', true, $config['redirect_http']);
  44. }
  45. }
  46. if (isset($_POST['username']))
  47. $args['username'] = $_POST['username'];
  48. mod_page(_('Login'), 'mod/login.html', $args);
  49. }
  50. function mod_confirm($request) {
  51. mod_page(_('Confirm action'), 'mod/confirm.html', array('request' => $request, 'token' => make_secure_link_token($request)));
  52. }
  53. function mod_logout() {
  54. global $config;
  55. destroyCookies();
  56. header('Location: ?/', true, $config['redirect_http']);
  57. }
  58. function mod_dashboard() {
  59. global $config, $mod;
  60. $args = array();
  61. $args['boards'] = listBoards();
  62. if (hasPermission($config['mod']['noticeboard'])) {
  63. if (!$config['cache']['enabled'] || !$args['noticeboard'] = cache::get('noticeboard_preview')) {
  64. $query = prepare("SELECT ``noticeboard``.*, `username` FROM ``noticeboard`` LEFT JOIN ``mods`` ON ``mods``.`id` = `mod` ORDER BY `id` DESC LIMIT :limit");
  65. $query->bindValue(':limit', $config['mod']['noticeboard_dashboard'], PDO::PARAM_INT);
  66. $query->execute() or error(db_error($query));
  67. $args['noticeboard'] = $query->fetchAll(PDO::FETCH_ASSOC);
  68. if ($config['cache']['enabled'])
  69. cache::set('noticeboard_preview', $args['noticeboard']);
  70. }
  71. }
  72. if (!$config['cache']['enabled'] || ($args['unread_pms'] = cache::get('pm_unreadcount_' . $mod['id'])) === false) {
  73. $query = prepare('SELECT COUNT(*) FROM ``pms`` WHERE `to` = :id AND `unread` = 1');
  74. $query->bindValue(':id', $mod['id']);
  75. $query->execute() or error(db_error($query));
  76. $args['unread_pms'] = $query->fetchColumn();
  77. if ($config['cache']['enabled'])
  78. cache::set('pm_unreadcount_' . $mod['id'], $args['unread_pms']);
  79. }
  80. $query = query('SELECT COUNT(*) FROM ``reports``') or error(db_error($query));
  81. $args['reports'] = $query->fetchColumn();
  82. if ($mod['type'] >= ADMIN && $config['check_updates']) {
  83. if (!$config['version'])
  84. error(_('Could not find current version! (Check .installed)'));
  85. if (isset($_COOKIE['update'])) {
  86. $latest = unserialize($_COOKIE['update']);
  87. } else {
  88. $ctx = stream_context_create(array('http' => array('timeout' => 5)));
  89. if ($code = @file_get_contents('http://engine.vichan.net/version.txt', 0, $ctx)) {
  90. $ver = strtok($code, "\n");
  91. if (preg_match('@^// v(\d+)\.(\d+)\.(\d+)\s*?$@', $ver, $matches)) {
  92. $latest = array(
  93. 'massive' => $matches[1],
  94. 'major' => $matches[2],
  95. 'minor' => $matches[3]
  96. );
  97. if (preg_match('/(\d+)\.(\d)\.(\d+)(-dev.+)?$/', $config['version'], $matches)) {
  98. $current = array(
  99. 'massive' => (int) $matches[1],
  100. 'major' => (int) $matches[2],
  101. 'minor' => (int) $matches[3]
  102. );
  103. if (isset($m[4])) {
  104. // Development versions are always ahead in the versioning numbers
  105. $current['minor'] --;
  106. }
  107. // Check if it's newer
  108. if (!( $latest['massive'] > $current['massive'] ||
  109. $latest['major'] > $current['major'] ||
  110. ($latest['massive'] == $current['massive'] &&
  111. $latest['major'] == $current['major'] &&
  112. $latest['minor'] > $current['minor']
  113. )))
  114. $latest = false;
  115. } else {
  116. $latest = false;
  117. }
  118. } else {
  119. // Couldn't get latest version
  120. $latest = false;
  121. }
  122. } else {
  123. // Couldn't get latest version
  124. $latest = false;
  125. }
  126. setcookie('update', serialize($latest), time() + $config['check_updates_time'], $config['cookies']['jail'] ? $config['cookies']['path'] : '/', null, !empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off', true);
  127. }
  128. if ($latest)
  129. $args['newer_release'] = $latest;
  130. }
  131. $args['logout_token'] = make_secure_link_token('logout');
  132. mod_page(_('Dashboard'), 'mod/dashboard.html', $args);
  133. }
  134. function mod_search_redirect() {
  135. global $config;
  136. if (!hasPermission($config['mod']['search']))
  137. error($config['error']['noaccess']);
  138. if (isset($_POST['query'], $_POST['type']) && in_array($_POST['type'], array('posts', 'IP_notes', 'bans', 'log'))) {
  139. $query = $_POST['query'];
  140. $query = urlencode($query);
  141. $query = str_replace('_', '%5F', $query);
  142. $query = str_replace('+', '_', $query);
  143. if ($query === '') {
  144. header('Location: ?/', true, $config['redirect_http']);
  145. return;
  146. }
  147. header('Location: ?/search/' . $_POST['type'] . '/' . $query, true, $config['redirect_http']);
  148. } else {
  149. header('Location: ?/', true, $config['redirect_http']);
  150. }
  151. }
  152. function mod_search($type, $search_query_escaped, $page_no = 1) {
  153. global $pdo, $config;
  154. if (!hasPermission($config['mod']['search']))
  155. error($config['error']['noaccess']);
  156. // Unescape query
  157. $query = str_replace('_', ' ', $search_query_escaped);
  158. $query = urldecode($query);
  159. $search_query = $query;
  160. // Form a series of LIKE clauses for the query.
  161. // This gets a little complicated.
  162. // Escape "escape" character
  163. $query = str_replace('!', '!!', $query);
  164. // Escape SQL wildcard
  165. $query = str_replace('%', '!%', $query);
  166. // Use asterisk as wildcard instead
  167. $query = str_replace('*', '%', $query);
  168. $query = str_replace('`', '!`', $query);
  169. // Array of phrases to match
  170. $match = array();
  171. // Exact phrases ("like this")
  172. if (preg_match_all('/"(.+?)"/', $query, $exact_phrases)) {
  173. $exact_phrases = $exact_phrases[1];
  174. foreach ($exact_phrases as $phrase) {
  175. $query = str_replace("\"{$phrase}\"", '', $query);
  176. $match[] = $pdo->quote($phrase);
  177. }
  178. }
  179. // Non-exact phrases (ie. plain keywords)
  180. $keywords = explode(' ', $query);
  181. foreach ($keywords as $word) {
  182. if (empty($word))
  183. continue;
  184. $match[] = $pdo->quote($word);
  185. }
  186. // Which `field` to search?
  187. if ($type == 'posts')
  188. $sql_field = array('body_nomarkup', 'files', 'subject', 'filehash', 'ip', 'name', 'trip');
  189. if ($type == 'IP_notes')
  190. $sql_field = 'body';
  191. if ($type == 'bans')
  192. $sql_field = 'reason';
  193. if ($type == 'log')
  194. $sql_field = 'text';
  195. // Build the "LIKE 'this' AND LIKE 'that'" etc. part of the SQL query
  196. $sql_like = '';
  197. foreach ($match as $phrase) {
  198. if (!empty($sql_like))
  199. $sql_like .= ' AND ';
  200. $phrase = preg_replace('/^\'(.+)\'$/', '\'%$1%\'', $phrase);
  201. if (is_array($sql_field)) {
  202. foreach ($sql_field as $field) {
  203. $sql_like .= '`' . $field . '` LIKE ' . $phrase . ' ESCAPE \'!\' OR';
  204. }
  205. $sql_like = preg_replace('/ OR$/', '', $sql_like);
  206. } else {
  207. $sql_like .= '`' . $sql_field . '` LIKE ' . $phrase . ' ESCAPE \'!\'';
  208. }
  209. }
  210. // Compile SQL query
  211. if ($type == 'posts') {
  212. $query = '';
  213. $boards = listBoards();
  214. if (empty($boards))
  215. error(_('There are no boards to search!'));
  216. foreach ($boards as $board) {
  217. openBoard($board['uri']);
  218. if (!hasPermission($config['mod']['search_posts'], $board['uri']))
  219. continue;
  220. if (!empty($query))
  221. $query .= ' UNION ALL ';
  222. $query .= sprintf("SELECT *, '%s' AS `board` FROM ``posts_%s`` WHERE %s", $board['uri'], $board['uri'], $sql_like);
  223. }
  224. // You weren't allowed to search any boards
  225. if (empty($query))
  226. error($config['error']['noaccess']);
  227. $query .= ' ORDER BY `sticky` DESC, `id` DESC';
  228. }
  229. if ($type == 'IP_notes') {
  230. $query = 'SELECT * FROM ``ip_notes`` LEFT JOIN ``mods`` ON `mod` = ``mods``.`id` WHERE ' . $sql_like . ' ORDER BY `time` DESC';
  231. $sql_table = 'ip_notes';
  232. if (!hasPermission($config['mod']['view_notes']) || !hasPermission($config['mod']['show_ip']))
  233. error($config['error']['noaccess']);
  234. }
  235. if ($type == 'bans') {
  236. $query = 'SELECT ``bans``.*, `username` FROM ``bans`` LEFT JOIN ``mods`` ON `creator` = ``mods``.`id` WHERE ' . $sql_like . ' ORDER BY (`expires` IS NOT NULL AND `expires` < UNIX_TIMESTAMP()), `created` DESC';
  237. $sql_table = 'bans';
  238. if (!hasPermission($config['mod']['view_banlist']))
  239. error($config['error']['noaccess']);
  240. }
  241. if ($type == 'log') {
  242. $query = 'SELECT `username`, `mod`, `ip`, `board`, `time`, `text` FROM ``modlogs`` LEFT JOIN ``mods`` ON `mod` = ``mods``.`id` WHERE ' . $sql_like . ' ORDER BY `time` DESC';
  243. $sql_table = 'modlogs';
  244. if (!hasPermission($config['mod']['modlog']))
  245. error($config['error']['noaccess']);
  246. }
  247. // Execute SQL query (with pages)
  248. $q = query($query . ' LIMIT ' . (($page_no - 1) * $config['mod']['search_page']) . ', ' . $config['mod']['search_page']) or error(db_error());
  249. $results = $q->fetchAll(PDO::FETCH_ASSOC);
  250. // Get total result count
  251. if ($type == 'posts') {
  252. $q = query("SELECT COUNT(*) FROM ($query) AS `tmp_table`") or error(db_error());
  253. $result_count = $q->fetchColumn();
  254. } else {
  255. $q = query('SELECT COUNT(*) FROM `' . $sql_table . '` WHERE ' . $sql_like) or error(db_error());
  256. $result_count = $q->fetchColumn();
  257. }
  258. if ($type == 'bans') {
  259. foreach ($results as &$ban) {
  260. $ban['mask'] = Bans::range_to_string(array($ban['ipstart'], $ban['ipend']));
  261. if (filter_var($ban['mask'], FILTER_VALIDATE_IP) !== false)
  262. $ban['single_addr'] = true;
  263. }
  264. }
  265. if ($type == 'posts') {
  266. foreach ($results as &$post) {
  267. $post['snippet'] = pm_snippet($post['body']);
  268. }
  269. }
  270. // $results now contains the search results
  271. mod_page(_('Search results'), 'mod/search_results.html', array(
  272. 'search_type' => $type,
  273. 'search_query' => $search_query,
  274. 'search_query_escaped' => $search_query_escaped,
  275. 'result_count' => $result_count,
  276. 'results' => $results
  277. ));
  278. }
  279. function mod_edit_board($boardName) {
  280. global $board, $config;
  281. if (!openBoard($boardName))
  282. error($config['error']['noboard']);
  283. if (!hasPermission($config['mod']['manageboards'], $board['uri']))
  284. error($config['error']['noaccess']);
  285. if (isset($_POST['title'], $_POST['subtitle'])) {
  286. if (isset($_POST['delete'])) {
  287. if (!hasPermission($config['mod']['manageboards'], $board['uri']))
  288. error($config['error']['deleteboard']);
  289. $query = prepare('DELETE FROM ``boards`` WHERE `uri` = :uri');
  290. $query->bindValue(':uri', $board['uri']);
  291. $query->execute() or error(db_error($query));
  292. if ($config['cache']['enabled']) {
  293. cache::delete('board_' . $board['uri']);
  294. cache::delete('all_boards');
  295. }
  296. modLog('Deleted board: ' . sprintf($config['board_abbreviation'], $board['uri']), false);
  297. // Delete posting table
  298. $query = query(sprintf('DROP TABLE IF EXISTS ``posts_%s``', $board['uri'])) or error(db_error());
  299. // Clear reports
  300. $query = prepare('DELETE FROM ``reports`` WHERE `board` = :id');
  301. $query->bindValue(':id', $board['uri'], PDO::PARAM_INT);
  302. $query->execute() or error(db_error($query));
  303. // Delete from table
  304. $query = prepare('DELETE FROM ``boards`` WHERE `uri` = :uri');
  305. $query->bindValue(':uri', $board['uri'], PDO::PARAM_INT);
  306. $query->execute() or error(db_error($query));
  307. $query = prepare("SELECT `board`, `post` FROM ``cites`` WHERE `target_board` = :board ORDER BY `board`");
  308. $query->bindValue(':board', $board['uri']);
  309. $query->execute() or error(db_error($query));
  310. while ($cite = $query->fetch(PDO::FETCH_ASSOC)) {
  311. if ($board['uri'] != $cite['board']) {
  312. if (!isset($tmp_board))
  313. $tmp_board = $board;
  314. openBoard($cite['board']);
  315. rebuildPost($cite['post']);
  316. }
  317. }
  318. if (isset($tmp_board))
  319. $board = $tmp_board;
  320. $query = prepare('DELETE FROM ``cites`` WHERE `board` = :board OR `target_board` = :board');
  321. $query->bindValue(':board', $board['uri']);
  322. $query->execute() or error(db_error($query));
  323. $query = prepare('DELETE FROM ``antispam`` WHERE `board` = :board');
  324. $query->bindValue(':board', $board['uri']);
  325. $query->execute() or error(db_error($query));
  326. // Remove board from users/permissions table
  327. $query = query('SELECT `id`,`boards` FROM ``mods``') or error(db_error());
  328. while ($user = $query->fetch(PDO::FETCH_ASSOC)) {
  329. $user_boards = explode(',', $user['boards']);
  330. if (in_array($board['uri'], $user_boards)) {
  331. unset($user_boards[array_search($board['uri'], $user_boards)]);
  332. $_query = prepare('UPDATE ``mods`` SET `boards` = :boards WHERE `id` = :id');
  333. $_query->bindValue(':boards', implode(',', $user_boards));
  334. $_query->bindValue(':id', $user['id']);
  335. $_query->execute() or error(db_error($_query));
  336. }
  337. }
  338. // Delete entire board directory
  339. rrmdir($board['uri'] . '/');
  340. } else {
  341. $query = prepare('UPDATE ``boards`` SET `title` = :title, `subtitle` = :subtitle WHERE `uri` = :uri');
  342. $query->bindValue(':uri', $board['uri']);
  343. $query->bindValue(':title', $_POST['title']);
  344. $query->bindValue(':subtitle', $_POST['subtitle']);
  345. $query->execute() or error(db_error($query));
  346. modLog('Edited board information for ' . sprintf($config['board_abbreviation'], $board['uri']), false);
  347. }
  348. if ($config['cache']['enabled']) {
  349. cache::delete('board_' . $board['uri']);
  350. cache::delete('all_boards');
  351. }
  352. rebuildThemes('boards');
  353. header('Location: ?/', true, $config['redirect_http']);
  354. } else {
  355. mod_page(sprintf('%s: ' . $config['board_abbreviation'], _('Edit board'), $board['uri']), 'mod/board.html', array(
  356. 'board' => $board,
  357. 'token' => make_secure_link_token('edit/' . $board['uri'])
  358. ));
  359. }
  360. }
  361. function mod_new_board() {
  362. global $config, $board;
  363. if (!hasPermission($config['mod']['newboard']))
  364. error($config['error']['noaccess']);
  365. if (isset($_POST['uri'], $_POST['title'], $_POST['subtitle'])) {
  366. if ($_POST['uri'] == '')
  367. error(sprintf($config['error']['required'], 'URI'));
  368. if ($_POST['title'] == '')
  369. error(sprintf($config['error']['required'], 'title'));
  370. if (!preg_match('/^' . $config['board_regex'] . '$/u', $_POST['uri']))
  371. error(sprintf($config['error']['invalidfield'], 'URI'));
  372. $bytes = 0;
  373. $chars = preg_split('//u', $_POST['uri'], -1, PREG_SPLIT_NO_EMPTY);
  374. foreach ($chars as $char) {
  375. $o = 0;
  376. $ord = ordutf8($char, $o);
  377. if ($ord > 0x0080)
  378. $bytes += 5; // @01ff
  379. else
  380. $bytes ++;
  381. }
  382. $bytes + strlen('posts_.frm');
  383. if ($bytes > 255) {
  384. error('Your filesystem cannot handle a board URI of that length (' . $bytes . '/255 bytes)');
  385. exit;
  386. }
  387. if (openBoard($_POST['uri'])) {
  388. error(sprintf($config['error']['boardexists'], $board['url']));
  389. }
  390. $query = prepare('INSERT INTO ``boards`` VALUES (:uri, :title, :subtitle)');
  391. $query->bindValue(':uri', $_POST['uri']);
  392. $query->bindValue(':title', $_POST['title']);
  393. $query->bindValue(':subtitle', $_POST['subtitle']);
  394. $query->execute() or error(db_error($query));
  395. modLog('Created a new board: ' . sprintf($config['board_abbreviation'], $_POST['uri']));
  396. if (!openBoard($_POST['uri']))
  397. error(_("Couldn't open board after creation."));
  398. $query = Element('posts.sql', array('board' => $board['uri']));
  399. if (mysql_version() < 50503)
  400. $query = preg_replace('/(CHARSET=|CHARACTER SET )utf8mb4/', '$1utf8', $query);
  401. query($query) or error(db_error());
  402. if ($config['cache']['enabled'])
  403. cache::delete('all_boards');
  404. // Build the board
  405. buildIndex();
  406. rebuildThemes('boards');
  407. header('Location: ?/' . $board['uri'] . '/' . $config['file_index'], true, $config['redirect_http']);
  408. }
  409. mod_page(_('New board'), 'mod/board.html', array('new' => true, 'token' => make_secure_link_token('new-board')));
  410. }
  411. function mod_noticeboard($page_no = 1) {
  412. global $config, $pdo, $mod;
  413. if ($page_no < 1)
  414. error($config['error']['404']);
  415. if (!hasPermission($config['mod']['noticeboard']))
  416. error($config['error']['noaccess']);
  417. if (isset($_POST['subject'], $_POST['body'])) {
  418. if (!hasPermission($config['mod']['noticeboard_post']))
  419. error($config['error']['noaccess']);
  420. $_POST['body'] = escape_markup_modifiers($_POST['body']);
  421. markup($_POST['body']);
  422. $query = prepare('INSERT INTO ``noticeboard`` VALUES (NULL, :mod, :time, :subject, :body)');
  423. $query->bindValue(':mod', $mod['id']);
  424. $query->bindvalue(':time', time());
  425. $query->bindValue(':subject', $_POST['subject']);
  426. $query->bindValue(':body', $_POST['body']);
  427. $query->execute() or error(db_error($query));
  428. if ($config['cache']['enabled'])
  429. cache::delete('noticeboard_preview');
  430. modLog('Posted a noticeboard entry');
  431. header('Location: ?/noticeboard#' . $pdo->lastInsertId(), true, $config['redirect_http']);
  432. }
  433. $query = prepare("SELECT ``noticeboard``.*, `username` FROM ``noticeboard`` LEFT JOIN ``mods`` ON ``mods``.`id` = `mod` ORDER BY `id` DESC LIMIT :offset, :limit");
  434. $query->bindValue(':limit', $config['mod']['noticeboard_page'], PDO::PARAM_INT);
  435. $query->bindValue(':offset', ($page_no - 1) * $config['mod']['noticeboard_page'], PDO::PARAM_INT);
  436. $query->execute() or error(db_error($query));
  437. $noticeboard = $query->fetchAll(PDO::FETCH_ASSOC);
  438. if (empty($noticeboard) && $page_no > 1)
  439. error($config['error']['404']);
  440. foreach ($noticeboard as &$entry) {
  441. $entry['delete_token'] = make_secure_link_token('noticeboard/delete/' . $entry['id']);
  442. }
  443. $query = prepare("SELECT COUNT(*) FROM ``noticeboard``");
  444. $query->execute() or error(db_error($query));
  445. $count = $query->fetchColumn();
  446. mod_page(_('Noticeboard'), 'mod/noticeboard.html', array(
  447. 'noticeboard' => $noticeboard,
  448. 'count' => $count,
  449. 'token' => make_secure_link_token('noticeboard')
  450. ));
  451. }
  452. function mod_noticeboard_delete($id) {
  453. global $config;
  454. if (!hasPermission($config['mod']['noticeboard_delete']))
  455. error($config['error']['noaccess']);
  456. $query = prepare('DELETE FROM ``noticeboard`` WHERE `id` = :id');
  457. $query->bindValue(':id', $id);
  458. $query->execute() or error(db_error($query));
  459. modLog('Deleted a noticeboard entry');
  460. if ($config['cache']['enabled'])
  461. cache::delete('noticeboard_preview');
  462. header('Location: ?/noticeboard', true, $config['redirect_http']);
  463. }
  464. function mod_news($page_no = 1) {
  465. global $config, $pdo, $mod;
  466. if ($page_no < 1)
  467. error($config['error']['404']);
  468. if (isset($_POST['subject'], $_POST['body'])) {
  469. if (!hasPermission($config['mod']['news']))
  470. error($config['error']['noaccess']);
  471. $_POST['body'] = escape_markup_modifiers($_POST['body']);
  472. markup($_POST['body']);
  473. $query = prepare('INSERT INTO ``news`` VALUES (NULL, :name, :time, :subject, :body)');
  474. $query->bindValue(':name', isset($_POST['name']) && hasPermission($config['mod']['news_custom']) ? $_POST['name'] : $mod['username']);
  475. $query->bindvalue(':time', time());
  476. $query->bindValue(':subject', $_POST['subject']);
  477. $query->bindValue(':body', $_POST['body']);
  478. $query->execute() or error(db_error($query));
  479. modLog('Posted a news entry');
  480. rebuildThemes('news');
  481. header('Location: ?/news#' . $pdo->lastInsertId(), true, $config['redirect_http']);
  482. }
  483. $query = prepare("SELECT * FROM ``news`` ORDER BY `id` DESC LIMIT :offset, :limit");
  484. $query->bindValue(':limit', $config['mod']['news_page'], PDO::PARAM_INT);
  485. $query->bindValue(':offset', ($page_no - 1) * $config['mod']['news_page'], PDO::PARAM_INT);
  486. $query->execute() or error(db_error($query));
  487. $news = $query->fetchAll(PDO::FETCH_ASSOC);
  488. if (empty($news) && $page_no > 1)
  489. error($config['error']['404']);
  490. foreach ($news as &$entry) {
  491. $entry['delete_token'] = make_secure_link_token('news/delete/' . $entry['id']);
  492. }
  493. $query = prepare("SELECT COUNT(*) FROM ``news``");
  494. $query->execute() or error(db_error($query));
  495. $count = $query->fetchColumn();
  496. mod_page(_('News'), 'mod/news.html', array('news' => $news, 'count' => $count, 'token' => make_secure_link_token('news')));
  497. }
  498. function mod_news_delete($id) {
  499. global $config;
  500. if (!hasPermission($config['mod']['news_delete']))
  501. error($config['error']['noaccess']);
  502. $query = prepare('DELETE FROM ``news`` WHERE `id` = :id');
  503. $query->bindValue(':id', $id);
  504. $query->execute() or error(db_error($query));
  505. modLog('Deleted a news entry');
  506. header('Location: ?/news', true, $config['redirect_http']);
  507. }
  508. function mod_log($page_no = 1) {
  509. global $config;
  510. if ($page_no < 1)
  511. error($config['error']['404']);
  512. if (!hasPermission($config['mod']['modlog']))
  513. error($config['error']['noaccess']);
  514. $query = prepare("SELECT `username`, `mod`, `ip`, `board`, `time`, `text` FROM ``modlogs`` LEFT JOIN ``mods`` ON `mod` = ``mods``.`id` ORDER BY `time` DESC LIMIT :offset, :limit");
  515. $query->bindValue(':limit', $config['mod']['modlog_page'], PDO::PARAM_INT);
  516. $query->bindValue(':offset', ($page_no - 1) * $config['mod']['modlog_page'], PDO::PARAM_INT);
  517. $query->execute() or error(db_error($query));
  518. $logs = $query->fetchAll(PDO::FETCH_ASSOC);
  519. if (empty($logs) && $page_no > 1)
  520. error($config['error']['404']);
  521. $query = prepare("SELECT COUNT(*) FROM ``modlogs``");
  522. $query->execute() or error(db_error($query));
  523. $count = $query->fetchColumn();
  524. mod_page(_('Moderation log'), 'mod/log.html', array('logs' => $logs, 'count' => $count));
  525. }
  526. function mod_user_log($username, $page_no = 1) {
  527. global $config;
  528. if ($page_no < 1)
  529. error($config['error']['404']);
  530. if (!hasPermission($config['mod']['modlog']))
  531. error($config['error']['noaccess']);
  532. $query = prepare("SELECT `username`, `mod`, `ip`, `board`, `time`, `text` FROM ``modlogs`` LEFT JOIN ``mods`` ON `mod` = ``mods``.`id` WHERE `username` = :username ORDER BY `time` DESC LIMIT :offset, :limit");
  533. $query->bindValue(':username', $username);
  534. $query->bindValue(':limit', $config['mod']['modlog_page'], PDO::PARAM_INT);
  535. $query->bindValue(':offset', ($page_no - 1) * $config['mod']['modlog_page'], PDO::PARAM_INT);
  536. $query->execute() or error(db_error($query));
  537. $logs = $query->fetchAll(PDO::FETCH_ASSOC);
  538. if (empty($logs) && $page_no > 1)
  539. error($config['error']['404']);
  540. $query = prepare("SELECT COUNT(*) FROM ``modlogs`` LEFT JOIN ``mods`` ON `mod` = ``mods``.`id` WHERE `username` = :username");
  541. $query->bindValue(':username', $username);
  542. $query->execute() or error(db_error($query));
  543. $count = $query->fetchColumn();
  544. mod_page(_('Moderation log'), 'mod/log.html', array('logs' => $logs, 'count' => $count, 'username' => $username));
  545. }
  546. function mod_board_log($board, $page_no = 1, $hide_names = false, $public = false) {
  547. global $config;
  548. if ($page_no < 1)
  549. error($config['error']['404']);
  550. if (!hasPermission($config['mod']['mod_board_log'], $board) && !$public)
  551. error($config['error']['noaccess']);
  552. $query = prepare("SELECT `username`, `mod`, `ip`, `board`, `time`, `text` FROM ``modlogs`` LEFT JOIN ``mods`` ON `mod` = ``mods``.`id` WHERE `board` = :board ORDER BY `time` DESC LIMIT :offset, :limit");
  553. $query->bindValue(':board', $board);
  554. $query->bindValue(':limit', $config['mod']['modlog_page'], PDO::PARAM_INT);
  555. $query->bindValue(':offset', ($page_no - 1) * $config['mod']['modlog_page'], PDO::PARAM_INT);
  556. $query->execute() or error(db_error($query));
  557. $logs = $query->fetchAll(PDO::FETCH_ASSOC);
  558. if (empty($logs) && $page_no > 1)
  559. error($config['error']['404']);
  560. if (!hasPermission($config['mod']['show_ip'])) {
  561. // Supports ipv4 only!
  562. foreach ($logs as $i => &$log) {
  563. $log['text'] = preg_replace_callback('/(?:<a href="\?\/IP\/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}">)?(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})(?:<\/a>)?/', function($matches) {
  564. return "xxxx";//less_ip($matches[1]);
  565. }, $log['text']);
  566. }
  567. }
  568. $query = prepare("SELECT COUNT(*) FROM ``modlogs`` LEFT JOIN ``mods`` ON `mod` = ``mods``.`id` WHERE `board` = :board");
  569. $query->bindValue(':board', $board);
  570. $query->execute() or error(db_error($query));
  571. $count = $query->fetchColumn();
  572. mod_page(_('Board log'), 'mod/log.html', array('logs' => $logs, 'count' => $count, 'board' => $board, 'hide_names' => $hide_names, 'public' => $public));
  573. }
  574. function mod_view_board($boardName, $page_no = 1) {
  575. global $config, $mod;
  576. if (!openBoard($boardName))
  577. error($config['error']['noboard']);
  578. if (!$page = index($page_no, $mod)) {
  579. error($config['error']['404']);
  580. }
  581. $page['pages'] = getPages(true);
  582. $page['pages'][$page_no-1]['selected'] = true;
  583. $page['btn'] = getPageButtons($page['pages'], true);
  584. $page['mod'] = true;
  585. $page['config'] = $config;
  586. echo Element('index.html', $page);
  587. }
  588. function mod_view_thread($boardName, $thread) {
  589. global $config, $mod;
  590. if (!openBoard($boardName))
  591. error($config['error']['noboard']);
  592. $page = buildThread($thread, true, $mod);
  593. echo $page;
  594. }
  595. function mod_view_thread50($boardName, $thread) {
  596. global $config, $mod;
  597. if (!openBoard($boardName))
  598. error($config['error']['noboard']);
  599. $page = buildThread50($thread, true, $mod);
  600. echo $page;
  601. }
  602. function mod_ip_remove_note($ip, $id) {
  603. global $config, $mod;
  604. if (!hasPermission($config['mod']['remove_notes']))
  605. error($config['error']['noaccess']);
  606. if (filter_var($ip, FILTER_VALIDATE_IP) === false)
  607. error("Invalid IP address.");
  608. $query = prepare('DELETE FROM ``ip_notes`` WHERE `ip` = :ip AND `id` = :id');
  609. $query->bindValue(':ip', $ip);
  610. $query->bindValue(':id', $id);
  611. $query->execute() or error(db_error($query));
  612. modLog("Removed a note for <a href=\"?/IP/{$ip}\">{$ip}</a>");
  613. header('Location: ?/IP/' . $ip . '#notes', true, $config['redirect_http']);
  614. }
  615. function mod_page_ip($ip) {
  616. global $config, $mod;
  617. if (filter_var($ip, FILTER_VALIDATE_IP) === false)
  618. error("Invalid IP address.");
  619. if (isset($_POST['ban_id'], $_POST['unban'])) {
  620. if (!hasPermission($config['mod']['unban']))
  621. error($config['error']['noaccess']);
  622. Bans::delete($_POST['ban_id'], true, $mod['boards']);
  623. header('Location: ?/IP/' . $ip . '#bans', true, $config['redirect_http']);
  624. return;
  625. }
  626. if (isset($_POST['note'])) {
  627. if (!hasPermission($config['mod']['create_notes']))
  628. error($config['error']['noaccess']);
  629. $_POST['note'] = escape_markup_modifiers($_POST['note']);
  630. markup($_POST['note']);
  631. $query = prepare('INSERT INTO ``ip_notes`` VALUES (NULL, :ip, :mod, :time, :body)');
  632. $query->bindValue(':ip', $ip);
  633. $query->bindValue(':mod', $mod['id']);
  634. $query->bindValue(':time', time());
  635. $query->bindValue(':body', $_POST['note']);
  636. $query->execute() or error(db_error($query));
  637. modLog("Added a note for <a href=\"?/IP/{$ip}\">{$ip}</a>");
  638. header('Location: ?/IP/' . $ip . '#notes', true, $config['redirect_http']);
  639. return;
  640. }
  641. $args = array();
  642. $args['ip'] = $ip;
  643. $args['posts'] = array();
  644. if ($config['mod']['dns_lookup'])
  645. $args['hostname'] = rDNS($ip);
  646. $boards = listBoards();
  647. foreach ($boards as $board) {
  648. openBoard($board['uri']);
  649. if (!hasPermission($config['mod']['show_ip'], $board['uri']))
  650. continue;
  651. $query = prepare(sprintf('SELECT * FROM ``posts_%s`` WHERE `ip` = :ip ORDER BY `sticky` DESC, `id` DESC LIMIT :limit', $board['uri']));
  652. $query->bindValue(':ip', $ip);
  653. $query->bindValue(':limit', $config['mod']['ip_recentposts'], PDO::PARAM_INT);
  654. $query->execute() or error(db_error($query));
  655. while ($post = $query->fetch(PDO::FETCH_ASSOC)) {
  656. if (!$post['thread']) {
  657. $po = new Thread($post, '?/', $mod, false);
  658. } else {
  659. $po = new Post($post, '?/', $mod);
  660. }
  661. if (!isset($args['posts'][$board['uri']]))
  662. $args['posts'][$board['uri']] = array('board' => $board, 'posts' => array());
  663. $args['posts'][$board['uri']]['posts'][] = $po->build(true);
  664. }
  665. }
  666. $args['boards'] = $boards;
  667. $args['token'] = make_secure_link_token('ban');
  668. if (hasPermission($config['mod']['view_ban'])) {
  669. $args['bans'] = Bans::find($ip, false, true);
  670. }
  671. if (hasPermission($config['mod']['view_notes'])) {
  672. $query = prepare("SELECT ``ip_notes``.*, `username` FROM ``ip_notes`` LEFT JOIN ``mods`` ON `mod` = ``mods``.`id` WHERE `ip` = :ip ORDER BY `time` DESC");
  673. $query->bindValue(':ip', $ip);
  674. $query->execute() or error(db_error($query));
  675. $args['notes'] = $query->fetchAll(PDO::FETCH_ASSOC);
  676. }
  677. if (hasPermission($config['mod']['modlog_ip'])) {
  678. $query = prepare("SELECT `username`, `mod`, `ip`, `board`, `time`, `text` FROM ``modlogs`` LEFT JOIN ``mods`` ON `mod` = ``mods``.`id` WHERE `text` LIKE :search ORDER BY `time` DESC LIMIT 50");
  679. $query->bindValue(':search', '%' . $ip . '%');
  680. $query->execute() or error(db_error($query));
  681. $args['logs'] = $query->fetchAll(PDO::FETCH_ASSOC);
  682. } else {
  683. $args['logs'] = array();
  684. }
  685. $args['security_token'] = make_secure_link_token('IP/' . $ip);
  686. mod_page(sprintf('%s: %s', _('IP'), htmlspecialchars($ip)), 'mod/view_ip.html', $args, $args['hostname']);
  687. }
  688. function mod_ban() {
  689. global $config;
  690. if (!hasPermission($config['mod']['ban']))
  691. error($config['error']['noaccess']);
  692. if (!isset($_POST['ip'], $_POST['reason'], $_POST['length'], $_POST['board'])) {
  693. mod_page(_('New ban'), 'mod/ban_form.html', array('token' => make_secure_link_token('ban')));
  694. return;
  695. }
  696. require_once 'inc/mod/ban.php';
  697. Bans::new_ban($_POST['ip'], $_POST['reason'], $_POST['length'], $_POST['board'] == '*' ? false : $_POST['board']);
  698. if (isset($_POST['redirect']))
  699. header('Location: ' . $_POST['redirect'], true, $config['redirect_http']);
  700. else
  701. header('Location: ?/', true, $config['redirect_http']);
  702. }
  703. function mod_bans() {
  704. global $config;
  705. global $mod;
  706. if (!hasPermission($config['mod']['view_banlist']))
  707. error($config['error']['noaccess']);
  708. if (isset($_POST['unban'])) {
  709. if (!hasPermission($config['mod']['unban']))
  710. error($config['error']['noaccess']);
  711. $unban = array();
  712. foreach ($_POST as $name => $unused) {
  713. if (preg_match('/^ban_(\d+)$/', $name, $match))
  714. $unban[] = $match[1];
  715. }
  716. if (isset($config['mod']['unban_limit']) && $config['mod']['unban_limit'] && count($unban) > $config['mod']['unban_limit'])
  717. error(sprintf($config['error']['toomanyunban'], $config['mod']['unban_limit'], count($unban)));
  718. foreach ($unban as $id) {
  719. Bans::delete($id, true, $mod['boards'], true);
  720. }
  721. rebuildThemes('bans');
  722. header('Location: ?/bans', true, $config['redirect_http']);
  723. return;
  724. }
  725. mod_page(_('Ban list'), 'mod/ban_list.html', array(
  726. 'mod' => $mod,
  727. 'boards' => json_encode($mod['boards']),
  728. 'token' => make_secure_link_token('bans'),
  729. 'token_json' => make_secure_link_token('bans.json')
  730. ));
  731. }
  732. function mod_bans_json() {
  733. global $config, $mod;
  734. if (!hasPermission($config['mod']['ban']))
  735. error($config['error']['noaccess']);
  736. // Compress the json for faster loads
  737. if (substr_count($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip')) ob_start("ob_gzhandler");
  738. Bans::stream_json(false, false, !hasPermission($config['mod']['view_banstaff']), $mod['boards']);
  739. }
  740. function mod_ban_appeals() {
  741. global $config, $board;
  742. if (!hasPermission($config['mod']['view_ban_appeals']))
  743. error($config['error']['noaccess']);
  744. // Remove stale ban appeals
  745. query("DELETE FROM ``ban_appeals`` WHERE NOT EXISTS (SELECT 1 FROM ``bans`` WHERE `ban_id` = ``bans``.`id`)")
  746. or error(db_error());
  747. if (isset($_POST['appeal_id']) && (isset($_POST['unban']) || isset($_POST['deny']))) {
  748. if (!hasPermission($config['mod']['ban_appeals']))
  749. error($config['error']['noaccess']);
  750. $query = query("SELECT *, ``ban_appeals``.`id` AS `id` FROM ``ban_appeals``
  751. LEFT JOIN ``bans`` ON `ban_id` = ``bans``.`id`
  752. WHERE ``ban_appeals``.`id` = " . (int)$_POST['appeal_id']) or error(db_error());
  753. if (!$ban = $query->fetch(PDO::FETCH_ASSOC)) {
  754. error(_('Ban appeal not found!'));
  755. }
  756. $ban['mask'] = Bans::range_to_string(array($ban['ipstart'], $ban['ipend']));
  757. if (isset($_POST['unban'])) {
  758. modLog('Accepted ban appeal #' . $ban['id'] . ' for ' . $ban['mask']);
  759. Bans::delete($ban['ban_id'], true);
  760. query("DELETE FROM ``ban_appeals`` WHERE `id` = " . $ban['id']) or error(db_error());
  761. } else {
  762. modLog('Denied ban appeal #' . $ban['id'] . ' for ' . $ban['mask']);
  763. query("UPDATE ``ban_appeals`` SET `denied` = 1 WHERE `id` = " . $ban['id']) or error(db_error());
  764. }
  765. header('Location: ?/ban-appeals', true, $config['redirect_http']);
  766. return;
  767. }
  768. $query = query("SELECT *, ``ban_appeals``.`id` AS `id` FROM ``ban_appeals``
  769. LEFT JOIN ``bans`` ON `ban_id` = ``bans``.`id`
  770. LEFT JOIN ``mods`` ON ``bans``.`creator` = ``mods``.`id`
  771. WHERE `denied` != 1 ORDER BY `time`") or error(db_error());
  772. $ban_appeals = $query->fetchAll(PDO::FETCH_ASSOC);
  773. foreach ($ban_appeals as &$ban) {
  774. if ($ban['post'])
  775. $ban['post'] = json_decode($ban['post'], true);
  776. $ban['mask'] = Bans::range_to_string(array($ban['ipstart'], $ban['ipend']));
  777. if ($ban['post'] && isset($ban['post']['board'], $ban['post']['id'])) {
  778. if (openBoard($ban['post']['board'])) {
  779. $query = query(sprintf("SELECT `num_files`, `files` FROM ``posts_%s`` WHERE `id` = " .
  780. (int)$ban['post']['id'], $board['uri']));
  781. if ($_post = $query->fetch(PDO::FETCH_ASSOC)) {
  782. $_post['files'] = $_post['files'] ? json_decode($_post['files']) : array();
  783. $ban['post'] = array_merge($ban['post'], $_post);
  784. } else {
  785. $ban['post']['files'] = array(array());
  786. $ban['post']['files'][0]['file'] = 'deleted';
  787. $ban['post']['files'][0]['thumb'] = false;
  788. $ban['post']['num_files'] = 1;
  789. }
  790. } else {
  791. $ban['post']['files'] = array(array());
  792. $ban['post']['files'][0]['file'] = 'deleted';
  793. $ban['post']['files'][0]['thumb'] = false;
  794. $ban['post']['num_files'] = 1;
  795. }
  796. if ($ban['post']['thread']) {
  797. $ban['post'] = new Post($ban['post']);
  798. } else {
  799. $ban['post'] = new Thread($ban['post'], null, false, false);
  800. }
  801. }
  802. }
  803. mod_page(_('Ban appeals'), 'mod/ban_appeals.html', array(
  804. 'ban_appeals' => $ban_appeals,
  805. 'token' => make_secure_link_token('ban-appeals')
  806. ));
  807. }
  808. function mod_lock($board, $unlock, $post) {
  809. global $config;
  810. if (!openBoard($board))
  811. error($config['error']['noboard']);
  812. if (!hasPermission($config['mod']['lock'], $board))
  813. error($config['error']['noaccess']);
  814. $query = prepare(sprintf('UPDATE ``posts_%s`` SET `locked` = :locked WHERE `id` = :id AND `thread` IS NULL', $board));
  815. $query->bindValue(':id', $post);
  816. $query->bindValue(':locked', $unlock ? 0 : 1);
  817. $query->execute() or error(db_error($query));
  818. if ($query->rowCount()) {
  819. modLog(($unlock ? 'Unlocked' : 'Locked') . " thread #{$post}");
  820. buildThread($post);
  821. buildIndex();
  822. }
  823. if ($config['mod']['dismiss_reports_on_lock']) {
  824. $query = prepare('DELETE FROM ``reports`` WHERE `board` = :board AND `post` = :id');
  825. $query->bindValue(':board', $board);
  826. $query->bindValue(':id', $post);
  827. $query->execute() or error(db_error($query));
  828. }
  829. header('Location: ?/' . sprintf($config['board_path'], $board) . $config['file_index'], true, $config['redirect_http']);
  830. if ($unlock)
  831. event('unlock', $post);
  832. else
  833. event('lock', $post);
  834. }
  835. function mod_sticky($board, $unsticky, $post) {
  836. global $config;
  837. if (!openBoard($board))
  838. error($config['error']['noboard']);
  839. if (!hasPermission($config['mod']['sticky'], $board))
  840. error($config['error']['noaccess']);
  841. $query = prepare(sprintf('UPDATE ``posts_%s`` SET `sticky` = :sticky WHERE `id` = :id AND `thread` IS NULL', $board));
  842. $query->bindValue(':id', $post);
  843. $query->bindValue(':sticky', $unsticky ? 0 : 1);
  844. $query->execute() or error(db_error($query));
  845. if ($query->rowCount()) {
  846. modLog(($unsticky ? 'Unstickied' : 'Stickied') . " thread #{$post}");
  847. buildThread($post);
  848. buildIndex();
  849. }
  850. header('Location: ?/' . sprintf($config['board_path'], $board) . $config['file_index'], true, $config['redirect_http']);
  851. }
  852. function mod_bumplock($board, $unbumplock, $post) {
  853. global $config;
  854. if (!openBoard($board))
  855. error($config['error']['noboard']);
  856. if (!hasPermission($config['mod']['bumplock'], $board))
  857. error($config['error']['noaccess']);
  858. $query = prepare(sprintf('UPDATE ``posts_%s`` SET `sage` = :bumplock WHERE `id` = :id AND `thread` IS NULL', $board));
  859. $query->bindValue(':id', $post);
  860. $query->bindValue(':bumplock', $unbumplock ? 0 : 1);
  861. $query->execute() or error(db_error($query));
  862. if ($query->rowCount()) {
  863. modLog(($unbumplock ? 'Unbumplocked' : 'Bumplocked') . " thread #{$post}");
  864. buildThread($post);
  865. buildIndex();
  866. }
  867. header('Location: ?/' . sprintf($config['board_path'], $board) . $config['file_index'], true, $config['redirect_http']);
  868. }
  869. function mod_move_reply($originBoard, $postID) {
  870. global $board, $config, $mod;
  871. if (!openBoard($originBoard))
  872. error($config['error']['noboard']);
  873. if (!hasPermission($config['mod']['move'], $originBoard))
  874. error($config['error']['noaccess']);
  875. $query = prepare(sprintf('SELECT * FROM ``posts_%s`` WHERE `id` = :id', $originBoard));
  876. $query->bindValue(':id', $postID);
  877. $query->execute() or error(db_error($query));
  878. if (!$post = $query->fetch(PDO::FETCH_ASSOC))
  879. error($config['error']['404']);
  880. if (isset($_POST['board'])) {
  881. $targetBoard = $_POST['board'];
  882. if ($_POST['target_thread']) {
  883. $query = prepare(sprintf('SELECT * FROM ``posts_%s`` WHERE `id` = :id', $targetBoard));
  884. $query->bindValue(':id', $_POST['target_thread']);
  885. $query->execute() or error(db_error($query)); // If it fails, thread probably does not exist
  886. $post['op'] = false;
  887. $post['thread'] = $_POST['target_thread'];
  888. }
  889. else {
  890. $post['op'] = true;
  891. }
  892. if ($post['files']) {
  893. $post['files'] = json_decode($post['files'], TRUE);
  894. $post['has_file'] = true;
  895. foreach ($post['files'] as $i => &$file) {
  896. $file['file_path'] = sprintf($config['board_path'], $board['uri']) . $config['dir']['img'] . $file['file'];
  897. $file['thumb_path'] = sprintf($config['board_path'], $board['uri']) . $config['dir']['thumb'] . $file['thumb'];
  898. }
  899. } else {
  900. $post['has_file'] = false;
  901. }
  902. // allow thread to keep its same traits (stickied, locked, etc.)
  903. $post['mod'] = true;
  904. if (!openBoard($targetBoard))
  905. error($config['error']['noboard']);
  906. // create the new post
  907. $newID = post($post);
  908. if ($post['has_file']) {
  909. foreach ($post['files'] as $i => &$file) {
  910. // move the image
  911. rename($file['file_path'], sprintf($config['board_path'], $board['uri']) . $config['dir']['img'] . $file['file']);
  912. if ($file['thumb'] != 'spoiler') { //trying to move/copy the spoiler thumb raises an error
  913. rename($file['thumb_path'], sprintf($config['board_path'], $board['uri']) . $config['dir']['thumb'] . $file['thumb']);
  914. }
  915. }
  916. }
  917. // build index
  918. buildIndex();
  919. // build new thread
  920. buildThread($newID);
  921. // trigger themes
  922. rebuildThemes('post', $targetBoard);
  923. // mod log
  924. modLog("Moved post #${postID} to " . sprintf($config['board_abbreviation'], $targetBoard) . " (#${newID})", $originBoard);
  925. // return to original board
  926. openBoard($originBoard);
  927. // delete original post
  928. deletePost($postID);
  929. buildIndex();
  930. // open target board for redirect
  931. openBoard($targetBoard);
  932. // Find new thread on our target board
  933. $query = prepare(sprintf('SELECT thread FROM ``posts_%s`` WHERE `id` = :id', $targetBoard));
  934. $query->bindValue(':id', $newID);
  935. $query->execute() or error(db_error($query));
  936. $post = $query->fetch(PDO::FETCH_ASSOC);
  937. // redirect
  938. header('Location: ?/' . sprintf($config['board_path'], $board['uri']) . $config['dir']['res'] . link_for($post) . '#' . $newID, true, $config['redirect_http']);
  939. }
  940. else {
  941. $boards = listBoards();
  942. $security_token = make_secure_link_token($originBoard . '/move_reply/' . $postID);
  943. mod_page(_('Move reply'), 'mod/move_reply.html', array('post' => $postID, 'board' => $originBoard, 'boards' => $boards, 'token' => $security_token));
  944. }
  945. }
  946. function mod_move($originBoard, $postID) {
  947. global $board, $config, $mod, $pdo;
  948. if (!openBoard($originBoard))
  949. error($config['error']['noboard']);
  950. if (!hasPermission($config['mod']['move'], $originBoard))
  951. error($config['error']['noaccess']);
  952. $query = prepare(sprintf('SELECT * FROM ``posts_%s`` WHERE `id` = :id AND `thread` IS NULL', $originBoard));
  953. $query->bindValue(':id', $postID);
  954. $query->execute() or error(db_error($query));
  955. if (!$post = $query->fetch(PDO::FETCH_ASSOC))
  956. error($config['error']['404']);
  957. if (isset($_POST['board'])) {
  958. $targetBoard = $_POST['board'];
  959. $shadow = isset($_POST['shadow']);
  960. if ($targetBoard === $originBoard)
  961. error(_('Target and source board are the same.'));
  962. // copy() if leaving a shadow thread behind; else, rename().
  963. $clone = $shadow ? 'copy' : 'rename';
  964. // indicate that the post is a thread
  965. $post['op'] = true;
  966. if ($post['files']) {
  967. $post['files'] = json_decode($post['files'], TRUE);
  968. $post['has_file'] = true;
  969. foreach ($post['files'] as $i => &$file) {
  970. if ($file['file'] === 'deleted')
  971. continue;
  972. $file['file_path'] = sprintf($config['board_path'], $board['uri']) . $config['dir']['img'] . $file['file'];
  973. $file['thumb_path'] = sprintf($config['board_path'], $board['uri']) . $config['dir']['thumb'] . $file['thumb'];
  974. }
  975. } else {
  976. $post['has_file'] = false;
  977. }
  978. // allow thread to keep its same traits (stickied, locked, etc.)
  979. $post['mod'] = true;
  980. if (!openBoard($targetBoard))
  981. error($config['error']['noboard']);
  982. // create the new thread
  983. $newID = post($post);
  984. $op = $post;
  985. $op['id'] = $newID;
  986. if ($post['has_file']) {
  987. // copy image
  988. foreach ($post['files'] as $i => &$file) {
  989. if ($file['file'] !== 'deleted')
  990. $clone($file['file_path'], sprintf($config['board_path'], $board['uri']) . $config['dir']['img'] . $file['file']);
  991. if (isset($file['thumb']) && !in_array($file['thumb'], array('spoiler', 'deleted', 'file')))
  992. $clone($file['thumb_path'], sprintf($config['board_path'], $board['uri']) . $config['dir']['thumb'] . $file['thumb']);
  993. }
  994. }
  995. // go back to the original board to fetch replies
  996. openBoard($originBoard);
  997. $query = prepare(sprintf('SELECT * FROM ``posts_%s`` WHERE `thread` = :id ORDER BY `id`', $originBoard));
  998. $query->bindValue(':id', $postID, PDO::PARAM_INT);
  999. $query->execute() or error(db_error($query));
  1000. $replies = array();
  1001. while ($post = $query->fetch(PDO::FETCH_ASSOC)) {
  1002. $post['mod'] = true;
  1003. $post['thread'] = $newID;
  1004. if ($post['files']) {
  1005. $post['files'] = json_decode($post['files'], TRUE);
  1006. $post['has_file'] = true;
  1007. foreach ($post['files'] as $i => &$file) {
  1008. $file['file_path'] = sprintf($config['board_path'], $board['uri']) . $config['dir']['img'] . $file['file'];
  1009. $file['thumb_path'] = sprintf($config['board_path'], $board['uri']) . $config['dir']['thumb'] . $file['thumb'];
  1010. }
  1011. } else {
  1012. $post['has_file'] = false;
  1013. }
  1014. $replies[] = $post;
  1015. }
  1016. $newIDs = array($postID => $newID);
  1017. openBoard($targetBoard);
  1018. foreach ($replies as &$post) {
  1019. $query = prepare('SELECT `target` FROM ``cites`` WHERE `target_board` = :board AND `board` = :board AND `post` = :post');
  1020. $query->bindValue(':board', $originBoard);
  1021. $query->bindValue(':post', $post['id'], PDO::PARAM_INT);
  1022. $query->execute() or error(db_error($qurey));
  1023. // correct >>X links
  1024. while ($cite = $query->fetch(PDO::FETCH_ASSOC)) {
  1025. if (isset($newIDs[$cite['target']])) {
  1026. $post['body_nomarkup'] = preg_replace(
  1027. '/(>>(>\/' . preg_quote($originBoard, '/') . '\/)?)' . preg_quote($cite['target'], '/') . '/',
  1028. '>>' . $newIDs[$cite['target']],
  1029. $post['body_nomarkup']);
  1030. $post['body'] = $post['body_nomarkup'];
  1031. }
  1032. }
  1033. $post['body'] = $post['body_nomarkup'];
  1034. $post['op'] = false;
  1035. $post['tracked_cites'] = markup($post['body'], true);
  1036. if ($post['has_file']) {
  1037. // copy image
  1038. foreach ($post['files'] as $i => &$file) {
  1039. $clone($file['file_path'], sprintf($config['board_path'], $board['uri']) . $config['dir']['img'] . $file['file']);
  1040. $clone($file['thumb_path'], sprintf($config['board_path'], $board['uri']) . $config['dir']['thumb'] . $file['thumb']);
  1041. }
  1042. }
  1043. // insert reply
  1044. $newIDs[$post['id']] = $newPostID = post($post);
  1045. if (!empty($post['tracked_cites'])) {
  1046. $insert_rows = array();
  1047. foreach ($post['tracked_cites'] as $cite) {
  1048. $insert_rows[] = '(' .
  1049. $pdo->quote($board['uri']) . ', ' . $newPostID . ', ' .
  1050. $pdo->quote($cite[0]) . ', ' . (int)$cite[1] . ')';
  1051. }
  1052. query('INSERT INTO ``cites`` VALUES ' . implode(', ', $insert_rows)) or error(db_error());
  1053. }
  1054. }
  1055. modLog("Moved thread #${postID} to " . sprintf($config['board_abbreviation'], $targetBoard) . " (#${newID})", $originBoard);
  1056. // build new thread
  1057. buildThread($newID);
  1058. clean();
  1059. buildIndex();
  1060. // trigger themes
  1061. rebuildThemes('post', $targetBoard);
  1062. $newboard = $board;
  1063. // return to original board
  1064. openBoard($originBoard);
  1065. if ($shadow) {
  1066. // lock old thread
  1067. $query = prepare(sprintf('UPDATE ``posts_%s`` SET `locked` = 1 WHERE `id` = :id', $originBoard));
  1068. $query->bindValue(':id', $postID, PDO::PARAM_INT);
  1069. $query->execute() or error(db_error($query));
  1070. // leave a reply, linking to the new thread
  1071. $spost = array(
  1072. 'mod' => true,
  1073. 'subject' => '',
  1074. 'email' => '',
  1075. 'name' => (!$config['mod']['shadow_name'] ? $config['anonymous'] : $config['mod']['shadow_name']),
  1076. 'capcode' => $config['mod']['shadow_capcode'],
  1077. 'trip' => '',
  1078. 'password' => '',
  1079. 'has_file' => false,
  1080. // attach to original thread
  1081. 'thread' => $postID,
  1082. 'op' => false
  1083. );
  1084. $spost['body'] = $spost['body_nomarkup'] = sprintf($config['mod']['shadow_mesage'], '>>>/' . $targetBoard . '/' . $newID);
  1085. markup($spost['body']);
  1086. $botID = post($spost);
  1087. buildThread($postID);
  1088. buildIndex();
  1089. header('Location: ?/' . sprintf($config['board_path'], $newboard['uri']) . $config['dir']['res'] . link_for($op, false, $newboard) .
  1090. '#' . $botID, true, $config['redirect_http']);
  1091. } else {
  1092. deletePost($postID);
  1093. buildIndex();
  1094. openBoard($targetBoard);
  1095. header('Location: ?/' . sprintf($config['board_path'], $newboard['uri']) . $config['dir']['res'] . link_for($op, false, $newboard), true, $config['redirect_http']);
  1096. }
  1097. }
  1098. $boards = listBoards();
  1099. if (count($boards) <= 1)
  1100. error(_('Impossible to move thread; there is only one board.'));
  1101. $security_token = make_secure_link_token($originBoard . '/move/' . $postID);
  1102. mod_page(_('Move thread'), 'mod/move.html', array('post' => $postID, 'board' => $originBoard, 'boards' => $boards, 'token' => $security_token));
  1103. }
  1104. function mod_ban_post($board, $delete, $post, $token = false) {
  1105. global $config, $mod;
  1106. if (!openBoard($board))
  1107. error($config['error']['noboard']);
  1108. if (!hasPermission($config['mod']['delete'], $board))
  1109. error($config['error']['noaccess']);
  1110. $security_token = make_secure_link_token($board . '/ban/' . $post);
  1111. $query = prepare(sprintf('SELECT ' . ($config['ban_show_post'] ? '*' : '`ip`, `thread`') .
  1112. ' FROM ``posts_%s`` WHERE `id` = :id', $board));
  1113. $query->bindValue(':id', $post);
  1114. $query->execute() or error(db_error($query));
  1115. if (!$_post = $query->fetch(PDO::FETCH_ASSOC))
  1116. error($config['error']['404']);
  1117. $thread = $_post['thread'];
  1118. $ip = $_post['ip'];
  1119. if (isset($_POST['new_ban'], $_POST['reason'], $_POST['length'], $_POST['board'])) {
  1120. require_once 'inc/mod/ban.php';
  1121. if (isset($_POST['ip']))
  1122. $ip = $_POST['ip'];
  1123. Bans::new_ban($_POST['ip'], $_POST['reason'], $_POST['length'], $_POST['board'] == '*' ? false : $_POST['board'],
  1124. false, $config['ban_show_post'] ? $_post : false);
  1125. if (isset($_POST['public_message'], $_POST['message'])) {
  1126. // public ban message
  1127. $length_english = Bans::parse_time($_POST['length']) ? 'for ' . until(Bans::parse_time($_POST['length'])) : 'permanently';
  1128. $_POST['message'] = preg_replace('/[\r\n]/', '', $_POST['message']);
  1129. $_POST['message'] = str_replace('%length%', $length_english, $_POST['message']);
  1130. $_POST['message'] = str_replace('%LENGTH%', strtoupper($length_english), $_POST['message']);
  1131. $query = prepare(sprintf('UPDATE ``posts_%s`` SET `body_nomarkup` = CONCAT(`body_nomarkup`, :body_nomarkup) WHERE `id` = :id', $board));
  1132. $query->bindValue(':id', $post);
  1133. $query->bindValue(':body_nomarkup', sprintf("\n<tinyboard ban message>%s</tinyboard>", utf8tohtml($_POST['message'])));
  1134. $query->execute() or error(db_error($query));
  1135. rebuildPost($post);
  1136. modLog("Attached a public ban message to post #{$post}: " . utf8tohtml($_POST['message']));
  1137. buildThread($thread ? $thread : $post);
  1138. buildIndex();
  1139. } elseif (isset($_POST['delete']) && (int) $_POST['delete']) {
  1140. // Delete post
  1141. deletePost($post);
  1142. modLog("Deleted post #{$post}");
  1143. // Rebuild board
  1144. buildIndex();
  1145. // Rebuild themes
  1146. rebuildThemes('post-delete', $board);
  1147. }
  1148. header('Location: ?/' . sprintf($config['board_path'], $board) . $config['file_index'], true, $config['redirect_http']);
  1149. }
  1150. $args = array(
  1151. 'ip' => $ip,
  1152. 'hide_ip' => !hasPermission($config['mod']['show_ip'], $board),
  1153. 'post' => $post,
  1154. 'board' => $board,
  1155. 'delete' => (bool)$delete,
  1156. 'boards' => listBoards(),
  1157. 'token' => $security_token
  1158. );
  1159. mod_page(_('New ban'), 'mod/ban_form.html', $args);
  1160. }
  1161. function mod_edit_post($board, $edit_raw_html, $postID) {
  1162. global $config, $mod;
  1163. if (!openBoard($board))
  1164. error($config['error']['noboard']);
  1165. if (!hasPermission($config['mod']['editpost'], $board))
  1166. error($config['error']['noaccess']);
  1167. if ($edit_raw_html && !hasPermission($config['mod']['rawhtml'], $board))
  1168. error($config['error']['noaccess']);
  1169. $security_token = make_secure_link_token($board . '/edit' . ($edit_raw_html ? '_raw' : '') . '/' . $postID);
  1170. $query = prepare(sprintf('SELECT * FROM ``posts_%s`` WHERE `id` = :id', $board));
  1171. $query->bindValue(':id', $postID);
  1172. $query->execute() or error(db_error($query));
  1173. if (!$post = $query->fetch(PDO::FETCH_ASSOC))
  1174. error($config['error']['404']);
  1175. if (isset($_POST['name'], $_POST['email'], $_POST['subject'], $_POST['body'])) {
  1176. // Remove any modifiers they may have put in
  1177. $_POST['body'] = remove_modifiers($_POST['body']);
  1178. // Add back modifiers in the original post
  1179. $modifiers = extract_modifiers($post['body_nomarkup']);
  1180. foreach ($modifiers as $key => $value) {
  1181. $_POST['body'] .= "<tinyboard $key>$value</tinyboard>";
  1182. }
  1183. if ($edit_raw_html)
  1184. $query = prepare(sprintf('UPDATE ``posts_%s`` SET `name` = :name, `email` = :email, `subject` = :subject, `body` = :body, `body_nomarkup` = :body_nomarkup WHERE `id` = :id', $board));
  1185. else
  1186. $query = prepare(sprintf('UPDATE ``posts_%s`` SET `name` = :name, `email` = :email, `subject` = :subject, `body_nomarkup` = :body WHERE `id` = :id', $board));
  1187. $query->bindValue(':id', $postID);
  1188. $query->bindValue('name', $_POST['name']);
  1189. $query->bindValue(':email', $_POST['email']);
  1190. $query->bindValue(':subject', $_POST['subject']);
  1191. $query->bindValue(':body', $_POST['body']);
  1192. if ($edit_raw_html) {
  1193. $body_nomarkup = $_POST['body'] . "\n<tinyboard raw html>1</tinyboard>";
  1194. $query->bindValue(':body_nomarkup', $body_nomarkup);
  1195. }
  1196. $query->execute() or error(db_error($query));
  1197. if ($edit_raw_html) {
  1198. modLog("Edited raw HTML of post #{$postID}");
  1199. } else {
  1200. modLog("Edited post #{$postID}");
  1201. rebuildPost($postID);
  1202. }
  1203. buildIndex();
  1204. rebuildThemes('post', $board);
  1205. header('Location: ?/' . sprintf($config['board_path'], $board) . $config['dir']['res'] . link_for($post) . '#' . $postID, true, $config['redirect_http']);
  1206. } else {
  1207. // Remove modifiers
  1208. $post['body_nomarkup'] = remove_modifiers($post['body_nomarkup']);
  1209. $post['body_nomarkup'] = utf8tohtml($post['body_nomarkup']);
  1210. $post['body'] = utf8tohtml($post['body']);
  1211. if ($config['minify_html']) {
  1212. $post['body_nomarkup'] = str_replace("\n", '&#010;', $post['body_nomarkup']);
  1213. $post['body'] = str_replace("\n", '&#010;', $post['body']);
  1214. $post['body_nomarkup'] = str_replace("\r", '', $post['body_nomarkup']);
  1215. $post['body'] = str_replace("\r", '', $post['body']);
  1216. $post['body_nomarkup'] = str_replace("\t", '&#09;', $post['body_nomarkup']);
  1217. $post['body'] = str_replace("\t", '&#09;', $post['body']);
  1218. }
  1219. mod_page(_('Edit post'), 'mod/edit_post_form.html', array('token' => $security_token, 'board' => $board, 'raw' => $edit_raw_html, 'post' => $post));
  1220. }
  1221. }
  1222. function mod_delete($board, $post) {
  1223. global $config, $mod;
  1224. if (!openBoard($board))
  1225. error($config['error']['noboard']);
  1226. if (!hasPermission($config['mod']['delete'], $board))
  1227. error($config['error']['noaccess']);
  1228. // Delete post
  1229. deletePost($post);
  1230. // Record the action
  1231. modLog("Deleted post #{$post}");
  1232. // Rebuild board
  1233. buildIndex();
  1234. // Rebuild themes
  1235. rebuildThemes('post-delete', $board);
  1236. // Redirect
  1237. header('Location: ?/' . sprintf($config['board_path'], $board) . $config['file_index'], true, $config['redirect_http']);
  1238. }
  1239. function mod_deletefile($board, $post, $file) {
  1240. global $config, $mod;
  1241. if (!openBoard($board))
  1242. error($config['error']['noboard']);
  1243. if (!hasPermission($config['mod']['deletefile'], $board))
  1244. error($config['error']['noaccess']);
  1245. // Delete file
  1246. deleteFile($post, TRUE, $file);
  1247. // Record the action
  1248. modLog("Deleted file from post #{$post}");
  1249. // Rebuild board
  1250. buildIndex();
  1251. // Rebuild themes
  1252. rebuildThemes('post-delete', $board);
  1253. // Redirect
  1254. header('Location: ?/' . sprintf($config['board_path'], $board) . $config['file_index'], true, $config['redirect_http']);
  1255. }
  1256. function mod_spoiler_image($board, $post, $file) {
  1257. global $config, $mod;
  1258. if (!openBoard($board))
  1259. error($config['error']['noboard']);
  1260. if (!hasPermission($config['mod']['spoilerimage'], $board))
  1261. error($config['error']['noaccess']);
  1262. // Delete file thumbnail
  1263. $query = prepare(sprintf("SELECT `files`, `thread` FROM ``posts_%s`` WHERE id = :id", $board));
  1264. $query->bindValue(':id', $post, PDO::PARAM_INT);
  1265. $query->execute() or error(db_error($query));
  1266. $result = $query->fetch(PDO::FETCH_ASSOC);
  1267. $files = json_decode($result['files']);
  1268. $size_spoiler_image = @getimagesize($config['spoiler_image']);
  1269. file_unlink($board . '/' . $config['dir']['thumb'] . $files[$file]->thumb);
  1270. $files[$file]->thumb = 'spoiler';
  1271. $files[$file]->thumbwidth = $size_spoiler_image[0];
  1272. $files[$file]->thumbheight = $size_spoiler_image[1];
  1273. // Make thumbnail spoiler
  1274. $query = prepare(sprintf("UPDATE ``posts_%s`` SET `files` = :files WHERE `id` = :id", $board));
  1275. $query->bindValue(':files', json_encode($files));
  1276. $query->bindValue(':id', $post, PDO::PARAM_INT);
  1277. $query->execute() or error(db_error($query));
  1278. // Record the action
  1279. modLog("Spoilered file from post #{$post}");
  1280. // Rebuild thread
  1281. buildThread($result['thread'] ? $result['thread'] : $post);
  1282. // Rebuild board
  1283. buildIndex();
  1284. // Rebuild themes
  1285. rebuildThemes('post-delete', $board);
  1286. // Redirect
  1287. header('Location: ?/' . sprintf($config['board_path'], $board) . $config['file_index'], true, $config['redirect_http']);
  1288. }
  1289. function mod_deletebyip($boardName, $post, $global = false) {
  1290. global $config, $mod, $board;
  1291. $global = (bool)$global;
  1292. if (!openBoard($boardName))
  1293. error($config['error']['noboard']);
  1294. if (!$global && !hasPermission($config['mod']['deletebyip'], $boardName))
  1295. error($config['error']['noaccess']);
  1296. if ($global && !hasPermission($config['mod']['deletebyip_global'], $boardName))
  1297. error($config['error']['noaccess']);
  1298. // Find IP address
  1299. $query = prepare(sprintf('SELECT `ip` FROM ``posts_%s`` WHERE `id` = :id', $boardName));
  1300. $query->bindValue(':id', $post);
  1301. $query->execute() or error(db_error($query));
  1302. if (!$ip = $query->fetchColumn())
  1303. error($config['error']['invalidpost']);
  1304. $boards = $global ? listBoards() : array(array('uri' => $boardName));
  1305. $query = '';
  1306. foreach ($boards as $_board) {
  1307. $query .= sprintf("SELECT `thread`, `id`, '%s' AS `board` FROM ``posts_%s`` WHERE `ip` = :ip UNION ALL ", $_board['uri'], $_board['uri']);
  1308. }
  1309. $query = preg_replace('/UNION ALL $/', '', $query);
  1310. $query = prepare($query);
  1311. $query->bindValue(':ip', $ip);
  1312. $query->execute() or error(db_error($query));
  1313. if ($query->rowCount() < 1)
  1314. error($config['error']['invalidpost']);
  1315. @set_time_limit($config['mod']['rebuild_timelimit']);
  1316. $threads_to_rebuild = array();
  1317. $threads_deleted = array();
  1318. while ($post = $query->fetch(PDO::FETCH_ASSOC)) {
  1319. openBoard($post['board']);
  1320. deletePost($post['id'], false, false);
  1321. rebuildThemes('post-delete', $board['uri']);
  1322. if ($post['thread'])
  1323. $threads_to_rebuild[$post['board']][$post['thread']] = true;
  1324. else
  1325. $threads_deleted[$post['board']][$post['id']] = true;
  1326. }
  1327. foreach ($threads_to_rebuild as $_board => $_threads) {
  1328. openBoard($_board);
  1329. foreach ($_threads as $_thread => $_dummy) {
  1330. if ($_dummy && !isset($threads_deleted[$_board][$_thread]))
  1331. buildThread($_thread);
  1332. }
  1333. buildIndex();
  1334. }
  1335. if ($global) {
  1336. $board = false;
  1337. }
  1338. // Record the action
  1339. modLog("Deleted all posts by IP address: <a href=\"?/IP/$ip\">$ip</a>");
  1340. // Redirect
  1341. header('Location: ?/' . sprintf($config['board_path'], $boardName) . $config['file_index'], true, $config['redirect_http']);
  1342. }
  1343. function mod_user($uid) {
  1344. global $config, $mod;
  1345. if (!hasPermission($config['mod']['editusers']) && !(hasPermission($config['mod']['change_password']) && $uid == $mod['id']))
  1346. error($config['error']['noaccess']);
  1347. $query = prepare('SELECT * FROM ``mods`` WHERE `id` = :id');
  1348. $query->bindValue(':id', $uid);
  1349. $query->execute() or error(db_error($query));
  1350. if (!$user = $query->fetch(PDO::FETCH_ASSOC))
  1351. error($config['error']['404']);
  1352. if (hasPermission($config['mod']['editusers']) && isset($_POST['username'], $_POST['password'])) {
  1353. if (isset($_POST['allboards'])) {
  1354. $boards = array('*');
  1355. } else {
  1356. $_boards = listBoards();
  1357. foreach ($_boards as &$board) {
  1358. $board = $board['uri'];
  1359. }
  1360. $boards = array();
  1361. foreach ($_POST as $name => $value) {
  1362. if (preg_match('/^board_(' . $config['board_regex'] . ')$/u', $name, $matches) && in_array($matches[1], $_boards))
  1363. $boards[] = $matches[1];
  1364. }
  1365. }
  1366. if (isset($_POST['delete'])) {
  1367. if (!hasPermission($config['mod']['deleteusers']))
  1368. error($config['error']['noaccess']);
  1369. $query = prepare('DELETE FROM ``mods`` WHERE `id` = :id');
  1370. $query->bindValue(':id', $uid);
  1371. $query->execute() or error(db_error($query));
  1372. modLog('Deleted user ' . utf8tohtml($user['username']) . ' <small>(#' . $user['id'] . ')</small>');
  1373. header('Location: ?/users', true, $config['redirect_http']);
  1374. return;
  1375. }
  1376. if ($_POST['username'] == '')
  1377. error(sprintf($config['error']['required'], 'username'));
  1378. $query = prepare('UPDATE ``mods`` SET `username` = :username, `boards` = :boards WHERE `id` = :id');
  1379. $query->bindValue(':id', $uid);
  1380. $query->bindValue(':username', $_POST['username']);
  1381. $query->bindValue(':boards', implode(',', $boards));
  1382. $query->execute() or error(db_error($query));
  1383. if ($user['username'] !== $_POST['username']) {
  1384. // account was renamed
  1385. modLog('Renamed user "' . utf8tohtml($user['username']) . '" <small>(#' . $user['id'] . ')</small> to "' . utf8tohtml($_POST['username']) . '"');
  1386. }
  1387. if ($_POST['password'] != '') {
  1388. list($version, $password) = crypt_password($_POST['password']);
  1389. $query = prepare('UPDATE ``mods`` SET `password` = :password, `version` = :version WHERE `id` = :id');
  1390. $query->bindValue(':id', $uid);
  1391. $query->bindValue(':password', $password);
  1392. $query->bindValue(':version', $version);
  1393. $query->execute() or error(db_error($query));
  1394. modLog('Changed password for ' . utf8tohtml($_POST['username']) . ' <small>(#' . $user['id'] . ')</small>');
  1395. if ($uid == $mod['id']) {
  1396. login($_POST['username'], $_POST['password']);
  1397. setCookies();
  1398. }
  1399. }
  1400. if (hasPermission($config['mod']['manageusers']))
  1401. header('Location: ?/users', true, $config['redirect_http']);
  1402. else
  1403. header('Location: ?/', true, $config['redirect_http']);
  1404. return;
  1405. }
  1406. if (hasPermission($config['mod']['change_password']) && $uid == $mod['id'] && isset($_POST['password'])) {
  1407. if ($_POST['password'] != '') {
  1408. list($version, $password) = crypt_password($_POST['password']);
  1409. $query = prepare('UPDATE ``mods`` SET `password` = :password, `version` = :version WHERE `id` = :id');
  1410. $query->bindValue(':id', $uid);
  1411. $query->bindValue(':password', $password);
  1412. $query->bindValue(':version', $version);
  1413. $query->execute() or error(db_error($query));
  1414. modLog('Changed own password');
  1415. login($user['username'], $_POST['password']);
  1416. setCookies();
  1417. }
  1418. if (hasPermission($config['mod']['manageusers']))
  1419. header('Location: ?/users', true, $config['redirect_http']);
  1420. else
  1421. header('Location: ?/', true, $config['redirect_http']);
  1422. return;
  1423. }
  1424. if (hasPermission($config['mod']['modlog'])) {
  1425. $query = prepare('SELECT * FROM ``modlogs`` WHERE `mod` = :id ORDER BY `time` DESC LIMIT 5');
  1426. $query->bindValue(':id', $uid);
  1427. $query->execute() or error(db_error($query));
  1428. $log = $query->fetchAll(PDO::FETCH_ASSOC);
  1429. } else {
  1430. $log = array();
  1431. }
  1432. $user['boards'] = explode(',', $user['boards']);
  1433. mod_page(_('Edit user'), 'mod/user.html', array(
  1434. 'user' => $user,
  1435. 'logs' => $log,
  1436. 'boards' => listBoards(),
  1437. 'token' => make_secure_link_token('users/' . $user['id'])
  1438. ));
  1439. }
  1440. function mod_user_new() {
  1441. global $pdo, $config;
  1442. if (!hasPermission($config['mod']['createusers']))
  1443. error($config['error']['noaccess']);
  1444. if (isset($_POST['username'], $_POST['password'], $_POST['type'])) {
  1445. if ($_POST['username'] == '')
  1446. error(sprintf($config['error']['required'], 'username'));
  1447. if ($_POST['password'] == '')
  1448. error(sprintf($config['error']['required'], 'password'));
  1449. if (isset($_POST['allboards'])) {
  1450. $boards = array('*');
  1451. } else {
  1452. $_boards = listBoards();
  1453. foreach ($_boards as &$board) {
  1454. $board = $board['uri'];
  1455. }
  1456. $boards = array();
  1457. foreach ($_POST as $name => $value) {
  1458. if (preg_match('/^board_(' . $config['board_regex'] . ')$/u', $name, $matches) && in_array($matches[1], $_boards))
  1459. $boards[] = $matches[1];
  1460. }
  1461. }
  1462. $type = (int)$_POST['type'];
  1463. if (!isset($config['mod']['groups'][$type]) || $type == DISABLED)
  1464. error(sprintf($config['error']['invalidfield'], 'type'));
  1465. list($version, $password) = crypt_password($_POST['password']);
  1466. $query = prepare('INSERT INTO ``mods`` VALUES (NULL, :username, :password, :version, :type, :boards)');
  1467. $query->bindValue(':username', $_POST['username']);
  1468. $query->bindValue(':password', $password);
  1469. $query->bindValue(':version', $version);
  1470. $query->bindValue(':type', $type);
  1471. $query->bindValue(':boards', implode(',', $boards));
  1472. $query->execute() or error(db_error($query));
  1473. $userID = $pdo->lastInsertId();
  1474. modLog('Created a new user: ' . utf8tohtml($_POST['username']) . ' <small>(#' . $userID . ')</small>');
  1475. header('Location: ?/users', true, $config['redirect_http']);
  1476. return;
  1477. }
  1478. mod_page(_('New user'), 'mod/user.html', array('new' => true, 'boards' => listBoards(), 'token' => make_secure_link_token('users/new')));
  1479. }
  1480. function mod_users() {
  1481. global $config;
  1482. if (!hasPermission($config['mod']['manageusers']))
  1483. error($config['error']['noaccess']);
  1484. $query = query("SELECT
  1485. *,
  1486. (SELECT `time` FROM ``modlogs`` WHERE `mod` = `id` ORDER BY `time` DESC LIMIT 1) AS `last`,
  1487. (SELECT `text` FROM ``modlogs`` WHERE `mod` = `id` ORDER BY `time` DESC LIMIT 1) AS `action`
  1488. FROM ``mods`` ORDER BY `type` DESC,`id`") or error(db_error());
  1489. $users = $query->fetchAll(PDO::FETCH_ASSOC);
  1490. foreach ($users as &$user) {
  1491. $user['promote_token'] = make_secure_link_token("users/{$user['id']}/promote");
  1492. $user['demote_token'] = make_secure_link_token("users/{$user['id']}/demote");
  1493. }
  1494. mod_page(sprintf('%s (%d)', _('Manage users'), count($users)), 'mod/users.html', array('users' => $users));
  1495. }
  1496. function mod_user_promote($uid, $action) {
  1497. global $config;
  1498. if (!hasPermission($config['mod']['promoteusers']))
  1499. error($config['error']['noaccess']);
  1500. $query = prepare("SELECT `type`, `username` FROM ``mods`` WHERE `id` = :id");
  1501. $query->bindValue(':id', $uid);
  1502. $query->execute() or error(db_error($query));
  1503. if (!$mod = $query->fetch(PDO::FETCH_ASSOC))
  1504. error($config['error']['404']);
  1505. $new_group = false;
  1506. $groups = $config['mod']['groups'];
  1507. if ($action == 'demote')
  1508. $groups = array_reverse($groups, true);
  1509. foreach ($groups as $group_value => $group_name) {
  1510. if ($action == 'promote' && $group_value > $mod['type']) {
  1511. $new_group = $group_value;
  1512. break;
  1513. } elseif ($action == 'demote' && $group_value < $mod['type']) {
  1514. $new_group = $group_value;
  1515. break;
  1516. }
  1517. }
  1518. if ($new_group === false || $new_group == DISABLED)
  1519. error(_('Impossible to promote/demote user.'));
  1520. $query = prepare("UPDATE ``mods`` SET `type` = :group_value WHERE `id` = :id");
  1521. $query->bindValue(':id', $uid);
  1522. $query->bindValue(':group_value', $new_group);
  1523. $query->execute() or error(db_error($query));
  1524. modLog(($action == 'promote' ? 'Promoted' : 'Demoted') . ' user "' .
  1525. utf8tohtml($mod['username']) . '" to ' . $config['mod']['groups'][$new_group]);
  1526. header('Location: ?/users', true, $config['redirect_http']);
  1527. }
  1528. function mod_pm($id, $reply = false) {
  1529. global $mod, $config;
  1530. if ($reply && !hasPermission($config['mod']['create_pm']))
  1531. error($config['error']['noaccess']);
  1532. $query = prepare("SELECT ``mods``.`username`, `mods_to`.`username` AS `to_username`, ``pms``.* FROM ``pms`` LEFT JOIN ``mods`` ON ``mods``.`id` = `sender` LEFT JOIN ``mods`` AS `mods_to` ON `mods_to`.`id` = `to` WHERE ``pms``.`id` = :id");
  1533. $query->bindValue(':id', $id);
  1534. $query->execute() or error(db_error($query));
  1535. if ((!$pm = $query->fetch(PDO::FETCH_ASSOC)) || ($pm['to'] != $mod['id'] && !hasPermission($config['mod']['master_pm'])))
  1536. error($config['error']['404']);
  1537. if (isset($_POST['delete'])) {
  1538. $query = prepare("DELETE FROM ``pms`` WHERE `id` = :id");
  1539. $query->bindValue(':id', $id);
  1540. $query->execute() or error(db_error($query));
  1541. if ($config['cache']['enabled']) {
  1542. cache::delete('pm_unread_' . $mod['id']);
  1543. cache::delete('pm_unreadcount_' . $mod['id']);
  1544. }
  1545. header('Location: ?/', true, $config['redirect_http']);
  1546. return;
  1547. }
  1548. if ($pm['unread'] && $pm['to'] == $mod['id']) {
  1549. $query = prepare("UPDATE ``pms`` SET `unread` = 0 WHERE `id` = :id");
  1550. $query->bindValue(':id', $id);
  1551. $query->execute() or error(db_error($query));
  1552. if ($config['cache']['enabled']) {
  1553. cache::delete('pm_unread_' . $mod['id']);
  1554. cache::delete('pm_unreadcount_' . $mod['id']);
  1555. }
  1556. modLog('Read a PM');
  1557. }
  1558. if ($reply) {
  1559. if (!$pm['to_username'])
  1560. error($config['error']['404']); // deleted?
  1561. mod_page(sprintf('%s %s', _('New PM for'), $pm['to_username']), 'mod/new_pm.html', array(
  1562. 'username' => $pm['username'],
  1563. 'id' => $pm['sender'],
  1564. 'message' => quote($pm['message']),
  1565. 'token' => make_secure_link_token('new_PM/' . $pm['username'])
  1566. ));
  1567. } else {
  1568. mod_page(sprintf('%s &ndash; #%d', _('Private message'), $id), 'mod/pm.html', $pm);
  1569. }
  1570. }
  1571. function mod_inbox() {
  1572. global $config, $mod;
  1573. $query = prepare('SELECT `unread`,``pms``.`id`, `time`, `sender`, `to`, `message`, `username` FROM ``pms`` LEFT JOIN ``mods`` ON ``mods``.`id` = `sender` WHERE `to` = :mod ORDER BY `unread` DESC, `time` DESC');
  1574. $query->bindValue(':mod', $mod['id']);
  1575. $query->execute() or error(db_error($query));
  1576. $messages = $query->fetchAll(PDO::FETCH_ASSOC);
  1577. $query = prepare('SELECT COUNT(*) FROM ``pms`` WHERE `to` = :mod AND `unread` = 1');
  1578. $query->bindValue(':mod', $mod['id']);
  1579. $query->execute() or error(db_error($query));
  1580. $unread = $query->fetchColumn();
  1581. foreach ($messages as &$message) {
  1582. $message['snippet'] = pm_snippet($message['message']);
  1583. }
  1584. mod_page(sprintf('%s (%s)', _('PM inbox'), count($messages) > 0 ? $unread . ' unread' : 'empty'), 'mod/inbox.html', array(
  1585. 'messages' => $messages,
  1586. 'unread' => $unread
  1587. ));
  1588. }
  1589. function mod_new_pm($username) {
  1590. global $config, $mod;
  1591. if (!hasPermission($config['mod']['create_pm']))
  1592. error($config['error']['noaccess']);
  1593. $query = prepare("SELECT `id` FROM ``mods`` WHERE `username` = :username");
  1594. $query->bindValue(':username', $username);
  1595. $query->execute() or error(db_error($query));
  1596. if (!$id = $query->fetchColumn()) {
  1597. // Old style ?/PM: by user ID
  1598. $query = prepare("SELECT `username` FROM ``mods`` WHERE `id` = :username");
  1599. $query->bindValue(':username', $username);
  1600. $query->execute() or error(db_error($query));
  1601. if ($username = $query->fetchColumn())
  1602. header('Location: ?/new_PM/' . $username, true, $config['redirect_http']);
  1603. else
  1604. error($config['error']['404']);
  1605. }
  1606. if (isset($_POST['message'])) {
  1607. $_POST['message'] = escape_markup_modifiers($_POST['message']);
  1608. markup($_POST['message']);
  1609. $query = prepare("INSERT INTO ``pms`` VALUES (NULL, :me, :id, :message, :time, 1)");
  1610. $query->bindValue(':me', $mod['id']);
  1611. $query->bindValue(':id', $id);
  1612. $query->bindValue(':message', $_POST['message']);
  1613. $query->bindValue(':time', time());
  1614. $query->execute() or error(db_error($query));
  1615. if ($config['cache']['enabled']) {
  1616. cache::delete('pm_unread_' . $id);
  1617. cache::delete('pm_unreadcount_' . $id);
  1618. }
  1619. modLog('Sent a PM to ' . utf8tohtml($username));
  1620. header('Location: ?/', true, $config['redirect_http']);
  1621. }
  1622. mod_page(sprintf('%s %s', _('New PM for'), $username), 'mod/new_pm.html', array(
  1623. 'username' => $username,
  1624. 'id' => $id,
  1625. 'token' => make_secure_link_token('new_PM/' . $username)
  1626. ));
  1627. }
  1628. function mod_rebuild() {
  1629. global $config, $twig;
  1630. if (!hasPermission($config['mod']['rebuild']))
  1631. error($config['error']['noaccess']);
  1632. if (isset($_POST['rebuild'])) {
  1633. @set_time_limit($config['mod']['rebuild_timelimit']);
  1634. $log = array();
  1635. $boards = listBoards();
  1636. $rebuilt_scripts = array();
  1637. if (isset($_POST['rebuild_cache'])) {
  1638. if ($config['cache']['enabled']) {
  1639. $log[] = 'Flushing cache';
  1640. Cache::flush();
  1641. }
  1642. $log[] = 'Clearing template cache';
  1643. load_twig();
  1644. $twig->clearCacheFiles();
  1645. }
  1646. if (isset($_POST['rebuild_themes'])) {
  1647. $log[] = 'Regenerating theme files';
  1648. rebuildThemes('all');
  1649. }
  1650. if (isset($_POST['rebuild_javascript'])) {
  1651. $log[] = 'Rebuilding <strong>' . $config['file_script'] . '</strong>';
  1652. buildJavascript();
  1653. $rebuilt_scripts[] = $config['file_script'];
  1654. }
  1655. foreach ($boards as $board) {
  1656. if (!(isset($_POST['boards_all']) || isset($_POST['board_' . $board['uri']])))
  1657. continue;
  1658. openBoard($board['uri']);
  1659. $config['try_smarter'] = false;
  1660. if (isset($_POST['rebuild_index'])) {
  1661. buildIndex();
  1662. $log[] = '<strong>' . sprintf($config['board_abbreviation'], $board['uri']) . '</strong>: Creating index pages';
  1663. }
  1664. if (isset($_POST['rebuild_javascript']) && !in_array($config['file_script'], $rebuilt_scripts)) {
  1665. $log[] = '<strong>' . sprintf($config['board_abbreviation'], $board['uri']) . '</strong>: Rebuilding <strong>' . $config['file_script'] . '</strong>';
  1666. buildJavascript();
  1667. $rebuilt_scripts[] = $config['file_script'];
  1668. }
  1669. if (isset($_POST['rebuild_thread'])) {
  1670. $query = query(sprintf("SELECT `id` FROM ``posts_%s`` WHERE `thread` IS NULL", $board['uri'])) or error(db_error());
  1671. while ($post = $query->fetch(PDO::FETCH_ASSOC)) {
  1672. $log[] = '<strong>' . sprintf($config['board_abbreviation'], $board['uri']) . '</strong>: Rebuilding thread #' . $post['id'];
  1673. buildThread($post['id']);
  1674. }
  1675. }
  1676. }
  1677. mod_page(_('Rebuild'), 'mod/rebuilt.html', array('logs' => $log));
  1678. return;
  1679. }
  1680. mod_page(_('Rebuild'), 'mod/rebuild.html', array(
  1681. 'boards' => listBoards(),
  1682. 'token' => make_secure_link_token('rebuild')
  1683. ));
  1684. }
  1685. function mod_reports() {
  1686. global $config, $mod;
  1687. if (!hasPermission($config['mod']['reports']))
  1688. error($config['error']['noaccess']);
  1689. $query = prepare("SELECT * FROM ``reports`` ORDER BY `time` DESC LIMIT :limit");
  1690. $query->bindValue(':limit', $config['mod']['recent_reports'], PDO::PARAM_INT);
  1691. $query->execute() or error(db_error($query));
  1692. $reports = $query->fetchAll(PDO::FETCH_ASSOC);
  1693. $report_queries = array();
  1694. foreach ($reports as $report) {
  1695. if (!isset($report_queries[$report['board']]))
  1696. $report_queries[$report['board']] = array();
  1697. $report_queries[$report['board']][] = $report['post'];
  1698. }
  1699. $report_posts = array();
  1700. foreach ($report_queries as $board => $posts) {
  1701. $report_posts[$board] = array();
  1702. $query = query(sprintf('SELECT * FROM ``posts_%s`` WHERE `id` = ' . implode(' OR `id` = ', $posts), $board)) or error(db_error());
  1703. while ($post = $query->fetch(PDO::FETCH_ASSOC)) {
  1704. $report_posts[$board][$post['id']] = $post;
  1705. }
  1706. }
  1707. $count = 0;
  1708. $body = '';
  1709. foreach ($reports as $report) {
  1710. if (!isset($report_posts[$report['board']][$report['post']])) {
  1711. // // Invalid report (post has since been deleted)
  1712. $query = prepare("DELETE FROM ``reports`` WHERE `post` = :id AND `board` = :board");
  1713. $query->bindValue(':id', $report['post'], PDO::PARAM_INT);
  1714. $query->bindValue(':board', $report['board']);
  1715. $query->execute() or error(db_error($query));
  1716. continue;
  1717. }
  1718. openBoard($report['board']);
  1719. $post = &$report_posts[$report['board']][$report['post']];
  1720. if (!$post['thread']) {
  1721. // Still need to fix this:
  1722. $po = new Thread($post, '?/', $mod, false);
  1723. } else {
  1724. $po = new Post($post, '?/', $mod);
  1725. }
  1726. // a little messy and inefficient
  1727. $append_html = Element('mod/report.html', array(
  1728. 'report' => $report,
  1729. 'config' => $config,
  1730. 'mod' => $mod,
  1731. 'token' => make_secure_link_token('reports/' . $report['id'] . '/dismiss'),
  1732. 'token_all' => make_secure_link_token('reports/' . $report['id'] . '/dismissall')
  1733. ));
  1734. // Bug fix for https://github.com/savetheinternet/Tinyboard/issues/21
  1735. $po->body = truncate($po->body, $po->link(), $config['body_truncate'] - substr_count($append_html, '<br>'));
  1736. if (mb_strlen($po->body) + mb_strlen($append_html) > $config['body_truncate_char']) {
  1737. // still too long; temporarily increase limit in the config
  1738. $__old_body_truncate_char = $config['body_truncate_char'];
  1739. $config['body_truncate_char'] = mb_strlen($po->body) + mb_strlen($append_html);
  1740. }
  1741. $po->body .= $append_html;
  1742. $body .= $po->build(true) . '<hr>';
  1743. if (isset($__old_body_truncate_char))
  1744. $config['body_truncate_char'] = $__old_body_truncate_char;
  1745. $count++;
  1746. }
  1747. mod_page(sprintf('%s (%d)', _('Report queue'), $count), 'mod/reports.html', array('reports' => $body, 'count' => $count));
  1748. }
  1749. function mod_report_dismiss($id, $all = false) {
  1750. global $config;
  1751. $query = prepare("SELECT `post`, `board`, `ip` FROM ``reports`` WHERE `id` = :id");
  1752. $query->bindValue(':id', $id);
  1753. $query->execute() or error(db_error($query));
  1754. if ($report = $query->fetch(PDO::FETCH_ASSOC)) {
  1755. $ip = $report['ip'];
  1756. $board = $report['board'];
  1757. $post = $report['post'];
  1758. } else
  1759. error($config['error']['404']);
  1760. if (!$all && !hasPermission($config['mod']['report_dismiss'], $board))
  1761. error($config['error']['noaccess']);
  1762. if ($all && !hasPermission($config['mod']['report_dismiss_ip'], $board))
  1763. error($config['error']['noaccess']);
  1764. if ($all) {
  1765. $query = prepare("DELETE FROM ``reports`` WHERE `ip` = :ip");
  1766. $query->bindValue(':ip', $ip);
  1767. } else {
  1768. $query = prepare("DELETE FROM ``reports`` WHERE `id` = :id");
  1769. $query->bindValue(':id', $id);
  1770. }
  1771. $query->execute() or error(db_error($query));
  1772. if ($all)
  1773. modLog("Dismissed all reports by <a href=\"?/IP/$ip\">$ip</a>");
  1774. else
  1775. modLog("Dismissed a report for post #{$id}", $board);
  1776. header('Location: ?/reports', true, $config['redirect_http']);
  1777. }
  1778. function mod_recent_posts($lim) {
  1779. global $config, $mod, $pdo;
  1780. if (!hasPermission($config['mod']['recent']))
  1781. error($config['error']['noaccess']);
  1782. $limit = (is_numeric($lim))? $lim : 25;
  1783. $last_time = (isset($_GET['last']) && is_numeric($_GET['last'])) ? $_GET['last'] : 0;
  1784. $mod_boards = array();
  1785. $boards = listBoards();
  1786. //if not all boards
  1787. if ($mod['boards'][0]!='*') {
  1788. foreach ($boards as $board) {
  1789. if (in_array($board['uri'], $mod['boards']))
  1790. $mod_boards[] = $board;
  1791. }
  1792. } else {
  1793. $mod_boards = $boards;
  1794. }
  1795. // Manually build an SQL query
  1796. $query = 'SELECT * FROM (';
  1797. foreach ($mod_boards as $board) {
  1798. $query .= sprintf('SELECT *, %s AS `board` FROM ``posts_%s`` UNION ALL ', $pdo->quote($board['uri']), $board['uri']);
  1799. }
  1800. // Remove the last "UNION ALL" seperator and complete the query
  1801. $query = preg_replace('/UNION ALL $/', ') AS `all_posts` WHERE (`time` < :last_time OR NOT :last_time) ORDER BY `time` DESC LIMIT ' . $limit, $query);
  1802. $query = prepare($query);
  1803. $query->bindValue(':last_time', $last_time);
  1804. $query->execute() or error(db_error($query));
  1805. $posts = $query->fetchAll(PDO::FETCH_ASSOC);
  1806. foreach ($posts as &$post) {
  1807. openBoard($post['board']);
  1808. if (!$post['thread']) {
  1809. // Still need to fix this:
  1810. $po = new Thread($post, '?/', $mod, false);
  1811. $post['built'] = $po->build(true);
  1812. } else {
  1813. $po = new Post($post, '?/', $mod);
  1814. $post['built'] = $po->build(true);
  1815. }
  1816. $last_time = $post['time'];
  1817. }
  1818. echo mod_page(_('Recent posts'), 'mod/recent_posts.html', array(
  1819. 'posts' => $posts,
  1820. 'limit' => $limit,
  1821. 'last_time' => $last_time
  1822. )
  1823. );
  1824. }
  1825. function mod_config($board_config = false) {
  1826. global $config, $mod, $board;
  1827. if ($board_config && !openBoard($board_config))
  1828. error($config['error']['noboard']);
  1829. if (!hasPermission($config['mod']['edit_config'], $board_config))
  1830. error($config['error']['noaccess']);
  1831. $config_file = $board_config ? $board['dir'] . 'config.php' : 'inc/instance-config.php';
  1832. if ($config['mod']['config_editor_php']) {
  1833. $readonly = !(is_file($config_file) ? is_writable($config_file) : is_writable(dirname($config_file)));
  1834. if (!$readonly && isset($_POST['code'])) {
  1835. $code = $_POST['code'];
  1836. // Save previous instance_config if php_check_syntax fails
  1837. $old_code = file_get_contents($config_file);
  1838. file_put_contents($config_file, $code);
  1839. $resp = shell_exec_error('php -l ' . $config_file);
  1840. if (preg_match('/No syntax errors detected/', $resp)) {
  1841. header('Location: ?/config' . ($board_config ? '/' . $board_config : ''), true, $config['redirect_http']);
  1842. return;
  1843. }
  1844. else {
  1845. file_put_contents($config_file, $old_code);
  1846. error($config['error']['badsyntax'] . $resp);
  1847. }
  1848. }
  1849. $instance_config = @file_get_contents($config_file);
  1850. if ($instance_config === false) {
  1851. $instance_config = "<?php\n\n// This file does not exist yet. You are creating it.";
  1852. }
  1853. $instance_config = str_replace("\n", '&#010;', utf8tohtml($instance_config));
  1854. mod_page(_('Config editor'), 'mod/config-editor-php.html', array(
  1855. 'php' => $instance_config,
  1856. 'readonly' => $readonly,
  1857. 'boards' => listBoards(),
  1858. 'board' => $board_config,
  1859. 'file' => $config_file,
  1860. 'token' => make_secure_link_token('config' . ($board_config ? '/' . $board_config : ''))
  1861. ));
  1862. return;
  1863. }
  1864. require_once 'inc/mod/config-editor.php';
  1865. $conf = config_vars();
  1866. foreach ($conf as &$var) {
  1867. if (is_array($var['name'])) {
  1868. $c = &$config;
  1869. foreach ($var['name'] as $n)
  1870. $c = &$c[$n];
  1871. } else {
  1872. $c = @$config[$var['name']];
  1873. }
  1874. $var['value'] = $c;
  1875. }
  1876. unset($var);
  1877. if (isset($_POST['save'])) {
  1878. $config_append = '';
  1879. foreach ($conf as $var) {
  1880. $field_name = 'cf_' . (is_array($var['name']) ? implode('/', $var['name']) : $var['name']);
  1881. if ($var['type'] == 'boolean')
  1882. $value = isset($_POST[$field_name]);
  1883. elseif (isset($_POST[$field_name]))
  1884. $value = $_POST[$field_name];
  1885. else
  1886. continue; // ???
  1887. if (!settype($value, $var['type']))
  1888. continue; // invalid
  1889. if ($value != $var['value']) {
  1890. // This value has been changed.
  1891. $config_append .= '$config';
  1892. if (is_array($var['name'])) {
  1893. foreach ($var['name'] as $name)
  1894. $config_append .= '[' . var_export($name, true) . ']';
  1895. } else {
  1896. $config_append .= '[' . var_export($var['name'], true) . ']';
  1897. }
  1898. $config_append .= ' = ';
  1899. if (@$var['permissions'] && isset($config['mod']['groups'][$value])) {
  1900. $config_append .= $config['mod']['groups'][$value];
  1901. } else {
  1902. $config_append .= var_export($value, true);
  1903. }
  1904. $config_append .= ";\n";
  1905. }
  1906. }
  1907. if (!empty($config_append)) {
  1908. $config_append = "\n// Changes made via web editor by \"" . $mod['username'] . "\" @ " . date('r') . ":\n" . $config_append . "\n";
  1909. if (!is_file($config_file))
  1910. $config_append = "<?php\n\n$config_append";
  1911. if (!@file_put_contents($config_file, $config_append, FILE_APPEND)) {
  1912. $config_append = htmlentities($config_append);
  1913. if ($config['minify_html'])
  1914. $config_append = str_replace("\n", '&#010;', $config_append);
  1915. $page = array();
  1916. $page['title'] = 'Cannot write to file!';
  1917. $page['config'] = $config;
  1918. $page['body'] = '
  1919. <p style="text-align:center">Tinyboard could not write to <strong>' . $config_file . '</strong> with the ammended configuration, probably due to a permissions error.</p>
  1920. <p style="text-align:center">You may proceed with these changes manually by copying and pasting the following code to the end of <strong>' . $config_file . '</strong>:</p>
  1921. <textarea style="width:700px;height:370px;margin:auto;display:block;background:white;color:black" readonly>' . $config_append . '</textarea>
  1922. ';
  1923. echo Element('page.html', $page);
  1924. exit;
  1925. }
  1926. }
  1927. header('Location: ?/config' . ($board_config ? '/' . $board_config : ''), true, $config['redirect_http']);
  1928. exit;
  1929. }
  1930. mod_page(_('Config editor') . ($board_config ? ': ' . sprintf($config['board_abbreviation'], $board_config) : ''),
  1931. 'mod/config-editor.html', array(
  1932. 'boards' => listBoards(),
  1933. 'board' => $board_config,
  1934. 'conf' => $conf,
  1935. 'file' => $config_file,
  1936. 'token' => make_secure_link_token('config' . ($board_config ? '/' . $board_config : ''))
  1937. ));
  1938. }
  1939. function mod_themes_list() {
  1940. global $config;
  1941. if (!hasPermission($config['mod']['themes']))
  1942. error($config['error']['noaccess']);
  1943. if (!is_dir($config['dir']['themes']))
  1944. error(_('Themes directory doesn\'t exist!'));
  1945. if (!$dir = opendir($config['dir']['themes']))
  1946. error(_('Cannot open themes directory; check permissions.'));
  1947. $query = query('SELECT `theme` FROM ``theme_settings`` WHERE `name` IS NULL AND `value` IS NULL') or error(db_error());
  1948. $themes_in_use = $query->fetchAll(PDO::FETCH_COLUMN);
  1949. // Scan directory for themes
  1950. $themes = array();
  1951. while ($file = readdir($dir)) {
  1952. if ($file[0] != '.' && is_dir($config['dir']['themes'] . '/' . $file)) {
  1953. $themes[$file] = loadThemeConfig($file);
  1954. }
  1955. }
  1956. closedir($dir);
  1957. foreach ($themes as $theme_name => &$theme) {
  1958. $theme['rebuild_token'] = make_secure_link_token('themes/' . $theme_name . '/rebuild');
  1959. $theme['uninstall_token'] = make_secure_link_token('themes/' . $theme_name . '/uninstall');
  1960. }
  1961. mod_page(_('Manage themes'), 'mod/themes.html', array(
  1962. 'themes' => $themes,
  1963. 'themes_in_use' => $themes_in_use,
  1964. ));
  1965. }
  1966. function mod_theme_configure($theme_name) {
  1967. global $config;
  1968. if (!hasPermission($config['mod']['themes']))
  1969. error($config['error']['noaccess']);
  1970. if (!$theme = loadThemeConfig($theme_name)) {
  1971. error($config['error']['invalidtheme']);
  1972. }
  1973. if (isset($_POST['install'])) {
  1974. // Check if everything is submitted
  1975. foreach ($theme['config'] as &$conf) {
  1976. if (!isset($_POST[$conf['name']]) && $conf['type'] != 'checkbox')
  1977. error(sprintf($config['error']['required'], $c['title']));
  1978. }
  1979. // Clear previous settings
  1980. $query = prepare("DELETE FROM ``theme_settings`` WHERE `theme` = :theme");
  1981. $query->bindValue(':theme', $theme_name);
  1982. $query->execute() or error(db_error($query));
  1983. foreach ($theme['config'] as &$conf) {
  1984. $query = prepare("INSERT INTO ``theme_settings`` VALUES(:theme, :name, :value)");
  1985. $query->bindValue(':theme', $theme_name);
  1986. $query->bindValue(':name', $conf['name']);
  1987. if ($conf['type'] == 'checkbox')
  1988. $query->bindValue(':value', isset($_POST[$conf['name']]) ? 1 : 0);
  1989. else
  1990. $query->bindValue(':value', $_POST[$conf['name']]);
  1991. $query->execute() or error(db_error($query));
  1992. }
  1993. $query = prepare("INSERT INTO ``theme_settings`` VALUES(:theme, NULL, NULL)");
  1994. $query->bindValue(':theme', $theme_name);
  1995. $query->execute() or error(db_error($query));
  1996. // Clean cache
  1997. Cache::delete("themes");
  1998. Cache::delete("theme_settings_".$theme_name);
  1999. $result = true;
  2000. $message = false;
  2001. if (isset($theme['install_callback'])) {
  2002. $ret = $theme['install_callback'](themeSettings($theme_name));
  2003. if ($ret && !empty($ret)) {
  2004. if (is_array($ret) && count($ret) == 2) {
  2005. $result = $ret[0];
  2006. $message = $ret[1];
  2007. }
  2008. }
  2009. }
  2010. if (!$result) {
  2011. // Install failed
  2012. $query = prepare("DELETE FROM ``theme_settings`` WHERE `theme` = :theme");
  2013. $query->bindValue(':theme', $theme_name);
  2014. $query->execute() or error(db_error($query));
  2015. }
  2016. // Build themes
  2017. rebuildThemes('all');
  2018. mod_page(sprintf(_($result ? 'Installed theme: %s' : 'Installation failed: %s'), $theme['name']), 'mod/theme_installed.html', array(
  2019. 'theme_name' => $theme_name,
  2020. 'theme' => $theme,
  2021. 'result' => $result,
  2022. 'message' => $message
  2023. ));
  2024. return;
  2025. }
  2026. $settings = themeSettings($theme_name);
  2027. mod_page(sprintf(_('Configuring theme: %s'), $theme['name']), 'mod/theme_config.html', array(
  2028. 'theme_name' => $theme_name,
  2029. 'theme' => $theme,
  2030. 'settings' => $settings,
  2031. 'token' => make_secure_link_token('themes/' . $theme_name)
  2032. ));
  2033. }
  2034. function mod_theme_uninstall($theme_name) {
  2035. global $config;
  2036. if (!hasPermission($config['mod']['themes']))
  2037. error($config['error']['noaccess']);
  2038. $query = prepare("DELETE FROM ``theme_settings`` WHERE `theme` = :theme");
  2039. $query->bindValue(':theme', $theme_name);
  2040. $query->execute() or error(db_error($query));
  2041. // Clean cache
  2042. Cache::delete("themes");
  2043. Cache::delete("theme_settings_".$theme);
  2044. header('Location: ?/themes', true, $config['redirect_http']);
  2045. }
  2046. function mod_theme_rebuild($theme_name) {
  2047. global $config;
  2048. if (!hasPermission($config['mod']['themes']))
  2049. error($config['error']['noaccess']);
  2050. rebuildTheme($theme_name, 'all');
  2051. mod_page(sprintf(_('Rebuilt theme: %s'), $theme_name), 'mod/theme_rebuilt.html', array(
  2052. 'theme_name' => $theme_name,
  2053. ));
  2054. }
  2055. // This needs to be done for `secure` CSRF prevention compatibility, otherwise the $board will be read in as the token if editing global pages.
  2056. function delete_page_base($page = '', $board = false) {
  2057. global $config, $mod;
  2058. if (empty($board))
  2059. $board = false;
  2060. if (!$board && $mod['boards'][0] !== '*')
  2061. error($config['error']['noaccess']);
  2062. if (!hasPermission($config['mod']['edit_pages'], $board))
  2063. error($config['error']['noaccess']);
  2064. if ($board !== FALSE && !openBoard($board))
  2065. error($config['error']['noboard']);
  2066. if ($board) {
  2067. $query = prepare('DELETE FROM ``pages`` WHERE `board` = :board AND `name` = :name');
  2068. $query->bindValue(':board', ($board ? $board : NULL));
  2069. } else {
  2070. $query = prepare('DELETE FROM ``pages`` WHERE `board` IS NULL AND `name` = :name');
  2071. }
  2072. $query->bindValue(':name', $page);
  2073. $query->execute() or error(db_error($query));
  2074. header('Location: ?/edit_pages' . ($board ? ('/' . $board) : ''), true, $config['redirect_http']);
  2075. }
  2076. function mod_delete_page($page = '') {
  2077. delete_page_base($page);
  2078. }
  2079. function mod_delete_page_board($page = '', $board = false) {
  2080. delete_page_base($page, $board);
  2081. }
  2082. function mod_edit_page($id) {
  2083. global $config, $mod, $board;
  2084. $query = prepare('SELECT * FROM ``pages`` WHERE `id` = :id');
  2085. $query->bindValue(':id', $id);
  2086. $query->execute() or error(db_error($query));
  2087. $page = $query->fetch();
  2088. if (!$page)
  2089. error(_('Could not find the page you are trying to edit.'));
  2090. if (!$page['board'] && $mod['boards'][0] !== '*')
  2091. error($config['error']['noaccess']);
  2092. if (!hasPermission($config['mod']['edit_pages'], $page['board']))
  2093. error($config['error']['noaccess']);
  2094. if ($page['board'] && !openBoard($page['board']))
  2095. error($config['error']['noboard']);
  2096. if (isset($_POST['method'], $_POST['content'])) {
  2097. $content = $_POST['content'];
  2098. $method = $_POST['method'];
  2099. $page['type'] = $method;
  2100. if (!in_array($method, array('markdown', 'html', 'infinity')))
  2101. error(_('Unrecognized page markup method.'));
  2102. switch ($method) {
  2103. case 'markdown':
  2104. $write = markdown($content);
  2105. break;
  2106. case 'html':
  2107. if (hasPermission($config['mod']['rawhtml'])) {
  2108. $write = $content;
  2109. } else {
  2110. $write = purify_html($content);
  2111. }
  2112. break;
  2113. case 'infinity':
  2114. $c = $content;
  2115. markup($content);
  2116. $write = $content;
  2117. $content = $c;
  2118. }
  2119. if (!isset($write) or !$write)
  2120. error(_('Failed to mark up your input for some reason...'));
  2121. $query = prepare('UPDATE ``pages`` SET `type` = :method, `content` = :content WHERE `id` = :id');
  2122. $query->bindValue(':method', $method);
  2123. $query->bindValue(':content', $content);
  2124. $query->bindValue(':id', $id);
  2125. $query->execute() or error(db_error($query));
  2126. $fn = ($board['uri'] ? ($board['uri'] . '/') : '') . $page['name'] . '.html';
  2127. $body = "<div class='ban'>$write</div>";
  2128. $html = Element('page.html', array('config' => $config, 'body' => $body, 'title' => utf8tohtml($page['title'])));
  2129. file_write($fn, $html);
  2130. }
  2131. if (!isset($content)) {
  2132. $query = prepare('SELECT `content` FROM ``pages`` WHERE `id` = :id');
  2133. $query->bindValue(':id', $id);
  2134. $query->execute() or error(db_error($query));
  2135. $content = $query->fetchColumn();
  2136. }
  2137. mod_page(sprintf(_('Editing static page: %s'), $page['name']), 'mod/edit_page.html', array('page' => $page, 'token' => make_secure_link_token("edit_page/$id"), 'content' => prettify_textarea($content), 'board' => $board));
  2138. }
  2139. function mod_pages($board = false) {
  2140. global $config, $mod, $pdo;
  2141. if (empty($board))
  2142. $board = false;
  2143. if (!$board && $mod['boards'][0] !== '*')
  2144. error($config['error']['noaccess']);
  2145. if (!hasPermission($config['mod']['edit_pages'], $board))
  2146. error($config['error']['noaccess']);
  2147. if ($board !== FALSE && !openBoard($board))
  2148. error($config['error']['noboard']);
  2149. if ($board) {
  2150. $query = prepare('SELECT * FROM ``pages`` WHERE `board` = :board');
  2151. $query->bindValue(':board', $board);
  2152. } else {
  2153. $query = query('SELECT * FROM ``pages`` WHERE `board` IS NULL');
  2154. }
  2155. $query->execute() or error(db_error($query));
  2156. $pages = $query->fetchAll(PDO::FETCH_ASSOC);
  2157. if (isset($_POST['page'])) {
  2158. if ($board and sizeof($pages) > $config['pages_max'])
  2159. error(sprintf(_('Sorry, this site only allows %d pages per board.'), $config['pages_max']));
  2160. if (!preg_match('/^[a-z0-9]{1,255}$/', $_POST['page']))
  2161. error(_('Page names must be < 255 chars and may only contain lowercase letters A-Z and digits 1-9.'));
  2162. foreach ($pages as $i => $p) {
  2163. if ($_POST['page'] === $p['name'])
  2164. error(_('Refusing to create a new page with the same name as an existing one.'));
  2165. }
  2166. $title = ($_POST['title'] ? $_POST['title'] : NULL);
  2167. $query = prepare('INSERT INTO ``pages``(board, title, name) VALUES(:board, :title, :name)');
  2168. $query->bindValue(':board', ($board ? $board : NULL));
  2169. $query->bindValue(':title', $title);
  2170. $query->bindValue(':name', $_POST['page']);
  2171. $query->execute() or error(db_error($query));
  2172. $pages[] = array('id' => $pdo->lastInsertId(), 'name' => $_POST['page'], 'board' => $board, 'title' => $title);
  2173. }
  2174. foreach ($pages as $i => &$p) {
  2175. $p['delete_token'] = make_secure_link_token('edit_pages/delete/' . $p['name'] . ($board ? ('/' . $board) : ''));
  2176. }
  2177. mod_page(_('Pages'), 'mod/pages.html', array('pages' => $pages, 'token' => make_secure_link_token('edit_pages' . ($board ? ('/' . $board) : '')), 'board' => $board));
  2178. }
  2179. function mod_debug_antispam() {
  2180. global $pdo, $config;
  2181. $args = array();
  2182. if (isset($_POST['board'], $_POST['thread'])) {
  2183. $where = '`board` = ' . $pdo->quote($_POST['board']);
  2184. if ($_POST['thread'] != '')
  2185. $where .= ' AND `thread` = ' . $pdo->quote($_POST['thread']);
  2186. if (isset($_POST['purge'])) {
  2187. $query = prepare(', DATE ``antispam`` SET `expires` = UNIX_TIMESTAMP() + :expires WHERE' . $where);
  2188. $query->bindValue(':expires', $config['spam']['hidden_inputs_expire']);
  2189. $query->execute() or error(db_error());
  2190. }
  2191. $args['board'] = $_POST['board'];
  2192. $args['thread'] = $_POST['thread'];
  2193. } else {
  2194. $where = '';
  2195. }
  2196. $query = query('SELECT COUNT(*) FROM ``antispam``' . ($where ? " WHERE $where" : '')) or error(db_error());
  2197. $args['total'] = number_format($query->fetchColumn());
  2198. $query = query('SELECT COUNT(*) FROM ``antispam`` WHERE `expires` IS NOT NULL' . ($where ? " AND $where" : '')) or error(db_error());
  2199. $args['expiring'] = number_format($query->fetchColumn());
  2200. $query = query('SELECT * FROM ``antispam`` ' . ($where ? "WHERE $where" : '') . ' ORDER BY `passed` DESC LIMIT 40') or error(db_error());
  2201. $args['top'] = $query->fetchAll(PDO::FETCH_ASSOC);
  2202. $query = query('SELECT * FROM ``antispam`` ' . ($where ? "WHERE $where" : '') . ' ORDER BY `created` DESC LIMIT 20') or error(db_error());
  2203. $args['recent'] = $query->fetchAll(PDO::FETCH_ASSOC);
  2204. mod_page(_('Debug: Anti-spam'), 'mod/debug/antispam.html', $args);
  2205. }
  2206. function mod_debug_recent_posts() {
  2207. global $pdo, $config;
  2208. $limit = 500;
  2209. $boards = listBoards();
  2210. // Manually build an SQL query
  2211. $query = 'SELECT * FROM (';
  2212. foreach ($boards as $board) {
  2213. $query .= sprintf('SELECT *, %s AS `board` FROM ``posts_%s`` UNION ALL ', $pdo->quote($board['uri']), $board['uri']);
  2214. }
  2215. // Remove the last "UNION ALL" seperator and complete the query
  2216. $query = preg_replace('/UNION ALL $/', ') AS `all_posts` ORDER BY `time` DESC LIMIT ' . $limit, $query);
  2217. $query = query($query) or error(db_error());
  2218. $posts = $query->fetchAll(PDO::FETCH_ASSOC);
  2219. // Fetch recent posts from flood prevention cache
  2220. $query = query("SELECT * FROM ``flood`` ORDER BY `time` DESC") or error(db_error());
  2221. $flood_posts = $query->fetchAll(PDO::FETCH_ASSOC);
  2222. foreach ($posts as &$post) {
  2223. $post['snippet'] = pm_snippet($post['body']);
  2224. foreach ($flood_posts as $flood_post) {
  2225. if ($flood_post['time'] == $post['time'] &&
  2226. $flood_post['posthash'] == make_comment_hex($post['body_nomarkup']) &&
  2227. $flood_post['filehash'] == $post['filehash'])
  2228. $post['in_flood_table'] = true;
  2229. }
  2230. }
  2231. mod_page(_('Debug: Recent posts'), 'mod/debug/recent_posts.html', array('posts' => $posts, 'flood_posts' => $flood_posts));
  2232. }
  2233. function mod_debug_sql() {
  2234. global $config;
  2235. if (!hasPermission($config['mod']['debug_sql']))
  2236. error($config['error']['noaccess']);
  2237. $args['security_token'] = make_secure_link_token('debug/sql');
  2238. if (isset($_POST['query'])) {
  2239. $args['query'] = $_POST['query'];
  2240. if ($query = query($_POST['query'])) {
  2241. $args['result'] = $query->fetchAll(PDO::FETCH_ASSOC);
  2242. if (!empty($args['result']))
  2243. $args['keys'] = array_keys($args['result'][0]);
  2244. else
  2245. $args['result'] = 'empty';
  2246. } else {
  2247. $args['error'] = db_error();
  2248. }
  2249. }
  2250. mod_page(_('Debug: SQL'), 'mod/debug/sql.html', $args);
  2251. }
  2252. function mod_debug_apc() {
  2253. global $config;
  2254. if (!hasPermission($config['mod']['debug_apc']))
  2255. error($config['error']['noaccess']);
  2256. if ($config['cache']['enabled'] != 'apc')
  2257. error('APC is not enabled.');
  2258. $cache_info = apc_cache_info('user');
  2259. // $cached_vars = new APCIterator('user', '/^' . $config['cache']['prefix'] . '/');
  2260. $cached_vars = array();
  2261. foreach ($cache_info['cache_list'] as $var) {
  2262. if ($config['cache']['prefix'] != '' && strpos(isset($var['key']) ? $var['key'] : $var['info'], $config['cache']['prefix']) !== 0)
  2263. continue;
  2264. $cached_vars[] = $var;
  2265. }
  2266. mod_page(_('Debug: APC'), 'mod/debug/apc.html', array('cached_vars' => $cached_vars));
  2267. }