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

pages.php 96KB

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