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 115KB

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
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
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498
  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. if (in_array($boardName,array_keys($config['overboards']))){
  582. $type = $config['overboards'][$boardName]['type'];
  583. require_once("templates/themes/$type/theme.php");
  584. global $mod;
  585. $overboard = new $type();
  586. $overboard->settings = array();
  587. $overboard->settings['uri'] = $boardName;
  588. $overboard->settings['title'] = $config['overboards'][$boardName]['title'];
  589. $overboard->settings['subtitle'] = $config['overboards'][$boardName]['subtitle'];
  590. $overboard->settings['thread_limit'] = $config['overboards'][$boardName]['thread_limit'];
  591. if (array_key_exists('exclude',$config['overboards'][$boardName])) {
  592. $overboard->settings['exclude'] = $config['overboards'][$boardName]['exclude'];
  593. }
  594. if (array_key_exists('include',$config['overboards'][$boardName])) {
  595. $overboard->settings['include'] = $config['overboards'][$boardName]['include'];
  596. }
  597. $overboard->settings['boards'] = listBoards();
  598. echo $overboard->build($mod);
  599. return;
  600. }
  601. elseif (in_array($boardName,array_keys($config['boards_alias']))){
  602. $boardName = $config['boards_alias'][$boardName];
  603. openBoard($boardName);
  604. }
  605. else {
  606. error($config['error']['noboard']);
  607. }
  608. }
  609. if (!$page = index($page_no, $mod)) {
  610. error($config['error']['404']);
  611. }
  612. $page['pages'] = getPages(true);
  613. $page['pages'][$page_no-1]['selected'] = true;
  614. $page['btn'] = getPageButtons($page['pages'], true);
  615. $page['mod'] = true;
  616. $page['config'] = $config;
  617. echo Element('index.html', $page);
  618. }
  619. function mod_view_thread($boardName, $thread) {
  620. global $config, $mod;
  621. if (!openBoard($boardName))
  622. error($config['error']['noboard']);
  623. $page = buildThread($thread, true, $mod);
  624. echo $page;
  625. }
  626. function mod_view_thread50($boardName, $thread) {
  627. global $config, $mod;
  628. if (!openBoard($boardName))
  629. error($config['error']['noboard']);
  630. $page = buildThread50($thread, true, $mod);
  631. echo $page;
  632. }
  633. function mod_ip_remove_note($ip, $id) {
  634. global $config, $mod;
  635. if (!hasPermission($config['mod']['remove_notes']))
  636. error($config['error']['noaccess']);
  637. if (filter_var($ip, FILTER_VALIDATE_IP) === false)
  638. error("Invalid IP address.");
  639. $query = prepare('DELETE FROM ``ip_notes`` WHERE `ip` = :ip AND `id` = :id');
  640. $query->bindValue(':ip', $ip);
  641. $query->bindValue(':id', $id);
  642. $query->execute() or error(db_error($query));
  643. modLog("Removed a note for <a href=\"?/IP/{$ip}\">{$ip}</a>");
  644. header('Location: ?/IP/' . $ip . '#notes', true, $config['redirect_http']);
  645. }
  646. function mod_page_ip($ip) {
  647. global $config, $mod;
  648. if (filter_var($ip, FILTER_VALIDATE_IP) === false)
  649. error("Invalid IP address.");
  650. if (isset($_POST['ban_id'], $_POST['unban'])) {
  651. if (!hasPermission($config['mod']['unban']))
  652. error($config['error']['noaccess']);
  653. Bans::delete($_POST['ban_id'], true, $mod['boards']);
  654. header('Location: ?/IP/' . $ip . '#bans', true, $config['redirect_http']);
  655. return;
  656. }
  657. if (isset($_POST['note'])) {
  658. if (!hasPermission($config['mod']['create_notes']))
  659. error($config['error']['noaccess']);
  660. $_POST['note'] = escape_markup_modifiers($_POST['note']);
  661. markup($_POST['note']);
  662. $query = prepare('INSERT INTO ``ip_notes`` VALUES (NULL, :ip, :mod, :time, :body)');
  663. $query->bindValue(':ip', $ip);
  664. $query->bindValue(':mod', $mod['id']);
  665. $query->bindValue(':time', time());
  666. $query->bindValue(':body', $_POST['note']);
  667. $query->execute() or error(db_error($query));
  668. modLog("Added a note for <a href=\"?/IP/{$ip}\">{$ip}</a>");
  669. header('Location: ?/IP/' . $ip . '#notes', true, $config['redirect_http']);
  670. return;
  671. }
  672. $args = array();
  673. $args['ip'] = $ip;
  674. $args['posts'] = array();
  675. if ($config['mod']['dns_lookup'])
  676. $args['hostname'] = rDNS($ip);
  677. $boards = listBoards();
  678. foreach ($boards as $board) {
  679. openBoard($board['uri']);
  680. if (!hasPermission($config['mod']['show_ip'], $board['uri']))
  681. continue;
  682. $query = prepare(sprintf('SELECT * FROM ``posts_%s`` WHERE `ip` = :ip ORDER BY `sticky` DESC, `id` DESC LIMIT :limit', $board['uri']));
  683. $query->bindValue(':ip', $ip);
  684. $query->bindValue(':limit', $config['mod']['ip_recentposts'], PDO::PARAM_INT);
  685. $query->execute() or error(db_error($query));
  686. while ($post = $query->fetch(PDO::FETCH_ASSOC)) {
  687. if (!$post['thread']) {
  688. $po = new Thread($post, '?/', $mod, false);
  689. } else {
  690. $po = new Post($post, '?/', $mod);
  691. }
  692. if (!isset($args['posts'][$board['uri']]))
  693. $args['posts'][$board['uri']] = array('board' => $board, 'posts' => array());
  694. $args['posts'][$board['uri']]['posts'][] = $po->build(true);
  695. }
  696. }
  697. $args['boards'] = $boards;
  698. $args['token'] = make_secure_link_token('ban');
  699. if (hasPermission($config['mod']['view_ban'])) {
  700. $args['bans'] = Bans::find($ip, false, true);
  701. }
  702. if (hasPermission($config['mod']['view_notes'])) {
  703. $query = prepare("SELECT ``ip_notes``.*, `username` FROM ``ip_notes`` LEFT JOIN ``mods`` ON `mod` = ``mods``.`id` WHERE `ip` = :ip ORDER BY `time` DESC");
  704. $query->bindValue(':ip', $ip);
  705. $query->execute() or error(db_error($query));
  706. $args['notes'] = $query->fetchAll(PDO::FETCH_ASSOC);
  707. }
  708. if (hasPermission($config['mod']['modlog_ip'])) {
  709. $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");
  710. $query->bindValue(':search', '%' . $ip . '%');
  711. $query->execute() or error(db_error($query));
  712. $args['logs'] = $query->fetchAll(PDO::FETCH_ASSOC);
  713. } else {
  714. $args['logs'] = array();
  715. }
  716. $args['security_token'] = make_secure_link_token('IP/' . $ip);
  717. mod_page(sprintf('%s: %s', _('IP'), htmlspecialchars($ip)), 'mod/view_ip.html', $args, $args['hostname']);
  718. }
  719. function mod_ban() {
  720. global $config;
  721. if (!hasPermission($config['mod']['ban']))
  722. error($config['error']['noaccess']);
  723. if (!isset($_POST['ip'], $_POST['reason'], $_POST['length'], $_POST['board'])) {
  724. mod_page(_('New ban'), 'mod/ban_form.html', array('token' => make_secure_link_token('ban')));
  725. return;
  726. }
  727. require_once 'inc/mod/ban.php';
  728. Bans::new_ban($_POST['ip'], $_POST['reason'], $_POST['length'], $_POST['board'] == '*' ? false : $_POST['board']);
  729. if (isset($_POST['redirect']))
  730. header('Location: ' . $_POST['redirect'], true, $config['redirect_http']);
  731. else
  732. header('Location: ?/', true, $config['redirect_http']);
  733. }
  734. function mod_warning() {
  735. global $config;
  736. if (!hasPermission($config['mod']['warning']))
  737. error($config['error']['noaccess']);
  738. if (!isset( $_POST['board'])) {
  739. mod_page(_('New warning'), 'mod/warning_form.html', array('token' => make_secure_link_token('ban')));
  740. return;
  741. }
  742. if (isset($_POST['redirect']))
  743. header('Location: ' . $_POST['redirect'], true, $config['redirect_http']);
  744. else
  745. header('Location: ?/', true, $config['redirect_http']);
  746. }
  747. function mod_bans() {
  748. global $config;
  749. global $mod;
  750. if (!hasPermission($config['mod']['view_banlist']))
  751. error($config['error']['noaccess']);
  752. if (isset($_POST['unban'])) {
  753. if (!hasPermission($config['mod']['unban']))
  754. error($config['error']['noaccess']);
  755. $unban = array();
  756. foreach ($_POST as $name => $unused) {
  757. if (preg_match('/^ban_(\d+)$/', $name, $match))
  758. $unban[] = $match[1];
  759. }
  760. if (isset($config['mod']['unban_limit']) && $config['mod']['unban_limit'] && count($unban) > $config['mod']['unban_limit'])
  761. error(sprintf($config['error']['toomanyunban'], $config['mod']['unban_limit'], count($unban)));
  762. foreach ($unban as $id) {
  763. Bans::delete($id, true, $mod['boards'], true);
  764. }
  765. rebuildThemes('bans');
  766. header('Location: ?/bans', true, $config['redirect_http']);
  767. return;
  768. }
  769. mod_page(_('Ban list'), 'mod/ban_list.html', array(
  770. 'mod' => $mod,
  771. 'boards' => json_encode($mod['boards']),
  772. 'token' => make_secure_link_token('bans'),
  773. 'token_json' => make_secure_link_token('bans.json')
  774. ));
  775. }
  776. function mod_bans_json() {
  777. global $config, $mod;
  778. if (!hasPermission($config['mod']['ban']))
  779. error($config['error']['noaccess']);
  780. // Compress the json for faster loads
  781. if (substr_count($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip')) ob_start("ob_gzhandler");
  782. Bans::stream_json(false, false, !hasPermission($config['mod']['view_banstaff']), $mod['boards']);
  783. }
  784. function mod_ban_appeals() {
  785. global $config, $board;
  786. if (!hasPermission($config['mod']['view_ban_appeals']))
  787. error($config['error']['noaccess']);
  788. // Remove stale ban appeals
  789. query("DELETE FROM ``ban_appeals`` WHERE NOT EXISTS (SELECT 1 FROM ``bans`` WHERE `ban_id` = ``bans``.`id`)")
  790. or error(db_error());
  791. if (isset($_POST['appeal_id']) && (isset($_POST['unban']) || isset($_POST['deny']))) {
  792. if (!hasPermission($config['mod']['ban_appeals']))
  793. error($config['error']['noaccess']);
  794. $query = query("SELECT *, ``ban_appeals``.`id` AS `id` FROM ``ban_appeals``
  795. LEFT JOIN ``bans`` ON `ban_id` = ``bans``.`id`
  796. WHERE ``ban_appeals``.`id` = " . (int)$_POST['appeal_id']) or error(db_error());
  797. if (!$ban = $query->fetch(PDO::FETCH_ASSOC)) {
  798. error(_('Ban appeal not found!'));
  799. }
  800. $ban['mask'] = Bans::range_to_string(array($ban['ipstart'], $ban['ipend']));
  801. if (isset($_POST['unban'])) {
  802. modLog('Accepted ban appeal #' . $ban['id'] . ' for ' . $ban['mask']);
  803. Bans::delete($ban['ban_id'], true);
  804. query("DELETE FROM ``ban_appeals`` WHERE `id` = " . $ban['id']) or error(db_error());
  805. } else {
  806. modLog('Denied ban appeal #' . $ban['id'] . ' for ' . $ban['mask']);
  807. query("UPDATE ``ban_appeals`` SET `denied` = 1 WHERE `id` = " . $ban['id']) or error(db_error());
  808. }
  809. header('Location: ?/ban-appeals', true, $config['redirect_http']);
  810. return;
  811. }
  812. $query = query("SELECT *, ``ban_appeals``.`id` AS `id` FROM ``ban_appeals``
  813. LEFT JOIN ``bans`` ON `ban_id` = ``bans``.`id`
  814. LEFT JOIN ``mods`` ON ``bans``.`creator` = ``mods``.`id`
  815. WHERE `denied` != 1 ORDER BY `time`") or error(db_error());
  816. $ban_appeals = $query->fetchAll(PDO::FETCH_ASSOC);
  817. foreach ($ban_appeals as &$ban) {
  818. if ($ban['post'])
  819. $ban['post'] = json_decode($ban['post'], true);
  820. $ban['mask'] = Bans::range_to_string(array($ban['ipstart'], $ban['ipend']));
  821. if ($ban['post'] && isset($ban['post']['board'], $ban['post']['id'])) {
  822. if (openBoard($ban['post']['board'])) {
  823. $query = query(sprintf("SELECT `num_files`, `files` FROM ``posts_%s`` WHERE `id` = " .
  824. (int)$ban['post']['id'], $board['uri']));
  825. if ($_post = $query->fetch(PDO::FETCH_ASSOC)) {
  826. $_post['files'] = $_post['files'] ? json_decode($_post['files']) : array();
  827. $ban['post'] = array_merge($ban['post'], $_post);
  828. } else {
  829. $ban['post']['files'] = array(array());
  830. $ban['post']['files'][0]['file'] = 'deleted';
  831. $ban['post']['files'][0]['thumb'] = false;
  832. $ban['post']['num_files'] = 1;
  833. }
  834. } else {
  835. $ban['post']['files'] = array(array());
  836. $ban['post']['files'][0]['file'] = 'deleted';
  837. $ban['post']['files'][0]['thumb'] = false;
  838. $ban['post']['num_files'] = 1;
  839. }
  840. if ($ban['post']['thread']) {
  841. $ban['post'] = new Post($ban['post']);
  842. } else {
  843. $ban['post'] = new Thread($ban['post'], null, false, false);
  844. }
  845. }
  846. }
  847. mod_page(_('Ban appeals'), 'mod/ban_appeals.html', array(
  848. 'ban_appeals' => $ban_appeals,
  849. 'token' => make_secure_link_token('ban-appeals')
  850. ));
  851. }
  852. function mod_lock($board, $unlock, $post) {
  853. global $config;
  854. if (!openBoard($board))
  855. error($config['error']['noboard']);
  856. if (!hasPermission($config['mod']['lock'], $board))
  857. error($config['error']['noaccess']);
  858. $query = prepare(sprintf('UPDATE ``posts_%s`` SET `locked` = :locked WHERE `id` = :id AND `thread` IS NULL', $board));
  859. $query->bindValue(':id', $post);
  860. $query->bindValue(':locked', $unlock ? 0 : 1);
  861. $query->execute() or error(db_error($query));
  862. if ($query->rowCount()) {
  863. modLog(($unlock ? 'Unlocked' : 'Locked') . " thread #{$post}");
  864. buildThread($post);
  865. buildIndex();
  866. }
  867. if ($config['mod']['dismiss_reports_on_lock']) {
  868. $query = prepare('DELETE FROM ``reports`` WHERE `board` = :board AND `post` = :id');
  869. $query->bindValue(':board', $board);
  870. $query->bindValue(':id', $post);
  871. $query->execute() or error(db_error($query));
  872. }
  873. header('Location: ?/' . sprintf($config['board_path'], $board) . $config['file_index'], true, $config['redirect_http']);
  874. if ($unlock)
  875. event('unlock', $post);
  876. else
  877. event('lock', $post);
  878. }
  879. function mod_sticky($board, $unsticky, $post) {
  880. global $config;
  881. if (!openBoard($board))
  882. error($config['error']['noboard']);
  883. if (!hasPermission($config['mod']['sticky'], $board))
  884. error($config['error']['noaccess']);
  885. $query = prepare(sprintf('UPDATE ``posts_%s`` SET `sticky` = :sticky WHERE `id` = :id AND `thread` IS NULL', $board));
  886. $query->bindValue(':id', $post);
  887. $query->bindValue(':sticky', $unsticky ? 0 : 1);
  888. $query->execute() or error(db_error($query));
  889. if ($query->rowCount()) {
  890. modLog(($unsticky ? 'Unstickied' : 'Stickied') . " thread #{$post}");
  891. buildThread($post);
  892. buildIndex();
  893. }
  894. header('Location: ?/' . sprintf($config['board_path'], $board) . $config['file_index'], true, $config['redirect_http']);
  895. }
  896. function mod_cycle($board, $uncycle, $post) {
  897. global $config;
  898. if (!openBoard($board))
  899. error($config['error']['noboard']);
  900. if (!hasPermission($config['mod']['cycle'], $board))
  901. error($config['error']['noaccess']);
  902. $query = prepare(sprintf('UPDATE ``posts_%s`` SET `cycle` = :cycle WHERE `id` = :id AND `thread` IS NULL', $board));
  903. $query->bindValue(':id', $post);
  904. $query->bindValue(':cycle', $uncycle ? 0 : 1);
  905. $query->execute() or error(db_error($query));
  906. if ($query->rowCount()) {
  907. modLog(($uncycle ? 'Made not cyclical' : 'Made cyclical') . " thread #{$post}");
  908. buildThread($post);
  909. buildIndex();
  910. }
  911. header('Location: ?/' . sprintf($config['board_path'], $board) . $config['file_index'], true, $config['redirect_http']);
  912. }
  913. function mod_bumplock($board, $unbumplock, $post) {
  914. global $config;
  915. if (!openBoard($board))
  916. error($config['error']['noboard']);
  917. if (!hasPermission($config['mod']['bumplock'], $board))
  918. error($config['error']['noaccess']);
  919. $query = prepare(sprintf('UPDATE ``posts_%s`` SET `sage` = :bumplock WHERE `id` = :id AND `thread` IS NULL', $board));
  920. $query->bindValue(':id', $post);
  921. $query->bindValue(':bumplock', $unbumplock ? 0 : 1);
  922. $query->execute() or error(db_error($query));
  923. if ($query->rowCount()) {
  924. modLog(($unbumplock ? 'Unbumplocked' : 'Bumplocked') . " thread #{$post}");
  925. buildThread($post);
  926. buildIndex();
  927. }
  928. header('Location: ?/' . sprintf($config['board_path'], $board) . $config['file_index'], true, $config['redirect_http']);
  929. }
  930. function mod_move_reply($originBoard, $postID) {
  931. global $board, $config, $mod;
  932. if (!openBoard($originBoard))
  933. error($config['error']['noboard']);
  934. if (!hasPermission($config['mod']['move'], $originBoard))
  935. error($config['error']['noaccess']);
  936. $query = prepare(sprintf('SELECT * FROM ``posts_%s`` WHERE `id` = :id', $originBoard));
  937. $query->bindValue(':id', $postID);
  938. $query->execute() or error(db_error($query));
  939. if (!$post = $query->fetch(PDO::FETCH_ASSOC))
  940. error($config['error']['404']);
  941. if (isset($_POST['board'])) {
  942. $targetBoard = $_POST['board'];
  943. if ($_POST['target_thread']) {
  944. $query = prepare(sprintf('SELECT * FROM ``posts_%s`` WHERE `id` = :id', $targetBoard));
  945. $query->bindValue(':id', $_POST['target_thread']);
  946. $query->execute() or error(db_error($query)); // If it fails, thread probably does not exist
  947. $post['op'] = false;
  948. $post['thread'] = $_POST['target_thread'];
  949. }
  950. else {
  951. $post['op'] = true;
  952. }
  953. if ($post['files']) {
  954. $post['files'] = json_decode($post['files'], TRUE);
  955. $post['has_file'] = true;
  956. foreach ($post['files'] as $i => &$file) {
  957. $file['file_path'] = sprintf($config['board_path'], $board['uri']) . $config['dir']['img'] . $file['file'];
  958. $file['thumb_path'] = sprintf($config['board_path'], $board['uri']) . $config['dir']['thumb'] . $file['thumb'];
  959. }
  960. } else {
  961. $post['has_file'] = false;
  962. }
  963. // allow thread to keep its same traits (stickied, locked, etc.)
  964. $post['mod'] = true;
  965. if (!openBoard($targetBoard))
  966. error($config['error']['noboard']);
  967. // create the new post
  968. $newID = post($post);
  969. if ($post['has_file']) {
  970. foreach ($post['files'] as $i => &$file) {
  971. // move the image
  972. rename($file['file_path'], sprintf($config['board_path'], $board['uri']) . $config['dir']['img'] . $file['file']);
  973. if ($file['thumb'] != 'spoiler') { //trying to move/copy the spoiler thumb raises an error
  974. rename($file['thumb_path'], sprintf($config['board_path'], $board['uri']) . $config['dir']['thumb'] . $file['thumb']);
  975. }
  976. }
  977. }
  978. // build index
  979. buildIndex();
  980. // build new thread
  981. buildThread($post['op'] ? $newID : $post['thread']);
  982. // trigger themes
  983. rebuildThemes('post', $targetBoard);
  984. // mod log
  985. modLog("Moved post #${postID} to " . sprintf($config['board_abbreviation'], $targetBoard) . " (#${newID})", $originBoard);
  986. // return to original board
  987. openBoard($originBoard);
  988. // delete original post
  989. deletePost($postID);
  990. buildIndex();
  991. // open target board for redirect
  992. openBoard($targetBoard);
  993. // Find new thread on our target board
  994. $query = prepare(sprintf('SELECT thread, id FROM ``posts_%s`` WHERE `id` = :id', $targetBoard));
  995. $query->bindValue(':id', $newID);
  996. $query->execute() or error(db_error($query));
  997. $post = $query->fetch(PDO::FETCH_ASSOC);
  998. // redirect
  999. header('Location: ?/' . sprintf($config['board_path'], $board['uri']) . $config['dir']['res'] . link_for($post) . '#' . $newID, true, $config['redirect_http']);
  1000. }
  1001. else {
  1002. $boards = listBoards();
  1003. $security_token = make_secure_link_token($originBoard . '/move_reply/' . $postID);
  1004. mod_page(_('Move reply'), 'mod/move_reply.html', array('post' => $postID, 'board' => $originBoard, 'boards' => $boards, 'token' => $security_token));
  1005. }
  1006. }
  1007. function mod_move($originBoard, $postID) {
  1008. global $board, $config, $mod, $pdo;
  1009. if (!openBoard($originBoard))
  1010. error($config['error']['noboard']);
  1011. if (!hasPermission($config['mod']['move'], $originBoard))
  1012. error($config['error']['noaccess']);
  1013. $query = prepare(sprintf('SELECT * FROM ``posts_%s`` WHERE `id` = :id AND `thread` IS NULL', $originBoard));
  1014. $query->bindValue(':id', $postID);
  1015. $query->execute() or error(db_error($query));
  1016. if (!$post = $query->fetch(PDO::FETCH_ASSOC))
  1017. error($config['error']['404']);
  1018. if (isset($_POST['board'])) {
  1019. $targetBoard = $_POST['board'];
  1020. $shadow = isset($_POST['shadow']);
  1021. if ($targetBoard === $originBoard)
  1022. error(_('Target and source board are the same.'));
  1023. // copy() if leaving a shadow thread behind; else, rename().
  1024. $clone = $shadow ? 'copy' : 'rename';
  1025. // indicate that the post is a thread
  1026. $post['op'] = true;
  1027. if ($post['files']) {
  1028. $post['files'] = json_decode($post['files'], TRUE);
  1029. $post['has_file'] = true;
  1030. foreach ($post['files'] as $i => &$file) {
  1031. if ($file['file'] === 'deleted')
  1032. continue;
  1033. $file['file_path'] = sprintf($config['board_path'], $board['uri']) . $config['dir']['img'] . $file['file'];
  1034. $file['thumb_path'] = sprintf($config['board_path'], $board['uri']) . $config['dir']['thumb'] . $file['thumb'];
  1035. }
  1036. } else {
  1037. $post['has_file'] = false;
  1038. }
  1039. // allow thread to keep its same traits (stickied, locked, etc.)
  1040. $post['mod'] = true;
  1041. if (!openBoard($targetBoard))
  1042. error($config['error']['noboard']);
  1043. // create the new thread
  1044. $newID = post($post);
  1045. $op = $post;
  1046. $op['id'] = $newID;
  1047. if ($post['has_file']) {
  1048. // copy image
  1049. foreach ($post['files'] as $i => &$file) {
  1050. if ($file['file'] !== 'deleted')
  1051. $clone($file['file_path'], sprintf($config['board_path'], $board['uri']) . $config['dir']['img'] . $file['file']);
  1052. if (isset($file['thumb']) && !in_array($file['thumb'], array('spoiler', 'deleted', 'file')))
  1053. $clone($file['thumb_path'], sprintf($config['board_path'], $board['uri']) . $config['dir']['thumb'] . $file['thumb']);
  1054. }
  1055. }
  1056. // go back to the original board to fetch replies
  1057. openBoard($originBoard);
  1058. $query = prepare(sprintf('SELECT * FROM ``posts_%s`` WHERE `thread` = :id ORDER BY `id`', $originBoard));
  1059. $query->bindValue(':id', $postID, PDO::PARAM_INT);
  1060. $query->execute() or error(db_error($query));
  1061. $replies = array();
  1062. while ($post = $query->fetch(PDO::FETCH_ASSOC)) {
  1063. $post['mod'] = true;
  1064. $post['thread'] = $newID;
  1065. if ($post['files']) {
  1066. $post['files'] = json_decode($post['files'], TRUE);
  1067. $post['has_file'] = true;
  1068. foreach ($post['files'] as $i => &$file) {
  1069. $file['file_path'] = sprintf($config['board_path'], $board['uri']) . $config['dir']['img'] . $file['file'];
  1070. $file['thumb_path'] = sprintf($config['board_path'], $board['uri']) . $config['dir']['thumb'] . $file['thumb'];
  1071. }
  1072. } else {
  1073. $post['has_file'] = false;
  1074. }
  1075. $replies[] = $post;
  1076. }
  1077. $newIDs = array($postID => $newID);
  1078. openBoard($targetBoard);
  1079. foreach ($replies as &$post) {
  1080. $query = prepare('SELECT `target` FROM ``cites`` WHERE `target_board` = :board AND `board` = :board AND `post` = :post');
  1081. $query->bindValue(':board', $originBoard);
  1082. $query->bindValue(':post', $post['id'], PDO::PARAM_INT);
  1083. $query->execute() or error(db_error($query));
  1084. // correct >>X links
  1085. while ($cite = $query->fetch(PDO::FETCH_ASSOC)) {
  1086. if (isset($newIDs[$cite['target']])) {
  1087. $post['body_nomarkup'] = preg_replace(
  1088. '/(>>(>\/' . preg_quote($originBoard, '/') . '\/)?)' . preg_quote($cite['target'], '/') . '/',
  1089. '>>' . $newIDs[$cite['target']],
  1090. $post['body_nomarkup']);
  1091. $post['body'] = $post['body_nomarkup'];
  1092. }
  1093. }
  1094. $post['body'] = $post['body_nomarkup'];
  1095. $post['op'] = false;
  1096. $post['tracked_cites'] = markup($post['body'], true);
  1097. if ($post['has_file']) {
  1098. // copy image
  1099. foreach ($post['files'] as $i => &$file) {
  1100. $clone($file['file_path'], sprintf($config['board_path'], $board['uri']) . $config['dir']['img'] . $file['file']);
  1101. $clone($file['thumb_path'], sprintf($config['board_path'], $board['uri']) . $config['dir']['thumb'] . $file['thumb']);
  1102. }
  1103. }
  1104. // insert reply
  1105. $newIDs[$post['id']] = $newPostID = post($post);
  1106. if (!empty($post['tracked_cites'])) {
  1107. $insert_rows = array();
  1108. foreach ($post['tracked_cites'] as $cite) {
  1109. $insert_rows[] = '(' .
  1110. $pdo->quote($board['uri']) . ', ' . $newPostID . ', ' .
  1111. $pdo->quote($cite[0]) . ', ' . (int)$cite[1] . ')';
  1112. }
  1113. query('INSERT INTO ``cites`` VALUES ' . implode(', ', $insert_rows)) or error(db_error());
  1114. }
  1115. }
  1116. modLog("Moved thread #${postID} to " . sprintf($config['board_abbreviation'], $targetBoard) . " (#${newID})", $originBoard);
  1117. // build new thread
  1118. buildThread($newID);
  1119. clean();
  1120. buildIndex();
  1121. // trigger themes
  1122. rebuildThemes('post', $targetBoard);
  1123. $newboard = $board;
  1124. // return to original board
  1125. openBoard($originBoard);
  1126. if ($shadow) {
  1127. // lock old thread
  1128. $query = prepare(sprintf('UPDATE ``posts_%s`` SET `locked` = 1 WHERE `id` = :id', $originBoard));
  1129. $query->bindValue(':id', $postID, PDO::PARAM_INT);
  1130. $query->execute() or error(db_error($query));
  1131. // leave a reply, linking to the new thread
  1132. $spost = array(
  1133. 'mod' => true,
  1134. 'subject' => '',
  1135. 'email' => '',
  1136. 'name' => (!$config['mod']['shadow_name'] ? $config['anonymous'] : $config['mod']['shadow_name']),
  1137. 'capcode' => $config['mod']['shadow_capcode'],
  1138. 'trip' => '',
  1139. 'password' => '',
  1140. 'has_file' => false,
  1141. // attach to original thread
  1142. 'thread' => $postID,
  1143. 'op' => false
  1144. );
  1145. $spost['body'] = $spost['body_nomarkup'] = sprintf($config['mod']['shadow_mesage'], '>>>/' . $targetBoard . '/' . $newID);
  1146. markup($spost['body']);
  1147. $botID = post($spost);
  1148. buildThread($postID);
  1149. buildIndex();
  1150. header('Location: ?/' . sprintf($config['board_path'], $newboard['uri']) . $config['dir']['res'] . link_for($op, false, $newboard) .
  1151. '#' . $botID, true, $config['redirect_http']);
  1152. } else {
  1153. deletePost($postID);
  1154. buildIndex();
  1155. openBoard($targetBoard);
  1156. header('Location: ?/' . sprintf($config['board_path'], $newboard['uri']) . $config['dir']['res'] . link_for($op, false, $newboard), true, $config['redirect_http']);
  1157. }
  1158. }
  1159. $boards = listBoards();
  1160. if (count($boards) <= 1)
  1161. error(_('Impossible to move thread; there is only one board.'));
  1162. $security_token = make_secure_link_token($originBoard . '/move/' . $postID);
  1163. mod_page(_('Move thread'), 'mod/move.html', array('post' => $postID, 'board' => $originBoard, 'boards' => $boards, 'token' => $security_token));
  1164. }
  1165. function mod_merge($originBoard, $postID) {
  1166. global $board, $config, $mod, $pdo;
  1167. if (!openBoard($originBoard))
  1168. error($config['error']['noboard']);
  1169. if (!hasPermission($config['mod']['merge'], $originBoard))
  1170. error($config['error']['noaccess']);
  1171. $query = prepare(sprintf('SELECT * FROM ``posts_%s`` WHERE `id` = :id AND `thread` IS NULL', $originBoard));
  1172. $query->bindValue(':id', $postID);
  1173. $query->execute() or error(db_error($query));
  1174. if (!$post = $query->fetch(PDO::FETCH_ASSOC))
  1175. error($config['error']['404']);
  1176. $sourceOp = "";
  1177. if ($post['thread']){
  1178. $sourceOp = $post['thread'];
  1179. }
  1180. else{
  1181. $sourceOp = $post['id'];
  1182. }
  1183. $newpost = "";
  1184. $boards = listBoards();
  1185. if (isset($_POST['board'])) {
  1186. $targetBoard = $_POST['board'];
  1187. $shadow = isset($_POST['shadow']);
  1188. $targetOp = "";
  1189. if ($_POST['target_thread']) {
  1190. $query = prepare(sprintf('SELECT * FROM ``posts_%s`` WHERE `id` = :id', $targetBoard));
  1191. $query->bindValue(':id', $_POST['target_thread']);
  1192. $query->execute() or error(db_error($query)); // If it fails, thread probably does not exist
  1193. if (!$newpost = $query->fetch(PDO::FETCH_ASSOC)){
  1194. error($config['error']['404']);
  1195. }
  1196. else
  1197. {
  1198. if ($newpost['thread']){
  1199. $targetOp = $newpost['thread'];
  1200. }
  1201. else{
  1202. $targetOp = $newpost['id'];
  1203. }
  1204. }
  1205. }
  1206. if ($targetBoard === $originBoard){
  1207. // Just update the thread id for all posts in the original thread to new op
  1208. $query = prepare(sprintf('UPDATE ``posts_%s`` SET `thread` = :newthread WHERE `id` = :oldthread OR `thread` = :oldthread', $originBoard));
  1209. $query->bindValue(':newthread', $targetOp, PDO::PARAM_INT);
  1210. $query->bindValue(':oldthread', $sourceOp, PDO::PARAM_INT);
  1211. $query->execute() or error(db_error($query));
  1212. // build index
  1213. // Delete thread HTML page
  1214. file_unlink($board['dir'] . $config['dir']['res'] . link_for($post) );
  1215. file_unlink($board['dir'] . $config['dir']['res'] . link_for($post, true) ); // noko50
  1216. file_unlink($board['dir'] . $config['dir']['res'] . sprintf('%d.json', $post['id']));
  1217. //deletePost($postID);
  1218. //modLog("Deleted post #{$postID}");
  1219. buildIndex();
  1220. // build new thread
  1221. buildThread($targetOp);
  1222. // trigger themes
  1223. rebuildThemes('post', $targetBoard);
  1224. modLog("Merged thread with #${sourceOp} to " . sprintf($config['board_abbreviation'], $targetBoard) . " (#${targetOp})", $originBoard);
  1225. // redirect
  1226. header('Location: ?/' . sprintf($config['board_path'], $board['uri']) . $config['dir']['res'] . link_for($newpost) . '#' . $targetOp, true, $config['redirect_http']);
  1227. }
  1228. else {
  1229. // Move thread to new board without shadow thread and then update the thread id for all posts in that thread to new op
  1230. // indicate that the post is a thread
  1231. if (count($boards) <= 1)
  1232. error(_('Impossible to merge thread to different board; there is only one board.'));
  1233. $post['op'] = true;
  1234. if ($post['files']) {
  1235. $post['files'] = json_decode($post['files'], TRUE);
  1236. $post['has_file'] = true;
  1237. foreach ($post['files'] as $i => &$file) {
  1238. if ($file['file'] === 'deleted')
  1239. continue;
  1240. $file['file_path'] = sprintf($config['board_path'], $board['uri']) . $config['dir']['img'] . $file['file'];
  1241. $file['thumb_path'] = sprintf($config['board_path'], $board['uri']) . $config['dir']['thumb'] . $file['thumb'];
  1242. }
  1243. } else {
  1244. $post['has_file'] = false;
  1245. }
  1246. // allow thread to keep its same traits (stickied, locked, etc.)
  1247. $post['mod'] = true;
  1248. if (!openBoard($targetBoard))
  1249. error($config['error']['noboard']);
  1250. // create the new thread
  1251. $newID = post($post);
  1252. $op = $post;
  1253. $op['id'] = $newID;
  1254. $clone = $shadow ? 'copy' : 'rename';
  1255. if ($post['has_file']) {
  1256. // copy image
  1257. foreach ($post['files'] as $i => &$file) {
  1258. if ($file['file'] !== 'deleted')
  1259. $clone($file['file_path'], sprintf($config['board_path'], $board['uri']) . $config['dir']['img'] . $file['file']);
  1260. if (isset($file['thumb']) && !in_array($file['thumb'], array('spoiler', 'deleted', 'file')))
  1261. $clone($file['thumb_path'], sprintf($config['board_path'], $board['uri']) . $config['dir']['thumb'] . $file['thumb']);
  1262. }
  1263. }
  1264. // go back to the original board to fetch replies
  1265. openBoard($originBoard);
  1266. $query = prepare(sprintf('SELECT * FROM ``posts_%s`` WHERE `thread` = :id ORDER BY `id`', $originBoard));
  1267. $query->bindValue(':id', $postID, PDO::PARAM_INT);
  1268. $query->execute() or error(db_error($query));
  1269. $replies = array();
  1270. while ($post = $query->fetch(PDO::FETCH_ASSOC)) {
  1271. $post['mod'] = true;
  1272. $post['thread'] = $newID;
  1273. if ($post['files']) {
  1274. $post['files'] = json_decode($post['files'], TRUE);
  1275. $post['has_file'] = true;
  1276. foreach ($post['files'] as $i => &$file) {
  1277. $file['file_path'] = sprintf($config['board_path'], $board['uri']) . $config['dir']['img'] . $file['file'];
  1278. $file['thumb_path'] = sprintf($config['board_path'], $board['uri']) . $config['dir']['thumb'] . $file['thumb'];
  1279. }
  1280. } else {
  1281. $post['has_file'] = false;
  1282. }
  1283. $replies[] = $post;
  1284. }
  1285. $newIDs = array($postID => $newID);
  1286. openBoard($targetBoard);
  1287. foreach ($replies as &$post) {
  1288. $query = prepare('SELECT `target` FROM ``cites`` WHERE `target_board` = :board AND `board` = :board AND `post` = :post');
  1289. $query->bindValue(':board', $originBoard);
  1290. $query->bindValue(':post', $post['id'], PDO::PARAM_INT);
  1291. $query->execute() or error(db_error($query));
  1292. // correct >>X links
  1293. while ($cite = $query->fetch(PDO::FETCH_ASSOC)) {
  1294. if (isset($newIDs[$cite['target']])) {
  1295. $post['body_nomarkup'] = preg_replace(
  1296. '/(>>(>\/' . preg_quote($originBoard, '/') . '\/)?)' . preg_quote($cite['target'], '/') . '/',
  1297. '>>' . $newIDs[$cite['target']],
  1298. $post['body_nomarkup']);
  1299. $post['body'] = $post['body_nomarkup'];
  1300. }
  1301. }
  1302. $post['body'] = $post['body_nomarkup'];
  1303. $post['op'] = false;
  1304. $post['tracked_cites'] = markup($post['body'], true);
  1305. if ($post['has_file']) {
  1306. // copy image
  1307. foreach ($post['files'] as $i => &$file) {
  1308. $clone($file['file_path'], sprintf($config['board_path'], $board['uri']) . $config['dir']['img'] . $file['file']);
  1309. $clone($file['thumb_path'], sprintf($config['board_path'], $board['uri']) . $config['dir']['thumb'] . $file['thumb']);
  1310. }
  1311. }
  1312. // insert reply
  1313. $newIDs[$post['id']] = $newPostID = post($post);
  1314. if (!empty($post['tracked_cites'])) {
  1315. $insert_rows = array();
  1316. foreach ($post['tracked_cites'] as $cite) {
  1317. $insert_rows[] = '(' .
  1318. $pdo->quote($board['uri']) . ', ' . $newPostID . ', ' .
  1319. $pdo->quote($cite[0]) . ', ' . (int)$cite[1] . ')';
  1320. }
  1321. query('INSERT INTO ``cites`` VALUES ' . implode(', ', $insert_rows)) or error(db_error());
  1322. }
  1323. }
  1324. modLog("Moved thread #${postID} to " . sprintf($config['board_abbreviation'], $targetBoard) . " (#${newID})", $originBoard);
  1325. // build new thread
  1326. buildThread($newID);
  1327. clean();
  1328. buildIndex();
  1329. // trigger themes
  1330. rebuildThemes('post', $targetBoard);
  1331. $newboard = $board;
  1332. // return to original board
  1333. openBoard($originBoard);
  1334. deletePost($postID);
  1335. modLog("Deleted post #{$postID}");
  1336. buildIndex();
  1337. openBoard($targetBoard);
  1338. // Just update the thread id for all posts in the original thread to new op
  1339. $query = prepare(sprintf('UPDATE ``posts_%s`` SET `thread` = :newthread WHERE `id` = :oldthread OR `thread` = :oldthread', $targetBoard));
  1340. $query->bindValue(':newthread', $targetOp, PDO::PARAM_INT);
  1341. $query->bindValue(':oldthread', $newID, PDO::PARAM_INT);
  1342. $query->execute() or error(db_error($query));
  1343. // build index
  1344. buildIndex();
  1345. // build new thread
  1346. buildThread($targetOp);
  1347. // trigger themes
  1348. rebuildThemes('post', $targetBoard);
  1349. modLog("Merged thread with #${newID} to " . sprintf($config['board_abbreviation'], $targetBoard) . " (#${targetOp})", $targetBoard);
  1350. // redirect
  1351. header('Location: ?/' . sprintf($config['board_path'], $board['uri']) . $config['dir']['res'] . link_for($newpost) . '#' . $targetOp, true, $config['redirect_http']);
  1352. }
  1353. }
  1354. $security_token = make_secure_link_token($originBoard . '/merge/' . $postID);
  1355. mod_page(_('Merge thread'), 'mod/merge.html', array('post' => $postID, 'board' => $originBoard, 'boards' => $boards, 'token' => $security_token));
  1356. }
  1357. function mod_ban_post($board, $delete, $post, $token = false) {
  1358. global $config, $mod;
  1359. if (!openBoard($board))
  1360. error($config['error']['noboard']);
  1361. if (!hasPermission($config['mod']['delete'], $board))
  1362. error($config['error']['noaccess']);
  1363. $security_token = make_secure_link_token($board . '/ban/' . $post);
  1364. $query = prepare(sprintf('SELECT ' . ($config['ban_show_post'] ? '*' : '`ip`, `thread`') .
  1365. ' FROM ``posts_%s`` WHERE `id` = :id', $board));
  1366. $query->bindValue(':id', $post);
  1367. $query->execute() or error(db_error($query));
  1368. if (!$_post = $query->fetch(PDO::FETCH_ASSOC))
  1369. error($config['error']['404']);
  1370. $thread = $_post['thread'];
  1371. $ip = $_post['ip'];
  1372. if (isset($_POST['new_ban'], $_POST['reason'], $_POST['length'], $_POST['board'])) {
  1373. require_once 'inc/mod/ban.php';
  1374. if (isset($_POST['ip']))
  1375. $ip = $_POST['ip'];
  1376. Bans::new_ban($_POST['ip'], $_POST['reason'], $_POST['length'], $_POST['board'] == '*' ? false : $_POST['board'],
  1377. false, $config['ban_show_post'] ? $_post : false);
  1378. if (isset($_POST['public_message'], $_POST['message'])) {
  1379. // public ban message
  1380. $length_english = Bans::parse_time($_POST['length']) ? 'for ' . until(Bans::parse_time($_POST['length'])) : 'permanently';
  1381. $_POST['message'] = preg_replace('/[\r\n]/', '', $_POST['message']);
  1382. $_POST['message'] = str_replace('%length%', $length_english, $_POST['message']);
  1383. $_POST['message'] = str_replace('%LENGTH%', strtoupper($length_english), $_POST['message']);
  1384. $query = prepare(sprintf('UPDATE ``posts_%s`` SET `body_nomarkup` = CONCAT(`body_nomarkup`, :body_nomarkup) WHERE `id` = :id', $board));
  1385. $query->bindValue(':id', $post);
  1386. $query->bindValue(':body_nomarkup', sprintf("\n<tinyboard ban message>%s</tinyboard>", utf8tohtml($_POST['message'])));
  1387. $query->execute() or error(db_error($query));
  1388. rebuildPost($post);
  1389. modLog("Attached a public ban message to post #{$post}: " . utf8tohtml($_POST['message']));
  1390. buildThread($thread ? $thread : $post);
  1391. buildIndex();
  1392. } elseif (isset($_POST['delete']) && (int) $_POST['delete']) {
  1393. // Delete post
  1394. if ($config['autotagging']){
  1395. $query = prepare(sprintf("SELECT * FROM ``posts_%s`` WHERE id = :id", $board));
  1396. $query->bindValue(':id', $post );
  1397. $query->execute() or error(db_error($query));
  1398. $ip = "";
  1399. $time = "";
  1400. $filename = "";
  1401. $filehash = "";
  1402. $subject = "";
  1403. $name = "";
  1404. $body = "";
  1405. while ($mypost = $query->fetch(PDO::FETCH_ASSOC)) {
  1406. $time = $mypost["time"];
  1407. $ip = $mypost["ip"];
  1408. $body = $mypost["body_nomarkup"];
  1409. $name = $mypost["name"];
  1410. $subject = $mypost["subject"];
  1411. $filehash = $mypost["filehash"];
  1412. $mypost['files'] = $mypost['files'] ? json_decode($mypost['files']) : array();
  1413. // For each file append file name
  1414. for ($file_count = 0; $file_count < $mypost["num_files"];$file_count++){
  1415. $filename .= $mypost['files'][$file_count]->name . "\r\n";
  1416. }
  1417. }
  1418. if ($time !== ''){
  1419. $dt = new DateTime("@$time");
  1420. $autotag = "";
  1421. $autotag .= $name . " " . $subject . " " . $dt->format('Y-m-d H:i:s') . " No.". $post . "\r\n";
  1422. $autotag .= "/${board}/" . " " . $filehash . " " . $filename ."\r\n";
  1423. $autotag .= $body . "\r\n";
  1424. $autotag = escape_markup_modifiers($autotag);
  1425. markup($autotag);
  1426. $query = prepare('INSERT INTO ``ip_notes`` VALUES (NULL, :ip, :mod, :time, :body)');
  1427. $query->bindValue(':ip', $ip);
  1428. $query->bindValue(':mod', $mod['id']);
  1429. $query->bindValue(':time', time());
  1430. $query->bindValue(':body', $autotag);
  1431. $query->execute() or error(db_error($query));
  1432. modLog("Added a note for <a href=\"?/IP/{$ip}\">{$ip}</a>");
  1433. }
  1434. }
  1435. deletePost($post);
  1436. modLog("Deleted post #{$post}");
  1437. // Rebuild board
  1438. buildIndex();
  1439. // Rebuild themes
  1440. rebuildThemes('post-delete', $board);
  1441. }
  1442. if(isset($_POST['thread'])) {
  1443. // Redirect to thread
  1444. header('Location: ?/' . sprintf($config['board_path'], $board) . $config['dir']['res'] . str_replace('%d', $_POST['thread'], $config['file_page']), true, $config['redirect_http']);
  1445. } else {
  1446. // Redirect to board index.
  1447. header('Location: ?/' . sprintf($config['board_path'], $board) . $config['file_index'], true, $config['redirect_http']);
  1448. }
  1449. }
  1450. $args = array(
  1451. 'ip' => $ip,
  1452. 'hide_ip' => !hasPermission($config['mod']['show_ip'], $board),
  1453. 'post' => $post,
  1454. 'board' => $board,
  1455. 'delete' => (bool)$delete,
  1456. 'boards' => listBoards(),
  1457. 'token' => $security_token
  1458. );
  1459. if($_GET['thread']) {
  1460. $args['thread'] = $_GET['thread'];
  1461. }
  1462. mod_page(_('New ban'), 'mod/ban_form.html', $args);
  1463. }
  1464. function mod_warning_post($board,$post, $token = false) {
  1465. global $config, $mod;
  1466. if (!openBoard($board))
  1467. error($config['error']['noboard']);
  1468. $security_token = make_secure_link_token($board . '/warning/' . $post);
  1469. $query = prepare(sprintf('SELECT ' . ('`ip`, `thread`') .
  1470. ' FROM ``posts_%s`` WHERE `id` = :id', $board));
  1471. $query->bindValue(':id', $post);
  1472. $query->execute() or error(db_error($query));
  1473. if (!$_post = $query->fetch(PDO::FETCH_ASSOC))
  1474. error($config['error']['404']);
  1475. $thread = $_post['thread'];
  1476. $ip = $_post['ip'];
  1477. if (isset($_POST['new_warning'])) {
  1478. if (isset($_POST['ip']))
  1479. $ip = $_POST['ip'];
  1480. if (isset($_POST['public_message'], $_POST['message'])) {
  1481. // public warning message
  1482. $_POST['message'] = preg_replace('/[\r\n]/', '', $_POST['message']);
  1483. $query = prepare(sprintf('UPDATE ``posts_%s`` SET `body_nomarkup` = CONCAT(`body_nomarkup`, :body_nomarkup) WHERE `id` = :id', $board));
  1484. $query->bindValue(':id', $post);
  1485. $query->bindValue(':body_nomarkup', sprintf("\n<tinyboard warning message>%s</tinyboard>", utf8tohtml($_POST['message'])));
  1486. $query->execute() or error(db_error($query));
  1487. rebuildPost($post);
  1488. modLog("Attached a public warning message to post #{$post}: " . utf8tohtml($_POST['message']));
  1489. buildThread($thread ? $thread : $post);
  1490. buildIndex();
  1491. if ($config['autotagging']){
  1492. $query = prepare(sprintf("SELECT * FROM ``posts_%s`` WHERE id = :id", $board));
  1493. $query->bindValue(':id', $post );
  1494. $query->execute() or error(db_error($query));
  1495. $ip = "";
  1496. $time = "";
  1497. $filename = "";
  1498. $filehash = "";
  1499. $subject = "";
  1500. $name = "";
  1501. $body = "";
  1502. while ($mypost = $query->fetch(PDO::FETCH_ASSOC)) {
  1503. $time = $mypost["time"];
  1504. $ip = $mypost["ip"];
  1505. $body = $mypost["body_nomarkup"];
  1506. $name = $mypost["name"];
  1507. $subject = $mypost["subject"];
  1508. $filehash = $mypost["filehash"];
  1509. $mypost['files'] = $mypost['files'] ? json_decode($mypost['files']) : array();
  1510. // For each file append file name
  1511. for ($file_count = 0; $file_count < $mypost["num_files"];$file_count++){
  1512. $filename .= $mypost['files'][$file_count]->name . "\r\n";
  1513. }
  1514. }
  1515. if ($time !== ''){
  1516. $dt = new DateTime("@$time");
  1517. $autotag = "Post warned\r\n";
  1518. $autotag .= $name . " " . $subject . " " . $dt->format('Y-m-d H:i:s') . " No.". $post . "\r\n";
  1519. $autotag .= "/${board}/" . " " . $filehash . " " . $filename ."\r\n";
  1520. $autotag .= $body . "\r\n";
  1521. $autotag = escape_markup_modifiers($autotag);
  1522. markup($autotag);
  1523. $query = prepare('INSERT INTO ``ip_notes`` VALUES (NULL, :ip, :mod, :time, :body)');
  1524. $query->bindValue(':ip', $ip);
  1525. $query->bindValue(':mod', $mod['id']);
  1526. $query->bindValue(':time', time());
  1527. $query->bindValue(':body', $autotag);
  1528. $query->execute() or error(db_error($query));
  1529. modLog("Added a note for <a href=\"?/IP/{$ip}\">{$ip}</a>");
  1530. }
  1531. }
  1532. }
  1533. if(isset($_POST['thread'])) {
  1534. // Redirect to thread
  1535. header('Location: ?/' . sprintf($config['board_path'], $board) . $config['dir']['res'] . str_replace('%d', $_POST['thread'], $config['file_page']), true, $config['redirect_http']);
  1536. } else {
  1537. // Redirect to board index.
  1538. header('Location: ?/' . sprintf($config['board_path'], $board) . $config['file_index'], true, $config['redirect_http']);
  1539. }
  1540. }
  1541. $args = array(
  1542. 'ip' => $ip,
  1543. 'hide_ip' => !hasPermission($config['mod']['show_ip'], $board),
  1544. 'post' => $post,
  1545. 'board' => $board,
  1546. 'token' => $security_token
  1547. );
  1548. if($_GET['thread']) {
  1549. $args['thread'] = $_GET['thread'];
  1550. }
  1551. mod_page(_('New warning'), 'mod/warning_form.html', $args);
  1552. }
  1553. function mod_edit_post($board, $edit_raw_html, $postID) {
  1554. global $config, $mod;
  1555. if (!openBoard($board))
  1556. error($config['error']['noboard']);
  1557. if (!hasPermission($config['mod']['editpost'], $board))
  1558. error($config['error']['noaccess']);
  1559. if ($edit_raw_html && !hasPermission($config['mod']['rawhtml'], $board))
  1560. error($config['error']['noaccess']);
  1561. $security_token = make_secure_link_token($board . '/edit' . ($edit_raw_html ? '_raw' : '') . '/' . $postID);
  1562. $query = prepare(sprintf('SELECT * FROM ``posts_%s`` WHERE `id` = :id', $board));
  1563. $query->bindValue(':id', $postID);
  1564. $query->execute() or error(db_error($query));
  1565. if (!$post = $query->fetch(PDO::FETCH_ASSOC))
  1566. error($config['error']['404']);
  1567. if (isset($_POST['name'], $_POST['email'], $_POST['subject'], $_POST['body'])) {
  1568. // Remove any modifiers they may have put in
  1569. $_POST['body'] = remove_modifiers($_POST['body']);
  1570. // Add back modifiers in the original post
  1571. $modifiers = extract_modifiers($post['body_nomarkup']);
  1572. foreach ($modifiers as $key => $value) {
  1573. $_POST['body'] .= "<tinyboard $key>$value</tinyboard>";
  1574. }
  1575. if ($edit_raw_html)
  1576. $query = prepare(sprintf('UPDATE ``posts_%s`` SET `name` = :name, `email` = :email, `subject` = :subject, `body` = :body, `body_nomarkup` = :body_nomarkup WHERE `id` = :id', $board));
  1577. else
  1578. $query = prepare(sprintf('UPDATE ``posts_%s`` SET `name` = :name, `email` = :email, `subject` = :subject, `body_nomarkup` = :body WHERE `id` = :id', $board));
  1579. $query->bindValue(':id', $postID);
  1580. $query->bindValue('name', $_POST['name']);
  1581. $query->bindValue(':email', $_POST['email']);
  1582. $query->bindValue(':subject', $_POST['subject']);
  1583. $query->bindValue(':body', $_POST['body']);
  1584. if ($edit_raw_html) {
  1585. $body_nomarkup = $_POST['body'] . "\n<tinyboard raw html>1</tinyboard>";
  1586. $query->bindValue(':body_nomarkup', $body_nomarkup);
  1587. }
  1588. $query->execute() or error(db_error($query));
  1589. if ($edit_raw_html) {
  1590. modLog("Edited raw HTML of post #{$postID}");
  1591. } else {
  1592. modLog("Edited post #{$postID}");
  1593. rebuildPost($postID);
  1594. }
  1595. buildIndex();
  1596. rebuildThemes('post', $board);
  1597. header('Location: ?/' . sprintf($config['board_path'], $board) . $config['dir']['res'] . link_for($post) . '#' . $postID, true, $config['redirect_http']);
  1598. } else {
  1599. // Remove modifiers
  1600. $post['body_nomarkup'] = remove_modifiers($post['body_nomarkup']);
  1601. $post['body_nomarkup'] = utf8tohtml($post['body_nomarkup']);
  1602. $post['body'] = utf8tohtml($post['body']);
  1603. if ($config['minify_html']) {
  1604. $post['body_nomarkup'] = str_replace("\n", '&#010;', $post['body_nomarkup']);
  1605. $post['body'] = str_replace("\n", '&#010;', $post['body']);
  1606. $post['body_nomarkup'] = str_replace("\r", '', $post['body_nomarkup']);
  1607. $post['body'] = str_replace("\r", '', $post['body']);
  1608. $post['body_nomarkup'] = str_replace("\t", '&#09;', $post['body_nomarkup']);
  1609. $post['body'] = str_replace("\t", '&#09;', $post['body']);
  1610. }
  1611. mod_page(_('Edit post'), 'mod/edit_post_form.html', array('token' => $security_token, 'board' => $board, 'raw' => $edit_raw_html, 'post' => $post));
  1612. }
  1613. }
  1614. function mod_delete($board, $post) {
  1615. global $config, $mod;
  1616. if (!openBoard($board))
  1617. error($config['error']['noboard']);
  1618. if (!hasPermission($config['mod']['delete'], $board))
  1619. error($config['error']['noaccess']);
  1620. // Delete post
  1621. if ($config['autotagging']){
  1622. $query = prepare(sprintf("SELECT * FROM ``posts_%s`` WHERE id = :id", $board));
  1623. $query->bindValue(':id', $post );
  1624. $query->execute() or error(db_error($query));
  1625. $ip = "";
  1626. $time = "";
  1627. $filename = "";
  1628. $filehash = "";
  1629. $subject = "";
  1630. $name = "";
  1631. $body = "";
  1632. while ($mypost = $query->fetch(PDO::FETCH_ASSOC)) {
  1633. $time = $mypost["time"];
  1634. $ip = $mypost["ip"];
  1635. $body = $mypost["body_nomarkup"];
  1636. $name = $mypost["name"];
  1637. $subject = $mypost["subject"];
  1638. $filehash = $mypost["filehash"];
  1639. $mypost['files'] = $mypost['files'] ? json_decode($mypost['files']) : array();
  1640. // For each file append file name
  1641. for ($file_count = 0; $file_count < $mypost["num_files"];$file_count++){
  1642. $filename .= $mypost['files'][$file_count]->name . "\r\n";
  1643. }
  1644. }
  1645. if ($time !== ''){
  1646. $dt = new DateTime("@$time");
  1647. $autotag = "";
  1648. $autotag .= $name . " " . $subject . " " . $dt->format('Y-m-d H:i:s') . " No.". $post . "\r\n";
  1649. $autotag .= "/${board}/" . " " . $filehash . " " . $filename ."\r\n";
  1650. $autotag .= $body . "\r\n";
  1651. $autotag = escape_markup_modifiers($autotag);
  1652. markup($autotag);
  1653. $query = prepare('INSERT INTO ``ip_notes`` VALUES (NULL, :ip, :mod, :time, :body)');
  1654. $query->bindValue(':ip', $ip);
  1655. $query->bindValue(':mod', $mod['id']);
  1656. $query->bindValue(':time', time());
  1657. $query->bindValue(':body', $autotag);
  1658. $query->execute() or error(db_error($query));
  1659. modLog("Added a note for <a href=\"?/IP/{$ip}\">{$ip}</a>");
  1660. }
  1661. }
  1662. deletePost($post);
  1663. // Record the action
  1664. modLog("Deleted post #{$post}");
  1665. // Rebuild board
  1666. buildIndex();
  1667. // Rebuild themes
  1668. rebuildThemes('post-delete', $board);
  1669. // Redirect
  1670. if(isset($_GET['thread'])) {
  1671. header('Location: ?/' . sprintf($config['board_path'], $board) . $config['dir']['res'] . str_replace('%d', $_GET['thread'], $config['file_page']), true, $config['redirect_http']);
  1672. } else {
  1673. header('Location: ?/' . sprintf($config['board_path'], $board) . $config['file_index'], true, $config['redirect_http']);
  1674. }
  1675. }
  1676. function mod_deletefile($board, $post, $file) {
  1677. global $config, $mod;
  1678. if (!openBoard($board))
  1679. error($config['error']['noboard']);
  1680. if (!hasPermission($config['mod']['deletefile'], $board))
  1681. error($config['error']['noaccess']);
  1682. // Delete file
  1683. deleteFile($post, TRUE, $file);
  1684. // Record the action
  1685. modLog("Deleted file from post #{$post}");
  1686. // Rebuild board
  1687. buildIndex();
  1688. // Rebuild themes
  1689. rebuildThemes('post-delete', $board);
  1690. // Redirect
  1691. header('Location: ?/' . sprintf($config['board_path'], $board) . $config['file_index'], true, $config['redirect_http']);
  1692. }
  1693. function mod_spoiler_image($board, $post, $file) {
  1694. global $config, $mod;
  1695. if (!openBoard($board))
  1696. error($config['error']['noboard']);
  1697. if (!hasPermission($config['mod']['spoilerimage'], $board))
  1698. error($config['error']['noaccess']);
  1699. // Delete file thumbnail
  1700. $query = prepare(sprintf("SELECT `files`, `thread` FROM ``posts_%s`` WHERE id = :id", $board));
  1701. $query->bindValue(':id', $post, PDO::PARAM_INT);
  1702. $query->execute() or error(db_error($query));
  1703. $result = $query->fetch(PDO::FETCH_ASSOC);
  1704. $files = json_decode($result['files']);
  1705. $size_spoiler_image = @getimagesize($config['spoiler_image']);
  1706. file_unlink($board . '/' . $config['dir']['thumb'] . $files[$file]->thumb);
  1707. $files[$file]->thumb = 'spoiler';
  1708. $files[$file]->thumbwidth = $size_spoiler_image[0];
  1709. $files[$file]->thumbheight = $size_spoiler_image[1];
  1710. // Make thumbnail spoiler
  1711. $query = prepare(sprintf("UPDATE ``posts_%s`` SET `files` = :files WHERE `id` = :id", $board));
  1712. $query->bindValue(':files', json_encode($files));
  1713. $query->bindValue(':id', $post, PDO::PARAM_INT);
  1714. $query->execute() or error(db_error($query));
  1715. // Record the action
  1716. modLog("Spoilered file from post #{$post}");
  1717. // Rebuild thread
  1718. buildThread($result['thread'] ? $result['thread'] : $post);
  1719. // Rebuild board
  1720. buildIndex();
  1721. // Rebuild themes
  1722. rebuildThemes('post-delete', $board);
  1723. // Redirect
  1724. header('Location: ?/' . sprintf($config['board_path'], $board) . $config['file_index'], true, $config['redirect_http']);
  1725. }
  1726. function mod_deletebyip($boardName, $post, $global = false) {
  1727. global $config, $mod, $board;
  1728. $global = (bool)$global;
  1729. if (!openBoard($boardName))
  1730. error($config['error']['noboard']);
  1731. if (!$global && !hasPermission($config['mod']['deletebyip'], $boardName))
  1732. error($config['error']['noaccess']);
  1733. if ($global && !hasPermission($config['mod']['deletebyip_global'], $boardName))
  1734. error($config['error']['noaccess']);
  1735. // Find IP address
  1736. $query = prepare(sprintf('SELECT `ip` FROM ``posts_%s`` WHERE `id` = :id', $boardName));
  1737. $query->bindValue(':id', $post);
  1738. $query->execute() or error(db_error($query));
  1739. if (!$ip = $query->fetchColumn())
  1740. error($config['error']['invalidpost']);
  1741. $boards = $global ? listBoards() : array(array('uri' => $boardName));
  1742. $query = '';
  1743. foreach ($boards as $_board) {
  1744. $query .= sprintf("SELECT `thread`, `id`, '%s' AS `board` FROM ``posts_%s`` WHERE `ip` = :ip UNION ALL ", $_board['uri'], $_board['uri']);
  1745. }
  1746. $query = preg_replace('/UNION ALL $/', '', $query);
  1747. $query = prepare($query);
  1748. $query->bindValue(':ip', $ip);
  1749. $query->execute() or error(db_error($query));
  1750. if ($query->rowCount() < 1)
  1751. error($config['error']['invalidpost']);
  1752. @set_time_limit($config['mod']['rebuild_timelimit']);
  1753. $threads_to_rebuild = array();
  1754. $threads_deleted = array();
  1755. while ($post = $query->fetch(PDO::FETCH_ASSOC)) {
  1756. openBoard($post['board']);
  1757. if ($config['autotagging']){
  1758. $query2 = prepare(sprintf("SELECT * FROM ``posts_%s`` WHERE id = :id", $post['board']));
  1759. $query2->bindValue(':id', $post['id'] );
  1760. $query2->execute() or error(db_error($query2));
  1761. $ip = "";
  1762. $time = "";
  1763. $filename = "";
  1764. $filehash = "";
  1765. $subject = "";
  1766. $name = "";
  1767. $body = "";
  1768. while ($mypost = $query2->fetch(PDO::FETCH_ASSOC)) {
  1769. $time = $mypost["time"];
  1770. $ip = $mypost["ip"];
  1771. $body = $mypost["body_nomarkup"];
  1772. $name = $mypost["name"];
  1773. $subject = $mypost["subject"];
  1774. $filehash = $mypost["filehash"];
  1775. $mypost['files'] = $mypost['files'] ? json_decode($mypost['files']) : array();
  1776. // For each file append file name
  1777. for ($file_count = 0; $file_count < $mypost["num_files"];$file_count++){
  1778. $filename .= $mypost['files'][$file_count]->name . "\r\n";
  1779. }
  1780. }
  1781. if ($time !== ''){
  1782. $dt = new DateTime("@$time");
  1783. $autotag = "";
  1784. $autotag .= $name . " " . $subject . " " . $dt->format('Y-m-d H:i:s') . " No.". $post['id'] . "\r\n";
  1785. $autotag .= "/${post['board']}/" . " " . $filehash . " " . $filename ."\r\n";
  1786. $autotag .= $body . "\r\n";
  1787. $autotag = escape_markup_modifiers($autotag);
  1788. markup($autotag);
  1789. $query2 = prepare('INSERT INTO ``ip_notes`` VALUES (NULL, :ip, :mod, :time, :body)');
  1790. $query2->bindValue(':ip', $ip);
  1791. $query2->bindValue(':mod', $mod['id']);
  1792. $query2->bindValue(':time', time());
  1793. $query2->bindValue(':body', $autotag);
  1794. $query2->execute() or error(db_error($query2));
  1795. modLog("Added a note for <a href=\"?/IP/{$ip}\">{$ip}</a>");
  1796. }
  1797. }
  1798. deletePost($post['id'], false, false);
  1799. rebuildThemes('post-delete', $board['uri']);
  1800. buildIndex();
  1801. if ($post['thread'])
  1802. $threads_to_rebuild[$post['board']][$post['thread']] = true;
  1803. else
  1804. $threads_deleted[$post['board']][$post['id']] = true;
  1805. }
  1806. foreach ($threads_to_rebuild as $_board => $_threads) {
  1807. openBoard($_board);
  1808. foreach ($_threads as $_thread => $_dummy) {
  1809. if ($_dummy && !isset($threads_deleted[$_board][$_thread]))
  1810. buildThread($_thread);
  1811. }
  1812. buildIndex();
  1813. }
  1814. if ($global) {
  1815. $board = false;
  1816. }
  1817. // Record the action
  1818. modLog("Deleted all posts by IP address: <a href=\"?/IP/$ip\">$ip</a>");
  1819. // Redirect
  1820. header('Location: ?/' . sprintf($config['board_path'], $boardName) . $config['file_index'], true, $config['redirect_http']);
  1821. }
  1822. function mod_user($uid) {
  1823. global $config, $mod;
  1824. if (!hasPermission($config['mod']['editusers']) && !(hasPermission($config['mod']['change_password']) && $uid == $mod['id']))
  1825. error($config['error']['noaccess']);
  1826. $query = prepare('SELECT * FROM ``mods`` WHERE `id` = :id');
  1827. $query->bindValue(':id', $uid);
  1828. $query->execute() or error(db_error($query));
  1829. if (!$user = $query->fetch(PDO::FETCH_ASSOC))
  1830. error($config['error']['404']);
  1831. if (hasPermission($config['mod']['editusers']) && isset($_POST['username'], $_POST['password'])) {
  1832. if (isset($_POST['allboards'])) {
  1833. $boards = array('*');
  1834. } else {
  1835. $_boards = listBoards();
  1836. foreach ($_boards as &$board) {
  1837. $board = $board['uri'];
  1838. }
  1839. $boards = array();
  1840. foreach ($_POST as $name => $value) {
  1841. if (preg_match('/^board_(' . $config['board_regex'] . ')$/u', $name, $matches) && in_array($matches[1], $_boards))
  1842. $boards[] = $matches[1];
  1843. }
  1844. }
  1845. if (isset($_POST['delete'])) {
  1846. if (!hasPermission($config['mod']['deleteusers']))
  1847. error($config['error']['noaccess']);
  1848. $query = prepare('DELETE FROM ``mods`` WHERE `id` = :id');
  1849. $query->bindValue(':id', $uid);
  1850. $query->execute() or error(db_error($query));
  1851. modLog('Deleted user ' . utf8tohtml($user['username']) . ' <small>(#' . $user['id'] . ')</small>');
  1852. header('Location: ?/users', true, $config['redirect_http']);
  1853. return;
  1854. }
  1855. if ($_POST['username'] == '')
  1856. error(sprintf($config['error']['required'], 'username'));
  1857. $query = prepare('UPDATE ``mods`` SET `username` = :username, `boards` = :boards WHERE `id` = :id');
  1858. $query->bindValue(':id', $uid);
  1859. $query->bindValue(':username', $_POST['username']);
  1860. $query->bindValue(':boards', implode(',', $boards));
  1861. $query->execute() or error(db_error($query));
  1862. if ($user['username'] !== $_POST['username']) {
  1863. // account was renamed
  1864. modLog('Renamed user "' . utf8tohtml($user['username']) . '" <small>(#' . $user['id'] . ')</small> to "' . utf8tohtml($_POST['username']) . '"');
  1865. }
  1866. if ($_POST['password'] != '') {
  1867. list($version, $password) = crypt_password($_POST['password']);
  1868. $query = prepare('UPDATE ``mods`` SET `password` = :password, `version` = :version WHERE `id` = :id');
  1869. $query->bindValue(':id', $uid);
  1870. $query->bindValue(':password', $password);
  1871. $query->bindValue(':version', $version);
  1872. $query->execute() or error(db_error($query));
  1873. modLog('Changed password for ' . utf8tohtml($_POST['username']) . ' <small>(#' . $user['id'] . ')</small>');
  1874. if ($uid == $mod['id']) {
  1875. login($_POST['username'], $_POST['password']);
  1876. setCookies();
  1877. }
  1878. }
  1879. if (hasPermission($config['mod']['manageusers']))
  1880. header('Location: ?/users', true, $config['redirect_http']);
  1881. else
  1882. header('Location: ?/', true, $config['redirect_http']);
  1883. return;
  1884. }
  1885. if (hasPermission($config['mod']['change_password']) && $uid == $mod['id'] && isset($_POST['password'])) {
  1886. if ($_POST['password'] != '') {
  1887. list($version, $password) = crypt_password($_POST['password']);
  1888. $query = prepare('UPDATE ``mods`` SET `password` = :password, `version` = :version WHERE `id` = :id');
  1889. $query->bindValue(':id', $uid);
  1890. $query->bindValue(':password', $password);
  1891. $query->bindValue(':version', $version);
  1892. $query->execute() or error(db_error($query));
  1893. modLog('Changed own password');
  1894. login($user['username'], $_POST['password']);
  1895. setCookies();
  1896. }
  1897. if (hasPermission($config['mod']['manageusers']))
  1898. header('Location: ?/users', true, $config['redirect_http']);
  1899. else
  1900. header('Location: ?/', true, $config['redirect_http']);
  1901. return;
  1902. }
  1903. if (hasPermission($config['mod']['modlog'])) {
  1904. $query = prepare('SELECT * FROM ``modlogs`` WHERE `mod` = :id ORDER BY `time` DESC LIMIT 5');
  1905. $query->bindValue(':id', $uid);
  1906. $query->execute() or error(db_error($query));
  1907. $log = $query->fetchAll(PDO::FETCH_ASSOC);
  1908. } else {
  1909. $log = array();
  1910. }
  1911. $user['boards'] = explode(',', $user['boards']);
  1912. mod_page(_('Edit user'), 'mod/user.html', array(
  1913. 'user' => $user,
  1914. 'logs' => $log,
  1915. 'boards' => listBoards(),
  1916. 'token' => make_secure_link_token('users/' . $user['id'])
  1917. ));
  1918. }
  1919. function mod_user_new() {
  1920. global $pdo, $config;
  1921. if (!hasPermission($config['mod']['createusers']))
  1922. error($config['error']['noaccess']);
  1923. if (isset($_POST['username'], $_POST['password'], $_POST['type'])) {
  1924. if ($_POST['username'] == '')
  1925. error(sprintf($config['error']['required'], 'username'));
  1926. if ($_POST['password'] == '')
  1927. error(sprintf($config['error']['required'], 'password'));
  1928. if (isset($_POST['allboards'])) {
  1929. $boards = array('*');
  1930. } else {
  1931. $_boards = listBoards();
  1932. foreach ($_boards as &$board) {
  1933. $board = $board['uri'];
  1934. }
  1935. $boards = array();
  1936. foreach ($_POST as $name => $value) {
  1937. if (preg_match('/^board_(' . $config['board_regex'] . ')$/u', $name, $matches) && in_array($matches[1], $_boards))
  1938. $boards[] = $matches[1];
  1939. }
  1940. }
  1941. $type = (int)$_POST['type'];
  1942. if (!isset($config['mod']['groups'][$type]) || $type == DISABLED)
  1943. error(sprintf($config['error']['invalidfield'], 'type'));
  1944. list($version, $password) = crypt_password($_POST['password']);
  1945. $query = prepare('INSERT INTO ``mods`` VALUES (NULL, :username, :password, :version, :type, :boards)');
  1946. $query->bindValue(':username', $_POST['username']);
  1947. $query->bindValue(':password', $password);
  1948. $query->bindValue(':version', $version);
  1949. $query->bindValue(':type', $type);
  1950. $query->bindValue(':boards', implode(',', $boards));
  1951. $query->execute() or error(db_error($query));
  1952. $userID = $pdo->lastInsertId();
  1953. modLog('Created a new user: ' . utf8tohtml($_POST['username']) . ' <small>(#' . $userID . ')</small>');
  1954. header('Location: ?/users', true, $config['redirect_http']);
  1955. return;
  1956. }
  1957. mod_page(_('New user'), 'mod/user.html', array('new' => true, 'boards' => listBoards(), 'token' => make_secure_link_token('users/new')));
  1958. }
  1959. function mod_users() {
  1960. global $config;
  1961. if (!hasPermission($config['mod']['manageusers']))
  1962. error($config['error']['noaccess']);
  1963. $query = query("SELECT
  1964. *,
  1965. (SELECT `time` FROM ``modlogs`` WHERE `mod` = `id` ORDER BY `time` DESC LIMIT 1) AS `last`,
  1966. (SELECT `text` FROM ``modlogs`` WHERE `mod` = `id` ORDER BY `time` DESC LIMIT 1) AS `action`
  1967. FROM ``mods`` ORDER BY `type` DESC,`id`") or error(db_error());
  1968. $users = $query->fetchAll(PDO::FETCH_ASSOC);
  1969. foreach ($users as &$user) {
  1970. $user['promote_token'] = make_secure_link_token("users/{$user['id']}/promote");
  1971. $user['demote_token'] = make_secure_link_token("users/{$user['id']}/demote");
  1972. }
  1973. mod_page(sprintf('%s (%d)', _('Manage users'), count($users)), 'mod/users.html', array('users' => $users));
  1974. }
  1975. function mod_user_promote($uid, $action) {
  1976. global $config;
  1977. if (!hasPermission($config['mod']['promoteusers']))
  1978. error($config['error']['noaccess']);
  1979. $query = prepare("SELECT `type`, `username` FROM ``mods`` WHERE `id` = :id");
  1980. $query->bindValue(':id', $uid);
  1981. $query->execute() or error(db_error($query));
  1982. if (!$mod = $query->fetch(PDO::FETCH_ASSOC))
  1983. error($config['error']['404']);
  1984. $new_group = false;
  1985. $groups = $config['mod']['groups'];
  1986. if ($action == 'demote')
  1987. $groups = array_reverse($groups, true);
  1988. foreach ($groups as $group_value => $group_name) {
  1989. if ($action == 'promote' && $group_value > $mod['type']) {
  1990. $new_group = $group_value;
  1991. break;
  1992. } elseif ($action == 'demote' && $group_value < $mod['type']) {
  1993. $new_group = $group_value;
  1994. break;
  1995. }
  1996. }
  1997. if ($new_group === false || $new_group == DISABLED)
  1998. error(_('Impossible to promote/demote user.'));
  1999. $query = prepare("UPDATE ``mods`` SET `type` = :group_value WHERE `id` = :id");
  2000. $query->bindValue(':id', $uid);
  2001. $query->bindValue(':group_value', $new_group);
  2002. $query->execute() or error(db_error($query));
  2003. modLog(($action == 'promote' ? 'Promoted' : 'Demoted') . ' user "' .
  2004. utf8tohtml($mod['username']) . '" to ' . $config['mod']['groups'][$new_group]);
  2005. header('Location: ?/users', true, $config['redirect_http']);
  2006. }
  2007. function mod_pm($id, $reply = false) {
  2008. global $mod, $config;
  2009. if ($reply && !hasPermission($config['mod']['create_pm']))
  2010. error($config['error']['noaccess']);
  2011. $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");
  2012. $query->bindValue(':id', $id);
  2013. $query->execute() or error(db_error($query));
  2014. if ((!$pm = $query->fetch(PDO::FETCH_ASSOC)) || ($pm['to'] != $mod['id'] && !hasPermission($config['mod']['master_pm'])))
  2015. error($config['error']['404']);
  2016. if (isset($_POST['delete'])) {
  2017. $query = prepare("DELETE FROM ``pms`` WHERE `id` = :id");
  2018. $query->bindValue(':id', $id);
  2019. $query->execute() or error(db_error($query));
  2020. if ($config['cache']['enabled']) {
  2021. cache::delete('pm_unread_' . $mod['id']);
  2022. cache::delete('pm_unreadcount_' . $mod['id']);
  2023. }
  2024. header('Location: ?/', true, $config['redirect_http']);
  2025. return;
  2026. }
  2027. if ($pm['unread'] && $pm['to'] == $mod['id']) {
  2028. $query = prepare("UPDATE ``pms`` SET `unread` = 0 WHERE `id` = :id");
  2029. $query->bindValue(':id', $id);
  2030. $query->execute() or error(db_error($query));
  2031. if ($config['cache']['enabled']) {
  2032. cache::delete('pm_unread_' . $mod['id']);
  2033. cache::delete('pm_unreadcount_' . $mod['id']);
  2034. }
  2035. modLog('Read a PM');
  2036. }
  2037. if ($reply) {
  2038. if (!$pm['to_username'])
  2039. error($config['error']['404']); // deleted?
  2040. mod_page(sprintf('%s %s', _('New PM for'), $pm['to_username']), 'mod/new_pm.html', array(
  2041. 'username' => $pm['username'],
  2042. 'id' => $pm['sender'],
  2043. 'message' => quote($pm['message']),
  2044. 'token' => make_secure_link_token('new_PM/' . $pm['username'])
  2045. ));
  2046. } else {
  2047. mod_page(sprintf('%s &ndash; #%d', _('Private message'), $id), 'mod/pm.html', $pm);
  2048. }
  2049. }
  2050. function mod_inbox() {
  2051. global $config, $mod;
  2052. $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');
  2053. $query->bindValue(':mod', $mod['id']);
  2054. $query->execute() or error(db_error($query));
  2055. $messages = $query->fetchAll(PDO::FETCH_ASSOC);
  2056. $query = prepare('SELECT COUNT(*) FROM ``pms`` WHERE `to` = :mod AND `unread` = 1');
  2057. $query->bindValue(':mod', $mod['id']);
  2058. $query->execute() or error(db_error($query));
  2059. $unread = $query->fetchColumn();
  2060. foreach ($messages as &$message) {
  2061. $message['snippet'] = pm_snippet($message['message']);
  2062. }
  2063. mod_page(sprintf('%s (%s)', _('PM inbox'), count($messages) > 0 ? $unread . ' unread' : 'empty'), 'mod/inbox.html', array(
  2064. 'messages' => $messages,
  2065. 'unread' => $unread
  2066. ));
  2067. }
  2068. function mod_new_pm($username) {
  2069. global $config, $mod;
  2070. if (!hasPermission($config['mod']['create_pm']))
  2071. error($config['error']['noaccess']);
  2072. $query = prepare("SELECT `id` FROM ``mods`` WHERE `username` = :username");
  2073. $query->bindValue(':username', $username);
  2074. $query->execute() or error(db_error($query));
  2075. if (!$id = $query->fetchColumn()) {
  2076. // Old style ?/PM: by user ID
  2077. $query = prepare("SELECT `username` FROM ``mods`` WHERE `id` = :username");
  2078. $query->bindValue(':username', $username);
  2079. $query->execute() or error(db_error($query));
  2080. if ($username = $query->fetchColumn())
  2081. header('Location: ?/new_PM/' . $username, true, $config['redirect_http']);
  2082. else
  2083. error($config['error']['404']);
  2084. }
  2085. if (isset($_POST['message'])) {
  2086. $_POST['message'] = escape_markup_modifiers($_POST['message']);
  2087. markup($_POST['message']);
  2088. $query = prepare("INSERT INTO ``pms`` VALUES (NULL, :me, :id, :message, :time, 1)");
  2089. $query->bindValue(':me', $mod['id']);
  2090. $query->bindValue(':id', $id);
  2091. $query->bindValue(':message', $_POST['message']);
  2092. $query->bindValue(':time', time());
  2093. $query->execute() or error(db_error($query));
  2094. if ($config['cache']['enabled']) {
  2095. cache::delete('pm_unread_' . $id);
  2096. cache::delete('pm_unreadcount_' . $id);
  2097. }
  2098. modLog('Sent a PM to ' . utf8tohtml($username));
  2099. header('Location: ?/', true, $config['redirect_http']);
  2100. }
  2101. mod_page(sprintf('%s %s', _('New PM for'), $username), 'mod/new_pm.html', array(
  2102. 'username' => $username,
  2103. 'id' => $id,
  2104. 'token' => make_secure_link_token('new_PM/' . $username)
  2105. ));
  2106. }
  2107. function mod_rebuild() {
  2108. global $config, $twig;
  2109. if (!hasPermission($config['mod']['rebuild']))
  2110. error($config['error']['noaccess']);
  2111. if (isset($_POST['rebuild'])) {
  2112. @set_time_limit($config['mod']['rebuild_timelimit']);
  2113. $log = array();
  2114. $boards = listBoards();
  2115. $rebuilt_scripts = array();
  2116. if (isset($_POST['rebuild_cache'])) {
  2117. if ($config['cache']['enabled']) {
  2118. $log[] = 'Flushing cache';
  2119. Cache::flush();
  2120. }
  2121. $log[] = 'Clearing template cache';
  2122. load_twig();
  2123. $twig->clearCacheFiles();
  2124. }
  2125. if (isset($_POST['rebuild_themes'])) {
  2126. $log[] = 'Regenerating theme files';
  2127. rebuildThemes('all');
  2128. }
  2129. if (isset($_POST['rebuild_javascript'])) {
  2130. $log[] = 'Rebuilding <strong>' . $config['file_script'] . '</strong>';
  2131. buildJavascript();
  2132. $rebuilt_scripts[] = $config['file_script'];
  2133. }
  2134. foreach ($boards as $board) {
  2135. if (!(isset($_POST['boards_all']) || isset($_POST['board_' . $board['uri']])))
  2136. continue;
  2137. openBoard($board['uri']);
  2138. $config['try_smarter'] = false;
  2139. if (isset($_POST['rebuild_index'])) {
  2140. buildIndex();
  2141. $log[] = '<strong>' . sprintf($config['board_abbreviation'], $board['uri']) . '</strong>: Creating index pages';
  2142. }
  2143. if (isset($_POST['rebuild_javascript']) && !in_array($config['file_script'], $rebuilt_scripts)) {
  2144. $log[] = '<strong>' . sprintf($config['board_abbreviation'], $board['uri']) . '</strong>: Rebuilding <strong>' . $config['file_script'] . '</strong>';
  2145. buildJavascript();
  2146. $rebuilt_scripts[] = $config['file_script'];
  2147. }
  2148. if (isset($_POST['rebuild_thread'])) {
  2149. $query = query(sprintf("SELECT `id` FROM ``posts_%s`` WHERE `thread` IS NULL", $board['uri'])) or error(db_error());
  2150. while ($post = $query->fetch(PDO::FETCH_ASSOC)) {
  2151. $log[] = '<strong>' . sprintf($config['board_abbreviation'], $board['uri']) . '</strong>: Rebuilding thread #' . $post['id'];
  2152. buildThread($post['id']);
  2153. }
  2154. }
  2155. }
  2156. mod_page(_('Rebuild'), 'mod/rebuilt.html', array('logs' => $log));
  2157. return;
  2158. }
  2159. mod_page(_('Rebuild'), 'mod/rebuild.html', array(
  2160. 'boards' => listBoards(),
  2161. 'token' => make_secure_link_token('rebuild')
  2162. ));
  2163. }
  2164. function mod_reports() {
  2165. global $config, $mod;
  2166. if (!hasPermission($config['mod']['reports']))
  2167. error($config['error']['noaccess']);
  2168. $query = prepare("SELECT * FROM ``reports`` ORDER BY `time` DESC LIMIT :limit");
  2169. $query->bindValue(':limit', $config['mod']['recent_reports'], PDO::PARAM_INT);
  2170. $query->execute() or error(db_error($query));
  2171. $reports = $query->fetchAll(PDO::FETCH_ASSOC);
  2172. $report_queries = array();
  2173. foreach ($reports as $report) {
  2174. if (!isset($report_queries[$report['board']]))
  2175. $report_queries[$report['board']] = array();
  2176. $report_queries[$report['board']][] = $report['post'];
  2177. }
  2178. $report_posts = array();
  2179. foreach ($report_queries as $board => $posts) {
  2180. $report_posts[$board] = array();
  2181. $query = query(sprintf('SELECT * FROM ``posts_%s`` WHERE `id` = ' . implode(' OR `id` = ', $posts), $board)) or error(db_error());
  2182. while ($post = $query->fetch(PDO::FETCH_ASSOC)) {
  2183. $report_posts[$board][$post['id']] = $post;
  2184. }
  2185. }
  2186. $count = 0;
  2187. $body = '';
  2188. foreach ($reports as $report) {
  2189. if (!isset($report_posts[$report['board']][$report['post']])) {
  2190. // // Invalid report (post has since been deleted)
  2191. $query = prepare("DELETE FROM ``reports`` WHERE `post` = :id AND `board` = :board");
  2192. $query->bindValue(':id', $report['post'], PDO::PARAM_INT);
  2193. $query->bindValue(':board', $report['board']);
  2194. $query->execute() or error(db_error($query));
  2195. continue;
  2196. }
  2197. openBoard($report['board']);
  2198. $post = &$report_posts[$report['board']][$report['post']];
  2199. if (!$post['thread']) {
  2200. // Still need to fix this:
  2201. $po = new Thread($post, '?/', $mod, false);
  2202. } else {
  2203. $po = new Post($post, '?/', $mod);
  2204. }
  2205. // a little messy and inefficient
  2206. $append_html = Element('mod/report.html', array(
  2207. 'report' => $report,
  2208. 'config' => $config,
  2209. 'mod' => $mod,
  2210. 'token' => make_secure_link_token('reports/' . $report['id'] . '/dismiss'),
  2211. 'token_all' => make_secure_link_token('reports/' . $report['id'] . '/dismissall')
  2212. ));
  2213. // Bug fix for https://github.com/savetheinternet/Tinyboard/issues/21
  2214. $po->body = truncate($po->body, $po->link(), $config['body_truncate'] - substr_count($append_html, '<br>'));
  2215. if (mb_strlen($po->body) + mb_strlen($append_html) > $config['body_truncate_char']) {
  2216. // still too long; temporarily increase limit in the config
  2217. $__old_body_truncate_char = $config['body_truncate_char'];
  2218. $config['body_truncate_char'] = mb_strlen($po->body) + mb_strlen($append_html);
  2219. }
  2220. $po->body .= $append_html;
  2221. $body .= $po->build(true) . '<hr>';
  2222. if (isset($__old_body_truncate_char))
  2223. $config['body_truncate_char'] = $__old_body_truncate_char;
  2224. $count++;
  2225. }
  2226. mod_page(sprintf('%s (%d)', _('Report queue'), $count), 'mod/reports.html', array('reports' => $body, 'count' => $count));
  2227. }
  2228. function mod_report_dismiss($id, $all = false) {
  2229. global $config;
  2230. $query = prepare("SELECT `post`, `board`, `ip` FROM ``reports`` WHERE `id` = :id");
  2231. $query->bindValue(':id', $id);
  2232. $query->execute() or error(db_error($query));
  2233. if ($report = $query->fetch(PDO::FETCH_ASSOC)) {
  2234. $ip = $report['ip'];
  2235. $board = $report['board'];
  2236. $post = $report['post'];
  2237. } else
  2238. error($config['error']['404']);
  2239. if (!$all && !hasPermission($config['mod']['report_dismiss'], $board))
  2240. error($config['error']['noaccess']);
  2241. if ($all && !hasPermission($config['mod']['report_dismiss_ip'], $board))
  2242. error($config['error']['noaccess']);
  2243. if ($all) {
  2244. $query = prepare("DELETE FROM ``reports`` WHERE `ip` = :ip");
  2245. $query->bindValue(':ip', $ip);
  2246. } else {
  2247. $query = prepare("DELETE FROM ``reports`` WHERE `id` = :id");
  2248. $query->bindValue(':id', $id);
  2249. }
  2250. $query->execute() or error(db_error($query));
  2251. if ($all)
  2252. modLog("Dismissed all reports by <a href=\"?/IP/$ip\">$ip</a>");
  2253. else
  2254. modLog("Dismissed a report for post #{$id}", $board);
  2255. header('Location: ?/reports', true, $config['redirect_http']);
  2256. }
  2257. function mod_recent_posts($lim,$board_list = false,$json=false) {
  2258. global $config, $mod, $pdo;
  2259. if (!hasPermission($config['mod']['recent']))
  2260. error($config['error']['noaccess']);
  2261. $limit = (is_numeric($lim))? $lim : 25;
  2262. $last_time = (isset($_GET['last']) && is_numeric($_GET['last'])) ? $_GET['last'] : 0;
  2263. $mod_boards = array();
  2264. $boards = listBoards();
  2265. //if not all boards
  2266. if ($mod['boards'][0]!='*') {
  2267. foreach ($boards as $board) {
  2268. if (in_array($board['uri'], $mod['boards']))
  2269. $mod_boards[] = $board;
  2270. }
  2271. } else {
  2272. $mod_boards = $boards;
  2273. }
  2274. if ($board_list != false){
  2275. $board_array = explode(",",$board_list);
  2276. $new_board_array = array();
  2277. foreach ($board_array as $board) {
  2278. if (array_key_exists($board,$config['boards_alias'])){
  2279. $newboard = $config['boards_alias'][$board];
  2280. }
  2281. else{
  2282. $newboard = $board;
  2283. }
  2284. $new_board_array[] = $newboard;
  2285. }
  2286. $mod_boards = array();
  2287. foreach ($boards as $board) {
  2288. if (in_array($board['uri'], $new_board_array)){
  2289. $mod_boards[] = $board;
  2290. }
  2291. }
  2292. }
  2293. // Manually build an SQL query
  2294. $query = 'SELECT * FROM (';
  2295. foreach ($mod_boards as $board) {
  2296. $query .= sprintf('SELECT *, %s AS `board` FROM ``posts_%s`` UNION ALL ', $pdo->quote($board['uri']), $board['uri']);
  2297. }
  2298. // Remove the last "UNION ALL" seperator and complete the query
  2299. $query = preg_replace('/UNION ALL $/', ') AS `all_posts` WHERE (`time` < :last_time OR NOT :last_time) ORDER BY `time` DESC LIMIT ' . $limit, $query);
  2300. $query = prepare($query);
  2301. $query->bindValue(':last_time', $last_time);
  2302. $query->execute() or error(db_error($query));
  2303. $posts = $query->fetchAll(PDO::FETCH_ASSOC);
  2304. if ($config['api']['enabled']) {
  2305. $apithreads = array();
  2306. }
  2307. foreach ($posts as &$post) {
  2308. openBoard($post['board']);
  2309. if (!$post['thread']) {
  2310. // Still need to fix this:
  2311. $po = new Thread($post, '?/', $mod, false);
  2312. $post['built'] = $po->build(true);
  2313. if ($config['api']['enabled']) {
  2314. $apithreads[] = $po;
  2315. }
  2316. } else {
  2317. $po = new Post($post, '?/', $mod);
  2318. $post['built'] = $po->build(true);
  2319. if ($config['api']['enabled']) {
  2320. $pot = new Thread($post, '?/', $mod, false);
  2321. $pot->add($po);
  2322. $apithreads[] = $pot;
  2323. }
  2324. }
  2325. $last_time = $post['time'];
  2326. }
  2327. if ($config['api']['enabled']) {
  2328. require_once __DIR__. '/../../inc/api.php';
  2329. $api = new Api();
  2330. $jsonFilename = 'mod/' . 'recent.json';
  2331. $jsondata = json_encode($api->translatePage($apithreads));
  2332. }
  2333. if ($json){
  2334. echo $jsondata;
  2335. }
  2336. else {
  2337. echo mod_page(_('Recent posts'), 'mod/recent_posts.html', array(
  2338. 'posts' => $posts,
  2339. 'limit' => $limit,
  2340. 'last_time' => $last_time
  2341. )
  2342. );
  2343. }
  2344. }
  2345. function mod_config($board_config = false) {
  2346. global $config, $mod, $board;
  2347. if ($board_config && !openBoard($board_config))
  2348. error($config['error']['noboard']);
  2349. if (!hasPermission($config['mod']['edit_config'], $board_config))
  2350. error($config['error']['noaccess']);
  2351. $config_file = $board_config ? $board['dir'] . 'config.php' : 'inc/instance-config.php';
  2352. if ($config['mod']['config_editor_php']) {
  2353. $readonly = !(is_file($config_file) ? is_writable($config_file) : is_writable(dirname($config_file)));
  2354. if (!$readonly && isset($_POST['code'])) {
  2355. $code = $_POST['code'];
  2356. // Save previous instance_config if php_check_syntax fails
  2357. $old_code = file_get_contents($config_file);
  2358. file_put_contents($config_file, $code);
  2359. $resp = shell_exec_error('php -l ' . $config_file);
  2360. if (preg_match('/No syntax errors detected/', $resp)) {
  2361. header('Location: ?/config' . ($board_config ? '/' . $board_config : ''), true, $config['redirect_http']);
  2362. return;
  2363. }
  2364. else {
  2365. file_put_contents($config_file, $old_code);
  2366. error($config['error']['badsyntax'] . $resp);
  2367. }
  2368. }
  2369. $instance_config = @file_get_contents($config_file);
  2370. if ($instance_config === false) {
  2371. $instance_config = "<?php\n\n// This file does not exist yet. You are creating it.";
  2372. }
  2373. $instance_config = str_replace("\n", '&#010;', utf8tohtml($instance_config));
  2374. mod_page(_('Config editor'), 'mod/config-editor-php.html', array(
  2375. 'php' => $instance_config,
  2376. 'readonly' => $readonly,
  2377. 'boards' => listBoards(),
  2378. 'board' => $board_config,
  2379. 'file' => $config_file,
  2380. 'token' => make_secure_link_token('config' . ($board_config ? '/' . $board_config : ''))
  2381. ));
  2382. return;
  2383. }
  2384. require_once 'inc/mod/config-editor.php';
  2385. $conf = config_vars();
  2386. foreach ($conf as &$var) {
  2387. if (is_array($var['name'])) {
  2388. $c = &$config;
  2389. foreach ($var['name'] as $n)
  2390. $c = &$c[$n];
  2391. } else {
  2392. $c = @$config[$var['name']];
  2393. }
  2394. $var['value'] = $c;
  2395. }
  2396. unset($var);
  2397. if (isset($_POST['save'])) {
  2398. $config_append = '';
  2399. foreach ($conf as $var) {
  2400. $field_name = 'cf_' . (is_array($var['name']) ? implode('/', $var['name']) : $var['name']);
  2401. if ($var['type'] == 'boolean')
  2402. $value = isset($_POST[$field_name]);
  2403. elseif (isset($_POST[$field_name]))
  2404. $value = $_POST[$field_name];
  2405. else
  2406. continue; // ???
  2407. if (!settype($value, $var['type']))
  2408. continue; // invalid
  2409. if ($value != $var['value']) {
  2410. // This value has been changed.
  2411. $config_append .= '$config';
  2412. if (is_array($var['name'])) {
  2413. foreach ($var['name'] as $name)
  2414. $config_append .= '[' . var_export($name, true) . ']';
  2415. } else {
  2416. $config_append .= '[' . var_export($var['name'], true) . ']';
  2417. }
  2418. $config_append .= ' = ';
  2419. if (@$var['permissions'] && isset($config['mod']['groups'][$value])) {
  2420. $config_append .= $config['mod']['groups'][$value];
  2421. } else {
  2422. $config_append .= var_export($value, true);
  2423. }
  2424. $config_append .= ";\n";
  2425. }
  2426. }
  2427. if (!empty($config_append)) {
  2428. $config_append = "\n// Changes made via web editor by \"" . $mod['username'] . "\" @ " . date('r') . ":\n" . $config_append . "\n";
  2429. if (!is_file($config_file))
  2430. $config_append = "<?php\n\n$config_append";
  2431. if (!@file_put_contents($config_file, $config_append, FILE_APPEND)) {
  2432. $config_append = htmlentities($config_append);
  2433. if ($config['minify_html'])
  2434. $config_append = str_replace("\n", '&#010;', $config_append);
  2435. $page = array();
  2436. $page['title'] = 'Cannot write to file!';
  2437. $page['config'] = $config;
  2438. $page['body'] = '
  2439. <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>
  2440. <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>
  2441. <textarea style="width:700px;height:370px;margin:auto;display:block;background:white;color:black" readonly>' . $config_append . '</textarea>
  2442. ';
  2443. echo Element('page.html', $page);
  2444. exit;
  2445. }
  2446. }
  2447. header('Location: ?/config' . ($board_config ? '/' . $board_config : ''), true, $config['redirect_http']);
  2448. exit;
  2449. }
  2450. mod_page(_('Config editor') . ($board_config ? ': ' . sprintf($config['board_abbreviation'], $board_config) : ''),
  2451. 'mod/config-editor.html', array(
  2452. 'boards' => listBoards(),
  2453. 'board' => $board_config,
  2454. 'conf' => $conf,
  2455. 'file' => $config_file,
  2456. 'token' => make_secure_link_token('config' . ($board_config ? '/' . $board_config : ''))
  2457. ));
  2458. }
  2459. function mod_themes_list() {
  2460. global $config;
  2461. if (!hasPermission($config['mod']['themes']))
  2462. error($config['error']['noaccess']);
  2463. if (!is_dir($config['dir']['themes']))
  2464. error(_('Themes directory doesn\'t exist!'));
  2465. if (!$dir = opendir($config['dir']['themes']))
  2466. error(_('Cannot open themes directory; check permissions.'));
  2467. $query = query('SELECT `theme` FROM ``theme_settings`` WHERE `name` IS NULL AND `value` IS NULL') or error(db_error());
  2468. $themes_in_use = $query->fetchAll(PDO::FETCH_COLUMN);
  2469. // Scan directory for themes
  2470. $themes = array();
  2471. while ($file = readdir($dir)) {
  2472. if ($file[0] != '.' && is_dir($config['dir']['themes'] . '/' . $file)) {
  2473. $themes[$file] = loadThemeConfig($file);
  2474. }
  2475. }
  2476. closedir($dir);
  2477. foreach ($themes as $theme_name => &$theme) {
  2478. $theme['rebuild_token'] = make_secure_link_token('themes/' . $theme_name . '/rebuild');
  2479. $theme['uninstall_token'] = make_secure_link_token('themes/' . $theme_name . '/uninstall');
  2480. }
  2481. mod_page(_('Manage themes'), 'mod/themes.html', array(
  2482. 'themes' => $themes,
  2483. 'themes_in_use' => $themes_in_use,
  2484. ));
  2485. }
  2486. function mod_theme_configure($theme_name) {
  2487. global $config;
  2488. if (!hasPermission($config['mod']['themes']))
  2489. error($config['error']['noaccess']);
  2490. if (!$theme = loadThemeConfig($theme_name)) {
  2491. error($config['error']['invalidtheme']);
  2492. }
  2493. if (isset($_POST['install'])) {
  2494. // Check if everything is submitted
  2495. foreach ($theme['config'] as &$conf) {
  2496. if (!isset($_POST[$conf['name']]) && $conf['type'] != 'checkbox')
  2497. error(sprintf($config['error']['required'], $c['title']));
  2498. }
  2499. // Clear previous settings
  2500. $query = prepare("DELETE FROM ``theme_settings`` WHERE `theme` = :theme");
  2501. $query->bindValue(':theme', $theme_name);
  2502. $query->execute() or error(db_error($query));
  2503. foreach ($theme['config'] as &$conf) {
  2504. $query = prepare("INSERT INTO ``theme_settings`` VALUES(:theme, :name, :value)");
  2505. $query->bindValue(':theme', $theme_name);
  2506. $query->bindValue(':name', $conf['name']);
  2507. if ($conf['type'] == 'checkbox')
  2508. $query->bindValue(':value', isset($_POST[$conf['name']]) ? 1 : 0);
  2509. else
  2510. $query->bindValue(':value', $_POST[$conf['name']]);
  2511. $query->execute() or error(db_error($query));
  2512. }
  2513. $query = prepare("INSERT INTO ``theme_settings`` VALUES(:theme, NULL, NULL)");
  2514. $query->bindValue(':theme', $theme_name);
  2515. $query->execute() or error(db_error($query));
  2516. // Clean cache
  2517. Cache::delete("themes");
  2518. Cache::delete("theme_settings_".$theme_name);
  2519. $result = true;
  2520. $message = false;
  2521. if (isset($theme['install_callback'])) {
  2522. $ret = $theme['install_callback'](themeSettings($theme_name));
  2523. if ($ret && !empty($ret)) {
  2524. if (is_array($ret) && count($ret) == 2) {
  2525. $result = $ret[0];
  2526. $message = $ret[1];
  2527. }
  2528. }
  2529. }
  2530. if (!$result) {
  2531. // Install failed
  2532. $query = prepare("DELETE FROM ``theme_settings`` WHERE `theme` = :theme");
  2533. $query->bindValue(':theme', $theme_name);
  2534. $query->execute() or error(db_error($query));
  2535. }
  2536. // Build themes
  2537. rebuildThemes('all');
  2538. mod_page(sprintf(_($result ? 'Installed theme: %s' : 'Installation failed: %s'), $theme['name']), 'mod/theme_installed.html', array(
  2539. 'theme_name' => $theme_name,
  2540. 'theme' => $theme,
  2541. 'result' => $result,
  2542. 'message' => $message
  2543. ));
  2544. return;
  2545. }
  2546. $settings = themeSettings($theme_name);
  2547. mod_page(sprintf(_('Configuring theme: %s'), $theme['name']), 'mod/theme_config.html', array(
  2548. 'theme_name' => $theme_name,
  2549. 'theme' => $theme,
  2550. 'settings' => $settings,
  2551. 'token' => make_secure_link_token('themes/' . $theme_name)
  2552. ));
  2553. }
  2554. function mod_theme_uninstall($theme_name) {
  2555. global $config;
  2556. if (!hasPermission($config['mod']['themes']))
  2557. error($config['error']['noaccess']);
  2558. $query = prepare("DELETE FROM ``theme_settings`` WHERE `theme` = :theme");
  2559. $query->bindValue(':theme', $theme_name);
  2560. $query->execute() or error(db_error($query));
  2561. // Clean cache
  2562. Cache::delete("themes");
  2563. Cache::delete("theme_settings_".$theme_name);
  2564. header('Location: ?/themes', true, $config['redirect_http']);
  2565. }
  2566. function mod_theme_rebuild($theme_name) {
  2567. global $config;
  2568. if (!hasPermission($config['mod']['themes']))
  2569. error($config['error']['noaccess']);
  2570. rebuildTheme($theme_name, 'all');
  2571. mod_page(sprintf(_('Rebuilt theme: %s'), $theme_name), 'mod/theme_rebuilt.html', array(
  2572. 'theme_name' => $theme_name,
  2573. ));
  2574. }
  2575. // This needs to be done for `secure` CSRF prevention compatibility, otherwise the $board will be read in as the token if editing global pages.
  2576. function delete_page_base($page = '', $board = false) {
  2577. global $config, $mod;
  2578. if (empty($board))
  2579. $board = false;
  2580. if (!$board && $mod['boards'][0] !== '*')
  2581. error($config['error']['noaccess']);
  2582. if (!hasPermission($config['mod']['edit_pages'], $board))
  2583. error($config['error']['noaccess']);
  2584. if ($board !== FALSE && !openBoard($board))
  2585. error($config['error']['noboard']);
  2586. if ($board) {
  2587. $query = prepare('DELETE FROM ``pages`` WHERE `board` = :board AND `name` = :name');
  2588. $query->bindValue(':board', ($board ? $board : NULL));
  2589. } else {
  2590. $query = prepare('DELETE FROM ``pages`` WHERE `board` IS NULL AND `name` = :name');
  2591. }
  2592. $query->bindValue(':name', $page);
  2593. $query->execute() or error(db_error($query));
  2594. header('Location: ?/edit_pages' . ($board ? ('/' . $board) : ''), true, $config['redirect_http']);
  2595. }
  2596. function mod_delete_page($page = '') {
  2597. delete_page_base($page);
  2598. }
  2599. function mod_delete_page_board($page = '', $board = false) {
  2600. delete_page_base($page, $board);
  2601. }
  2602. function mod_edit_page($id) {
  2603. global $config, $mod, $board;
  2604. $query = prepare('SELECT * FROM ``pages`` WHERE `id` = :id');
  2605. $query->bindValue(':id', $id);
  2606. $query->execute() or error(db_error($query));
  2607. $page = $query->fetch();
  2608. if (!$page)
  2609. error(_('Could not find the page you are trying to edit.'));
  2610. if (!$page['board'] && $mod['boards'][0] !== '*')
  2611. error($config['error']['noaccess']);
  2612. if (!hasPermission($config['mod']['edit_pages'], $page['board']))
  2613. error($config['error']['noaccess']);
  2614. if ($page['board'] && !openBoard($page['board']))
  2615. error($config['error']['noboard']);
  2616. if (isset($_POST['method'], $_POST['content'])) {
  2617. $content = $_POST['content'];
  2618. $method = $_POST['method'];
  2619. $page['type'] = $method;
  2620. if (!in_array($method, array('markdown', 'html', 'infinity')))
  2621. error(_('Unrecognized page markup method.'));
  2622. switch ($method) {
  2623. case 'markdown':
  2624. $write = markdown($content);
  2625. break;
  2626. case 'html':
  2627. if (hasPermission($config['mod']['rawhtml'])) {
  2628. $write = $content;
  2629. } else {
  2630. $write = purify_html($content);
  2631. }
  2632. break;
  2633. case 'infinity':
  2634. $c = $content;
  2635. markup($content);
  2636. $write = $content;
  2637. $content = $c;
  2638. }
  2639. if (!isset($write) or !$write)
  2640. error(_('Failed to mark up your input for some reason...'));
  2641. $query = prepare('UPDATE ``pages`` SET `type` = :method, `content` = :content WHERE `id` = :id');
  2642. $query->bindValue(':method', $method);
  2643. $query->bindValue(':content', $content);
  2644. $query->bindValue(':id', $id);
  2645. $query->execute() or error(db_error($query));
  2646. $fn = ($board['uri'] ? ($board['uri'] . '/') : '') . $page['name'] . '.html';
  2647. $body = "<div class='ban'>$write</div>";
  2648. $html = Element('page.html', array('config' => $config, 'body' => $body, 'title' => utf8tohtml($page['title'])));
  2649. file_write($fn, $html);
  2650. }
  2651. if (!isset($content)) {
  2652. $query = prepare('SELECT `content` FROM ``pages`` WHERE `id` = :id');
  2653. $query->bindValue(':id', $id);
  2654. $query->execute() or error(db_error($query));
  2655. $content = $query->fetchColumn();
  2656. }
  2657. 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));
  2658. }
  2659. function mod_pages($board = false) {
  2660. global $config, $mod, $pdo;
  2661. if (empty($board))
  2662. $board = false;
  2663. if (!$board && $mod['boards'][0] !== '*')
  2664. error($config['error']['noaccess']);
  2665. if (!hasPermission($config['mod']['edit_pages'], $board))
  2666. error($config['error']['noaccess']);
  2667. if ($board !== FALSE && !openBoard($board))
  2668. error($config['error']['noboard']);
  2669. if ($board) {
  2670. $query = prepare('SELECT * FROM ``pages`` WHERE `board` = :board');
  2671. $query->bindValue(':board', $board);
  2672. } else {
  2673. $query = query('SELECT * FROM ``pages`` WHERE `board` IS NULL');
  2674. }
  2675. $query->execute() or error(db_error($query));
  2676. $pages = $query->fetchAll(PDO::FETCH_ASSOC);
  2677. if (isset($_POST['page'])) {
  2678. if ($board and sizeof($pages) > $config['pages_max'])
  2679. error(sprintf(_('Sorry, this site only allows %d pages per board.'), $config['pages_max']));
  2680. if (!preg_match('/^[a-z0-9]{1,255}$/', $_POST['page']))
  2681. error(_('Page names must be < 255 chars and may only contain lowercase letters A-Z and digits 1-9.'));
  2682. foreach ($pages as $i => $p) {
  2683. if ($_POST['page'] === $p['name'])
  2684. error(_('Refusing to create a new page with the same name as an existing one.'));
  2685. }
  2686. $title = ($_POST['title'] ? $_POST['title'] : NULL);
  2687. $query = prepare('INSERT INTO ``pages``(board, title, name) VALUES(:board, :title, :name)');
  2688. $query->bindValue(':board', ($board ? $board : NULL));
  2689. $query->bindValue(':title', $title);
  2690. $query->bindValue(':name', $_POST['page']);
  2691. $query->execute() or error(db_error($query));
  2692. $pages[] = array('id' => $pdo->lastInsertId(), 'name' => $_POST['page'], 'board' => $board, 'title' => $title);
  2693. }
  2694. foreach ($pages as $i => &$p) {
  2695. $p['delete_token'] = make_secure_link_token('edit_pages/delete/' . $p['name'] . ($board ? ('/' . $board) : ''));
  2696. }
  2697. mod_page(_('Pages'), 'mod/pages.html', array('pages' => $pages, 'token' => make_secure_link_token('edit_pages' . ($board ? ('/' . $board) : '')), 'board' => $board));
  2698. }
  2699. function mod_debug_antispam() {
  2700. global $pdo, $config;
  2701. $args = array();
  2702. if (isset($_POST['board'], $_POST['thread'])) {
  2703. $where = '`board` = ' . $pdo->quote($_POST['board']);
  2704. if ($_POST['thread'] != '')
  2705. $where .= ' AND `thread` = ' . $pdo->quote($_POST['thread']);
  2706. if (isset($_POST['purge'])) {
  2707. $query = prepare(', DATE ``antispam`` SET `expires` = UNIX_TIMESTAMP() + :expires WHERE' . $where);
  2708. $query->bindValue(':expires', $config['spam']['hidden_inputs_expire']);
  2709. $query->execute() or error(db_error());
  2710. }
  2711. $args['board'] = $_POST['board'];
  2712. $args['thread'] = $_POST['thread'];
  2713. } else {
  2714. $where = '';
  2715. }
  2716. $query = query('SELECT COUNT(*) FROM ``antispam``' . ($where ? " WHERE $where" : '')) or error(db_error());
  2717. $args['total'] = number_format($query->fetchColumn());
  2718. $query = query('SELECT COUNT(*) FROM ``antispam`` WHERE `expires` IS NOT NULL' . ($where ? " AND $where" : '')) or error(db_error());
  2719. $args['expiring'] = number_format($query->fetchColumn());
  2720. $query = query('SELECT * FROM ``antispam`` ' . ($where ? "WHERE $where" : '') . ' ORDER BY `passed` DESC LIMIT 40') or error(db_error());
  2721. $args['top'] = $query->fetchAll(PDO::FETCH_ASSOC);
  2722. $query = query('SELECT * FROM ``antispam`` ' . ($where ? "WHERE $where" : '') . ' ORDER BY `created` DESC LIMIT 20') or error(db_error());
  2723. $args['recent'] = $query->fetchAll(PDO::FETCH_ASSOC);
  2724. mod_page(_('Debug: Anti-spam'), 'mod/debug/antispam.html', $args);
  2725. }
  2726. function mod_debug_recent_posts() {
  2727. global $pdo, $config;
  2728. $limit = 500;
  2729. $boards = listBoards();
  2730. // Manually build an SQL query
  2731. $query = 'SELECT * FROM (';
  2732. foreach ($boards as $board) {
  2733. $query .= sprintf('SELECT *, %s AS `board` FROM ``posts_%s`` UNION ALL ', $pdo->quote($board['uri']), $board['uri']);
  2734. }
  2735. // Remove the last "UNION ALL" seperator and complete the query
  2736. $query = preg_replace('/UNION ALL $/', ') AS `all_posts` ORDER BY `time` DESC LIMIT ' . $limit, $query);
  2737. $query = query($query) or error(db_error());
  2738. $posts = $query->fetchAll(PDO::FETCH_ASSOC);
  2739. // Fetch recent posts from flood prevention cache
  2740. $query = query("SELECT * FROM ``flood`` ORDER BY `time` DESC") or error(db_error());
  2741. $flood_posts = $query->fetchAll(PDO::FETCH_ASSOC);
  2742. foreach ($posts as &$post) {
  2743. $post['snippet'] = pm_snippet($post['body']);
  2744. foreach ($flood_posts as $flood_post) {
  2745. if ($flood_post['time'] == $post['time'] &&
  2746. $flood_post['posthash'] == make_comment_hex($post['body_nomarkup']) &&
  2747. $flood_post['filehash'] == $post['filehash'])
  2748. $post['in_flood_table'] = true;
  2749. }
  2750. }
  2751. mod_page(_('Debug: Recent posts'), 'mod/debug/recent_posts.html', array('posts' => $posts, 'flood_posts' => $flood_posts));
  2752. }
  2753. function mod_debug_sql() {
  2754. global $config;
  2755. if (!hasPermission($config['mod']['debug_sql']))
  2756. error($config['error']['noaccess']);
  2757. $args['security_token'] = make_secure_link_token('debug/sql');
  2758. if (isset($_POST['query'])) {
  2759. $args['query'] = $_POST['query'];
  2760. if ($query = query($_POST['query'])) {
  2761. $args['result'] = $query->fetchAll(PDO::FETCH_ASSOC);
  2762. if (!empty($args['result']))
  2763. $args['keys'] = array_keys($args['result'][0]);
  2764. else
  2765. $args['result'] = 'empty';
  2766. } else {
  2767. $args['error'] = db_error();
  2768. }
  2769. }
  2770. mod_page(_('Debug: SQL'), 'mod/debug/sql.html', $args);
  2771. }
  2772. function mod_debug_apc() {
  2773. global $config;
  2774. if (!hasPermission($config['mod']['debug_apc']))
  2775. error($config['error']['noaccess']);
  2776. if ($config['cache']['enabled'] != 'apc')
  2777. error('APC is not enabled.');
  2778. $cache_info = apc_cache_info('user');
  2779. // $cached_vars = new APCIterator('user', '/^' . $config['cache']['prefix'] . '/');
  2780. $cached_vars = array();
  2781. foreach ($cache_info['cache_list'] as $var) {
  2782. if ($config['cache']['prefix'] != '' && strpos(isset($var['key']) ? $var['key'] : $var['info'], $config['cache']['prefix']) !== 0)
  2783. continue;
  2784. $cached_vars[] = $var;
  2785. }
  2786. mod_page(_('Debug: APC'), 'mod/debug/apc.html', array('cached_vars' => $cached_vars));
  2787. }