Compare commits

...

193 Commits

Author SHA1 Message Date
discomrade 1e80c4b724 Set default webm volume to 0.75 1 year ago
discomrade 81152e1392 Add overboard support to mod catalog 1 year ago
perdedora 0432d8d533 feat: edit pre-existing bans (#528) 1 year ago
discomrade ac037596ef Allow exclamation point after cite 1 year ago
discomrade 0596bf7124 Add default value for flood_cache 1 year ago
discomrade 036b48c78b Handle statistics case where query returns no posts 1 year ago
discomrade 8eeb7b1968 Replace deprecated raw tags 1 year ago
Majin Bejitto 4820ac5a94 fix "scandir should not need to sort in b.php (banner code)" 1 year ago
deysu 7229adeef5 Add banners as default functionality & display them on mod login / dashboard when enabled (#513) 1 year ago
清靈語 93d7bead55 Replace Google reCAPTCHA API's domain (#507) 1 year ago
runit e9af479521 I'd just like to interject for a moment. 2 years ago
Fred Brennan 35bafa52d7 Use ENT_QUOTES when converting UTF-8 to HTML (#448) 2 years ago
sshscp15 0f984f7299 Fix display warning/errors (#496) 2 years ago
27chan 0c35faa4ba Removed regex with possibiblity of XSS 2 years ago
sshscp15 e742811f32 fixes boardlist in catalog when in mod mode 2 years ago
sshscp15 7d5cd8111b fix mod.php. check if key is there and is valid 2 years ago
Fred Brennan a1e7478d90 Flip insane default for non-developers 2 years ago
Fred Brennan f57584dbf8 Add support for APC(u) 2 years ago
C Hatfield 3e53fa3a01 Moved hardcoded html filepaths into config file for extensibility (#354) 2 years ago
haruhianon609 20590d1aa5 Add yandex images as image identification option (#430) 2 years ago
Fredrick Brennan c1ebe99b4d Change illogical default of $config[force_body] 2 years ago
370chan 4d63ddcb27 Fix files not being saved in certain cases (#483) 2 years ago
bebyx d991acb6e3 Fix editing global pages for 7.4 (#487) 2 years ago
Junicchi a4a1ac7e70 fix Undefined index ip problem, fixes #434 2 years ago
ben2613 5a202f3674 Fix mobile browser hang issue when auto-reload by reducing DOM modification in forloop 2 years ago
sshscp15 10d5ceb556 simple catalog support for moderators 2 years ago
27chan 72aa19b3b4 Adjust css theme 2 years ago
27chan 268391f437 Updating information 2 years ago
discomrade fdff445469 Fix missing variable in auto-reload 2 years ago
discomrade 74df4aec50 Add options for auto-reload.js, remove orphaned function call 2 years ago
discomrade 316da513dc Remove update thread on scroll to bottom of page 2 years ago
discomrade f77f462910 Remove false requirement for banners enabled to use auto-reload.js 2 years ago
discomrade b01f5dce68 Disable broken, unknown-purpose styling on boxs and tables 2 years ago
discomrade 10ca647470 Make Ubuntu font local instead of Google API call 2 years ago
discomrade c09bb8e1da Add style to all a tags, not only links 2 years ago
discomrade 20a066581e Remove static position from dark.css desktop style to fix topbar not following page and padding above the top bar 2 years ago
discomrade 5d22e5bc2a Remove CSS external links, replace random filenames, move images into img folder 2 years ago
discomrade 1a8ec2d295 Add CSS from lainchan 2 years ago
discomrade 7bfac5913c Check file exists before unlinking in 'file_unlink' 2 years ago
discomrade 38fe235d86 Check for file exif_stripped flag without accessing invalid key 2 years ago
discomrade 8bc467e259 Check temp file exists before unlinking when processing images 2 years ago
discomrade 9f855d7291 Add status.php for mobile API 2 years ago
discomrade 374770e79c Enable compatibility with Twig 2.x 2 years ago
discomrade d6256875fc Replace deprecated Twig clearTemplateCache() code 2 years ago
discomrade 66fe31446b Replace deprecated Twig 'filter' with 'apply' 2 years ago
discomrade e231fbb644 Check files exist before deleting when rebuilding 2 years ago
discomrade 6afaa1b0a3 Unset $matches['token'] after use 2 years ago
discomrade 179dd0a828 Handle empty page key index properly 2 years ago
discomrade 2b054e9f34 Prevent filling debug parameter when debug is turned off in config 2 years ago
discomrade 7b3fccfabd Defer youtube.js 2 years ago
discomrade c5a1c43919 Add labels to form input elements 2 years ago
discomrade 0bca2b022c Add filename as alt text to images 2 years ago
discomrade fbce45b2ee Fix obsoleted getJSON(...).error function 2 years ago
discomrade 94f91fce30 Add frame around forced anon options 2 years ago
discomrade 318f7ff120 Remove some desktop CSS 2 years ago
discomrade bfff0e2e56 Fix titles on Rand and Semirand themes 2 years ago
discomrade e87f16175b Reduce padding after forms 2 years ago
discomrade 77fa071ff5 Reset auto-reload.js countdown after post 2 years ago
discomrade 009af18519 Remove redundant Posting Mode banner 2 years ago
discomrade 181fcdd774 Change layout of view IP page 2 years ago
discomrade 49c21a3b19 Don't sort Random order in catalog 2 years ago
discomrade e8bba537f1 Add class to overboard show/hide board buttons 2 years ago
discomrade 1cd27f249c Update Tor blocklist 2 years ago
discomrade 923b208430 Change order of static page markup methods 2 years ago
discomrade 0e8cc7a66d Extract footer into a template 2 years ago
discomrade 710db4e749 Enable configurations by default: moving, subject in title, hourly stats, anti-bump-flood 2 years ago
discomrade 8e2b779c30 Change dates to ISO 8601-like, fix deprecation of strfdate() in PHP 8.1, fix non-GMT dates 2 years ago
discomrade e843576519 Add randomized banners 2 years ago
discomrade 32aa1802c7 Disable jumping down to the bottom after a new post 2 years ago
discomrade 9b29b30330 Set auto-reload.js to minimum countdown delay after making a post 2 years ago
discomrade 7324135025 Add PPH and IPs stats table (doesn't require JS) 2 years ago
discomrade 5b74355046 Collapse post form by default on index page. 2 years ago
discomrade 6645a8f989 Add meaningful error codes for captchas 2 years ago
discomrade 175022f519 Move PHP session into captcha code to prevent unnecessary cookies. 2 years ago
discomrade 0afbeac7bd Remove captcha JavaScript call for local captchas, add automatic and manual reloading of captchas 2 years ago
discomrade 58d719c7f9 Allow local securimage captcha to be run without cURL library 2 years ago
discomrade 070ae35161 Remove redundant subject check in catalog-search.js 2 years ago
discomrade 88c25af7cf Fix bumplock not appearing on threads and catalog 2 years ago
discomrade 288f09c1f9 Remove unused functions from inc/function.php 2 years ago
discomrade e1eb910ef4 Add required include for mod_board_log in log.php 2 years ago
discomrade 8cd2ad7b10 Drop remaining MySQL <5.5.3 support (utf8 without multibyte) 2 years ago
discomrade a8115d2e53 Remove wizchan remnants 2 years ago
discomrade 6fb529e745 Add option to force a subject when posting threads 2 years ago
discomrade 17ed5819f6 Add maximum text length to ban appeals. 2 years ago
discomrade d5284f3a34 Limit maximum email length to 30 to prevent database error 2 years ago
discomrade 321c742124 Prevent raw HTML editing a post with tinyboard modifiers (e.g. user flags) from inserting them into the visible post body 2 years ago
discomrade 6bcf20804b Solve dynamically loaded threads not being filtered. Possibly a hack-fix. 2 years ago
discomrade 8d3b5c86ab Update jQuery libraries 2 years ago
discomrade f6b4eb9040 Fix multiple issues with anti_bump_flood 2 years ago
discomrade 354f70fa97 Improve telegrams + refactor rebuilding after posts, finish reply requests before rebuilding index 2 years ago
discomrade 8dcdf00d18 Allow users to disable and clear (You)s 2 years ago
discomrade 341729abff Add IP address link to ban appeals 2 years ago
discomrade e0be553970 Quick hack to prevent oversized PDF thumbnails. 2 years ago
discomrade 20a86fd50e Only add drag-drop handlers to dropzone, not the entire document 2 years ago
discomrade e3ee4b0ce9 Add default ban length of 1 hour instead of permanent ban 2 years ago
discomrade a13330a77a Make bans default to board of offending post 2 years ago
discomrade 5df46e0c77 Improve page titles and social media cards 2 years ago
tmp-server 7876df0a50 Fix new thread notifications title, fix thread body breaking script 2 years ago
discomrade 5fb6acf047 Use stored title instead of document.title in notifications 2 years ago
discomrade 7535aad664 Make all notifications show, not just the first unread 2 years ago
discomrade 4cf0c59eee Separate mod auto-update settings from normal settings 2 years ago
discomrade bd120bbda7 Clean up duplicate code for IPv6 2 years ago
discomrade d606deba0e Add IPv6 support for DNSBL 2 years ago
Your Name 74ecc4fe17 Automatically embeds with Tor if on the hidden site. Adds clearnet and onion invidious embed. 3 years ago
discomrade 485c077187 Minor flag javascript fixes 3 years ago
discomrade 92d7181932 Enable filtering by flag name 3 years ago
discomrade 81bc9d23dd Enable posting from catalog without JS 3 years ago
discomrade d3d3e2e969 Add catalog thumbnails for file placeholder, handle the first of multiple OP images being deleted 3 years ago
discomrade b1004367b0 Add noindex meta tag to 'Last 50 posts' thread pages 3 years ago
discomrade ea6f004022 Adjust board search on index page 3 years ago
discomrade 151261fa80 Fix post-filter.js row highlighting making text ineligible 3 years ago
discomrade 39d2ad5212 Improve citing in replies 3 years ago
discomrade f7478bc840 Remove newlines and modifiers from thread title 3 years ago
discomrade 98cfb3ad2b Add mod recents auto-reload 3 years ago
discomrade ec917def7c Block D+ and D++ on special IP addresses 3 years ago
discomrade 0ac2b7ec70 Show ban appeal count on mod dashboard 3 years ago
discomrade 49b5fb75a6 Fix 387ebe9c0c for reports, extract limit to variable, give more descriptive error name 3 years ago
discomrade 1f155498b0 Fix combining character set, allow limit for combining characters 3 years ago
discomrade b91d8694a9 Prevent double-encoding of HTML entities in [code] 3 years ago
discomrade 617a924826 Recalculate filesize after stripping metadata 3 years ago
Pietro Carrara 43cc3fd465 Check if file fields are set on the API 3 years ago
Pietro Carrara cb8c7ddd2e Load board config while generating json 3 years ago
Pietro Carrara 68dc6bb495 Check fields, use config paths 3 years ago
Pietro Carrara 3b9494e362 Use thumb, not thumb_path 3 years ago
Pietro Carrara 2b914f6145 Modify api image fields 3 years ago
discomrade 49e1ab9a87 Extend maxlength of IP for IPv6 subnets 3 years ago
discomrade 04c41a4f4a Fix image hover for catalog 3 years ago
discomrade 6dfedaeb4c Enable download with filename without JavaScript 3 years ago
discomrade 713b08b689 Redirect to thread after deleting child post 3 years ago
discomrade b1629a33e9 Fix thumb_ext being ignored 3 years ago
discomrade e310803f95 Create flag preview 3 years ago
discomrade 0fdf53319a Move NPFchan's scripts into tools 3 years ago
towards-a-new-leftypol 19e216b924 Display YouTube video thumbnail (with local url) in embeds 3 years ago
towards-a-new-leftypol eb0a8ac991 catalog page - rewrite youtube url to be local 3 years ago
PVNFU-28 a54e286b66 Update style.css 3 years ago
towards-a-new-leftypol 146f7a341a Improve robustness when moving / merging posts and threads. 3 years ago
nonmakina ba235ac2bd Introduces the youtube embed changes. 3 years ago
nonmakina 95fc4a1f42 Adds the proposed ffmpeg.php changes 3 years ago
nonmakina c3de0f1feb Removes rebuilds after each deleted post when deleting by IP. 3 years ago
Dedushka d55dd98e68 Protect IPv6 IPs in public moderation logs. 3 years ago
Pietro Carrara 38065ff02c Add unique_ips field to the API 3 years ago
nonmakina 971dd24441 Fixes inline-expanding.js to allow clickable pdf thumbnails 3 years ago
Pietro Carrara 7002bc7698 Add warning and ban messages to the API 3 years ago
Pietro Carrara f60b4ea6b3 Enable API flags for user_flag and country_flags 3 years ago
Pietro Carrara 0b8c4b49b1 Enable non-country flags on the JSON api 3 years ago
Barbara Pitt 22246a2961 Fix sticky posts to top of catalog 3 years ago
Couchy 2a1d99a4bb Fix rejected images not being deleted 5 years ago
Benjamin Southall d63a54cf7f Add apple touch icon support and configuration option 5 years ago
Benjamin Southall 768fd905fe Add support for gopher url:// markup as hyperlinks 5 years ago
Benjamin Southall 3c89b30861 Allow user to set the boardlist to point to catalog links instead of regular index page in Options. 5 years ago
Equus2 14972d15e7 Fix auto reload bug when inlining replies 5 years ago
fallenPineapple 9253f61298 Update install.sql 5 years ago
H1K1CH4N 6906318d4e Fixed deletion of mod archive thread. 6 years ago
PupperWoff 6d80285513 Add image spoiler flag to JSON API 6 years ago
Benjamin Southall 66793eee6d Add missing merge moderation template 6 years ago
PupperWoff e754a6fead Added: Archive moves json files to archive too 6 years ago
PupperWoff 9406a72d7d Bugfix : If you moved a reply to same board it deleted the image. 6 years ago
PupperWoff fda9ae12c0 FileSelector isn't working on iOS so prevented it from loading on those devices 6 years ago
PupperWoff 37b37f0e04 BugFix: Removed markup that was shown in title of thread. 6 years ago
PupperWoff d568e5a311 BugFix - Shadow Delete - if spoilered image getting "extension" index error 6 years ago
PupperWoff 060ae1e547 Added option for mods to send threads directly to archive (removing them from catalog) 6 years ago
Equus 902dff94c1 Fix char counter bug 6 years ago
PupperWoff b72b335f3e Added function: Option to disable GETs on boards 6 years ago
PupperWoff 81bdcfdf6d Added multiple file select dialog for post form 7 years ago
Benjamin Southall 67ed3c9f10 Changes to local-time.js storage boolean checks 7 years ago
PupperWoff 9083090c46 Changed archive list to order by thread ID and not archive time (added option to change it via function call) 7 years ago
PupperWoff 787fa11309 Small fix to config for - auto send to feature archive 7 years ago
PupperWoff c76a1bd3ef Added multiple dice roll. Automatic feature archive if op uses configured trip. Increaced post char limit 7 years ago
Equus ee2c58724f Comment out placeholder example code in user-js and user-css 7 years ago
PupperWoff 2b05fd12f4 Added Feature - Statistics 7 years ago
PupperWoff fca78a3a04 BugFix - Small BugFix to Archive and Voting (adding line of text) 7 years ago
PupperWoff dd0847b751 Added Archive Feature - Added feature allowing Users to vote for threads they want Featured in the Archive 7 years ago
Arvo Huru 4989762458 Added Feature - Different wait-time for OP and reply deletion. 7 years ago
Arvo Huru 008273f707 Small BugFix - Small fix to Shadow Delete 7 years ago
Arvo Huru 371d87e840 Bugfix - Small bugfix to Shadow Delete 7 years ago
Arvo Huru ce750b8a20 Added Function - Mod can shadow delete if they are unsure 7 years ago
Equus 7a5b2aef35 Add a link to the board index on the catalog page 7 years ago
animepony 177c87d465 More accurate string for recent shadow deleted posts 7 years ago
PupperWoff 7ffa8a00f9 Added Feature - Seperate Archive Mods can put posts in - only mods can see this archive 7 years ago
Equus c500b5c6e9 Fix incorrect parenthesis and missing arguments 7 years ago
fallenPineapple 9eacd5def2 Update with archive and shadow tables. 7 years ago
fallenPineapple 47b3756762 Integrate shadow into install files. 7 years ago
PupperWoff 0778052ef7 Small Update - Updated Rebuild to archive threads/running cleanup before building index, Also added php_timeout to update scripts. 7 years ago
PupperWoff b359d89de5 BugFix - Bugfix to Archive Code 7 years ago
PupperWoff 73a0547fe6 Added Function to Shadow Delete - You can set a mod level for automatically permanently delete posts that mod deletes 7 years ago
PupperWoff 45b00c76b5 Update to Shadow Delete - Option to view deleted threads, and view recent shadow deleted posts and threads (threads are listed first - posts in these threads are not shown in recent list - to see these posts you need to view whole shadow deleted thread) 7 years ago
PupperWoff 93d316f0fc BugFix - More stupid errors in code I had to fix 7 years ago
PupperWoff c353e1284d Added Feature - Shadow Delete of Posts and Threads (can be restored if deleted by mod) - Still need a way to see and restore deleted threads 7 years ago
PupperWoff e74c54601e BugFix - Small bugfix to Archive Code 7 years ago
PupperWoff 932b0b9e63 Bugfix - Bugfix to Archive code 7 years ago
PupperWoff 16c4f2719a Added Feature - Cutoff for deletion of threads by OP if they have a certain number of replies. 7 years ago
PupperWoff e712b12ad9 Added Feature - Warn and Delete [W&D] function 7 years ago
PupperWoff 8544714057 Update and BugFix - Added Links to Archive and Featured Archive and Reload to Thread Pages and Catalog - Added Announcements to Catalog - And Small Bugfix to Announcements where link to [Show all] was wrong 7 years ago
  1. 14
      README.md
  2. 19
      b.php
  3. 16
      banner.php
  4. 4
      composer.json
  5. 4
      composer.lock
  6. 17
      inc/announcements.php
  7. 78
      inc/api.php
  8. 185
      inc/archive.php
  9. 11
      inc/bans.php
  10. 11
      inc/cache.php
  11. 385
      inc/config.php
  12. 16
      inc/database.php
  13. 18
      inc/display.php
  14. 2
      inc/filters.php
  15. 453
      inc/functions.php
  16. 6
      inc/image.php
  17. 0
      inc/instance-config.php
  18. 40
      inc/lib/twig/extensions/Extension/I18n.php
  19. 5
      inc/lib/twig/extensions/Extension/Tinyboard.php
  20. 97
      inc/lib/twig/extensions/Node/Trans.php
  21. 73
      inc/lib/twig/extensions/TokenParser/Trans.php
  22. 165
      inc/lib/webm/ffmpeg.php
  23. 648
      inc/mod/pages.php
  24. 436
      inc/shadow-delete.php
  25. 188
      inc/statistics.php
  26. 6
      inc/template.php
  27. 116
      install.php
  28. 91
      install.sql
  29. 24
      js/ajax.js
  30. 121
      js/auto-reload.js
  31. 33
      js/boardlist-catalog-links.js
  32. 5
      js/catalog-search.js
  33. 10
      js/charcount.js
  34. 10
      js/chartist/chartist.min.js
  35. 4
      js/comment-toolbar.js
  36. 8
      js/compact-boardlist.js
  37. 42
      js/download-original.js
  38. 4
      js/expand-all-images.js
  39. 66
      js/file-selector.js
  40. 45
      js/flag-preview.js
  41. 11
      js/forced-anon.js
  42. 4
      js/gallery-view.js
  43. 2
      js/id_colors.js
  44. 64
      js/image-hover.js
  45. 2
      js/infinite-scroll.js
  46. 3
      js/inline-expanding.js
  47. 8
      js/jquery-ui.custom.min.js
  48. 6
      js/jquery.min.js
  49. 2
      js/keyboard-shortcuts.js
  50. 4
      js/live-index.js
  51. 6
      js/local-time.js
  52. 9
      js/mod/ban-list.js
  53. 274
      js/mod/recent-posts-auto-reload.js
  54. 2
      js/mod/recent-posts.js
  55. 4
      js/multi-image.js
  56. 2
      js/no-animated-gif.js
  57. 4
      js/options.js
  58. 2
      js/options/user-css.js
  59. 2
      js/options/user-js.js
  60. 25
      js/post-filter.js
  61. 4
      js/quick-post-controls.js
  62. 12
      js/quick-reply.js
  63. 2
      js/save-user_flag.js
  64. 2
      js/show-op.js
  65. 24
      js/show-own-posts.js
  66. 4
      js/thread-stats.js
  67. 4
      js/thread-watcher.js
  68. 2
      js/toggle-images.js
  69. 2
      js/toggle-locked-threads.js
  70. 2
      js/treeview.js
  71. 162
      js/watch.js
  72. 2
      js/webm-settings.js
  73. 82
      js/youtube.js
  74. 1
      log.php
  75. 29
      mod.php
  76. 375
      post.php
  77. 4
      report.php
  78. 6
      search.php
  79. 42
      securimage.php
  80. BIN
      static/banners/defaultbanner.png
  81. BIN
      static/banners/placeholder.png
  82. 60
      status.php
  83. 371
      stylesheets/500px.css
  84. 482
      stylesheets/8ch.cyber.css
  85. 175
      stylesheets/bunker_like.css
  86. 1
      stylesheets/chartist/chartist.min.css
  87. 96
      stylesheets/cupcake.css
  88. 277
      stylesheets/cyberpunk.css
  89. 107
      stylesheets/dark.css
  90. 242
      stylesheets/dark_red.css
  91. 10
      stylesheets/dark_roach.css
  92. 398
      stylesheets/dark_solarized.css
  93. 59
      stylesheets/dead.css
  94. BIN
      stylesheets/delete.css
  95. 212
      stylesheets/demain.css
  96. 213
      stylesheets/demain_large.css
  97. 226
      stylesheets/fauux.css
  98. 4
      stylesheets/ferus.css
  99. BIN
      stylesheets/fonts/DejaVuSansMono.ttf
  100. BIN
      stylesheets/fonts/OpenSans-Light-webfont.eot

14
README.md

@ -3,6 +3,8 @@ vichan - A lightweight and full featured PHP imageboard.
**Vichan has next to no active development<!--, however you can still pay for support. Basic support costs $40/hr, and is only payable in BTC. New features depend on what you want. Email COPYPASTE &lt;AT&gt; KITTENS &lt;DOT&gt; PH if you're interested&mdash;Vichan forks such as OpenIB are included in this offer-->.**
As of 29 August 2022, though, it supports PHP8.1.
About
------------
vichan is a free light-weight, fast, highly configurable and user-friendly
@ -37,16 +39,18 @@ Requirements
3. [mbstring](http://www.php.net/manual/en/mbstring.installation.php)
4. [PHP GD](http://www.php.net/manual/en/intro.image.php)
5. [PHP PDO](http://www.php.net/manual/en/intro.pdo.php)
6. A Unix-like OS, preferrably FreeBSD or Linux
6. A Unix-like OS, preferrably FreeBSD or GNU/Linux
We try to make sure vichan is compatible with all major web servers. vichan does not include an Apache ```.htaccess``` file nor does it need one.
We try to make sure vichan is compatible with all major web servers. vichan does not include an Apache `.htaccess` file nor does it need one.
### Recommended
1. MySQL/MariaDB server >= 5.5.3
2. ImageMagick (command-line ImageMagick or GraphicsMagick preferred).
3. [APC (Alternative PHP Cache)](http://php.net/manual/en/book.apc.php),
[XCache](http://xcache.lighttpd.net/) or
[Memcached](http://www.php.net/manual/en/intro.memcached.php)
3. ~~[APC (Alternative PHP Cache)](http://php.net/manual/en/book.apc.php)~~,
[APCu (Alternative PHP Cache)](http://php.net/manual/en/book.apcu.php),
[XCache](http://xcache.lighttpd.net/),
[Memcached](http://www.php.net/manual/en/intro.memcached.php) or
[Redis](https://redis.io/docs/about/)
Contributing
------------

19
b.php

@ -0,0 +1,19 @@
<?php
$dir = "static/banners/";
$files = scandir($dir, SCANDIR_SORT_NONE);
$images = array_diff($files, array('.', '..'));
$name = $images[array_rand($images)];
// open the file in a binary mode
$fp = fopen($dir . $name, 'rb');
// send the right headers
header('Cache-Control: no-cache, no-store, must-revalidate'); // HTTP 1.1
header('Pragma: no-cache'); // HTTP 1.0
header('Expires: 0'); // Proxies
header('Content-Type: ' . $fp['type']);
header('Content-Length: ' . $fp['bytes']);
// dump the picture and stop the script
fpassthru($fp);
exit;
?>

16
banner.php

@ -0,0 +1,16 @@
<?php
function getBannerSrc(){
$files = scandir(__dir__.'/static/banners/');
$files = array_diff($files, array('.', '..'));
return $files[array_rand($files)];
}
$filename = getBannerSrc();
$filename = "static/banners/" . $filename;
$fp = fopen($filename, 'rb');
header("Content-Type: image/png");
header("Content-Length: " . filesize($filename));
fpassthru($fp);
?>

4
composer.json

@ -6,7 +6,7 @@
"ext-mbstring": ">=5.4",
"ext-gd": ">=5.4",
"ext-pdo": ">=5.4",
"twig/twig": "^1.44.2",
"twig/twig": "^2.14.11",
"lifo/ip": "^1.0",
"gettext/gettext": "^1.0",
"mrclay/minify": "^2.1.6",
@ -28,6 +28,8 @@
"inc/polyfill.php",
"inc/announcements.php",
"inc/archive.php",
"inc/shadow-delete.php",
"inc/statistics.php",
"inc/functions.php"
]
},

4
composer.lock

@ -348,7 +348,7 @@
},
{
"name": "twig/twig",
"version": "v1.44.5",
"version": "v2.14.11",
"source": {
"type": "git",
"url": "https://github.com/twigphp/Twig.git",
@ -410,7 +410,7 @@
],
"support": {
"issues": "https://github.com/twigphp/Twig/issues",
"source": "https://github.com/twigphp/Twig/tree/v1.44.5"
"source": "https://github.com/twigphp/Twig/tree/v2.14.11"
},
"funding": [
{

17
inc/announcements.php

@ -64,7 +64,7 @@ class Announcements {
$announcements = $query->fetchAll(PDO::FETCH_ASSOC);
foreach ($announcements as &$announce) {
$announce['date_formated'] = strftime($config['announcements']['date_format'], $announce['date']);
$announce['date_formated'] = gmdate($config['announcements']['date_format'], $announce['date']);
}
$announcements_short = Element('announcements.html', array(
@ -78,7 +78,7 @@ class Announcements {
static public function buildAnnouncementPages() {
global $config;
// Generate page for full list of announcements
// Generate pages for full list of announcements
if($config['announcements']['page'])
{
// Generate JSON file for full list of announcements
@ -89,7 +89,7 @@ class Announcements {
$announcements = $query->fetchAll(PDO::FETCH_ASSOC);
foreach ($announcements as &$announce) {
$announce['date_formated'] = strftime($config['announcements']['date_format'], $announce['date']);
$announce['date_formated'] = gmdate($config['announcements']['date_format'], $announce['date']);
}
// Generate page for full list of announcements
@ -105,12 +105,21 @@ class Announcements {
'announcements' => $announcements,
'mod' => false,
'token_json' => false,
'uri_json' => $config['root'] . $config['announcements']['file_json'],
))
));
file_write($config['dir']['home'] . $config['announcements']['page_html'], $announcement_page);
}
}
static public function stream_json($out = false, $filter_staff = false, $date_format = "%m/%d/%Y", $count = false) {
$query = query("SELECT ``announcements``.*, `username` FROM ``announcements``
LEFT JOIN ``mods`` ON ``mods``.`id` = `creator`
ORDER BY `date` DESC" . (($count === false)?"":" LIMIT " . (int)$count)) or error(db_error($query));
$announcements = $query->fetchAll(PDO::FETCH_ASSOC);
}
/* Might be used later for mobile API
static public function stream_json($out = false, $filter_staff = false, $date_format, $count = false) {
$query = query("SELECT ``announcements``.*, `username` FROM ``announcements``
@ -128,7 +137,7 @@ class Announcements {
if($filter_staff)
$announce['username'] = '?';
$announce['date_formated'] = strftime($date_format, $announce['date']);
$announce['date_formated'] = gmdate($date_format, $announce['date']);
$json = json_encode($announce);
$out ? fputs($out, $json) : print($json);

78
inc/api.php

@ -43,8 +43,9 @@ class Api {
);
$this->fileFields = array(
'thumbheight' => 'tn_h',
'thumbwidth' => 'tn_w',
'file_id' => 'id',
'type' => 'mime',
'extension' => 'ext',
'height' => 'h',
'width' => 'w',
'size' => 'fsize',
@ -88,17 +89,44 @@ class Api {
}
private function translateFile($file, $post, &$apiPost) {
global $config;
$this->translateFields($this->fileFields, $file, $apiPost);
$apiPost['filename'] = @substr($file->name, 0, strrpos($file->name, '.'));
$dotPos = strrpos($file->file, '.');
$apiPost['ext'] = substr($file->file, $dotPos);
$apiPost['tim'] = substr($file->file, 0, $dotPos);
// Add spoiler flag to API data
$apiPost['spoiler'] = 0;
if(isset($file->thumb) && $file->thumb == 'spoiler')
$apiPost['spoiler'] = 1;
if (isset ($file->hash) && $file->hash) {
$apiPost['md5'] = base64_encode(hex2bin($file->hash));
}
else if (isset ($post->filehash) && $post->filehash) {
$apiPost['md5'] = base64_encode(hex2bin($post->filehash));
}
$apiPost['file_path'] = $config['uri_img'] . $file->file;
// Pick the correct thumbnail
if (isset($file->thumb) && $file->thumb === 'spoiler') {
// Spoiler
$apiPost['thumb_path'] = $config['root'] . $config['spoiler_image'];
} else if (!isset($file->thumb) || $file->thumb === 'file') {
// Default file format image
$thumbFile = $config['file_icons']['default'];
if (isset($file->extension) && isset($config['file_icons'][$file->extension])) {
$thumbFile = $config['file_icons'][$file->extension];
}
$apiPost['thumb_path'] = $config['root'] . sprintf($config['file_thumb'], $thumbFile);
} else {
// The file's own thumbnail
$apiPost['thumb_path'] = $config['uri_thumb'] . $file->thumb;
}
}
private function translatePost($post, $threadsPage = false) {
@ -110,16 +138,27 @@ class Api {
if (isset($config['poster_ids']) && $config['poster_ids']) $apiPost['id'] = poster_id($post->ip, $post->thread, $board['uri']);
if ($threadsPage) return $apiPost;
// Handle country field
if (isset($post->body_nomarkup) && $this->config['country_flags']) {
// Load board info
if (isset($post->board)) {
openBoard($post->board);
}
// Handle special fields
if (isset($post->body_nomarkup) && ($this->config['country_flags'] || $this->config['user_flag'])) {
$modifiers = extract_modifiers($post->body_nomarkup);
if (isset($modifiers['flag']) && isset($modifiers['flag alt']) && preg_match('/^[a-z]{2}$/', $modifiers['flag'])) {
$country = strtoupper($modifiers['flag']);
if (isset($modifiers['flag']) && isset($modifiers['flag alt']) && preg_match('/^[1-9a-z_-]{2,}$/', $modifiers['flag'])) {
$country = strtolower($modifiers['flag']);
if ($country) {
$apiPost['country'] = $country;
$apiPost['country_name'] = $modifiers['flag alt'];
}
}
if (isset($modifiers['warning message'])) {
$apiPost['warning_msg'] = $modifiers['warning message'];
}
if (isset($modifiers['ban message'])) {
$apiPost['ban_msg'] = $modifiers['ban message'];
}
}
if ($config['slugify'] && !$post->thread) {
@ -127,21 +166,13 @@ class Api {
}
// Handle files
// Note: 4chan only supports one file, so only the first file is taken into account for 4chan-compatible API.
if (isset($post->files) && $post->files && !$threadsPage) {
$file = $post->files[0];
$this->translateFile($file, $post, $apiPost);
if (sizeof($post->files) > 1) {
$extra_files = array();
foreach ($post->files as $i => $f) {
if ($i == 0) continue;
$extra_file = array();
$this->translateFile($f, $post, $extra_file);
$extra_files[] = $extra_file;
}
$apiPost['extra_files'] = $extra_files;
$apiPost['files'] = [];
foreach ($post->files as $f) {
$file = array();
$this->translateFile($f, $post, $file);
$apiPost['files'][] = $file;
}
}
@ -158,6 +189,13 @@ class Api {
$apiPosts['posts'][] = $this->translatePost($p, $threadsPage);
}
// Count unique IPs
$ips = array($thread->ip);
foreach ($thread->posts as $p) {
$ips[] = $p->ip;
}
$apiPosts['posts'][0]['unique_ips'] = count(array_unique($ips));
return $apiPosts;
}

185
inc/archive.php

@ -16,7 +16,7 @@ class Archive {
return;
// Check if it is a thread
$thread_query = prepare(sprintf("SELECT `thread`, `subject`, `body_nomarkup` FROM ``posts_%s`` WHERE `id` = :id", $board['uri']));
$thread_query = prepare(sprintf("SELECT `thread`, `subject`, `body_nomarkup`, `trip` FROM ``posts_%s`` WHERE `id` = :id", $board['uri']));
$thread_query->bindValue(':id', $thread_id, PDO::PARAM_INT);
$thread_query->execute() or error(db_error($thread_query));
$thread_data = $thread_query->fetch(PDO::FETCH_ASSOC);
@ -52,6 +52,9 @@ class Archive {
// Remove Post Form from HTML (First Form)
$thread_file_content = preg_replace("/<form name=\"post\"(.*?)<\/form>/i", "", $thread_file_content);
// Refix archive link that will be wrong
$thread_file_content = str_replace(sprintf('href="/' . $config['board_path'] . $config['dir']['archive'] . $config['dir']['archive'], $board['uri']), sprintf('href="/' . $config['board_path'] . $config['dir']['archive'], $board['uri']), $thread_file_content);
// Remove Form from HTML
$thread_file_content = preg_replace("/<form(.*?)>/i", "", $thread_file_content);
$thread_file_content = preg_replace("/<\/form>/i", "", $thread_file_content);
@ -65,6 +68,16 @@ class Archive {
@file_put_contents($board['dir'] . $config['dir']['archive'] . $config['dir']['res'] . sprintf($config['file_page'], $thread_id), $thread_file_content, LOCK_EX);
}
// Copy json file to Archive
// Read Content of Json file
$json_file_content = @file_get_contents($board['dir'] . $config['dir']['res'] . $thread_id . ".json");
// Replace links and posting mode to Archived
$json_file_content = str_replace(substr($board['dir'], 0, -1) . '\/' . substr($config['dir']['res'], 0, -1), substr($board['dir'], 0, -1) . '\/' . substr($config['dir']['archive'], 0, -1) . '\/' . substr($config['dir']['res'], 0, -1), $json_file_content);
// Write altered thread json to archive location
@file_put_contents($board['dir'] . $config['dir']['archive'] . $config['dir']['res'] . $thread_id . ".json", $json_file_content, LOCK_EX);
// Copy Images and Files Associated with Thread
if ($post['files']) {
foreach (json_decode($post['files']) as $i => $f) {
@ -82,16 +95,21 @@ class Archive {
}
// Insert Archive Data in Database
$query = prepare(sprintf("INSERT INTO ``archive_%s`` VALUES (:thread_id, :snippet, :time, :files, 0)", $board['uri']));
$query = prepare(sprintf("INSERT INTO ``archive_%s`` VALUES (:thread_id, :snippet, :lifetime, :files, 0, 0, 0)", $board['uri']));
$query->bindValue(':thread_id', $thread_id, PDO::PARAM_INT);
$query->bindValue(':snippet', $thread_data['snippet'], PDO::PARAM_STR);
$query->bindValue(':time', (!$config['archive']['lifetime'])?0:strtotime("+".$config['archive']['lifetime']." days"), PDO::PARAM_INT);
$query->bindValue(':lifetime', time(), PDO::PARAM_INT);
$query->bindValue(':files', json_encode($file_list));
$query->execute() or error(db_error($query));
// Check if Thread should be Auto Featured based on OP Trip
if(in_array($thread_data['trip'], $config['archive']['auto_feature_trips']))
self::featureThread($thread_id);
// Purge Threads that have timed out
if(!$config['archive']['cron_job'])
if(!$config['archive']['cron_job']['purge'])
self::purgeArchive();
// Rebuild Archive Index
@ -108,8 +126,14 @@ class Archive {
static public function purgeArchive() {
global $config, $board;
// If archive is set to live forever return
if(!$config['archive']['lifetime'])
return;
// Delete all static pages and files for archived threads that has timed out
$query = query(sprintf("SELECT `id`, `files` FROM ``archive_%s`` WHERE `lifetime` < %d AND `featured` = 0", $board['uri'], time())) or error(db_error());
$query = prepare(sprintf("SELECT `id`, `files` FROM ``archive_%s`` WHERE `lifetime` < :lifetime AND `featured` = 0 AND `mod_archived` = 0", $board['uri']));
$query->bindValue(':lifetime', strtotime("-" . $config['archive']['lifetime']), PDO::PARAM_INT);
$query->execute() or error(db_error($query));
while($thread = $query->fetch(PDO::FETCH_ASSOC)) {
// Delete Files
foreach (json_decode($thread['files']) as $f) {
@ -119,11 +143,22 @@ class Archive {
// Delete Thread
@unlink($board['dir'] . $config['dir']['archive'] . $config['dir']['res'] . sprintf($config['file_page'], $thread['id']));
// Delete Vote Data
$del_query = prepare("DELETE FROM ``votes_archive`` WHERE `board` = :board AND `thread_id` = :thread_id");
$del_query->bindValue(':board', $board['uri']);
$del_query->bindValue(':thread_id', $thread['id'], PDO::PARAM_INT);
$del_query->execute() or error(db_error($del_query));
}
// Delete Archive Entries
if($query->rowCount() != 0)
$query = query(sprintf("DELETE FROM ``archive_%s`` WHERE `lifetime` < %d AND `featured` = 0", $board['uri'], time())) or error(db_error());
if($query->rowCount() != 0) {
$query = prepare(sprintf("DELETE FROM ``archive_%s`` WHERE `lifetime` < :lifetime AND `featured` = 0 AND `mod_archived` = 0", $board['uri'])) or error(db_error());
$query->bindValue(':lifetime', strtotime("-" . $config['archive']['lifetime']), PDO::PARAM_INT);
$query->execute() or error(db_error($query));
modLog(sprintf("Purged %d archived threads due to expiration date", $query->rowCount()));
}
return $query->rowCount();
}
@ -134,14 +169,17 @@ class Archive {
// Feature thread and replies
static public function featureThread($thread_id) {
global $config, $board;
static public function featureThread($thread_id, $mod_archive = false) {
global $config, $board, $mod;
// If featuring of threads is turned off return
if(!$config['feature']['threads'])
if(!$mod_archive && !$config['feature']['threads'])
return;
// If mod archive of threads is turned off return
if($mod_archive && !$config['mod_archive']['threads'])
return;
$query = query(sprintf("SELECT `files` FROM ``archive_%s`` WHERE `id` = %d AND `featured` = 0", $board['uri'], (int)$thread_id)) or error(db_error());
$query = query(sprintf("SELECT `files` FROM ``archive_%s`` WHERE `id` = %d AND " . ($mod_archive?"`mod_archived`":"`featured`") . " = 0", $board['uri'], (int)$thread_id)) or error(db_error());
if(!$thread = $query->fetch(PDO::FETCH_ASSOC))
error($config['error']['invalidpost']);
@ -149,23 +187,29 @@ class Archive {
$thread_file_content = @file_get_contents($board['dir'] . $config['dir']['archive'] . $config['dir']['res'] . sprintf($config['file_page'], $thread_id));
// Replace links and posting mode to Archived
$thread_file_content = str_replace(sprintf('src="/' . $config['board_path'] . $config['dir']['archive'], $board['uri']), sprintf('src="/' . $config['board_path'] . $config['dir']['featured'], $board['uri']), $thread_file_content);
$thread_file_content = str_replace(sprintf('href="/' . $config['board_path'] . $config['dir']['archive'], $board['uri']), sprintf('href="/' . $config['board_path'] . $config['dir']['featured'], $board['uri']), $thread_file_content);
$thread_file_content = str_replace(sprintf('src="/' . $config['board_path'] . $config['dir']['archive'], $board['uri']), sprintf('src="/' . $config['board_path'] . ($mod_archive?$config['dir']['mod_archive']:$config['dir']['featured']), $board['uri']), $thread_file_content);
$thread_file_content = str_replace(sprintf('href="/' . $config['board_path'] . $config['dir']['archive'], $board['uri']), sprintf('href="/' . $config['board_path'] . ($mod_archive?$config['dir']['mod_archive']:$config['dir']['featured']), $board['uri']), $thread_file_content);
$thread_file_content = str_replace('Archived thread', 'Featured thread', $thread_file_content);
// Write altered thread HTML to archive location
@file_put_contents($board['dir'] . $config['dir']['featured'] . $config['dir']['res'] . sprintf($config['file_page'], $thread_id), $thread_file_content, LOCK_EX);
@file_put_contents($board['dir'] . ($mod_archive?$config['dir']['mod_archive']:$config['dir']['featured']) . $config['dir']['res'] . sprintf($config['file_page'], $thread_id), $thread_file_content, LOCK_EX);
foreach (json_decode($thread['files']) as $f) {
@copy($board['dir'] . $config['dir']['archive'] . $config['dir']['img'] . $f->file, $board['dir'] . $config['dir']['featured'] . $config['dir']['img'] . $f->file);
@copy($board['dir'] . $config['dir']['archive'] . $config['dir']['thumb'] . $f->thumb, $board['dir'] . $config['dir']['featured'] . $config['dir']['thumb'] . $f->thumb);
@copy($board['dir'] . $config['dir']['archive'] . $config['dir']['img'] . $f->file, $board['dir'] . ($mod_archive?$config['dir']['mod_archive']:$config['dir']['featured']) . $config['dir']['img'] . $f->file);
@copy($board['dir'] . $config['dir']['archive'] . $config['dir']['thumb'] . $f->thumb, $board['dir'] . ($mod_archive?$config['dir']['mod_archive']:$config['dir']['featured']) . $config['dir']['thumb'] . $f->thumb);
}
// Update DB entry
query(sprintf("UPDATE ``archive_%s`` SET `featured` = 1 WHERE `id` = %d", $board['uri'], (int)$thread_id)) or error(db_error());
query(sprintf("UPDATE ``archive_%s`` SET " . ($mod_archive?"`mod_archived`":"`featured`") . " = 1 WHERE `id` = %d", $board['uri'], (int)$thread_id)) or error(db_error());
// Add mod log entry
modLog(sprintf("Added thread #%d to " . ($mod_archive?"mod archive":"featured threads"), $thread_id));
// Rebuild Featured Index
self::buildFeaturedIndex();
// Rebuild Archive Index
self::buildArchiveIndex();
return true;
}
@ -176,31 +220,36 @@ class Archive {
static public function deleteFeatured($thread_id) {
global $config, $board;
static public function deleteFeatured($thread_id, $mod_archive = false) {
global $config, $board, $mod;
$query = query(sprintf("SELECT `id`, `files`, `lifetime` FROM ``archive_%s`` WHERE `featured` = 1", $board['uri'])) or error(db_error());
$query = query(sprintf("SELECT `id`, `files`, `lifetime` FROM ``archive_%s`` WHERE `featured` = 1 OR `mod_archived` = 1", $board['uri'])) or error(db_error());
if(!$thread = $query->fetch(PDO::FETCH_ASSOC))
error($config['error']['invalidpost']);
// Delete Files
foreach (json_decode($thread['files']) as $f) {
@unlink($board['dir'] . $config['dir']['featured'] . $config['dir']['img'] . $f->file);
@unlink($board['dir'] . $config['dir']['featured'] . $config['dir']['img'] . $f->thumb);
@unlink($board['dir'] . ($mod_archive?$config['dir']['mod_archive']:$config['dir']['featured']) . $config['dir']['img'] . $f->file);
@unlink($board['dir'] . ($mod_archive?$config['dir']['mod_archive']:$config['dir']['featured']) . $config['dir']['img'] . $f->thumb);
}
// Delete Thread
@unlink($board['dir'] . $config['dir']['featured'] . $config['dir']['res'] . sprintf($config['file_page'], $thread_id));
@unlink($board['dir'] . ($mod_archive?$config['dir']['mod_archive']:$config['dir']['featured']) . $config['dir']['res'] . sprintf($config['file_page'], $thread_id));
// Delete Entry in DB if it has timed out
if($thread['lifetime'] != 0 && $thread['lifetime'] < time())
query(sprintf("DELETE FROM ``archive_%s`` WHERE `id` = %d", $board['uri'], (int)$thread_id)) or error(db_error());
if($thread['lifetime'] != 0 && $thread['lifetime'] < strtotime("-" . $config['archive']['lifetime']))
query(sprintf("DELETE FROM ``archive_%s`` WHERE `id` = %d AND " . ($mod_archive?"`featured`":"`mod_archived`") . " = 0", $board['uri'], (int)$thread_id)) or error(db_error());
else
query(sprintf("UPDATE ``archive_%s`` SET `featured` = 0 WHERE `id` = %d", $board['uri'], (int)$thread_id)) or error(db_error());
query(sprintf("UPDATE ``archive_%s`` SET " . ($mod_archive?"`mod_archived`":"`featured`") . " = 0 WHERE `id` = %d", $board['uri'], (int)$thread_id)) or error(db_error());
// Add mod log entry
modLog(sprintf("Deleted thread #%d from " . ($mod_archive?"mod archive":"featured threads"), $thread_id));
// Rebuild Featured Index
self::buildFeaturedIndex();
// Rebuild Archive Index
self::buildArchiveIndex();
}
@ -232,8 +281,8 @@ class Archive {
if(!$config['archive']['threads'])
return;
$query = query(sprintf("SELECT `id`, `snippet`, `featured` FROM ``archive_%s`` WHERE `lifetime` > %d ORDER BY `lifetime` DESC", $board['uri'], time())) or error(db_error());
$archive = $query->fetchAll(PDO::FETCH_ASSOC);
// Get archive List
$archive = self::getArchiveList();
foreach($archive as &$thread)
$thread['archived_url'] = $config['dir']['res'] . sprintf($config['file_page'], $thread['id']);
@ -246,10 +295,10 @@ class Archive {
'boardlist' => createBoardList(false),
'title' => $title,
'subtitle' => "",
'boardlist' => createBoardlist(),
'body' => Element("mod/archive_list.html", array(
'config' => $config,
'thread_count' => $query->rowCount(),
'thread_count' => count($archive),
'board' => $board,
'archive' => $archive
))
));
@ -257,15 +306,39 @@ class Archive {
file_write($config['dir']['home'] . $board['dir'] . $config['dir']['archive'] . $config['file_index'], $archive_page);
}
static public function getArchiveList($featured = false, $mod_archive = false, $order_by_lifetime = false) {
global $config, $board;
$archive = false;
if($featured) {
$query = query(sprintf("SELECT `id`, `snippet`, `featured`, `mod_archived` FROM ``archive_%s`` WHERE `featured` = 1", $board['uri']) . ($order_by_lifetime?" ORDER BY `lifetime` DESC":" ORDER BY `id` DESC")) or error(db_error());
$archive = $query->fetchAll(PDO::FETCH_ASSOC);
} else if($mod_archive) {
$query = query(sprintf("SELECT `id`, `snippet`, `featured`, `mod_archived` FROM ``archive_%s`` WHERE `mod_archived` = 1", $board['uri']) . ($order_by_lifetime?" ORDER BY `lifetime` DESC":" ORDER BY `id` DESC")) or error(db_error());
$archive = $query->fetchAll(PDO::FETCH_ASSOC);
} else {
$query = prepare(sprintf("SELECT `id`, `snippet`, `featured`, `mod_archived`, `votes` FROM ``archive_%s`` WHERE `lifetime` > :lifetime", $board['uri']) . ($order_by_lifetime?" ORDER BY `lifetime` DESC":" ORDER BY `id` DESC"));
$query->bindValue(':lifetime', strtotime("-" . $config['archive']['lifetime']), PDO::PARAM_INT);
$query->execute() or error(db_error());
$archive = $query->fetchAll(PDO::FETCH_ASSOC);
}
return $archive;
}
static public function buildFeaturedIndex() {
global $config, $board;
// If featuring of threads is turned off return
if(!$config['feature']['threads'])
return;
$query = query(sprintf("SELECT `id`, `snippet`, `featured` FROM ``archive_%s`` WHERE `featured` = 1 ORDER BY `lifetime` DESC", $board['uri'])) or error(db_error());
$archive = $query->fetchAll(PDO::FETCH_ASSOC);
// Get featured archived threads
$archive = self::getArchiveList(true);
foreach($archive as &$thread)
$thread['featured_url'] = $config['dir']['res'] . sprintf($config['file_page'], $thread['id']);
@ -278,9 +351,9 @@ class Archive {
'boardlist' => createBoardList(false),
'title' => $title,
'subtitle' => "",
'boardlist' => createBoardlist(),
'body' => Element("mod/archive_featured_list.html", array(
'config' => $config,
'board' => $board,
'archive' => $archive
))
));
@ -288,6 +361,48 @@ class Archive {
file_write($config['dir']['home'] . $board['dir'] . $config['dir']['featured'] . $config['file_index'], $archive_page);
}
static public function addVote($board, $thread_id) {
global $config;
// Check if thread exist in archive
$query = prepare(sprintf("SELECT COUNT(*) FROM ``archive_%s`` WHERE `id` = %d", $board, $thread_id));
$query->execute() or error(db_error($query));
if ($query->fetchColumn(0) == 0)
error($config['error']['nonexistant']);
// Check if ip has voted
$query = prepare("SELECT COUNT(*) FROM ``votes_archive`` WHERE `board` = :board AND `thread_id` = :thread_id AND `ip` = :ip");
$query->bindValue(':board', $board, PDO::PARAM_STR);
$query->bindValue(':thread_id', $thread_id, PDO::PARAM_INT);
$query->bindValue(':ip', ($config['bcrypt_ip_addresses'] ? get_ip_hash($_SERVER['REMOTE_ADDR']) : $_SERVER['REMOTE_ADDR']), PDO::PARAM_STR);
// Error if already voted
$query->execute() or error(db_error($query));
if ($query->fetchColumn(0) != 0)
error($config['error']['already_voted']);
// Increase vote count for thread
query(sprintf("UPDATE ``archive_%s`` SET `votes` = `votes`+1 WHERE `id` = %d", $board, (int)$thread_id)) or error(db_error());
// Add ip to voted db
$query = prepare("INSERT INTO ``votes_archive`` VALUES (NULL, :board, :thread_id, :ip)");
$query->bindValue(':board', $board, PDO::PARAM_STR);
$query->bindValue(':thread_id', $thread_id, PDO::PARAM_INT);
$query->bindValue(':ip', ($config['bcrypt_ip_addresses'] ? get_ip_hash($_SERVER['REMOTE_ADDR']) : $_SERVER['REMOTE_ADDR']), PDO::PARAM_STR);
$query->execute() or error(db_error($query));
// Rebuild Archive Index
self::buildArchiveIndex();
}
}
?>
?>

11
inc/bans.php

@ -113,20 +113,22 @@ class Bans {
return array($ipstart, $ipend);
}
static public function find($ip, $board = false, $get_mod_info = false) {
static public function find($ip, $board = false, $get_mod_info = false, $banid = null) {
global $config;
$query = prepare('SELECT ``bans``.*' . ($get_mod_info ? ', `username`' : '') . ' FROM ``bans``
' . ($get_mod_info ? 'LEFT JOIN ``mods`` ON ``mods``.`id` = `creator`' : '') . '
WHERE
(' . ($board !== false ? '(`board` IS NULL OR `board` = :board) AND' : '') . '
(`ipstart` = :ip OR (:ip >= `ipstart` AND :ip <= `ipend`)))
(`ipstart` = :ip OR (:ip >= `ipstart` AND :ip <= `ipend`)) OR (``bans``.id = :id))
ORDER BY `expires` IS NULL, `expires` DESC');
if ($board !== false)
$query->bindValue(':board', $board, PDO::PARAM_STR);
$query->bindValue(':id', $banid);
$query->bindValue(':ip', inet_pton($ip));
$query->execute() or error(db_error($query));
$ban_list = array();
@ -318,6 +320,9 @@ class Bans {
$query->bindValue(':board', null, PDO::PARAM_NULL);
if ($post) {
if (!isset($board['uri']))
openBoard($post['board']);
$post['board'] = $board['uri'];
$query->bindValue(':post', json_encode($post));
} else

11
inc/cache.php

@ -44,6 +44,9 @@ class Cache {
case 'apc':
$data = apc_fetch($key);
break;
case 'apcu':
$data = apcu_fetch($key);
break;
case 'xcache':
$data = xcache_get($key);
break;
@ -95,6 +98,9 @@ class Cache {
case 'apc':
apc_store($key, $value, $expires);
break;
case 'apcu':
apcu_store($key, $value, $expires);
break;
case 'xcache':
xcache_set($key, $value, $expires);
break;
@ -130,6 +136,9 @@ class Cache {
case 'apc':
apc_delete($key);
break;
case 'apcu':
apcu_delete($key);
break;
case 'xcache':
xcache_unset($key);
break;
@ -156,6 +165,8 @@ class Cache {
return self::$cache->flush();
case 'apc':
return apc_clear_cache('user');
case 'apcu':
return apcu_clear_cache('user');
case 'php':
self::$cache = array();
break;

385
inc/config.php

@ -44,7 +44,7 @@
// Shows some extra information at the bottom of pages. Good for development/debugging.
$config['debug'] = false;
// For development purposes. Displays (and "dies" on) all errors and warnings. Turn on with the above.
$config['verbose_errors'] = true;
$config['verbose_errors'] = false;
// Warn about deprecations? See vichan-devel/vichan#363 and https://www.youtube.com/watch?v=9crnlHLVdno
$config['deprecation_errors'] = false;
@ -200,10 +200,12 @@
// Prevents most Tor exit nodes from making posts. Recommended, as a lot of abuse comes from Tor because
// of the strong anonymity associated with it.
// Example: $config['dnsbl'][] = 'another.blacklist.net';
// $config['dnsbl'][] = array('tor.dnsbl.sectoor.de', 1); //sectoor.de site is dead. the number stands for (an) ip adress(es) I guess.
// Replacement for sectoor.de
// efnet blocklist
$config['dnsbl'][] = array('rbl.efnetrbl.org', 4);
// Tor exit node blocklist
$config['dnsbl'][] = 'torexit.dan.me.uk';
// http://www.sorbs.net/using.shtml
// $config['dnsbl'][] = array('dnsbl.sorbs.net', array(2, 3, 4, 5, 6, 7, 8, 9));
@ -227,7 +229,7 @@
$config['dnsbl_exceptions'][] = '127.0.0.1';
// To prevent bump atacks; returns the thread to last position after the last post is deleted.
$config['anti_bump_flood'] = false;
$config['anti_bump_flood'] = true;
/*
* Introduction to Tinyboard's spam filter:
@ -328,18 +330,29 @@
$config['captcha']['enabled'] = false;
//New thread captcha
//Require solving a captcha to post a thread.
//Default off.
$config['new_thread_capt'] = false;
//Require solving a captcha to post a thread.
//Default off.
$config['new_thread_capt'] = false;
// Directly use the local securimage captcha (or another local file) instead of making cURL requests
// This probably increases speed and reduces potential misconfiguration issues.
$config['captcha']['local'] = true;
// Custom captcha get provider path (if not working get the absolute path aka your url.)
$config['captcha']['provider_get'] = '../inc/captcha/entrypoint.php';
$config['captcha']['provider_get'] = '/securimage.php';
// Custom captcha check provider path
$config['captcha']['provider_check'] = '../inc/captcha/entrypoint.php';
$config['captcha']['provider_check'] = '/securimage.php';
// Custom captcha extra field (eg. charset)
$config['captcha']['extra'] = 'abcdefghijklmnopqrstuvwxyz';
// Custom options for the local securimage captcha.
// See https://github.com/dapphp/securimage/blob/master/securimage.php#L236
$config['captcha']['securimage_options'] = array(
'send_headers' => false,
'no_exit' => true
);
// Ability to lock a board for normal users and still allow mods to post. Could also be useful for making an archive board
$config['board_locked'] = false;
@ -475,7 +488,9 @@
// outside the current board. This means that if you have a special flood condition for a specific board
// (contained in a board configuration file) which has a flood-time greater than any of those in the
// global configuration, you need to set the following variable to the maximum flood-time condition value.
// Set to -1 to disable.
// $config['flood_cache'] = 60 * 60 * 24; // 24 hours
$config['flood_cache'] = -1;
/*
* ====================
@ -484,19 +499,24 @@
*/
// Do you need a body for your reply posts?
$config['force_body'] = false;
$config['force_body'] = true;
// Do you need a body for new threads?
$config['force_body_op'] = true;
// Require an image for threads?
$config['force_image_op'] = true;
// Require a subject for threads?
$config['force_subject_op'] = false;
// Strip superfluous new lines at the end of a post.
$config['strip_superfluous_returns'] = true;
// Strip combining characters from Unicode strings (eg. "Zalgo").
// Strip combining characters from Unicode strings (eg. "Zalgo"). This will impact some non-English languages.
$config['strip_combining_chars'] = true;
// Maximum number of combining characters in a row allowed in Unicode strings so that they can still be used in moderation.
// Requires $config['strip_combining_chars'] = true;
$config['max_combining_chars'] = 3;
// Maximum post body length.
$config['max_body'] = 1800;
$config['max_body'] = 6000;
// Minimum post body length.
$config['min_body'] = 0;
// Minimum post body length for OPs.
@ -524,8 +544,12 @@
// Allow users to delete their own posts?
$config['allow_delete'] = true;
// How long after posting should you have to wait before being able to delete that post? (In seconds.)
$config['delete_time'] = 10;
// If thread has at least this number of replies, OP can't delete it anymore. (Set to false to disable)
$config['allow_delete_cutoff'] = 10;
// How long after posting should you have to wait before being able to delete an OP thread? (In seconds.)
$config['delete_time_op'] = 10;
// How long after posting should you have to wait before being able to delete a reply post? (In seconds.)
$config['delete_time_reply'] = 0;
// Reply limit (stops bumping thread when this is reached).
$config['reply_limit'] = 250;
@ -615,7 +639,7 @@
// Attach country flags to posts.
$config['country_flags'] = false;
// Allow the user to decide whether or not he wants to display his country
// Allow the user to decide whether or not he wants to display their country
$config['allow_no_country'] = false;
// Load all country flags from one file
@ -672,6 +696,9 @@
// How many ban appeals can be made for a single ban?
$config['ban_appeals_max'] = 1;
// Maximum text length of an appeal.
$config['appeal_text_max_length'] = 300;
// Show moderator name on ban page.
$config['show_modname'] = false;
@ -708,7 +735,7 @@
* ====================
*/
// "Wiki" markup syntax ($config['wiki_markup'] in pervious versions):
// "Wiki" markup syntax ($config['wiki_markup'] in previous versions):
$config['markup'][] = array("/'''(.+?)'''/", "<strong>\$1</strong>");
$config['markup'][] = array("/''(.+?)''/", "<em>\$1</em>");
$config['markup'][] = array("/\*\*(.+?)\*\*/", "<span class=\"spoiler\">\$1</span>");
@ -719,6 +746,9 @@
// "/```([a-z0-9-]{0,20})\n(.*?)\n?```\n?/s"
$config['markup_code'] = false;
// // Dice Roll Markup
// $config['markup'][] = array("/\[diceroll\](.+?)\[\/diceroll\]/s", "<img src='" . $config['root'] . "static/icons/dice.png' width=16 height=16/><b>\$1</b>");
// Repair markup with HTML Tidy. This may be slower, but it solves nesting mistakes. Tinyboard, at the
// time of writing this, can not prevent out-of-order markup tags (eg. "**''test**'') without help from
// HTML Tidy.
@ -894,6 +924,7 @@
$config['image_identification_imgops'] = true;
$config['image_identification_exif'] = true;
$config['image_identification_google'] = true;
$config['image_identification_yandex'] = true;
// Anime/manga search engine.
$config['image_identification_iqdb'] = false;
@ -943,6 +974,9 @@
// Number of reports you can create at once.
$config['report_limit'] = 3;
// Maximum character length of report.
$config['report_max_length'] = 100;
// Allow unfiltered HTML in board subtitle. This is useful for placing icons and links.
$config['allow_subtitle_html'] = false;
@ -958,7 +992,7 @@
$config['announcements']['show_count'] = 3;
// Date format for announcements.
$config['announcements']['date_format'] = '%m/%d/%Y';
$config['announcements']['date_format'] = 'Y-m-d';
// Create full announcements page.
$config['announcements']['page'] = true;
@ -977,11 +1011,16 @@
// Timezone to use for displaying dates/times.
$config['timezone'] = 'America/Los_Angeles';
// The format string passed to strftime() for displaying dates.
// http://www.php.net/manual/en/function.strftime.php
$config['post_date'] = '%m/%d/%y (%a) %H:%M:%S';
// Same as above, but used for "you are banned' pages.
$config['ban_date'] = '%A %e %B, %Y';
// The format string passed to date() for displaying dates.
// https://www.php.net/manual/en/datetime.format.php
$config['post_date'] = 'Y-m-d (D) H:i:s';
// The format string passed to JavaScript's strfdate() for displaying local dates.
// https://www.php.net/manual/en/function.strftime.php
$config['post_date_js'] = '%F (%a) %T';
// Same as above, but used for catalog tooltips.
$config['catalog_date'] = 'M d H:i';
// Same as above, but used for 'you are banned' pages.
$config['ban_date'] = 'l j F, Y';
// The names on the post buttons. (On most imageboards, these are both just "Post").
$config['button_newtopic'] = _('New Topic');
@ -993,7 +1032,10 @@
$config['poster_id_length'] = 5;
// Show thread subject in page title.
$config['thread_subject_in_title'] = false;
$config['thread_subject_in_title'] = true;
// Collapse the new thread form on the index page until clicked, like the catalog.
$config['index_collapse_post_form'] = true;
// Additional lines added to the footer of all pages.
$config['footer'][] = _('All trademarks, copyrights, comments, and images on this page are owned by and are the responsibility of their respective parties.');
@ -1001,12 +1043,13 @@
// Characters used to generate a random password (with Javascript).
$config['genpassword_chars'] = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()_+';
// Optional banner image at the top of every page.
// $config['url_banner'] = '/banner.php';
// Banner dimensions are also optional. As the banner loads after the rest of the page, everything may be
// shifted down a few pixels when it does. Making the banner a fixed size will prevent this.
// $config['banner_width'] = 300;
// $config['banner_height'] = 100;
// Banner settings.
// Banners are rotating, random images displayed to users at the top of thread pages and the catalog.
// You should upload your banners to static/banners.
$config['url_banner'] = '/b.php'; // Custom script may be used.
// Setting the banner dimensions stops the page shifting as it loads. If you have banners of various different sizes, unset these.
$config['banner_width'] = 300;
$config['banner_height'] = 100;
// Custom stylesheets available for the user to choose. See the "stylesheets/" folder for a list of
// available stylesheets (or create your own).
@ -1151,9 +1194,10 @@
// Custom embedding (YouTube, vimeo, etc.)
// It's very important that you match the entire input (with ^ and $) or things will not work correctly.
// Be careful when creating a new embed, because depending on the URL you end up exposing yourself to an XSS.
$config['embedding'] = array(
array(
'/^https?:\/\/(\w+\.)?youtube\.com\/watch\?v=([a-zA-Z0-9\-_]{10,11})(&.+)?$/i',
'/^https?:\/\/(\w+\.)?youtube\.com\/watch\?v=([a-zA-Z0-9\-_]{10,11})?$/i',
'<iframe style="float: left; margin: 10px 20px;" width="%%tb_width%%" height="%%tb_height%%" frameborder="0" id="ytplayer" src="https://www.youtube.com/embed/$2"></iframe>'
),
array(
@ -1174,6 +1218,29 @@
)
);
// Youtube.js embed HTML code
$config['youtube_js_html'] = '<div class="video-container" data-video="$2">'.
'<a href="https://youtu.be/$2" target="_blank" class="file">'.
'<img style="width:360px;height:270px;" src="//img.youtube.com/vi/$2/0.jpg" class="post-image" alt="YouTube thumbnail: $2"/>'.
'</a></div>';
// To embed YouTube thumbnails locally and avoid users connecting to YouTube servers when loading threads,
// add the following code to your config. You will probably need to fix it for the other services.
/*
$config['youtube_js_html']
= '<div class="video-container" data-video="$2">'
. '<a href="https://youtu.be/$2" target="_blank" class="file">'
. '<img style="width:255px;height:190px;" src="/vi/$2/0.jpg" class="post-image" alt="YouTube thumbnail: $2"/>'
. '</a></div>';
$config['embedding'] = array();
$config['embedding'][0] =
array(
'/^https?:\/\/(\w+\.)?(?:youtube\.com\/watch\?v=|youtu\.be\/)([a-zA-Z0-9\-_]{10,11})(&.+)?$/i',
$config['youtube_js_html']
);
*/
// Embedding width and height.
$config['embed_width'] = 300;
$config['embed_height'] = 246;
@ -1193,6 +1260,7 @@
$config['error']['tooshort_body_op'] = _('OP must be at least %d characters long.');
$config['error']['no_body'] = _('The body was too short or empty.');
$config['error']['noimage'] = _('You must upload an image.');
$config['error']['nosubject'] = _('You must add a subject.');
$config['error']['toomanyimages'] = _('You have attempted to upload too many images!');
$config['error']['nomove'] = _('The server failed to handle your upload.');
$config['error']['fileext'] = _('Unsupported image format.');
@ -1213,7 +1281,7 @@
$config['error']['toomanycross'] = _('Too many cross-board links; post discarded.');
$config['error']['nodelete'] = _('You didn\'t select anything to delete.');
$config['error']['noreport'] = _('You didn\'t select anything to report.');
$config['error']['invalidreport'] = _('The reason was too long.');
$config['error']['toolongreport'] = _('The reason was too long.');
$config['error']['toomanyreports'] = _('You can\'t report that many posts at once.');
$config['error']['invalidpassword'] = _('Wrong password…');
$config['error']['invalidimg'] = _('Invalid image.');
@ -1232,10 +1300,15 @@
$config['error']['fileexistsinthread'] = _('That file <a href="%s">already exists</a> in this thread!');
$config['error']['delete_too_soon'] = _('You\'ll have to wait another %s before deleting that.');
$config['error']['delete_post_cutoff'] = _('You can\'t delete a post with this many replies.');
$config['error']['mime_exploit'] = _('MIME type detection XSS exploit (IE) detected; post discarded.');
$config['error']['invalid_embed'] = _('Couldn\'t make sense of the URL of the video you tried to embed.');
$config['error']['captcha'] = _('You seem to have mistyped the verification.');
$config['error']['captcha_incorrect'] = _('You seem to have mistyped the verification.');
$config['error']['captcha_expired'] = _('That captcha has expired.');
$config['error']['captcha'] = _('Captcha failed.');
$config['error']['already_voted'] = _('You have already voted for this thread to be featured.');
$config['error']['flag_undefined'] = _('The flag %s is undefined, your PHP version is too old!');
$config['error']['flag_wrongtype'] = _('defined_flags_accumulate(): The flag %s is of the wrong type!');
// Moderator errors
$config['error']['toomanyunban'] = _('You are only allowed to unban %s users at a time. You tried to unban %u users.');
@ -1282,10 +1355,11 @@
// enter the directory path here. Otherwise, keep it false.
$config['root_file'] = false;
// Location of files.
// Location of primary files.
$config['file_index'] = 'index.html';
$config['file_catalog'] = 'catalog.html'; // Catalog page (used in preg_match for post referer)
$config['file_page'] = '%d.html'; // NB: page is both an index page and a thread
$config['file_catalog'] = 'catalog.html';
$config['file_page50'] = '%d+50.html';
$config['file_page_slug'] = '%d-%s.html';
$config['file_page50_slug'] = '%d-%s+50.html';
@ -1293,6 +1367,67 @@
$config['file_post'] = 'post.php';
$config['file_script'] = 'main.js';
$config['file_board_index'] = 'index.html';
$config['file_page_template'] = 'page.html';
$config['file_report'] = 'report.html';
$config['file_error'] = 'error.html';
$config['file_login'] = 'login.html';
$config['file_banned'] = 'banned.html';
$config['file_fileboard'] = 'fileboard.html';
$config['file_thread'] = 'thread.html';
$config['file_post_reply'] = 'post_reply.html';
$config['file_post_thread'] = 'post_thread.html';
$config['file_post_thread_fileboard'] = 'post_thread_fileboard.html';
// Mod page file settings
$config['file_mod_dashboard'] = 'mod/dashboard.html';
$config['file_mod_login'] = 'mod/login.html';
$config['file_mod_confim'] = 'mod/confirm.html';
$config['file_mod_board'] = 'mod/board.html';
$config['file_mod_news'] = 'mod/news.html';
$config['file_mod_log'] = 'mod/log.html';
$config['file_mod_view_ip'] = 'mod/view_ip.html';
$config['file_mod_ban_form'] = 'mod/ban_form.html';
$config['file_mod_ban_list'] = 'mod/ban_list.html';
$config['file_mod_ban_appeals'] = 'mod/ban_appeals.html';
$config['file_mod_noticeboard'] = 'mod/noticeboard.html';
$config['file_mod_search_results'] = 'mod/search_results.html';
$config['file_mod_move'] = 'mod/move.html';
$config['file_mod_move_reply'] = 'mod/move_reply.html';
$config['file_mod_edit_post_form'] = 'mod/edit_post_form.html';
$config['file_mod_user'] = 'mod/user.html';
$config['file_mod_users'] = 'mod/users.html';
$config['file_mod_pm'] = 'mod/pm.html';
$config['file_mod_new_pm'] = 'mod/new_pm.html';
$config['file_mod_inbox'] = 'mod/inbox.html';
$config['file_mod_rebuilt'] = 'mod/rebuilt.html';
$config['file_mod_rebuild'] = 'mod/rebuild.html';
$config['file_mod_report'] = 'mod/report.html';
$config['file_mod_reports'] = 'mod/reports.html';
$config['file_mod_recent_posts'] = 'mod/recent_posts.html';
$config['file_mod_config_editor'] = 'mod/config-editor.html';
$config['file_mod_config_editor_php'] = 'mod/config-editor-php.html';
$config['file_mod_themes'] = 'mod/themes.html';
$config['file_mod_theme_installed'] = 'mod/theme_installed.html';
$config['file_mod_theme_config'] = 'mod/theme_config.html';
$config['file_mod_theme_rebuilt'] = 'mod/theme_rebuilt.html';
$config['file_mod_pages'] = 'mod/pages.html';
$config['file_mod_edit_page'] = 'mod/edit_page.html';
$config['file_mod_debug_antispam'] = 'mod/debug/antispam.html';
$config['file_mod_debug_recent_posts'] = 'mod/debug/recent_posts.html';
$config['file_mod_debug_sql'] = 'mod/debug/sql.html';
$config['file_mod_debug_apc'] = 'mod/debug/apc.html';
// Board directory, followed by a forward-slash (/).
$config['board_path'] = '%s/';
// Misc directories.
@ -1301,27 +1436,24 @@
$config['dir']['res'] = 'res/';
// Shadow Del dir for files (non perm deleted)
$config['dir']['shadow_del'] = 'tempura/';
// Use shadow delete instead of immediate permanent delete
$config['shadow_del']['use'] = true;
// Hash Seed used to obscure filenames of shadow deleted files for posts
$config['shadow_del']['filename_seed'] = '5azs5co3wAN67tlqbINEmWuERtTX4FatsMVe446JbHVIJbZyjephDsdRtULw501';
// Lifetime for shadow deleted threads before permanent delete (ex. "60 minutes", "6 hours", "1 day", "1 week")
$config['shadow_del']['lifetime'] = "6 hours";
// Directory for archived threads
$config['dir']['archive'] = 'archive/';
// Directory for "Featured Threads" (threads makred for permanent storage)
$config['dir']['featured'] = 'featured/';
// Indicate if threads should be archived
$config['archive']['threads'] = true;
// Indicate if it is possible to mark threads as featured (stored forever)
$config['feature']['threads'] = true;
// Days to keep archived threads before deletion (if set to false all archived threads are kept forever)
$config['archive']['lifetime'] = 3;
// Number of chars in snippet
$config['archive']['snippet_len'] = 400;
// If any is set to run in crom both will be run in cron regardless
// Archiving is run in cron job
$config['archive']['cron_job']['archiving'] = false;
// Purging of archive is run in cron job
$config['archive']['cron_job']['purge'] = false;
// Directory for "Featured Threads" (threads makred for permanent storage)
$config['dir']['mod_archive'] = 'mod_archive/';
// For load balancing, having a seperate server (and domain/subdomain) for serving static content is
@ -1360,9 +1492,49 @@
// Website favicon.
// $config['url_favicon'] = '/favicon.gif';
// Website Apple touch icon.
// $config['url_appletouchicon'] = '/favicon.gif';
// Try not to build pages when we shouldn't have to.
$config['try_smarter'] = true;
/*
* ====================
* Archive settings
* ====================
*/
// Indicate if threads should be archived
$config['archive']['threads'] = true;
// Indicate if it is possible to mark threads as featured (stored forever)
$config['feature']['threads'] = true;
// Indicate if link to featured archive should be shown on post and thread page
$config['feature']['link_post_page'] = false;
// Indicate if it is possible to mark threads as nostalgic (stored forever but will only be accessable to mods)
$config['mod_archive']['threads'] = true;
// Days to keep archived threads before deletion (ex. "60 minutes", "6 hours", "1 day", "1 week"), if set to false all archived threads are kept forever
$config['archive']['lifetime'] = "3 days";
// Number of chars in snippet
$config['archive']['snippet_len'] = 400;
// If any is set to run in crom both will be run in cron regardless
// Archiving is run in cron job
$config['archive']['cron_job']['archiving'] = false;
// Purging of archive is run in cron job
$config['archive']['cron_job']['purge'] = false;
// Automatically send threads with thiese trips to Featured Archive
// $config['archive']['auto_feature_trips'] = array("!!securetrip", "!trip");
$config['archive']['auto_feature_trips'] = array();
/*
* ====================
* Advanced build
@ -1441,6 +1613,12 @@
// Limit how many bans can be removed via the ban list. Set to false (or zero) for no limit.
$config['mod']['unban_limit'] = false;
// An array of IP addresses that throw an error when trying to D+ and D++.
// This is to prevent massively-shared IP addresses such as Tor from accidental mass deletion.
// The IP address is checked literally, not as a numerical value, so make sure it's identical
// to how that address is in code, as there are different valid ways of presenting the same address.
$config['mod']['protected_ips'] = array('127.0.0.1');
// Whether or not to lock moderator sessions to IP addresses. This makes cookie theft ineffective.
$config['mod']['lock_ip'] = true;
@ -1449,7 +1627,9 @@
// Mod links (full HTML).
$config['mod']['link_delete'] = '[D]';
$config['mod']['link_force_shadow_delete'] = '[ShD]';
$config['mod']['link_warning'] = '[W]';
$config['mod']['link_warningdelete'] = '[W&amp;D]';
$config['mod']['link_ban'] = '[B]';
$config['mod']['link_bandelete'] = '[B&amp;D]';
$config['mod']['link_deletefile'] = '[F]';
@ -1468,7 +1648,10 @@
$config['mod']['link_merge'] = '[Merge]';
$config['mod']['link_cycle'] = '[Cycle]';
$config['mod']['link_uncycle'] = '[-Cycle]';
$config['mod']['link_shadow_restore'] = '[SD Restore]';
$config['mod']['link_shadow_delete'] = '[SD Delete]';
$config['mod']['link_send_to_archive'] = '[Archive]';
// Moderator capcodes.
$config['capcode'] = ' <span class="capcode">## %s</span>';
@ -1490,7 +1673,7 @@
//);
// Enable the moving of single replies
$config['move_replies'] = false;
$config['move_replies'] = true;
// How often (minimum) to purge the ban list of expired bans (which have been seen). Only works when
// $config['cache'] is enabled and working.
@ -1612,6 +1795,8 @@
$config['mod']['unban'] = MOD;
// Spoiler image
$config['mod']['spoilerimage'] = JANITOR;
// Edit bans
$config['mod']['edit_ban'] = &$config['mod']['ban'];
// Delete file (and keep post)
$config['mod']['deletefile'] = JANITOR;
// Delete all posts by IP
@ -1633,8 +1818,8 @@
$config['mod']['view_bumplock'] = MOD;
// Edit posts
$config['mod']['editpost'] = ADMIN;
// "Move" a thread to another board (EXPERIMENTAL; has some known bugs)
$config['mod']['move'] = DISABLED;
// "Move" a thread to another board
$config['mod']['move'] = MOD;
// "Merge" a thread to same board or another board
$config['mod']['merge'] = MOD;
// Bypass "field_disable_*" (forced anonymity, etc.)
@ -1731,10 +1916,28 @@
$config['mod']['news_custom'] = ADMIN;
// Delete news entries
$config['mod']['news_delete'] = ADMIN;
// Send Threads directly to Archive (need to be greater than or equal to ['mod']['delete'] permission)
$config['mod']['send_threads_to_archive'] = MOD;
if($config['mod']['send_threads_to_archive'] < $config['mod']['delete'])
$config['mod']['send_threads_to_archive'] = $config['mod']['delete'];
// Feature archived threads
$config['mod']['feature_archived_threads'] = JANITOR;
// Delete featured archived threads
$config['mod']['delete_featured_archived_threads'] = MOD;
// View mod archive
$config['mod']['view_mod_archive'] = MOD;
// Archive threads for mods
$config['mod']['add_to_mod_archive'] = MOD;
// Archive threads for mods
$config['mod']['remove_from_mod_archive'] = MOD;
// Automatically permanently delete posts and threads (set to false if you want to keep for all)
$config['mod']['auto_delete_shadow_post'] = MOD;
// View shadow deleted posts and threads
$config['mod']['view_shadow_posts'] = MOD;
// Restore shadow deleted posts and threads
$config['mod']['restore_shadow_post'] = MOD;
// Permanently delete shadow deleted posts and threads
$config['mod']['delete_shadow_post'] = ADMIN;
// Execute un-filtered SQL queries on the database (?/debug/sql)
$config['mod']['debug_sql'] = DISABLED;
// Look through all cache values for debugging when APC is enabled (?/debug/apc)
@ -1747,6 +1950,8 @@
$config['mod']['ban_appeals'] = MOD;
// View the recent posts page
$config['mod']['recent'] = MOD;
// View site statistics
$config['mod']['view_statistics'] = MOD;
// Create pages
$config['mod']['edit_pages'] = MOD;
$config['pages_max'] = 10;
@ -1784,7 +1989,7 @@
// 'db',
// );
// Allow OP to remove arbitrary posts in his thread
// Allow OP to remove arbitrary posts in their thread
$config['user_moderation'] = false;
// File board. Like 4chan /f/
@ -1858,6 +2063,14 @@
// Example: Adding the pre-markup post body to the API as "com_nomarkup".
// $config['api']['extra_fields'] = array('body_nomarkup' => 'com_nomarkup');
// Variables for status.php, used for mobile app API
// Unlisted boards to list.
$config['api']['status']['whitelist'] = [];
// Boards marked as NSFW
$config['api']['status']['nsfw_boards'] = [];
// Boards where posts are not allowed to be created
$config['api']['status']['locked_boards'] = [];
/*
* ==================
* NNTPChan settings
@ -1895,12 +2108,66 @@
// Please set this setting in your board/config.php, not globally.
$config['nntpchan']['group'] = false; // eg. 'overchan.test'
/*
* ====================
* Public Statistics settings
* ====================
*/
// The public stats page is not generated automatically.
// It is recommended you create a cron job to run
// tools/public_statistics_cli.php statistics.html
// set to the times you want it updated, such as once every hour or once every day.
// Alternatively, if your imageboard has low traffic, you can make it run from a Theme
// defined to update every time rebuildThemes('post') is run.
// Set to true if all boards are to be shown
// Set to false to use $config['boards'] to select boards to show in stats
// Set to array of boards if only boards listed in this array are to be shown
$config['public_stats']['boards'] = true;
// Include hourly stats in public stats
// (need to run tools/public_statistics_cli.php once an hour to update data)
$config['public_stats']['hourly'] = true;
// Include the current hour in public stats.
$config['public_stats']['realtime'] = false;
// The format string passed to date() for displaying dates.
// https://www.php.net/manual/en/datetime.format.php
$config['public_stats']['date'] = 'Y-m-d\TH:i:s\Z';
/*
* ====================
* Deny GET settings
* ====================
*/
// Set to true if you want to disable all GETs globaly (can also be set for each board in it's config)
$config['post_GETs']['disable'] = false;
// Allow MODS to get the GET if they manage to post at the right time
$config['post_GETs']['not_disabled_for_mods'] = false;
// Minimum length of number to be considered a GET number
$config['post_GETs']['minimum_length'] = 5;
// Post ID that ends in a sequense of repeating number that has "repeating_digits_count" of the same number.
$config['post_GETs']['repeating_digits'] = true;
$config['post_GETs']['repeating_digits_count'] = 4;
// Post ID that is made up of consecutive digits starting with 1, e.g. 12345, 123456 ... 1234567890.
$config['post_GETs']['sequential_digits'] = true;
/*
* ====================
* Other/uncategorized
* ====================
*/
// Site logo. This is used in the SEO tags and can be used in front page themes.
$config['logo'] = "static/logo.png";
// Base address of your imageboard for other sites to link pages and images. Probably should include $config['root'].
$config['SEO']['address'] = "https://exampleimageboard.net/";
// Meta keywords. It's probably best to include these in per-board configurations.
// $config['meta_keywords'] = 'chan,anonymous discussion,imageboard,tinyboard';
@ -1971,12 +2238,6 @@
// is the absolute maximum, because MySQL cannot handle table names greater than 64 characters.
$config['board_regex'] = '[0-9a-zA-Z$_\x{0080}-\x{FFFF}]{1,58}';
// Youtube.js embed HTML code
$config['youtube_js_html'] = '<div class="video-container" data-video="$2">'.
'<a href="https://youtu.be/$2" target="_blank" class="file">'.
'<img style="width:360px;height:270px;" src="//img.youtube.com/vi/$2/0.jpg" class="post-image"/>'.
'</a></div>';
// Password hashing function
//
// $5$ <- SHA256

16
inc/database.php

@ -78,10 +78,7 @@ function sql_open() {
if ($config['debug'])
$debug['time']['db_connect'] = '~' . round((microtime(true) - $start) * 1000, 2) . 'ms';
if (mysql_version() >= 50503)
query('SET NAMES utf8mb4') or error(db_error());
else
query('SET NAMES utf8') or error(db_error());
query('SET NAMES utf8mb4') or error(db_error());
return $pdo;
} catch(PDOException $e) {
$message = $e->getMessage();
@ -95,17 +92,6 @@ function sql_open() {
}
}
// 5.6.10 becomes 50610
function mysql_version() {
global $pdo;
$version = $pdo->getAttribute(PDO::ATTR_SERVER_VERSION);
$v = explode('.', $version);
if (count($v) != 3)
return false;
return (int) sprintf("%02d%02d%02d", $v[0], $v[1], $v[2]);
}
function prepare($query) {
global $pdo, $debug, $config;

18
inc/display.php

@ -35,7 +35,7 @@ function doBoardListPart($list, $root, &$boards) {
$title = ' title="'.$boards[$board].'"';
}
$body .= ' <a href="' . $root . $board . '/' . $config['file_index'] . '"'.$title.'>' . $board . '</a> /';
$body .= ' <a class="board" href="' . $root . $board . '/' . $config['file_index'] . '"'.$title.'>' . $board . '</a> /';
}
}
}
@ -105,7 +105,7 @@ function error($message, $priority = true, $debug_stuff = false) {
}
$pw = $config['db']['password'];
$debug_callback = function(&$item) use (&$debug_callback, $pw) {
$debug_callback = function($item) use (&$debug_callback, $pw) {
if (is_array($item)) {
$item = array_filter($item, $debug_callback);
}
@ -116,16 +116,16 @@ function error($message, $priority = true, $debug_stuff = false) {
if ($debug_stuff)
$debug_stuff = array_filter($debug_stuff, $debug_callback);
die(Element('page.html', array(
die(Element($config['file_page_template'], array(
'config' => $config,
'title' => _('Error'),
'subtitle' => _('An error has occured.'),
'body' => Element('error.html', array(
'body' => Element($config['file_error'], array(
'config' => $config,
'message' => $message,
'mod' => $mod,
'board' => isset($board) ? $board : false,
'debug' => is_array($debug_stuff) ? str_replace("\n", '&#10;', utf8tohtml(print_r($debug_stuff, true))) : utf8tohtml($debug_stuff)
'debug' => $config['debug'] ? (is_array($debug_stuff) ? str_replace("\n", '&#10;', utf8tohtml(print_r($debug_stuff, true))) : utf8tohtml($debug_stuff)) : null
))
)));
}
@ -133,11 +133,11 @@ function error($message, $priority = true, $debug_stuff = false) {
function loginForm($error=false, $username=false, $redirect=false) {
global $config;
die(Element('page.html', array(
die(Element($config['file_page_template'], array(
'index' => $config['root'],
'title' => _('Login'),
'config' => $config,
'body' => Element('login.html', array(
'body' => Element($config['file_login'], array(
'config'=>$config,
'error'=>$error,
'username'=>utf8tohtml($username),
@ -384,7 +384,7 @@ class Post {
public function build($index=false) {
global $board, $config;
return Element('post_reply.html', array('config' => $config, 'board' => $board, 'post' => &$this, 'index' => $index, 'mod' => $this->mod));
return Element($config['file_post_reply'], array('config' => $config, 'board' => $board, 'post' => &$this, 'index' => $index, 'mod' => $this->mod));
}
};
@ -473,7 +473,7 @@ class Thread {
event('show-thread', $this);
$file = ($index && $config['file_board']) ? 'post_thread_fileboard.html' : 'post_thread.html';
$file = ($index && $config['file_board']) ? $config['file_post_thread_fileboard'] : $config['file_post_thread'];
$built = Element($file, array('config' => $config, 'board' => $board, 'post' => &$this, 'index' => $index, 'hasnoko50' => $hasnoko50, 'isnoko50' => $isnoko50, 'mod' => $this->mod));
return $built;

2
inc/filters.php

@ -192,7 +192,7 @@ function purge_flood_table() {
// aware of flood filters in other board configurations. You can solve this problem by settings the
// config variable $config['flood_cache'] (seconds).
if (isset($config['flood_cache'])) {
if ($config['flood_cache'] != -1) {
$max_time = &$config['flood_cache'];
} else {
$max_time = 0;

453
inc/functions.php

@ -12,6 +12,8 @@ if (realpath($_SERVER['SCRIPT_FILENAME']) == str_replace('\\', '/', __FILE__)) {
$microtime_start = microtime(true);
use Lifo\IP\IP; // for expanding IPv6 address in DNSBL()
// the user is not currently logged in as a moderator
$mod = false;
@ -227,6 +229,16 @@ function loadConfig() {
$config['uri_img'] = sprintf($config['uri_img'], $board['dir']);
}
if (!isset($config['uri_shadow_thumb']))
$config['uri_shadow_thumb'] = $config['root'] . $board['dir'] . $config['dir']['shadow_del'] . $config['dir']['thumb'];
elseif (isset($board['dir']))
$config['uri_shadow_thumb'] = sprintf($config['uri_shadow_thumb'], $board['dir']);
if (!isset($config['uri_shadow_img']))
$config['uri_shadow_img'] = $config['root'] . $board['dir'] . $config['dir']['shadow_del'] . $config['dir']['img'];
elseif (isset($board['dir']))
$config['uri_shadow_img'] = sprintf($config['uri_shadow_img'], $board['dir']);
if (!isset($config['uri_stylesheets']))
$config['uri_stylesheets'] = $config['root'] . 'stylesheets/';
@ -553,7 +565,7 @@ function setupBoard($array) {
@mkdir($board['dir'] . $config['dir']['res'], 0777)
or error("Couldn't create " . $board['dir'] . $config['dir']['img'] . ". Check permissions.", true);
// Create Archive Folders
// Create archive folders
if (!file_exists($board['dir'] . $config['dir']['archive']))
@mkdir($board['dir'] . $config['dir']['archive'], 0777)
or error("Couldn't create " . $board['dir'] . $config['dir']['archive'] . ". Check permissions.", true);
@ -566,7 +578,7 @@ function setupBoard($array) {
if (!file_exists($board['dir'] . $config['dir']['archive'] . $config['dir']['res']))
@mkdir($board['dir'] . $config['dir']['archive'] . $config['dir']['res'], 0777)
or error("Couldn't create " . $board['dir'] . $config['dir']['archive'] . $config['dir']['img'] . ". Check permissions.", true);
// Create Featured threads Folders
// Create featured threads folders
if (!file_exists($board['dir'] . $config['dir']['featured']))
@mkdir($board['dir'] . $config['dir']['featured'], 0777)
or error("Couldn't create " . $board['dir'] . $config['dir']['featured'] . ". Check permissions.", true);
@ -579,6 +591,29 @@ function setupBoard($array) {
if (!file_exists($board['dir'] . $config['dir']['featured'] . $config['dir']['res']))
@mkdir($board['dir'] . $config['dir']['featured'] . $config['dir']['res'], 0777)
or error("Couldn't create " . $board['dir'] . $config['dir']['featured'] . $config['dir']['img'] . ". Check permissions.", true);
// Create mod archive threads folders
if (!file_exists($board['dir'] . $config['dir']['mod_archive']))
@mkdir($board['dir'] . $config['dir']['mod_archive'], 0777)
or $file_errors .= "Couldn't create " . $board['dir'] . $config['dir']['mod_archive'] . ". Check permissions.<br/>";
if (!file_exists($board['dir'] . $config['dir']['mod_archive'] . $config['dir']['img']))
@mkdir($board['dir'] . $config['dir']['mod_archive'] . $config['dir']['img'], 0777)
or $file_errors .= "Couldn't create " . $board['dir'] . $config['dir']['feamod_archivetured'] . $config['dir']['img'] . ". Check permissions.<br/>";
if (!file_exists($board['dir'] . $config['dir']['mod_archive'] . $config['dir']['thumb']))
@mkdir($board['dir'] . $config['dir']['mod_archive'] . $config['dir']['thumb'], 0777)
or $file_errors .= "Couldn't create " . $board['dir'] . $config['dir']['mod_archive'] . $config['dir']['thumb'] . ". Check permissions.<br/>";
if (!file_exists($board['dir'] . $config['dir']['mod_archive'] . $config['dir']['res']))
@mkdir($board['dir'] . $config['dir']['mod_archive'] . $config['dir']['res'], 0777)
or $file_errors .= "Couldn't create " . $board['dir'] . $config['dir']['mod_archive'] . $config['dir']['res'] . ". Check permissions.<br/>";
// Create shadow delete folders to save files in
if (!file_exists($board['dir'] . $config['dir']['shadow_del']))
@mkdir($board['dir'] . $config['dir']['shadow_del'], 0777)
or $file_errors .= "Couldn't create " . $board['dir'] . $config['dir']['shadow_del'] . ". Check permissions.<br/>";
if (!file_exists($board['dir'] . $config['dir']['shadow_del'] . $config['dir']['img']))
@mkdir($board['dir'] . $config['dir']['shadow_del'] . $config['dir']['img'], 0777)
or $file_errors .= "Couldn't create " . $board['dir'] . $config['dir']['shadow_del'] . $config['dir']['img'] . ". Check permissions.<br/>";
if (!file_exists($board['dir'] . $config['dir']['shadow_del'] . $config['dir']['thumb']))
@mkdir($board['dir'] . $config['dir']['shadow_del'] . $config['dir']['thumb'], 0777)
or $file_errors .= "Couldn't create " . $board['dir'] . $config['dir']['shadow_del'] . $config['dir']['thumb'] . ". Check permissions.<br/>";
}
function openBoard($uri) {
@ -760,12 +795,16 @@ function file_unlink($path) {
$debug['unlink'][] = $path;
}
$ret = @unlink($path);
if (file_exists($path)) {
$ret = @unlink($path);
if ($config['gzip_static']) {
$gzpath = "$path.gz";
if ($config['gzip_static']) {
$gzpath = "$path.gz";
@unlink($gzpath);
@unlink($gzpath);
}
} else {
$ret = false;
}
if (isset($config['purge']) && $path[0] != '/' && isset($_SERVER['HTTP_HOST'])) {
@ -915,11 +954,11 @@ function displayBan($ban) {
// Show banned page and exit
die(
Element('page.html', array(
Element($config['file_page_template'], array(
'title' => _('Banned!'),
'config' => $config,
'boardlist' => createBoardlist(isset($mod) ? $mod : false),
'body' => Element('banned.html', array(
'body' => Element($config['file_banned'], array(
'config' => $config,
'ban' => $ban,
'board' => $board,
@ -1072,60 +1111,6 @@ function checkWarning($board = false) {
}
}
function threadLocked($id) {
global $board;
if (event('check-locked', $id))
return true;
$query = prepare(sprintf("SELECT `locked` FROM ``posts_%s`` WHERE `id` = :id AND `thread` IS NULL LIMIT 1", $board['uri']));
$query->bindValue(':id', $id, PDO::PARAM_INT);
$query->execute() or error(db_error());
if (($locked = $query->fetchColumn()) === false) {
// Non-existant, so it can't be locked...
return false;
}
return (bool)$locked;
}
function threadSageLocked($id) {
global $board;
if (event('check-sage-locked', $id))
return true;
$query = prepare(sprintf("SELECT `sage` FROM ``posts_%s`` WHERE `id` = :id AND `thread` IS NULL LIMIT 1", $board['uri']));
$query->bindValue(':id', $id, PDO::PARAM_INT);
$query->execute() or error(db_error());
if (($sagelocked = $query->fetchColumn()) === false) {
// Non-existant, so it can't be locked...
return false;
}
return (bool)$sagelocked;
}
function threadExists($id) {
global $board;
$query = prepare(sprintf("SELECT 1 FROM ``posts_%s`` WHERE `id` = :id AND `thread` IS NULL LIMIT 1", $board['uri']));
$query->bindValue(':id', $id, PDO::PARAM_INT);
$query->execute() or error(db_error());
if ($query->rowCount()) {
return true;
}
return false;
}
function insertFloodPost(array $post) {
global $board;
@ -1234,6 +1219,17 @@ function post(array $post) {
// Save Post ID
$postID = $pdo->lastInsertId();
// Skip GETS
if(postGETCheck($postID)){
// Delete current post entry
$query = prepare(sprintf("DELETE FROM ``posts_%s`` WHERE `id` = :id", $board['uri']));
$query->bindValue(':id', $postID, PDO::PARAM_INT);
$query->execute() or error(db_error($query));
// Create a new post entry and return new ID
return post($post);
}
// Add file-hashes to database
if($post['has_file'])
{
@ -1259,6 +1255,53 @@ function post(array $post) {
return $postID;
}
function postGETCheck($postID)
{
global $config, $mod;
if (!$config['post_GETs']['disable'] || ($config['post_GETs']['not_disabled_for_mods'] && $mod && $mod['type'] >= MOD))
return false;
//
// Post ID that ends in a sequense of repeating number that has "repeating_digits_count" of the same number.
//
// \b[0-9]+([0-9])\1{4,}\b
//
// Explanation:
// ^(?=.{4,}$) # Number must be of five or more digits
// \b # match word boundary
// [1-9]* # match zero or more instances of digit 1-9
// ([0-9]){3,} # match three more instances of same number at the end (four or more total)
// \b # match word boundary
//
if($config['post_GETs']['repeating_digits']) {
// if(preg_match('/^(?=.{4,}$)\b[1-9]*([0-9])\1{3,}\b/', $postID))
if(preg_match('/^(?=.{' . ($config['post_GETs']['minimum_length'] - 1) . ',10}$)\b[0-9]+([0-9])\1{' . ($config['post_GETs']['repeating_digits_count'] - 1) . ',}\b/', $postID))
return true;
}
//
// Post ID that is made up of consecutive digits starting with 1 ex. 12345, 123456 ... 1234567890.
//
// \b(123|1234|12345|123456|123457|12345678|123456789|1234567890)\b
//
// Explanation:
// ^(?=.{5,}$) # Number must be of five or more digits
// \b(123|1234|.. # Match 1234, or, 12345, or ...
//
if($config['post_GETs']['sequential_digits']) {
if(preg_match('/^(?=.{' . ($config['post_GETs']['minimum_length'] - 1) . ',10}$)\b(123|1234|12345|123456|1234567|12345678|123456789|1234567890){1}\b/', $postID))
return true;
}
// If post ID was not a GET number return false
return false;
}
function bumpThread($id) {
global $config, $board, $build_pages;
@ -1420,7 +1463,18 @@ function rebuildPost($id) {
}
// Delete a post (reply or thread)
function deletePost($id, $error_if_doesnt_exist=true, $rebuild_after=true) {
function deletePostShadow($id, $error_if_doesnt_exist=true, $rebuild_after=true, $force_shadow_delete = false) {
global $board, $config;
// If we are using non permanent delete run that function
if($force_shadow_delete || ($config['shadow_del']['use'] && ($config['mod']['auto_delete_shadow_post'] === false || !hasPermission($config['mod']['auto_delete_shadow_post']))))
return ShadowDelete::deletePost($id, $error_if_doesnt_exist, $rebuild_after);
else
return deletePostPermanent($id, $error_if_doesnt_exist, $rebuild_after);
}
// Delete a post (reply or thread)
function deletePostPermanent($id, $error_if_doesnt_exist=true, $rebuild_after=true, $delete_files=true) {
global $board, $config;
// Select post and replies (if thread) in one query
@ -1439,7 +1493,7 @@ function deletePost($id, $error_if_doesnt_exist=true, $rebuild_after=true) {
// Delete posts and maybe replies
while ($post = $query->fetch(PDO::FETCH_ASSOC)) {
event('delete', $post);
$thread_id = $post['thread'];
if (!$post['thread']) {
// Delete thread HTML page
@ -1455,7 +1509,7 @@ function deletePost($id, $error_if_doesnt_exist=true, $rebuild_after=true) {
// Rebuild thread
$rebuild = &$post['thread'];
}
if ($post['files']) {
if ($post['files'] && $delete_files) {
// Delete file
foreach (json_decode($post['files']) as $i => $f) {
if ($f->file !== 'deleted') {
@ -1497,16 +1551,24 @@ function deletePost($id, $error_if_doesnt_exist=true, $rebuild_after=true) {
$query->bindValue(':board', $board['uri']);
$query->execute() or error(db_error($query));
if ($config['anti_bump_flood']) {
$query = prepare(sprintf("SELECT `time` FROM ``posts_%s`` WHERE (`thread` = :thread OR `id` = :thread) AND `sage` = 0 ORDER BY `time` DESC LIMIT 1", $board['uri']));
$query->bindValue(':thread', $thread_id);
$query->execute() or error(db_error($query));
$bump = $query->fetchColumn();
$query = prepare(sprintf("UPDATE ``posts_%s`` SET `bump` = :bump WHERE `id` = :thread", $board['uri']));
$query->bindValue(':bump', $bump);
$query->bindValue(':thread', $thread_id);
$query->execute() or error(db_error($query));
// No need to run on OPs
if ($config['anti_bump_flood'] && isset($thread_id)) {
$query = prepare(sprintf("SELECT `sage` FROM ``posts_%s`` WHERE `id` = :thread", $board['uri']));
$query->bindValue(':thread', $thread_id);
$query->execute() or error(db_error($query));
$bumplocked = (bool)$query->fetchColumn();
if (!$bumplocked) {
$query = prepare(sprintf("SELECT `time` FROM ``posts_%s`` WHERE (`thread` = :thread AND NOT email <=> 'sage') OR `id` = :thread ORDER BY `time` DESC LIMIT 1", $board['uri']));
$query->bindValue(':thread', $thread_id);
$query->execute() or error(db_error($query));
$bump = $query->fetchColumn();
$query = prepare(sprintf("UPDATE ``posts_%s`` SET `bump` = :bump WHERE `id` = :thread", $board['uri']));
$query->bindValue(':bump', $bump);
$query->bindValue(':thread', $thread_id);
$query->execute() or error(db_error($query));
}
}
if (isset($rebuild) && $rebuild_after) {
@ -1534,10 +1596,12 @@ function clean($pid = false) {
while ($post = $query->fetch(PDO::FETCH_ASSOC)) {
if($config['archive']['threads']) {
Archive::archiveThread($post['id']);
deletePostPermanent($post['id'], false, false);
if ($pid) modLog("Automatically archived thread #{$post['id']} due to new thread #{$pid}");
} else {
deletePostPermanent($post['id'], false, false);
if ($pid) modLog("Automatically deleting thread #{$post['id']} due to new thread #{$pid}");
}
deletePost($post['id'], false, false);
if ($pid) modLog("Automatically deleting thread #{$post['id']} due to new thread #{$pid}");
}
// Bump off threads with X replies earlier, spam prevention method
@ -1559,10 +1623,12 @@ function clean($pid = false) {
if ($post['reply_count'] < $page*$config['early_404_replies']) {
if($config['archive']['threads']) {
Archive::archiveThread($post['thread_id']);
deletePostPermanent($post['thread_id'], false, false);
if ($pid) modLog("Automatically archived thread #{$post['thread_id']} due to new thread #{$pid} (early 404 is set, #{$post['thread_id']} had {$post['reply_count']} replies)");
} else {
deletePostPermanent($post['thread_id'], false, false);
if ($pid) modLog("Automatically deleting thread #{$post['thread_id']} due to new thread #{$pid} (early 404 is set, #{$post['thread_id']} had {$post['reply_count']} replies)");
}
deletePost($post['thread_id'], false, false);
if ($pid) modLog("Automatically deleting thread #{$post['thread_id']} due to new thread #{$pid} (early 404 is set, #{$post['thread_id']} had {$post['reply_count']} replies)");
}
if ($config['early_404_staged']) {
@ -1666,7 +1732,7 @@ function index($page, $mod=false, $brief = false) {
}
if ($config['file_board']) {
$body = Element('fileboard.html', array('body' => $body, 'mod' => $mod));
$body = Element($config['file_fileboard'], array('body' => $body, 'mod' => $mod));
}
return array(
@ -1862,7 +1928,7 @@ function checkMute() {
$query->execute() or error(db_error($query));
if (!$mute = $query->fetch(PDO::FETCH_ASSOC)) {
// What!? He's muted but he's not muted...
// What!? They're muted but they're not muted...
return;
}
@ -2026,7 +2092,7 @@ function buildIndex($global_api = "yes") {
$content['btn'] = getPageButtons($content['pages']);
$content['antibot'] = $antibot;
file_write($filename, Element('index.html', $content));
file_write($filename, Element($config['file_board_index'], $content));
}
elseif ($action == 'delete' || $catalog_api_action == 'delete') {
file_unlink($filename);
@ -2038,11 +2104,15 @@ function buildIndex($global_api = "yes") {
if (($catalog_api_action == 'rebuild' || $action == 'rebuild' || $action == 'delete') && $page < $config['max_pages']) {
for (;$page<=$config['max_pages'];$page++) {
$filename = $board['dir'] . ($page==1 ? $config['file_index'] : sprintf($config['file_page'], $page));
file_unlink($filename);
if (file_exists($filename)) {
file_unlink($filename);
}
if ($config['api']['enabled']) {
$jsonFilename = $board['dir'] . ($page - 1) . '.json';
file_unlink($jsonFilename);
if (file_exists($jsonFilename)) {
file_unlink($jsonFilename);
}
}
}
}
@ -2111,9 +2181,6 @@ function buildJavascript() {
function checkDNSBL() {
global $config;
if (isIPv6())
return; // No IPv6 support yet.
if (!isset($_SERVER['REMOTE_ADDR']))
return; // Fix your web server configuration
@ -2123,7 +2190,11 @@ function checkDNSBL() {
if (in_array($_SERVER['REMOTE_ADDR'], $config['dnsbl_exceptions']))
return;
$ipaddr = ReverseIPOctets($_SERVER['REMOTE_ADDR']);
if (isIPv6()) {
$ipaddr = ReverseIPv6Octets($_SERVER['REMOTE_ADDR']);
} else {
$ipaddr = ReverseIPv4Octets($_SERVER['REMOTE_ADDR']);
}
foreach ($config['dnsbl'] as $blacklist) {
if (!is_array($blacklist))
@ -2159,10 +2230,14 @@ function isIPv6() {
return strstr($_SERVER['REMOTE_ADDR'], ':') !== false;
}
function ReverseIPOctets($ip) {
function ReverseIPv4Octets($ip) {
return implode('.', array_reverse(explode('.', $ip)));
}
function ReverseIPv6Octets($ip) {
return strrev(implode(".", str_split(str_replace(':', '', IP::inet_expand($ip)))));
}
function wordfilters(&$body) {
global $config;
@ -2271,6 +2346,16 @@ function unicodify($body) {
return $body;
}
function end_on_newline($body) {
return strstr($body, "\n", true);
}
// If there is no punctuation before newline, add a full-stop.
// Then replace all newlines/whitespace with a space.
function remove_paragraphs($body) {
return preg_replace("/\s+/",' ', preg_replace("/\b(?=\n*$)/m",'.',$body));
}
function extract_modifiers($body) {
$modifiers = array();
@ -2285,6 +2370,16 @@ function extract_modifiers($body) {
return $modifiers;
}
function remove_markup($body) {
global $config;
foreach ($config['markup'] as $markup) {
if (is_string($markup[1]))
$body = preg_replace($markup[0], "$1", $body);
}
return $body;
}
function remove_modifiers($body) {
return preg_replace('@<tinyboard ([\w\s]+)>(.+?)</tinyboard>@usm', '', $body);
}
@ -2304,9 +2399,6 @@ function markup(&$body, $track_cites = false, $op = false) {
$body = str_replace("\r", '', $body);
$body = utf8tohtml($body);
if (mysql_version() < 50503)
$body = mb_encode_numericentity($body, array(0x010000, 0xffffff, 0, 0xffffff), 'UTF-8');
if ($config['markup_code']) {
$code_markup = array();
$body = preg_replace_callback($config['markup_code'], function($matches) use (&$code_markup) {
@ -2328,7 +2420,7 @@ function markup(&$body, $track_cites = false, $op = false) {
$markup_urls = array();
$body = preg_replace_callback(
'/((?:https?:\/\/|ftp:\/\/|irc:\/\/)[^\s<>()"]+?(?:\([^\s<>()"]*?\)[^\s<>()"]*?)*)((?:\s|<|>|"|\.||\]|!|\?|,|&#44;|&quot;)*(?:[\s<>()"]|$))/',
'/((?:https?:\/\/|ftp:\/\/|irc:\/\/|gopher:\/\/)[^\s<>()"]+?(?:\([^\s<>()"]*?\)[^\s<>()"]*?)*)((?:\s|<|>|"|\.||\]|!|\?|,|&#44;|&quot;)*(?:[\s<>()"]|$))/',
'markup_url',
$body,
-1,
@ -2354,7 +2446,7 @@ function markup(&$body, $track_cites = false, $op = false) {
$tracked_cites = array();
// Cites
if (isset($board) && preg_match_all('/(^|[\s(])&gt;&gt;(\d+?)(?=$|[\s,.?)])/m', $body, $cites, PREG_SET_ORDER | PREG_OFFSET_CAPTURE)) {
if (isset($board) && preg_match_all('/(^|[\s(])&gt;&gt;(\d+?)(?=$|[\s,.?!)])/m', $body, $cites, PREG_SET_ORDER | PREG_OFFSET_CAPTURE)) {
if (count($cites[0]) > $config['max_cites']) {
error($config['error']['toomanycites']);
}
@ -2401,7 +2493,7 @@ function markup(&$body, $track_cites = false, $op = false) {
}
// Cross-board linking
if (preg_match_all('/(^|[\s(])&gt;&gt;&gt;\/(' . $config['board_regex'] . ')\/(?:(\d+)\/?)?(?=$|[\s,.?)])/um', $body, $cites, PREG_SET_ORDER | PREG_OFFSET_CAPTURE)) {
if (preg_match_all('/(^|[\s(])&gt;&gt;&gt;\/(' . $config['board_regex'] . ')\/(?:(\d+)\/?)?(?=$|[\s,.?!)])/um', $body, $cites, PREG_SET_ORDER | PREG_OFFSET_CAPTURE)) {
if (count($cites[0]) > $config['max_cites']) {
error($config['error']['toomanycross']);
}
@ -2522,7 +2614,7 @@ function markup(&$body, $track_cites = false, $op = false) {
$code = rtrim(ltrim($code, "\r\n"));
$code = "<pre class='code lang-$code_lang'>".str_replace(array("\n","\t"), array("&#10;","&#9;"), htmlspecialchars($code))."</pre>";
$code = "<pre class='code lang-$code_lang'>".str_replace(array("\n","\t"), array("&#10;","&#9;"), htmlspecialchars($code, ENT_COMPAT, "UTF-8", false))."</pre>";
$body = str_replace("<code $id>", $code, $body);
}
@ -2571,8 +2663,25 @@ function escape_markup_modifiers($string) {
return preg_replace('@<(tinyboard) ([\w\s]+)>@mi', '<$1 escape $2>', $string);
}
function defined_flags_accumulate($desired_flags) {
$output_flags = 0x0;
foreach ($desired_flags as $flagname) {
if (defined($flagname)) {
$flag = constant($flagname);
if (gettype($flag) != 'integer')
error(sprintf($config['error']['flag_wrongtype'], $flagname));
$output_flags |= $flag;
} else {
if ($config['deprecation_errors'])
error(sprintf($config['error']['flag_undefined'], $flagname));
}
}
return $output_flags;
}
function utf8tohtml($utf8) {
return htmlspecialchars($utf8, ENT_NOQUOTES, 'UTF-8');
$flags = defined_flags_accumulate(['ENT_QUOTES', 'ENT_SUBSTITUTE', 'ENT_DISALLOWED']);
return htmlspecialchars($utf8, $flags, 'UTF-8');
}
function ordutf8($string, &$offset) {
@ -2598,22 +2707,14 @@ function ordutf8($string, &$offset) {
return $code;
}
// Limit Non_Spacing_Mark and Enclosing_Mark characters
function strip_combining_chars($str) {
$chars = preg_split('//u', $str, -1, PREG_SPLIT_NO_EMPTY);
$str = '';
foreach ($chars as $char) {
$o = 0;
$ord = ordutf8($char, $o);
if ( ($ord >= 768 && $ord <= 879) || ($ord >= 1536 && $ord <= 1791) || ($ord >= 3655 && $ord <= 3659) || ($ord >= 7616 && $ord <= 7679) || ($ord >= 8400 && $ord <= 8447) || ($ord >= 65056 && $ord <= 65071))
continue;
$str .= $char;
}
return $str;
global $config;
$limit = strval($config['max_combining_chars']+1);
return preg_replace('/(\p{Me}|\p{Mn}){'.$limit.',}/u','', $str);
}
function buildThread($id, $return = false, $mod = false) {
function buildThread($id, $return = false, $mod = false, $shadow = false) {
global $board, $config, $build_pages;
$id = round($id);
@ -2632,14 +2733,22 @@ function buildThread($id, $return = false, $mod = false) {
$action = generation_strategy('sb_thread', array($board['uri'], $id));
if ($action == 'rebuild' || $return || $mod) {
$query = prepare(sprintf("SELECT * FROM ``posts_%s`` WHERE (`thread` IS NULL AND `id` = :id) OR `thread` = :id ORDER BY `thread`,`id`", $board['uri']));
$query = prepare(
sprintf("SELECT ``posts_%s``.*, 0 AS `shadow` FROM ``posts_%s`` WHERE (`thread` IS NULL AND `id` = :id) OR `thread` = :id", $board['uri'], $board['uri']) .
($shadow?" UNION ALL " . sprintf("SELECT ``shadow_posts_%s``.*, 1 AS `shadow` FROM ``shadow_posts_%s`` WHERE (`thread` IS NULL AND `id` = :id) OR `thread` = :id", $board['uri'], $board['uri']):"") .
" ORDER BY `thread`,`id`");
$query->bindValue(':id', $id, PDO::PARAM_INT);
$query->execute() or error(db_error($query));
while ($post = $query->fetch(PDO::FETCH_ASSOC)) {
// Fix Filenames if shadow copy
if($post['shadow'] && $post['files'])
$post['files'] = Shadowdelete::hashShadowDelFilenamesDBJSON($post['files']);
if (!isset($thread)) {
$thread = new Thread($post, $mod ? '?/' : $config['root'], $mod);
} else {
$post['no_shadow_restore'] = false;
$thread->add(new Post($post, $mod ? '?/' : $config['root'], $mod));
}
}
@ -2651,7 +2760,7 @@ function buildThread($id, $return = false, $mod = false) {
$hasnoko50 = $thread->postCount() >= $config['noko50_min'];
$antibot = $mod || $return ? false : create_antibot($board['uri'], $id);
$body = Element('thread.html', array(
$body = Element($config['file_thread'], array(
'board' => $board,
'thread' => $thread,
'body' => $thread->build(),
@ -2754,7 +2863,7 @@ function buildThread50($id, $return = false, $mod = false, $thread = null, $anti
$hasnoko50 = $thread->postCount() >= $config['noko50_min'];
$body = Element('thread.html', array(
$body = Element($config['file_thread'], array(
'board' => $board,
'thread' => $thread,
'body' => $thread->build(false, true),
@ -3030,6 +3139,17 @@ function shell_exec_error($command, $suppress_stdout = false) {
return $return === 'TB_SUCCESS' ? false : $return;
}
// Clone all the files and thumbnails on a post's file array, performing sanity checks.
// Used for moving and merging posts. Assumes source and destination boards are different.
// Checks that files exist and aren't special thumbnails.
function clone_files($clone_function, &$file, $dest_uri) {
global $config;
if ($file['file'] !== 'deleted' && file_exists($file['file_path']))
$clone_function($file['file_path'], sprintf($config['board_path'], $dest_uri) . $config['dir']['img'] . $file['file']);
if (isset($file['thumb']) && !in_array($file['thumb'], array('spoiler', 'deleted', 'file')) && file_exists($file['thumb_path']))
$clone_function($file['thumb_path'], sprintf($config['board_path'], $dest_uri) . $config['dir']['thumb'] . $file['thumb']);
}
/* Die rolling:
* If "dice XdY+/-Z" is in the email field (where X or +/-Z may be
* missing), X Y-sided dice are rolled and summed, with the modifier Z
@ -3038,53 +3158,60 @@ function shell_exec_error($command, $suppress_stdout = false) {
function diceRoller($post) {
global $config;
if(strpos(strtolower($post->email), 'dice%20') === 0) {
$dicestr = str_split(substr($post->email, strlen('dice%20')));
// Get params
$diceX = '';
$diceY = '';
$diceZ = '';
$curd = 'diceX';
for($i = 0; $i < count($dicestr); $i ++) {
if(is_numeric($dicestr[$i])) {
$$curd .= $dicestr[$i];
} else if($dicestr[$i] == 'd') {
$curd = 'diceY';
} else if($dicestr[$i] == '-' || $dicestr[$i] == '+') {
$curd = 'diceZ';
$$curd = $dicestr[$i];
// $dicestr_all = str_split(substr($post->email, strlen('dice%20')));
$dicestr_all = substr($post->email, strlen('dice%20'));
$dicestr_all = explode("%20", $dicestr_all);
foreach($dicestr_all as $dicestr) {
$dicestr = str_split($dicestr);
// Get params
$diceX = '';
$diceY = '';
$diceZ = '';
$curd = 'diceX';
for($i = 0; $i < count($dicestr); $i ++) {
if(is_numeric($dicestr[$i])) {
$$curd .= $dicestr[$i];
} else if($dicestr[$i] == 'd') {
$curd = 'diceY';
} else if($dicestr[$i] == '-' || $dicestr[$i] == '+') {
$curd = 'diceZ';
$$curd = $dicestr[$i];
}
}
}
// Default values for X and Z
if($diceX == '') {
$diceX = '1';
}
// Default values for X and Z
if($diceX == '') {
$diceX = '1';
}
if($diceZ == '') {
$diceZ = '+0';
}
if($diceZ == '') {
$diceZ = '+0';
}
// Intify them
$diceX = intval($diceX);
$diceY = intval($diceY);
$diceZ = intval($diceZ);
// Intify them
$diceX = intval($diceX);
$diceY = intval($diceY);
$diceZ = intval($diceZ);
// Continue only if we have valid values
if($diceX > 0 && $diceY > 0) {
$dicerolls = array();
$dicesum = $diceZ;
for($i = 0; $i < $diceX; $i++) {
$roll = rand(1, $diceY);
$dicerolls[] = $roll;
$dicesum += $roll;
}
// Continue only if we have valid values
if($diceX > 0 && $diceY > 0) {
$dicerolls = array();
$dicesum = $diceZ;
for($i = 0; $i < $diceX; $i++) {
$roll = rand(1, $diceY);
$dicerolls[] = $roll;
$dicesum += $roll;
// Prepend the result to the post body
$modifier = ($diceZ != 0) ? ((($diceZ < 0) ? ' - ' : ' + ') . abs($diceZ)) : '';
$dicesum = ($diceX > 1) ? ' = ' . $dicesum : '';
$post->body = '<table class="diceroll"><tr><td><img src="'.$config['dir']['static'].'d10.svg" alt="Dice roll" width="24"></td><td>Rolled ' . implode(', ', $dicerolls) . $modifier . $dicesum . '</td></tr></table><br/>' . $post->body;
}
// Prepend the result to the post body
$modifier = ($diceZ != 0) ? ((($diceZ < 0) ? ' - ' : ' + ') . abs($diceZ)) : '';
$dicesum = ($diceX > 1) ? ' = ' . $dicesum : '';
$post->body = '<table class="diceroll"><tr><td><img src="'.$config['dir']['static'].'d10.svg" alt="Dice roll" width="24"></td><td>Rolled ' . implode(', ', $dicerolls) . $modifier . $dicesum . '</td></tr></table><br/>' . $post->body;
}
}
}
@ -3175,6 +3302,24 @@ function link_for($post, $page50 = false, $foreignlink = false, $thread = false)
return sprintf($tpl, $id, $slug);
}
// Generate filename, extension, file id and file and thumb paths of a file
function process_filenames($file, $board_dir, $multiple, $i){
global $config;
$file['filename'] = urldecode($file['name']);
$file['extension'] = strtolower(mb_substr($file['filename'], mb_strrpos($file['filename'], '.') + 1));
if (isset($config['filename_func']))
$file['file_id'] = $config['filename_func']($file);
else
$file['file_id'] = time() . substr(microtime(), 2, 3);
if ($multiple)
$file['file_id'] .= "-$i";
$file['file_path'] = $board_dir . $config['dir']['img'] . $file['file_id'] . '.' . $file['extension'];
$file['thumb_path'] = $board_dir . $config['dir']['thumb'] . $file['file_id'] . '.' . ($config['thumb_ext'] ? $config['thumb_ext'] : $file['extension']);
return $file;
}
function prettify_textarea($s){
return str_replace("\t", '&#09;', str_replace("\n", '&#13;&#10;', htmlentities($s)));
}

6
inc/image.php

@ -300,7 +300,9 @@ class ImageConvert extends ImageBase {
return $this->height;
}
public function destroy() {
@unlink($this->temp);
if (file_exists($this->temp)) {
unlink($this->temp);
}
$this->temp = false;
}
public function resize() {
@ -311,7 +313,7 @@ class ImageConvert extends ImageBase {
$this->destroy();
}
$this->temp = tempnam($config['tmp'], 'convert');
$this->temp = tempnam($config['tmp'], 'convert') . ($config['thumb_ext'] == '' ? '' : '.' . $config['thumb_ext']);
$config['thumb_keep_animation_frames'] = (int)$config['thumb_keep_animation_frames'];

0
inc/instance-config.php

40
inc/lib/twig/extensions/Extension/I18n.php

@ -3,43 +3,29 @@
/*
* This file is part of Twig.
*
* (c) 2010 Fabien Potencier
* (c) 2010-2019 Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
class Twig_Extensions_Extension_I18n extends Twig_Extension
namespace Twig\Extensions;
use Twig\Extension\AbstractExtension;
use Twig\Extensions\TokenParser\TransTokenParser;
use Twig\TwigFilter;
class I18nExtension extends AbstractExtension
{
/**
* Returns the token parser instances to add to the existing list.
*
* @return array An array of Twig_TokenParserInterface or Twig_TokenParserBrokerInterface instances
*/
public function getTokenParsers()
{
return array(new Twig_Extensions_TokenParser_Trans());
return [new TransTokenParser()];
}
/**
* Returns a list of filters to add to the existing list.
*
* @return array An array of filters
*/
public function getFilters()
{
return array(
new Twig_SimpleFilter('trans', 'gettext'),
);
}
/**
* Returns the name of the extension.
*
* @return string The extension name
*/
public function getName()
{
return 'i18n';
return [
new TwigFilter('trans', 'gettext'),
];
}
}

5
inc/lib/twig/extensions/Extension/Tinyboard.php

@ -18,6 +18,9 @@ class Twig_Extensions_Extension_Tinyboard extends Twig_Extension
new Twig_SimpleFilter('sprintf', 'sprintf'),
new Twig_SimpleFilter('capcode', 'capcode'),
new Twig_SimpleFilter('remove_modifiers', 'remove_modifiers'),
new Twig_SimpleFilter('remove_markup', 'remove_markup'),
new Twig_SimpleFilter('end_on_newline', 'end_on_newline'),
new Twig_SimpleFilter('remove_paragraphs', 'remove_paragraphs'),
new Twig_SimpleFilter('hasPermission', 'twig_hasPermission_filter'),
new Twig_SimpleFilter('date', 'twig_date_filter'),
new Twig_SimpleFilter('poster_id', 'poster_id'),
@ -78,7 +81,7 @@ function twig_remove_whitespace_filter($data) {
}
function twig_date_filter($date, $format) {
return gmstrftime($format, $date);
return gmdate($format, $date);
}
function twig_hasPermission_filter($mod, $permission, $board = null) {

97
inc/lib/twig/extensions/Node/Trans.php

@ -3,43 +3,66 @@
/*
* This file is part of Twig.
*
* (c) 2010 Fabien Potencier
* (c) 2010-2019 Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Extensions\Node;
use Twig\Compiler;
use Twig\Node\CheckToStringNode;
use Twig\Node\Expression\AbstractExpression;
use Twig\Node\Expression\ConstantExpression;
use Twig\Node\Expression\FilterExpression;
use Twig\Node\Expression\NameExpression;
use Twig\Node\Expression\TempNameExpression;
use Twig\Node\Node;
use Twig\Node\PrintNode;
/**
* Represents a trans node.
*
* @package twig
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
* @author Fabien Potencier <fabien@symfony.com>
*/
class Twig_Extensions_Node_Trans extends Twig_Node
class TransNode extends Node
{
public function __construct(Twig_NodeInterface $body, Twig_NodeInterface $plural = null, Twig_Node_Expression $count = null, $lineno, $tag = null)
public function __construct(Node $body, Node $plural = null, AbstractExpression $count = null, Node $notes = null, $lineno, $tag = null)
{
parent::__construct(array('count' => $count, 'body' => $body, 'plural' => $plural), array(), $lineno, $tag);
$nodes = ['body' => $body];
if (null !== $count) {
$nodes['count'] = $count;
}
if (null !== $plural) {
$nodes['plural'] = $plural;
}
if (null !== $notes) {
$nodes['notes'] = $notes;
}
parent::__construct($nodes, [], $lineno, $tag);
}
/**
* Compiles the node to PHP.
*
* @param Twig_Compiler A Twig_Compiler instance
*/
public function compile(Twig_Compiler $compiler)
public function compile(Compiler $compiler)
{
$compiler->addDebugInfo($this);
list($msg, $vars) = $this->compileString($this->getNode('body'));
if (null !== $this->getNode('plural')) {
if ($this->hasNode('plural')) {
list($msg1, $vars1) = $this->compileString($this->getNode('plural'));
$vars = array_merge($vars, $vars1);
}
$function = null === $this->getNode('plural') ? 'gettext' : 'ngettext';
$function = $this->getTransFunction($this->hasNode('plural'));
if ($this->hasNode('notes')) {
$message = trim($this->getNode('notes')->getAttribute('data'));
// line breaks are not allowed cause we want a single line comment
$message = str_replace(["\n", "\r"], ' ', $message);
$compiler->write("// notes: {$message}\n");
}
if ($vars) {
$compiler
@ -47,12 +70,12 @@ class Twig_Extensions_Node_Trans extends Twig_Node
->subcompile($msg)
;
if (null !== $this->getNode('plural')) {
if ($this->hasNode('plural')) {
$compiler
->raw(', ')
->subcompile($msg1)
->raw(', abs(')
->subcompile($this->getNode('count'))
->subcompile($this->hasNode('count') ? $this->getNode('count') : null)
->raw(')')
;
}
@ -64,7 +87,7 @@ class Twig_Extensions_Node_Trans extends Twig_Node
$compiler
->string('%count%')
->raw(' => abs(')
->subcompile($this->getNode('count'))
->subcompile($this->hasNode('count') ? $this->getNode('count') : null)
->raw('), ')
;
} else {
@ -84,12 +107,12 @@ class Twig_Extensions_Node_Trans extends Twig_Node
->subcompile($msg)
;
if (null !== $this->getNode('plural')) {
if ($this->hasNode('plural')) {
$compiler
->raw(', ')
->subcompile($msg1)
->raw(', abs(')
->subcompile($this->getNode('count'))
->subcompile($this->hasNode('count') ? $this->getNode('count') : null)
->raw(')')
;
}
@ -98,28 +121,27 @@ class Twig_Extensions_Node_Trans extends Twig_Node
}
}
protected function compileString(Twig_NodeInterface $body)
private function compileString(Node $body): array
{
if ($body instanceof Twig_Node_Expression_Name || $body instanceof Twig_Node_Expression_Constant || $body instanceof Twig_Node_Expression_TempName) {
return array($body, array());
if ($body instanceof NameExpression || $body instanceof ConstantExpression || $body instanceof TempNameExpression) {
return [$body, []];
}
$vars = array();
if (count($body)) {
$vars = [];
if (\count($body)) {
$msg = '';
foreach ($body as $node) {
if (get_class($node) === 'Twig_Node' && $node->getNode(0) instanceof Twig_Node_SetTemp) {
$node = $node->getNode(1);
}
if ($node instanceof Twig_Node_Print) {
if ($node instanceof PrintNode) {
$n = $node->getNode('expr');
while ($n instanceof Twig_Node_Expression_Filter) {
while ($n instanceof FilterExpression) {
$n = $n->getNode('node');
}
while ($n instanceof CheckToStringNode) {
$n = $n->getNode('expr');
}
$msg .= sprintf('%%%s%%', $n->getAttribute('name'));
$vars[] = new Twig_Node_Expression_Name($n->getAttribute('name'), $n->getLine());
$vars[] = new NameExpression($n->getAttribute('name'), $n->getTemplateLine());
} else {
$msg .= $node->getAttribute('data');
}
@ -128,6 +150,11 @@ class Twig_Extensions_Node_Trans extends Twig_Node
$msg = $body->getAttribute('data');
}
return array(new Twig_Node(array(new Twig_Node_Expression_Constant(trim($msg), $body->getLine()))), $vars);
return [new Node([new ConstantExpression(trim($msg), $body->getTemplateLine())]), $vars];
}
private function getTransFunction(bool $plural): string
{
return $plural ? 'ngettext' : 'gettext';
}
}
}

73
inc/lib/twig/extensions/TokenParser/Trans.php

@ -3,78 +3,89 @@
/*
* This file is part of Twig.
*
* (c) 2010 Fabien Potencier
* (c) 2010-2019 Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
class Twig_Extensions_TokenParser_Trans extends Twig_TokenParser
namespace Twig\Extensions\TokenParser;
use Twig\Error\SyntaxError;
use Twig\Extensions\Node\TransNode;
use Twig\Node\Expression\NameExpression;
use Twig\Node\Node;
use Twig\Node\PrintNode;
use Twig\Node\TextNode;
use Twig\Token;
use Twig\TokenParser\AbstractTokenParser;
class TransTokenParser extends AbstractTokenParser
{
/**
* Parses a token and returns a node.
*
* @param Twig_Token $token A Twig_Token instance
*
* @return Twig_NodeInterface A Twig_NodeInterface instance
*/
public function parse(Twig_Token $token)
public function parse(Token $token)
{
$lineno = $token->getLine();
$stream = $this->parser->getStream();
$count = null;
$plural = null;
$notes = null;
if (!$stream->test(Twig_Token::BLOCK_END_TYPE)) {
if (!$stream->test(Token::BLOCK_END_TYPE)) {
$body = $this->parser->getExpressionParser()->parseExpression();
} else {
$stream->expect(Twig_Token::BLOCK_END_TYPE);
$body = $this->parser->subparse(array($this, 'decideForFork'));
if ('plural' === $stream->next()->getValue()) {
$stream->expect(Token::BLOCK_END_TYPE);
$body = $this->parser->subparse([$this, 'decideForFork']);
$next = $stream->next()->getValue();
if ('plural' === $next) {
$count = $this->parser->getExpressionParser()->parseExpression();
$stream->expect(Twig_Token::BLOCK_END_TYPE);
$plural = $this->parser->subparse(array($this, 'decideForEnd'), true);
$stream->expect(Token::BLOCK_END_TYPE);
$plural = $this->parser->subparse([$this, 'decideForFork']);
if ('notes' === $stream->next()->getValue()) {
$stream->expect(Token::BLOCK_END_TYPE);
$notes = $this->parser->subparse([$this, 'decideForEnd'], true);
}
} elseif ('notes' === $next) {
$stream->expect(Token::BLOCK_END_TYPE);
$notes = $this->parser->subparse([$this, 'decideForEnd'], true);
}
}
$stream->expect(Twig_Token::BLOCK_END_TYPE);
$stream->expect(Token::BLOCK_END_TYPE);
$this->checkTransString($body, $lineno);
return new Twig_Extensions_Node_Trans($body, $plural, $count, $lineno, $this->getTag());
return new TransNode($body, $plural, $count, $notes, $lineno, $this->getTag());
}
public function decideForFork(Twig_Token $token)
public function decideForFork(Token $token)
{
return $token->test(array('plural', 'endtrans'));
return $token->test(['plural', 'notes', 'endtrans']);
}
public function decideForEnd(Twig_Token $token)
public function decideForEnd(Token $token)
{
return $token->test('endtrans');
}
/**
* Gets the tag name associated with this token parser.
*
* @param string The tag name
*/
public function getTag()
{
return 'trans';
}
protected function checkTransString(Twig_NodeInterface $body, $lineno)
private function checkTransString(Node $body, $lineno)
{
foreach ($body as $i => $node) {
if (
$node instanceof Twig_Node_Text
$node instanceof TextNode
||
($node instanceof Twig_Node_Print && $node->getNode('expr') instanceof Twig_Node_Expression_Name)
($node instanceof PrintNode && $node->getNode('expr') instanceof NameExpression)
) {
continue;
}
throw new Twig_Error_Syntax(sprintf('The text to be translated with "trans" can only contain references to simple variables'), $lineno);
throw new SyntaxError(sprintf('The text to be translated with "trans" can only contain references to simple variables'), $lineno);
}
}
}
}

165
inc/lib/webm/ffmpeg.php

@ -4,62 +4,121 @@
* A barebones ffmpeg based webm implementation for vichan.
*/
function get_webm_info($filename) {
global $board, $config;
$filename = escapeshellarg($filename);
$ffprobe = $config['webm']['ffprobe_path'];
$ffprobe_out = array();
$webminfo = array();
exec("$ffprobe -v quiet -print_format json -show_format -show_streams $filename", $ffprobe_out);
$ffprobe_out = json_decode(implode("\n", $ffprobe_out), 1);
$webminfo['error'] = is_valid_webm($ffprobe_out);
if(empty($webminfo['error'])) {
$webminfo['width'] = $ffprobe_out['streams'][0]['width'];
$webminfo['height'] = $ffprobe_out['streams'][0]['height'];
$webminfo['duration'] = $ffprobe_out['format']['duration'];
global $board, $config;
$filename = escapeshellarg($filename);
$ffprobe = $config['webm']['ffprobe_path'];
$ffprobe_out = array();
$webminfo = array();
exec("$ffprobe -v quiet -print_format json -show_format -show_streams $filename", $ffprobe_out);
$ffprobe_out = json_decode(implode("\n", $ffprobe_out), 1);
$validcheck = is_valid_webm($ffprobe_out);
$webminfo['error'] = $validcheck['error'];
if(empty($webminfo['error'])) {
$trackmap = $validcheck['trackmap'];
$videoidx = $trackmap['videoat'][0];
$webminfo['width'] = $ffprobe_out['streams'][$videoidx]['width'];
$webminfo['height'] = $ffprobe_out['streams'][$videoidx]['height'];
$webminfo['duration'] = $ffprobe_out['format']['duration'];
}
return $webminfo;
}
function locate_webm_tracks($ffprobe_out) {
$streams = $ffprobe_out['streams'];
$streamcount = count($streams);
$videoat = array();
$audioat = array();
$others = array();
for ($k = 0; $k < $streamcount; $k++) {
$stream = $streams[$k];
$codec_type = $stream['codec_type'];
if ($codec_type === 'video') {
$videoat[] = $k;
} elseif($codec_type === 'audio') {
$audioat[] = $k;
} else {
if (isset($others[$codec_type])) {
$others[$codec_type][] = $k;
} else {
$others[$codec_type] = [$k];
}
}
}
return array('videoat' => $videoat, 'audioat' => $audioat, 'others' => $others);
/*
var_dump example:
array(3) {
["videoat"]=> array(1) {
[0]=> int(0)
}
["audioat"]=> array(1) {
[0]=> int(1)
}
["others"]=> array(1) {
["data"]=> array(2) {
[0]=> int(2)
[1]=> int(3)
}
}
return $webminfo;
}
*/
}
function is_valid_webm($ffprobe_out) {
global $board, $config;
if (empty($ffprobe_out))
return array('code' => 1, 'msg' => $config['error']['genwebmerror']);
$extension = pathinfo($ffprobe_out['format']['filename'], PATHINFO_EXTENSION);
if ($extension === 'webm') {
if ($ffprobe_out['format']['format_name'] != 'matroska,webm')
return array('code' => 2, 'msg' => $config['error']['invalidwebm']);
} elseif ($extension === 'mp4') {
if ($ffprobe_out['streams'][0]['codec_name'] != 'h264' && $ffprobe_out['streams'][1]['codec_name'] != 'aac')
return array('code' => 2, 'msg' => $config['error']['invalidwebm']);
} else {
return array('code' => 1, 'msg' => $config['error']['genwebmerror']);
}
if ((count($ffprobe_out['streams']) > 1) && (!$config['webm']['allow_audio']))
return array('code' => 3, 'msg' => $config['error']['webmhasaudio']);
if (empty($ffprobe_out['streams'][0]['width']) || (empty($ffprobe_out['streams'][0]['height'])))
return array('code' => 2, 'msg' => $config['error']['invalidwebm']);
if ($ffprobe_out['format']['duration'] > $config['webm']['max_length'])
return array('code' => 4, 'msg' => sprintf($config['error']['webmtoolong'], $config['webm']['max_length']));
global $board, $config;
if (empty($ffprobe_out))
return array('error' => array('code' => 1, 'msg' => $config['error']['genwebmerror']));
$trackmap = locate_webm_tracks($ffprobe_out);
// one video track
if (count($trackmap['videoat']) != 1)
return array('error' => array('code' => 2, 'msg' => $config['error']['invalidwebm']." [video track count]"));
$videoidx = $trackmap['videoat'][0];
$extension = pathinfo($ffprobe_out['format']['filename'], PATHINFO_EXTENSION);
if ($extension === 'webm' && !stristr($ffprobe_out['format']['format_name'], 'mp4')) {
if ($ffprobe_out['format']['format_name'] != 'matroska,webm')
return array('error' => array('code' => 2, 'msg' => $config['error']['invalidwebm']."error 1"));
} elseif ($extension === 'mp4' || stristr($ffprobe_out['format']['format_name'], 'mp4')) {
// if the video is not h264 or (there is audio but it's not aac)
if (($ffprobe_out['streams'][$videoidx]['codec_name'] != 'h264') || ((count($trackmap['audioat']) > 0) && ($ffprobe_out['streams'][$trackmap['audioat'][0]]['codec_name'] != 'aac')))
return array('error' => array('code' => 2, 'msg' => $config['error']['invalidwebm']." [h264/aac check]"));
} else {
return array('error' => array('code' => 1, 'msg' => $config['error']['genwebmerror']."error 3"));
}
if ((count($ffprobe_out['streams']) > 1) && (!$config['webm']['allow_audio']))
return array('error' => array('code' => 3, 'msg' => $config['error']['webmhasaudio']."error 4"));
if ((count($trackmap['audioat']) > 0) && !$config['webm']['allow_audio']) {
return array('error' => array('code' => 3, 'msg' => $config['error']['webmhasaudio']."error 5"));
}
if ($ffprobe_out['format']['duration'] > $config['webm']['max_length'])
return array('error' => array('code' => 4, 'msg' => sprintf($config['error']['webmtoolong'], $config['webm']['max_length'])."error 6"));
return array('error' => array(), 'trackmap' => $trackmap);
}
function make_webm_thumbnail($filename, $thumbnail, $width, $height, $duration) {
global $board, $config;
$filename = escapeshellarg($filename);
$thumbnailfc = escapeshellarg($thumbnail); // Should be safe by default but you
// can never be too safe.
$width = escapeshellarg($width);
$height = escapeshellarg($height); // Same as above.
$ffmpeg = $config['webm']['ffmpeg_path'];
$ret = 0;
$ffmpeg_out = array();
exec("$ffmpeg -strict -2 -ss " . floor($duration / 2) . " -i $filename -v quiet -an -vframes 1 -f mjpeg -vf scale=$width:$height $thumbnailfc 2>&1", $ffmpeg_out, $ret);
// Work around for https://trac.ffmpeg.org/ticket/4362
if (filesize($thumbnail) === 0) {
// try again with first frame
exec("$ffmpeg -y -strict -2 -ss 0 -i $filename -v quiet -an -vframes 1 -f mjpeg -vf scale=$width:$height $thumbnailfc 2>&1", $ffmpeg_out, $ret);
clearstatcache();
// failed if no thumbnail size even if ret code 0, ffmpeg is buggy
if (filesize($thumbnail) === 0) {
$ret = 1;
}
}
return $ret;
global $board, $config;
$filename = escapeshellarg($filename);
$thumbnailfc = escapeshellarg($thumbnail); // Should be safe by default but you
// can never be too safe.
$width = escapeshellarg($width);
$height = escapeshellarg($height); // Same as above.
$ffmpeg = $config['webm']['ffmpeg_path'];
$ret = 0;
$ffmpeg_out = array();
exec("$ffmpeg -strict -2 -ss " . floor($duration / 2) . " -i $filename -v quiet -an -vframes 1 -f mjpeg -vf scale=$width:$height $thumbnailfc 2>&1", $ffmpeg_out, $ret);
// Work around for https://trac.ffmpeg.org/ticket/4362
if (filesize($thumbnail) === 0) {
// try again with first frame
exec("$ffmpeg -y -strict -2 -ss 0 -i $filename -v quiet -an -vframes 1 -f mjpeg -vf scale=$width:$height $thumbnailfc 2>&1", $ffmpeg_out, $ret);
clearstatcache();
// failed if no thumbnail size even if ret code 0, ffmpeg is buggy
if (filesize($thumbnail) === 0) {
$ret = 1;
}
}
return $ret;
}

648
inc/mod/pages.php

File diff suppressed because it is too large

436
inc/shadow-delete.php

@ -0,0 +1,436 @@
<?php
class ShadowDelete {
static public function hashShadowDelFilename($filename)
{
global $config;
if($filename == 'spoiler')
return $config['spoiler_image'];
$file = pathinfo($filename);
return sha1($file['filename'] . $config['shadow_del']['filename_seed']) . "." . $file['extension'];
}
static public function hashShadowDelFilenamesDBJSON($files_db_json)
{
// Fix Filenames if shadow copy
$files_new = array();
foreach (json_decode($files_db_json) as $i => $f) {
if ($f->file !== 'deleted') {
// Add file to array of all files
$f->file = self::hashShadowDelFilename($f->file);
$f->thumb = self::hashShadowDelFilename($f->thumb);
$files_new[] = $f;
}
}
return json_encode($files_new);
}
// Delete a post (reply or thread)
static public function deletePost($id, $error_if_doesnt_exist=true, $rebuild_after=true) {
global $board, $config;
// Select post and replies (if thread) in one query
$query = prepare(sprintf("SELECT `id`,`thread`,`files`,`slug` FROM ``posts_%s`` WHERE `id` = :id OR `thread` = :id", $board['uri']));
$query->bindValue(':id', $id, PDO::PARAM_INT);
$query->execute() or error(db_error($query));
if ($query->rowCount() < 1) {
if ($error_if_doesnt_exist)
error($config['error']['invalidpost']);
else return false;
}
$ids = array();
$files = array();
// Temporarly Delete posts and maybe replies
while ($post = $query->fetch(PDO::FETCH_ASSOC)) {
event('shadow-delete', $post);
// If thread
if (!$post['thread']) {
// Delete thread HTML page
file_unlink($board['dir'] . $config['dir']['res'] . link_for($post) );
file_unlink($board['dir'] . $config['dir']['res'] . link_for($post, true) ); // noko50
file_unlink($board['dir'] . $config['dir']['res'] . sprintf('%d.json', $post['id']));
// Insert antispam to temp table
$antispam_query = prepare("INSERT INTO ``shadow_antispam`` SELECT * FROM ``antispam`` WHERE `board` = :board AND `thread` = :thread");
$antispam_query->bindValue(':board', $board['uri']);
$antispam_query->bindValue(':thread', $post['id']);
$antispam_query->execute() or error(db_error($antispam_query));
// Delete Antispam entry
$antispam_query = prepare('DELETE FROM ``antispam`` WHERE `board` = :board AND `thread` = :thread');
$antispam_query->bindValue(':board', $board['uri']);
$antispam_query->bindValue(':thread', $post['id']);
$antispam_query->execute() or error(db_error($antispam_query));
} elseif ($query->rowCount() == 1) {
// Rebuild thread
$rebuild = &$post['thread'];
}
if ($post['files']) {
// Move files to temp storage
foreach (json_decode($post['files']) as $i => $f) {
if ($f->file !== 'deleted') {
// Add file to array of all files
$files[] = $f;
// Move files to temp storage
@rename($board['dir'] . $config['dir']['img'] . $f->file, $board['dir'] . $config['dir']['shadow_del'] . $config['dir']['img'] . self::hashShadowDelFilename($f->file));
@rename($board['dir'] . $config['dir']['thumb'] . $f->thumb, $board['dir'] . $config['dir']['shadow_del'] . $config['dir']['thumb'] . self::hashShadowDelFilename($f->thumb));
}
}
}
$ids[] = (int)$post['id'];
}
// Determine if it is an thread or just post we are deleting
$query = prepare(sprintf("SELECT `thread` FROM ``posts_%s`` WHERE `id` = :id", $board['uri']));
$query->bindValue(':id', $id, PDO::PARAM_INT);
$query->execute() or error(db_error($query));
$thread_id = $query->fetch(PDO::FETCH_ASSOC)['thread'];
// Insert data into temp table
$insert_query = prepare("INSERT INTO ``shadow_deleted`` VALUES(NULL, :board, :post_id, :del_time, :files, :cite_ids)");
$insert_query->bindValue(':board', $board['uri'], PDO::PARAM_STR);
$insert_query->bindValue(':post_id', $id, PDO::PARAM_INT);
$insert_query->bindValue(':del_time', time(), PDO::PARAM_INT);
$insert_query->bindValue(':files', json_encode($files));
$insert_query->bindValue(':cite_ids', json_encode($ids));
$insert_query->execute() or error(db_error($insert_query));
// Insert post table into temp post table
$insert_query = prepare(sprintf("INSERT INTO ``shadow_posts_%s`` SELECT * FROM ``posts_%s`` WHERE `id` = " . implode(' OR `id` = ', $ids), $board['uri'], $board['uri']));
$insert_query->execute() or error(db_error($insert_query));
// Delete post table entries
$query = prepare(sprintf("DELETE FROM ``posts_%s`` WHERE `id` = :id OR `thread` = :id", $board['uri']));
$query->bindValue(':id', $id, PDO::PARAM_INT);
$query->execute() or error(db_error($query));
// Insert filehash table into temp filehash table
$insert_query = prepare("INSERT INTO ``shadow_filehashes`` SELECT * FROM ``filehashes`` WHERE `board` = :board AND (`post` = " . implode(' OR `post` = ', $ids) . ")");
$insert_query->bindValue(':board', $board['uri'], PDO::PARAM_STR);
$insert_query->execute() or error(db_error($insert_query));
// Delete filehash entries for thread from filehash table
$query = prepare(sprintf("DELETE FROM ``filehashes`` WHERE ( `thread` = :id OR `post` = :id ) AND `board` = '%s'", $board['uri']));
$query->bindValue(':id', $id, PDO::PARAM_INT);
$query->execute() or error(db_error($query));
// Update bump order
if (isset($thread_id))
{
$query = prepare(sprintf('SELECT MAX(`time`) AS `correct_bump` FROM `posts_%s` WHERE (`thread` = :thread AND NOT email <=> "sage") OR `id` = :thread', $board['uri']));
$query->bindValue(':thread', $thread_id, PDO::PARAM_INT);
$query->execute() or error(db_error($query));
$correct_bump = $query->fetch(PDO::FETCH_ASSOC)['correct_bump'];
$query = prepare(sprintf("UPDATE ``posts_%s`` SET `bump` = :bump WHERE `id` = :id", $board['uri']));
$query->bindValue(':bump', $correct_bump, PDO::PARAM_INT);
$query->bindValue(':id', $thread_id, PDO::PARAM_INT);
$query->execute() or error(db_error($query));
}
// Update Cite Links
$query = prepare("SELECT `board`, `post` FROM ``cites`` WHERE `target_board` = :board AND (`target` = " . implode(' OR `target` = ', $ids) . ") ORDER BY `board`");
$query->bindValue(':board', $board['uri']);
$query->execute() or error(db_error($query));
while ($cite = $query->fetch(PDO::FETCH_ASSOC)) {
if ($board['uri'] != $cite['board']) {
if (!isset($tmp_board))
$tmp_board = $board['uri'];
openBoard($cite['board']);
}
rebuildPost($cite['post']);
}
if (isset($tmp_board))
openBoard($tmp_board);
// Insert Cited to temp table
$query = prepare("INSERT INTO ``shadow_cites`` SELECT * FROM ``cites`` WHERE (`target_board` = :board AND (`target` = " . implode(' OR `target` = ', $ids) . ")) OR (`board` = :board AND (`post` = " . implode(' OR `post` = ', $ids) . "))");
$query->bindValue(':board', $board['uri']);
$query->execute() or error(db_error($query));
// Delete Cites
$query = prepare("DELETE FROM ``cites`` WHERE (`target_board` = :board AND (`target` = " . implode(' OR `target` = ', $ids) . ")) OR (`board` = :board AND (`post` = " . implode(' OR `post` = ', $ids) . "))");
$query->bindValue(':board', $board['uri']);
$query->execute() or error(db_error($query));
if (isset($rebuild) && $rebuild_after) {
buildThread($rebuild);
buildIndex();
}
// If Thread ID is set return it (deleted post within thread) this will pe a positive number and thus viewed as true for legacy purposes
if(isset($thread_id))
return $thread_id;
return true;
}
// Restore a post (reply or thread)
static public function restorePost($id, $error_if_doesnt_exist=true, $rebuild_after=true) {
global $board, $config;
// Select post and replies (if thread) in one query
$query = prepare(sprintf("SELECT `id`,`thread`,`files`,`slug` FROM ``shadow_posts_%s`` WHERE `id` = :id OR `thread` = :id", $board['uri']));
$query->bindValue(':id', $id, PDO::PARAM_INT);
$query->execute() or error(db_error($query));
if ($query->rowCount() < 1) {
if ($error_if_doesnt_exist)
error($config['error']['invalidpost']);
else return false;
}
$ids = array();
// Restore posts and maybe replies
while ($post = $query->fetch(PDO::FETCH_ASSOC)) {
event('shadow-restore', $post);
// If thread
if (!$post['thread']) {
// Insert temp antispam to table
$antispam_query = prepare("INSERT INTO ``antispam`` SELECT * FROM ``shadow_antispam`` WHERE `board` = :board AND `thread` = :thread");
$antispam_query->bindValue(':board', $board['uri']);
$antispam_query->bindValue(':thread', $post['id']);
$antispam_query->execute() or error(db_error($antispam_query));
// Delete Temp Antispam entry
$antispam_query = prepare('DELETE FROM ``shadow_antispam`` WHERE `board` = :board AND `thread` = :thread');
$antispam_query->bindValue(':board', $board['uri']);
$antispam_query->bindValue(':thread', $post['id']);
$antispam_query->execute() or error(db_error($antispam_query));
}
// Restore Files
if ($post['files']) {
// Move files from temp storage
foreach (json_decode($post['files']) as $i => $f) {
if ($f->file !== 'deleted') {
@rename($board['dir'] . $config['dir']['shadow_del'] . $config['dir']['img'] . self::hashShadowDelFilename($f->file), $board['dir'] . $config['dir']['img'] . $f->file);
@rename($board['dir'] . $config['dir']['shadow_del'] . $config['dir']['thumb'] . self::hashShadowDelFilename($f->thumb), $board['dir'] . $config['dir']['thumb'] . $f->thumb);
}
}
}
$ids[] = (int)$post['id'];
}
// Delete data from temp table
$insert_query = prepare("DELETE FROM ``shadow_deleted`` WHERE `board` = :board AND `post_id` = :id");
$insert_query->bindValue(':board', $board['uri'], PDO::PARAM_STR);
$insert_query->bindValue(':id', $post['id'], PDO::PARAM_INT);
$insert_query->execute() or error(db_error($insert_query));
// Determin if it is an thread or just post we are restoring
$query = prepare(sprintf("SELECT `thread` FROM ``shadow_posts_%s`` WHERE `id` = :id", $board['uri']));
$query->bindValue(':id', $id, PDO::PARAM_INT);
$query->execute() or error(db_error($query));
$thread_id = $query->fetch(PDO::FETCH_ASSOC)['thread'];
// Insert temp post table into post table
$insert_query = prepare(sprintf("INSERT INTO ``posts_%s`` SELECT * FROM ``shadow_posts_%s`` WHERE `id` = " . implode(' OR `id` = ', $ids), $board['uri'], $board['uri']));
$insert_query->execute() or error(db_error($insert_query));
// Delete post table entries
$query = prepare(sprintf("DELETE FROM ``shadow_posts_%s`` WHERE `id` = :id OR `thread` = :id", $board['uri']));
$query->bindValue(':id', $id, PDO::PARAM_INT);
$query->execute() or error(db_error($query));
// Insert filehash table into temp filehash table
$insert_query = prepare("INSERT INTO ``filehashes`` SELECT * FROM ``shadow_filehashes`` WHERE `board` = :board AND (`post` = " . implode(' OR `post` = ', $ids) . ")");
$insert_query->bindValue(':board', $board['uri'], PDO::PARAM_STR);
$insert_query->execute() or error(db_error($insert_query));
// Delete filehash entries for thread from filehash table
$query = prepare(sprintf("DELETE FROM ``shadow_filehashes`` WHERE ( `thread` = :id OR `post` = :id ) AND `board` = '%s'", $board['uri']));
$query->bindValue(':id', $id, PDO::PARAM_INT);
$query->execute() or error(db_error($query));
// Update bump order
if (isset($thread_id))
{
$query = prepare(sprintf('SELECT MAX(`time`) AS `correct_bump` FROM `posts_%s` WHERE (`thread` = :thread AND NOT email <=> "sage") OR `id` = :thread', $board['uri']));
$query->bindValue(':thread', $thread_id, PDO::PARAM_INT);
$query->execute() or error(db_error($query));
$correct_bump = $query->fetch(PDO::FETCH_ASSOC)['correct_bump'];
$query = prepare(sprintf("UPDATE ``posts_%s`` SET `bump` = :bump WHERE `id` = :id", $board['uri']));
$query->bindValue(':bump', $correct_bump, PDO::PARAM_INT);
$query->bindValue(':id', $thread_id, PDO::PARAM_INT);
$query->execute() or error(db_error($query));
}
$query = prepare("SELECT `board`, `post` FROM ``shadow_cites`` WHERE `target_board` = :board AND (`target` = " . implode(' OR `target` = ', $ids) . ") ORDER BY `board`");
$query->bindValue(':board', $board['uri']);
$query->execute() or error(db_error($query));
while ($cite = $query->fetch(PDO::FETCH_ASSOC)) {
if ($board['uri'] != $cite['board']) {
if (!isset($tmp_board))
$tmp_board = $board['uri'];
openBoard($cite['board']);
}
rebuildPost($cite['post']);
}
if (isset($tmp_board))
openBoard($tmp_board);
// Insert Temp Cited to Cited Table
$query = prepare("INSERT INTO ``cites`` SELECT * FROM ``shadow_cites`` WHERE (`target_board` = :board AND (`target` = " . implode(' OR `target` = ', $ids) . ")) OR (`board` = :board AND (`post` = " . implode(' OR `post` = ', $ids) . "))");
$query->bindValue(':board', $board['uri']);
$query->execute() or error(db_error($query));
// Delete Temp Cites
$query = prepare("DELETE FROM ``shadow_cites`` WHERE (`target_board` = :board AND (`target` = " . implode(' OR `target` = ', $ids) . ")) OR (`board` = :board AND (`post` = " . implode(' OR `post` = ', $ids) . "))");
$query->bindValue(':board', $board['uri']);
$query->execute() or error(db_error($query));
if (isset($rebuild) && $rebuild_after) {
buildThread($rebuild);
buildIndex();
}
// If Thread ID is set return it (deleted post within thread) this will pe a positive number and thus viewed as true for legacy purposes
if(isset($thread_id))
return $thread_id;
return true;
}
// Purge a post (reply or thread)
static public function purgePost($id, $error_if_doesnt_exist=true, $rebuild_after=true) {
global $board, $config;
// Select post and replies (if thread) in one query
$query = prepare(sprintf("SELECT `id`,`thread`,`files`,`slug` FROM ``shadow_posts_%s`` WHERE `id` = :id OR `thread` = :id", $board['uri']));
$query->bindValue(':id', $id, PDO::PARAM_INT);
$query->execute() or error(db_error($query));
if ($query->rowCount() < 1) {
if ($error_if_doesnt_exist)
error($config['error']['invalidpost']);
else return false;
}
$ids = array();
// Delete files
while ($post = $query->fetch(PDO::FETCH_ASSOC)) {
event('shadow-perm-delete', $post);
if ($post['files']) {
foreach (json_decode($post['files']) as $i => $f) {
if ($f->file !== 'deleted') {
@unlink($board['dir'] . $config['dir']['shadow_del'] . $config['dir']['img'] . self::hashShadowDelFilename($f->file));
@unlink($board['dir'] . $config['dir']['shadow_del'] . $config['dir']['thumb'] . self::hashShadowDelFilename($f->thumb));
}
}
}
$ids[] = (int)$post['id'];
}
// Delete data from temp table
$insert_query = prepare("DELETE FROM ``shadow_deleted`` WHERE `board` = :board AND `post_id` = :id");
$insert_query->bindValue(':board', $board['uri'], PDO::PARAM_STR);
$insert_query->bindValue(':id', $post['id'], PDO::PARAM_INT);
$insert_query->execute() or error(db_error($insert_query));
// Determin if it is an thread or just post we are restoring
$query = prepare(sprintf("SELECT `thread` FROM ``shadow_posts_%s`` WHERE `id` = :id", $board['uri']));
$query->bindValue(':id', $id, PDO::PARAM_INT);
$query->execute() or error(db_error($query));
$thread_id = $query->fetch(PDO::FETCH_ASSOC)['thread'];
// Delete post table entries
$query = prepare(sprintf("DELETE FROM ``shadow_posts_%s`` WHERE `id` = :id OR `thread` = :id", $board['uri']));
$query->bindValue(':id', $id, PDO::PARAM_INT);
$query->execute() or error(db_error($query));
// Delete filehash entries for thread from filehash table
$query = prepare(sprintf("DELETE FROM ``shadow_filehashes`` WHERE ( `thread` = :id OR `post` = :id ) AND `board` = '%s'", $board['uri']));
$query->bindValue(':id', $id, PDO::PARAM_INT);
$query->execute() or error(db_error($query));
// Delete Temp Cites
$query = prepare("DELETE FROM ``shadow_cites`` WHERE (`target_board` = :board AND (`target` = " . implode(' OR `target` = ', $ids) . ")) OR (`board` = :board AND (`post` = " . implode(' OR `post` = ', $ids) . "))");
$query->bindValue(':board', $board['uri']);
$query->execute() or error(db_error($query));
// If Thread ID is set return it (deleted post within thread) this will pe a positive number and thus viewed as true for legacy purposes
if(isset($thread_id))
return $thread_id;
return true;
}
static public function purge() {
global $config;
// Delete data from temp table
$query = prepare("SELECT * FROM ``shadow_deleted`` WHERE `del_time` < :del_time");
$query->bindValue(':del_time', strtotime("-" . $config['shadow_del']['lifetime']), PDO::PARAM_INT);
$query->execute() or error(db_error($query));
// Temporarly Delete posts and maybe replies
while ($shadow_post = $query->fetch(PDO::FETCH_ASSOC)) {
event('shadow-perm-delete', $shadow_post);
// Set Board Dir for Deletion
$board['dir'] = sprintf($config['board_path'], $shadow_post['board']);
// Delete files from temp storage
foreach (json_decode($shadow_post['files']) as $i => $f) {
@unlink($board['dir'] . $config['dir']['shadow_del'] . $config['dir']['img'] . self::hashShadowDelFilename($f->file));
@unlink($board['dir'] . $config['dir']['shadow_del'] . $config['dir']['thumb'] . self::hashShadowDelFilename($f->thumb));
}
// Delete post table entries
$delete_query = prepare(sprintf("DELETE FROM ``shadow_posts_%s`` WHERE `id` = :id OR `thread` = :id", $shadow_post['board']));
$delete_query->bindValue(':id', $shadow_post['post_id'], PDO::PARAM_INT);
$delete_query->execute() or error(db_error($delete_query));
// Delete filehash entries for thread from filehash table
$delete_query = prepare("DELETE FROM ``shadow_filehashes`` WHERE ( `thread` = :id OR `post` = :id ) AND `board` = :board");
$delete_query->bindValue(':id', $shadow_post['post_id'], PDO::PARAM_INT);
$delete_query->bindValue(':board', $shadow_post['board'], PDO::PARAM_STR);
$delete_query->execute() or error(db_error($delete_query));
// Delete Temp Antispam entry
$delete_query = prepare('DELETE FROM ``shadow_antispam`` WHERE `board` = :board AND `thread` = :thread');
$delete_query->bindValue(':board', $shadow_post['board']);
$delete_query->bindValue(':thread', $shadow_post['post_id']);
$delete_query->execute() or error(db_error($delete_query));
// Delete Temp Cites
$ids = array();
foreach (json_decode($shadow_post['cite_ids']) as $c)
$ids[] = $c;
// Delete Temp Cites
$delete_query = prepare("DELETE FROM ``cites`` WHERE (`target_board` = :board AND (`target` = " . implode(' OR `target` = ', $ids) . ")) OR (`board` = :board AND (`post` = " . implode(' OR `post` = ', $ids) . "))");
$delete_query->bindValue(':board', $shadow_post['board']);
$delete_query->execute() or error(db_error($delete_query));
}
// Delete data from temp table
$query = prepare("DELETE FROM ``shadow_deleted`` WHERE `del_time` < :del_time");
$query->bindValue(':del_time', strtotime("-" . $config['shadow_del']['lifetime']), PDO::PARAM_INT);
$query->execute() or error(db_error($query));
return true;
}
}
?>

188
inc/statistics.php

@ -0,0 +1,188 @@
<?php
class Statistics {
static public function get_stat_24h($boardName = false, $realtime = true, $boards = false) {
global $config, $pdo;
$query = "";
if(!$boardName) {
// Get list of all boards
if($boards === false)
$boards = listBoards();
// Get post count by hour for the last day
$query = "SELECT SUM(count) AS count, hour FROM (";
foreach ($boards as $board) {
if($realtime)
$query .= sprintf("SELECT COUNT(*) AS count, HOUR(FROM_UNIXTIME(time)) AS hour FROM posts_%s WHERE DATE(FROM_UNIXTIME(time)) = CURDATE() GROUP BY hour UNION ALL ", $board['uri']);
else
$query .= sprintf("SELECT COUNT(*) AS count, HOUR(FROM_UNIXTIME(time)) AS hour FROM posts_%s WHERE DATE(FROM_UNIXTIME(time)) = DATE(NOW() - INTERVAL 1 HOUR) AND HOUR(FROM_UNIXTIME(time)) <= HOUR(NOW() - INTERVAL 1 HOUR) GROUP BY hour UNION ALL ", $board['uri']);
}
// Remove the last "UNION ALL" seperator and complete the query
$query = preg_replace('/UNION ALL $/', ') AS deriv_all GROUP BY hour ORDER BY hour ASC', $query);
} else {
if($realtime)
$query = sprintf("SELECT COUNT(*) AS count, HOUR(FROM_UNIXTIME(time)) AS hour FROM posts_%s WHERE DATE(FROM_UNIXTIME(time)) = CURDATE() GROUP BY hour", $boardName);
else
$query = sprintf("SELECT COUNT(*) AS count, HOUR(FROM_UNIXTIME(time)) AS hour FROM posts_%s WHERE DATE(FROM_UNIXTIME(time)) = DATE(NOW() - INTERVAL 1 HOUR) AND HOUR(FROM_UNIXTIME(time)) <= HOUR(NOW() - INTERVAL 1 HOUR) GROUP BY hour", $boardName);
}
$query = query($query) or error(db_error($query));
$query_result = $query->fetchAll(PDO::FETCH_ASSOC);
if(empty($query_result))
$query_result = [['hour' => 0, 'count' => "0"]];
// Get 24h array over post count
$statistics_hour = array_fill(0,24,0);
foreach ($query_result as &$hour_data) {
$statistics_hour[$hour_data['hour']] = $hour_data['count'];
}
// Set last variables to 'null' for JavaScript
$last_hour = end($query_result)['hour'];
if($last_hour != 23)
for($i=$last_hour+1; $i<24; $i++)
$statistics_hour[$i] = 'null';
// Make string for JS
$statistics_hour = implode(",", $statistics_hour);
return $statistics_hour;
}
static public function get_stat_week($previous_week = false, $boardName = false, $realtime = true, $hour_realtime = true, $boards = false) {
global $config, $pdo;
$query = "";
if(!$boardName) {
// Get list of all boards
if($boards === false)
$boards = listBoards();
// Get post count by hour for the last week
$query = "SELECT SUM(count) AS count, day FROM (";
foreach ($boards as $board) {
if($previous_week) {
if($realtime)
$query .= sprintf("SELECT COUNT(*) AS count, WEEKDAY(FROM_UNIXTIME(time)) AS day FROM posts_%s WHERE YEARWEEK(FROM_UNIXTIME(time), 1) = YEARWEEK(DATE_SUB(NOW(), INTERVAL 1 WEEK), 1) GROUP BY day UNION ALL ", $board['uri']);
else if($hour_realtime)
$query .= sprintf("SELECT COUNT(*) AS count, WEEKDAY(FROM_UNIXTIME(time)) AS day FROM posts_%s WHERE YEARWEEK(FROM_UNIXTIME(time), 1) = YEARWEEK(DATE_SUB(NOW() - INTERVAL 1 HOUR, INTERVAL 1 WEEK), 1) GROUP BY day UNION ALL ", $board['uri']);
else
$query .= sprintf("SELECT COUNT(*) AS count, WEEKDAY(FROM_UNIXTIME(time)) AS day FROM posts_%s WHERE YEARWEEK(FROM_UNIXTIME(time), 1) = YEARWEEK(DATE_SUB(NOW() - INTERVAL 1 DAY, INTERVAL 1 WEEK), 1) GROUP BY day UNION ALL ", $board['uri']);
} else {
if($realtime)
$query .= sprintf("SELECT COUNT(*) AS count, WEEKDAY(FROM_UNIXTIME(time)) AS day FROM posts_%s WHERE YEARWEEK(FROM_UNIXTIME(time), 1) = YEARWEEK(NOW(), 1) GROUP BY day UNION ALL ", $board['uri']);
else if($hour_realtime)
$query .= sprintf("SELECT COUNT(*) AS count, WEEKDAY(FROM_UNIXTIME(time)) AS day FROM posts_%s WHERE YEARWEEK(FROM_UNIXTIME(time), 1) = YEARWEEK(NOW() - INTERVAL 1 HOUR, 1) AND ( (DATE(FROM_UNIXTIME(time)) = DATE(NOW() - INTERVAL 1 HOUR) AND HOUR(FROM_UNIXTIME(time)) <= HOUR(NOW() - INTERVAL 1 HOUR)) OR (DATE(FROM_UNIXTIME(time)) < DATE(NOW() - INTERVAL 1 HOUR)) ) GROUP BY day UNION ALL ", $board['uri']);
else
$query .= sprintf("SELECT COUNT(*) AS count, WEEKDAY(FROM_UNIXTIME(time)) AS day FROM posts_%s WHERE YEARWEEK(FROM_UNIXTIME(time), 1) = YEARWEEK(NOW() - INTERVAL 1 DAY, 1) AND WEEKDAY(FROM_UNIXTIME(time)) <= WEEKDAY(NOW() - INTERVAL 1 DAY) GROUP BY day UNION ALL ", $board['uri']);
}
}
// Remove the last "UNION ALL" seperator and complete the query
$query = preg_replace('/UNION ALL $/', ') AS deriv_all GROUP BY day ORDER BY day ASC', $query);
} else {
if($previous_week) {
if($realtime)
$query = sprintf("SELECT COUNT(*) AS count, WEEKDAY(FROM_UNIXTIME(time)) AS day FROM posts_%s WHERE YEARWEEK(FROM_UNIXTIME(time), 1) = YEARWEEK(DATE_SUB(NOW(), INTERVAL 1 WEEK), 1) GROUP BY day", $boardName);
else if($hour_realtime)
$query = sprintf("SELECT COUNT(*) AS count, WEEKDAY(FROM_UNIXTIME(time)) AS day FROM posts_%s WHERE YEARWEEK(FROM_UNIXTIME(time), 1) = YEARWEEK(DATE_SUB(NOW() - INTERVAL 1 HOUR, INTERVAL 1 WEEK), 1) GROUP BY day", $boardName);
else
$query = sprintf("SELECT COUNT(*) AS count, WEEKDAY(FROM_UNIXTIME(time)) AS day FROM posts_%s WHERE YEARWEEK(FROM_UNIXTIME(time), 1) = YEARWEEK(DATE_SUB(NOW() - INTERVAL 1 DAY, INTERVAL 1 WEEK), 1) GROUP BY day", $boardName);
} else {
if($realtime)
$query .= sprintf("SELECT COUNT(*) AS count, WEEKDAY(FROM_UNIXTIME(time)) AS day FROM posts_%s WHERE YEARWEEK(FROM_UNIXTIME(time), 1) = YEARWEEK(NOW(), 1) GROUP BY day", $boardName);
else if($hour_realtime)
$query .= sprintf("SELECT COUNT(*) AS count, WEEKDAY(FROM_UNIXTIME(time)) AS day FROM posts_%s WHERE YEARWEEK(FROM_UNIXTIME(time), 1) = YEARWEEK(NOW() - INTERVAL 1 HOUR, 1) AND ( (DATE(FROM_UNIXTIME(time)) = DATE(NOW() - INTERVAL 1 HOUR) AND HOUR(FROM_UNIXTIME(time)) <= HOUR(NOW() - INTERVAL 1 HOUR)) OR (DATE(FROM_UNIXTIME(time)) < DATE(NOW() - INTERVAL 1 HOUR)) ) GROUP BY day", $boardName);
else
$query .= sprintf("SELECT COUNT(*) AS count, WEEKDAY(FROM_UNIXTIME(time)) AS day FROM posts_%s WHERE YEARWEEK(FROM_UNIXTIME(time), 1) = YEARWEEK(NOW() - INTERVAL 1 DAY, 1) AND WEEKDAY(FROM_UNIXTIME(time)) <= WEEKDAY(NOW() - INTERVAL 1 DAY) GROUP BY day", $boardName);
}
}
$query = query($query) or error(db_error($query));
$query_result = $query->fetchAll(PDO::FETCH_ASSOC);
// Get week array over post count
$statistics_week = array_fill(0,7,0);
foreach ($query_result as &$day_data) {
$statistics_week[$day_data['day']] = $day_data['count'];
}
return $statistics_week;
}
static public function get_stat_week_labels($week_data) {
return sprintf("'Monday\\n(%d)', 'Tuesday\\n(%d)', 'Wednesday\\n(%d)', 'Thursday\\n(%d)', 'Friday\\n(%d)', 'Saturday\\n(%d)', 'Sunday\\n(%d)'", $week_data[0], $week_data[1], $week_data[2], $week_data[3], $week_data[4], $week_data[5], $week_data[6]);
}
static public function get_stat_week_jsdata($week_data) {
// Make string for JS
return implode(",", $week_data);
}
static public function getPostStatistics($boards) {
global $config;
if (!isset($config['boards'])) {
return null;
}
$HOUR = 3600;
$DAY = $HOUR * 24;
$WEEK = $DAY * 7;
$stats = [];
$hourly = [];
$daily = [];
$weekly = [];
foreach ($boards as $board) {
if (!array_key_exists('uri', $board)) {
// board doesn't exist.
continue;
}
$_board = getBoardInfo($board['uri']);
if (!$_board) {
// board doesn't exist.
continue;
}
$boardStat['title'] = $_board['title'];
$boardStat['hourly_ips'] = Statistics::countUniqueIps($hourly, $HOUR, $_board);
$boardStat['daily_ips'] = Statistics::countUniqueIps($daily, $DAY, $_board);
$boardStat['weekly_ips'] = Statistics::countUniqueIps($weekly, $WEEK, $_board);
$pph_query = query(
sprintf("SELECT COUNT(*) AS count FROM ``posts_%s`` WHERE time > %d",
$_board['uri'],
time()-3600)
) or error(db_error());
$boardStat['pph'] = $pph_query->fetch()['count'];
$stats['boards'][] = $boardStat;
}
$stats['hourly_ips'] = count($hourly);
$stats['daily_ips'] = count($daily);
$stats['weekly_ips'] = count($weekly);
$stats['pph'] = array_sum(array_column($stats['boards'], 'pph'));
return $stats;
}
static private function countUniqueIps(&$markAsCounted, $timespan, $_board) {
$unique_query = query(
sprintf("SELECT DISTINCT ip FROM ``posts_%s`` WHERE time > %d",
$_board['uri'],
time()-$timespan)
) or error(db_error());
$uniqueIps = $unique_query->fetchAll();
foreach ($uniqueIps as $_k => $row) {
$markAsCounted[$row['ip']] = true;
}
return count($uniqueIps);
}
}
?>

6
inc/template.php

@ -10,16 +10,16 @@ $twig = false;
function load_twig() {
global $twig, $config;
$loader = new Twig_Loader_Filesystem($config['dir']['template']);
$loader = new Twig\Loader\FilesystemLoader($config['dir']['template']);
$loader->setPaths($config['dir']['template']);
$twig = new Twig_Environment($loader, array(
$twig = new Twig\Environment($loader, array(
'autoescape' => false,
'cache' => is_writable('templates') || (is_dir('templates/cache') && is_writable('templates/cache')) ?
"{$config['dir']['template']}/cache" : false,
'debug' => $config['debug']
));
$twig->addExtension(new Twig_Extensions_Extension_Tinyboard());
$twig->addExtension(new Twig_Extensions_Extension_I18n());
$twig->addExtension(new Twig\Extensions\I18nExtension());
}
function Element($templateFile, array $options) {

116
install.php

@ -1,7 +1,7 @@
<?php
// Installation/upgrade file
define('VERSION', '5.1.5');
// Installation/upgrade file
define('VERSION', '6.0.4');
require 'inc/bootstrap.php';
loadConfig();
@ -68,10 +68,7 @@ if (file_exists($config['has_installed'])) {
function __query($sql) {
sql_open();
if (mysql_version() >= 50503)
return query($sql);
else
return query(str_replace('utf8mb4', 'utf8', $sql));
return query($sql);
}
$boards = listBoards();
@ -561,8 +558,11 @@ if (file_exists($config['has_installed'])) {
break;
}
case '4.4.98-pre':
if (!$twig) load_twig();
$twig->clearCacheFiles();
foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator('templates/cache/'), RecursiveIteratorIterator::LEAVES_ONLY) as $file) {
if ($file->isFile()) {
@unlink($file->getPathname());
}
}
case '4.4.98':
case '4.5.0':
case '4.5.1':
@ -668,6 +668,103 @@ if (file_exists($config['has_installed'])) {
KEY `ipstart` (`ip`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;
') or error(db_error());
case '6.0.2':
query('CREATE TABLE IF NOT EXISTS ``announcements`` (
`id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`creator` int(10) NOT NULL,
`date` int(10) NOT NULL,
`text` text NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;
') or error(db_error());
case '6.0.3':
foreach ($boards as &$_board) {
query(sprintf("CREATE TABLE IF NOT EXISTS ``archive_%s`` (
`id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT,
`snippet` text NOT NULL,
`lifetime` int(11) NOT NULL,
`files` text NOT NULL,
`featured` int(1) NOT NULL,
UNIQUE KEY `id` (`id`),
KEY `lifetime` (`lifetime`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;
", $_board['uri']) or error(db_error()));
query(sprintf("CREATE TABLE IF NOT EXISTS ``shadow_posts_archive_%s`` (
`id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT,
`thread` int(11) DEFAULT NULL,
`subject` varchar(100) DEFAULT NULL,
`email` varchar(30) DEFAULT NULL,
`name` varchar(35) DEFAULT NULL,
`trip` varchar(15) DEFAULT NULL,
`capcode` varchar(50) DEFAULT NULL,
`body` text NOT NULL,
`body_nomarkup` text,
`time` int(11) NOT NULL,
`bump` int(11) DEFAULT NULL,
`files` text,
`num_files` int(11) DEFAULT '0',
`filehash` text CHARACTER SET ascii,
`password` varchar(20) DEFAULT NULL,
`ip` varchar(61) CHARACTER SET ascii NOT NULL,
`sticky` int(1) NOT NULL,
`locked` int(1) NOT NULL,
`cycle` int(1) NOT NULL,
`sage` int(1) NOT NULL,
`embed` text,
`slug` varchar(256) DEFAULT NULL,
UNIQUE KEY `id` (`id`),
KEY `thread_id` (`thread`,`id`),
KEY `filehash` (`filehash`(40)),
KEY `time` (`time`),
KEY `ip` (`ip`),
KEY `list_threads` (`thread`,`sticky`,`bump`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;
", $_board['uri']) or error(db_error()));
}
query('CREATE TABLE IF NOT EXISTS ``shadow_antispam`` (
`board` varchar(58) NOT NULL,
`thread` int(11) DEFAULT NULL,
`hash` char(40) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
`created` int(11) NOT NULL,
`expires` int(11) DEFAULT NULL,
`passed` smallint(6) NOT NULL,
PRIMARY KEY (`hash`),
KEY `board` (`board`,`thread`),
KEY `expires` (`expires`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;
') or error(db_error());
query('CREATE TABLE IF NOT EXISTS ``shadow_cites`` (
`board` varchar(58) NOT NULL,
`post` int(11) NOT NULL,
`target_board` varchar(58) NOT NULL,
`target` int(11) NOT NULL,
KEY `target` (`target_board`,`target`),
KEY `post` (`board`,`post`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;
') or error(db_error());
query('CREATE TABLE IF NOT EXISTS ``shadow_deleted`` (
`id` int(10) NOT NULL AUTO_INCREMENT,
`board` varchar(58) NOT NULL,
`post_id` int(10) NOT NULL,
`del_time` int(11) NOT NULL,
`files` text CHARACTER SET ascii NOT NULL,
`cite_ids` text CHARACTER SET armscii8 NOT NULL,
PRIMARY KEY (`id`),
KEY `board` (`board`),
KEY `post_id` (`post_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;
') or error(db_error());
query('CREATE TABLE IF NOT EXISTS ``shadow_filehashes`` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`board` varchar(58) NOT NULL,
`thread` int(11) NOT NULL,
`post` int(11) NOT NULL,
`filehash` text CHARACTER SET ascii NOT NULL,
PRIMARY KEY (`id`),
KEY `thread_id` (`thread`),
KEY `post_id` (`post`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;
') or error(db_error());
case false:
// TODO: enhance Tinyboard -> vichan upgrade path.
query("CREATE TABLE IF NOT EXISTS ``search_queries`` ( `ip` varchar(39) NOT NULL, `time` int(11) NOT NULL, `query` text NOT NULL) ENGINE=MyISAM DEFAULT CHARSET=utf8;") or error(db_error());
@ -1021,7 +1118,6 @@ if ($step == 0) {
$sql = @file_get_contents('install.sql') or error("Couldn't load install.sql.");
sql_open();
$mysql_version = mysql_version();
// This code is probably horrible, but what I'm trying
// to do is find all of the SQL queires and put them
@ -1033,8 +1129,6 @@ if ($step == 0) {
$sql_errors = '';
foreach ($queries as $query) {
if ($mysql_version < 50503)
$query = preg_replace('/(CHARSET=|CHARACTER SET )utf8mb4/', '$1utf8', $query);
$query = preg_replace('/^([\w\s]*)`([0-9a-zA-Z$_\x{0080}-\x{FFFF}]+)`/u', '$1``$2``', $query);
if (!query($query))
$sql_errors .= '<li>' . db_error() . '</li>';

91
install.sql

@ -3,9 +3,9 @@
-- http://www.phpmyadmin.net
--
-- Host: localhost
-- Generation Time: Jul 30, 2013 at 09:45 PM
-- Server version: 5.6.10
-- PHP Version: 5.3.15
-- Generation Time: Jun 12, 2017 at 03:36 PM
-- Server version: 5.7.18-0ubuntu0.16.04.1
-- PHP Version: 7.0.18-0ubuntu0.16.04.1
SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
SET time_zone = "+00:00";
@ -412,6 +412,91 @@ CREATE TABLE IF NOT EXISTS `telegrams` (
PRIMARY KEY(`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;
-- --------------------------------------------------------
--
-- Table structure for table `shadow_antispam`
--
CREATE TABLE IF NOT EXISTS `shadow_antispam` (
`board` varchar(58) NOT NULL,
`thread` int(11) DEFAULT NULL,
`hash` char(40) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
`created` int(11) NOT NULL,
`expires` int(11) DEFAULT NULL,
`passed` smallint(6) NOT NULL,
PRIMARY KEY (`hash`),
KEY `board` (`board`,`thread`),
KEY `expires` (`expires`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;
-- --------------------------------------------------------
--
-- Table structure for table `shadow_cites`
--
CREATE TABLE IF NOT EXISTS `shadow_cites` (
`board` varchar(58) NOT NULL,
`post` int(11) NOT NULL,
`target_board` varchar(58) NOT NULL,
`target` int(11) NOT NULL,
KEY `target` (`target_board`,`target`),
KEY `post` (`board`,`post`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;
-- --------------------------------------------------------
--
-- Table structure for table `shadow_deleted`
--
CREATE TABLE IF NOT EXISTS `shadow_deleted` (
`id` int(10) NOT NULL AUTO_INCREMENT,
`board` varchar(58) NOT NULL,
`post_id` int(10) NOT NULL,
`del_time` int(11) NOT NULL,
`files` text CHARACTER SET ascii NOT NULL,
`cite_ids` text CHARACTER SET armscii8 NOT NULL,
PRIMARY KEY (`id`),
KEY `board` (`board`),
KEY `post_id` (`post_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;
-- --------------------------------------------------------
--
-- Table structure for table `shadow_filehashes`
--
CREATE TABLE IF NOT EXISTS `shadow_filehashes` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`board` varchar(58) NOT NULL,
`thread` int(11) NOT NULL,
`post` int(11) NOT NULL,
`filehash` text CHARACTER SET ascii NOT NULL,
PRIMARY KEY (`id`),
KEY `thread_id` (`thread`),
KEY `post_id` (`post`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;
-- --------------------------------------------------------
--
-- Table structure for table `votes_archive`
--
CREATE TABLE IF NOT EXISTS `votes_archive` (
`id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`board` varchar(58) NOT NULL,
`thread_id` int(10) NOT NULL,
`ip` varchar(61) CHARACTER SET ascii NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `id` (`id`),
KEY `ip` (`ip`,`board`,`thread_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;
COMMIT;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;

24
js/ajax.js

@ -56,9 +56,22 @@ $(window).ready(function() {
return xhr;
},
success: function(post_response) {
reloadCaptcha()
if (post_response.error) {
if (post_response.banned) {
// You are banned. Must post the form normally so the user can see the ban message.
// You are banned or warned. Must post the form normally so the user can see the ban message.
do_not_ajax = true;
$(form).find('input[type="submit"]').each(function() {
var $replacement = $('<input type="hidden">');
$replacement.attr('name', $(this).attr('name'));
$replacement.val(submit_txt);
$(this)
.after($replacement)
.replaceWith($('<input type="button">').val(submit_txt));
});
$(form).submit();
} else if (post_response.telegram) {
// You received a telegram. Must post the form normally so the user can see the telegram message.
do_not_ajax = true;
$(form).find('input[type="submit"]').each(function() {
var $replacement = $('<input type="hidden">');
@ -85,16 +98,17 @@ $(window).ready(function() {
$(data).find('div.post.reply').each(function() {
var id = $(this).attr('id');
if($('#' + id).length == 0) {
$(this).insertAfter($('div.post:last').next()).after('<br class="clear">');
$(this).insertAfter($('div.post').last().next()).after('<br class="clear">');
$(document).trigger('new_post', this);
// watch.js & auto-reload.js retrigger
setTimeout(function() { $(window).trigger("scroll"); }, 100);
}
});
highlightReply(post_response.id);
window.location.hash = post_response.id;
$(window).scrollTop($('div.post#reply_' + post_response.id).offset().top);
// Jump down to new post.
//highlightReply(post_response.id);
//window.location.hash = post_response.id;
//$(window).scrollTop($('div.post#reply_' + post_response.id).offset().top);
$(form).find('input[type="submit"]').val(submit_txt);
$(form).find('input[type="submit"]').removeAttr('disabled');

121
js/auto-reload.js

@ -17,20 +17,69 @@
*
*/
$(document).ready(function(){
var notify = false;
auto_reload_enabled = true; // for watch.js to interop
auto_reload_enabled = true; // for watch.js to interop
if(active_page != 'thread')
return;
// Adds Options panel item
if (typeof localStorage.auto_thread_update === 'undefined') {
localStorage.auto_thread_update = 'true'; //default value
}
if (window.Options && Options.get_tab('general')) {
Options.extend_tab("general", "<fieldset id='auto-update-fs'><legend>"+_("Auto update")+"</legend>"
+ ('<label id="auto-thread-update"><input type="checkbox">' + _('Auto update thread') + '</label>')
+ ('<label id="auto_thread_desktop_notifications"><input type="checkbox">' + _('Show desktop notifications when users quote me') + '</label>')
+ ('<label id="auto_thread_desktop_notifications_all"><input type="checkbox">' + _('Show desktop notifications on all replies') + '</label>')
+ '</fieldset>');
$(document).ready(function(){
if($('div.banner').length == 0)
return; // not index
if($(".post.op").size() != 1)
return; //not thread page
$('#auto-thread-update>input').on('click', function() {
if ($('#auto-thread-update>input').is(':checked')) {
localStorage.auto_thread_update = 'true';
} else {
localStorage.auto_thread_update = 'false';
}
});
$('#auto_thread_desktop_notifications>input,#auto_thread_desktop_notifications_all>input').on('click', function() {
if (!("Notification" in window)) return;
var setting = $(this).parent().attr('id');
if ($(this).is(':checked')) {
Notification.requestPermission(function(permission){
if (permission === "granted") {
localStorage[setting] = 'true';
}
});
if (Notification.permission === "granted") {
localStorage[setting] = 'true';
}
} else {
localStorage[setting] = 'false';
}
});
if (localStorage.auto_thread_update === 'true') {
$('#auto-thread-update>input').prop('checked', true);
}
if (localStorage.auto_thread_desktop_notifications === 'true') {
$('#auto_thread_desktop_notifications>input').prop('checked', true);
notify = "mention";
}
if (localStorage.auto_thread_desktop_notifications_all === 'true') {
$('#auto_thread_desktop_notifications_all>input').prop('checked', true);
notify = "all";
}
}
var countdown_interval;
// Add an update link
$('span#thread-links-bottom').append("<span id='updater'><a href='#' id='update_thread'>["+_("Update")+"]</a> (<input type='checkbox' id='auto_update_status'> "+_("Auto")+") <span id='update_secs'></span></span>");
$('span#thread-links-bottom').append("<span id='updater'><a href='#' id='update_thread'>["+_("Update")+"]</a> (<label><input type='checkbox' id='auto_update_status'> "+_("Auto")+"</label>) <span id='update_secs'></span></span>");
// Set the updater checkbox according to user setting
if (localStorage.auto_thread_update === 'true') {
@ -39,9 +88,9 @@ $(document).ready(function(){
// Grab the settings
var settings = new script_settings('auto-reload');
var poll_interval_mindelay = settings.get('min_delay_bottom', 5000);
var poll_interval_maxdelay = settings.get('max_delay', 600000);
var poll_interval_errordelay = settings.get('error_delay', 30000);
var poll_interval_mindelay = settings.get('min_delay_bottom', 5000);
var poll_interval_maxdelay = settings.get('max_delay', 600000);
var poll_interval_errordelay = settings.get('error_delay', 30000);
// number of ms to wait before reloading
var poll_interval_delay = poll_interval_mindelay;
@ -49,9 +98,8 @@ $(document).ready(function(){
var end_of_page = false;
var new_posts = 0;
var first_new_post = null;
var new_posts = 0;
var title = document.title;
if (typeof update_title == "undefined") {
@ -112,7 +160,6 @@ $(document).ready(function(){
new_posts = 0;
}
update_title();
first_new_post = null;
};
// automatically updates the thread after a specified delay
@ -128,9 +175,9 @@ $(document).ready(function(){
clearInterval(countdown_interval);
}
var epoch = (new Date).getTime();
var epochold = epoch;
var epoch = (new Date).getTime();
var epochold = epoch;
var timeDiff = function (delay) {
if((epoch-epochold) > delay) {
epochold = epoch = (new Date).getTime();
@ -149,19 +196,27 @@ $(document).ready(function(){
url: document.location,
success: function(data) {
var loaded_posts = 0; // the number of new posts loaded in this update
var elementsToAppend = [];
var elementsToTriggerNewpostEvent = [];
$(data).find('div.post.reply').each(function() {
var id = $(this).attr('id');
if($('#' + id).length == 0) {
if (!new_posts) {
first_new_post = this;
if (notify === "all" || (notify === "mention" && $(this).find('.own_post').length)) {
var body = $(this).children('.body').html().replace(/<br\s*[\/]?>/gi, "\n");
var n = new Notification("New reply to "+ title, {body: $('<div/>').html(body).text()});
}
$(this).insertAfter($('div.post:last').next()).after('<br class="clear">');
new_posts++;
loaded_posts++;
$(document).trigger('new_post', this);
recheck_activated();
elementsToAppend.push($(this));
elementsToAppend.push($('<br class="clear">'));
elementsToTriggerNewpostEvent.push(this);
}
});
$('div.post:last').next().after(elementsToAppend);
recheck_activated();
elementsToTriggerNewpostEvent.forEach(function(ele){
$(document).trigger('new_post', ele);
});
time_loaded = Date.now(); // interop with watch.js
@ -216,22 +271,10 @@ $(document).ready(function(){
return false;
};
$(window).scroll(function() {
recheck_activated();
// if the newest post is not visible
if($(this).scrollTop() + $(this).height() <
$('div.post:last').position().top + $('div.post:last').height()) {
end_of_page = false;
return;
} else {
if($("#auto_update_status").is(':checked') && timeDiff(poll_interval_mindelay)) {
poll(manualUpdate = true);
}
end_of_page = true;
}
});
$(document).on('ajax_after_post', function(e){
poll_current_time = poll_interval_mindelay;
});
$('#update_thread').on('click', function() { poll(manualUpdate = true); return false; });

33
js/boardlist-catalog-links.js

@ -0,0 +1,33 @@
/*
* boardlist-catalog-links.js
*
* Usage:
* $config['additional_javascript'][] = 'js/jquery.min.js';
* $config['additional_javascript'][] = 'js/boardlist-catalog-links.js';
*
*/
$(document).ready(function() {
function replace_index_links() {
$('.boardlist').children('span[class="sub"]').children('a.board').each(function() {
this.href = this.href.replace('index.html', 'catalog.html');
});
}
if (window.Options && Options.get_tab('general')) {
Options.extend_tab("general", "<fieldset><legend> Board List Catalog Links </legend><label><input type='checkbox' id='boardlist_catalog_links' /> "+_('Use catalog links for the board list')+"</label></fieldset>");
$('#boardlist_catalog_links').on('change', function(){
var setting = $(this).attr('id');
localStorage[setting] = $(this).is(':checked');
location.reload();
});
if (localStorage.boardlist_catalog_links === 'true') {
$('#boardlist_catalog_links').prop('checked', true);
replace_index_links();
}
}
});

5
js/catalog-search.js

@ -21,11 +21,10 @@ if (active_page == 'catalog') {
//search and hide none matching threads
function filter(search_term) {
$('.replies').each(function () {
var subject = $(this).children('.intro').text().toLowerCase();
var comment = $(this).clone().children().remove(':lt(2)').end().text().trim().toLowerCase();
var comment = $(this).clone().children().slice(0,2).remove().end().end().text().trim().toLowerCase();
search_term = search_term.toLowerCase();
if (subject.indexOf(search_term) == -1 && comment.indexOf(search_term) == -1) {
if (comment.indexOf(search_term) == -1) {
$(this).parents('div[id="Grid"]>.mix').css('display', 'none');
} else {
$(this).parents('div[id="Grid"]>.mix').css('display', 'inline-block');

10
js/charcount.js

@ -1,3 +1,4 @@
/*
* charcount.js
*
@ -14,16 +15,19 @@ $(document).ready(function(){
// every time an event is fired.
var $inputArea = $('#body');
var $coundownField = $('#countchar');
var $maxChars = 3601;
var $maxChars = 6001;
if ($inputArea.length == 0)
return;
// Preset countdown field to max initial content length
$coundownField.text($maxChars - $inputArea.length);
$('.countdown').text($maxChars - $inputArea.val().length);
// input :: for all modern browsers [1]
// selectionchange :: for IE9 [2]
// propertychange :: for <IE9 [3]
$inputArea.on('input selectionchange propertychange', function() {
$charCount = $maxChars - $inputArea.val().length;
$coundownField.text($charCount);
$('.countdown').text($charCount);
});
});

10
js/chartist/chartist.min.js

File diff suppressed because one or more lines are too long

4
js/comment-toolbar.js

@ -265,8 +265,8 @@ if (active_page == 'thread' || active_page == 'index') {
');
} else {
var s1 = '#formatText_keybinds', s2 = '#formatText_toolbar', e = 'click';
$('hr:first').before('<div id="formatText_keybinds" style="text-align:right"><a class="unimportant" href="javascript:void(0)">'+ _('Enable formatting keybinds') +'</a></div>');
$('hr:first').before('<div id="formatText_toolbar" style="text-align:right"><a class="unimportant" href="javascript:void(0)">'+ _('Show formatting toolbar') +'</a></div>');
$('hr').first().before('<div id="formatText_keybinds" style="text-align:right"><a class="unimportant" href="javascript:void(0)">'+ _('Enable formatting keybinds') +'</a></div>');
$('hr').first().before('<div id="formatText_toolbar" style="text-align:right"><a class="unimportant" href="javascript:void(0)">'+ _('Show formatting toolbar') +'</a></div>');
}
// add the tab for customizing the format settings

8
js/compact-boardlist.js

@ -21,7 +21,7 @@
* //$config['additional_javascript'][] = 'js/watch.js';
*
*/
$(document).on("ready", function() {
$(document).ready(function() {
if (window.Options && Options.get_tab('general')) {
Options.extend_tab("general",
"<fieldset><legend> Compact Board List </legend>"
@ -84,7 +84,7 @@ function initCompactBoardList() { //Pashe, influenced by tux, et al, WTFPL
do_boardlist = function() {
var categories = [];
var topbl = $('.boardlist:first');
var topbl = $('.boardlist').first();
topbl.find('>.sub').each(function() {
var cat = {name: $(this).data('description'), boards: []};
@ -176,8 +176,8 @@ options_tablist = $("<div id='options_tablist'></div>").appendTo(options_div);
options_button.addClass("cb-item cb-fa").html("<i class='fa fa-gear'></i>");
}
if ($(".boardlist:first").length) {
options_button.css('float', 'right').appendTo($(".boardlist:first"));
if ($(".boardlist").first().length) {
options_button.css('float', 'right').appendTo($(".boardlist").first());
}
else {
var optsdiv = $('<div style="text-align: right"></div>');

42
js/download-original.js

@ -1,42 +0,0 @@
/*
* download-original.js
* https://github.com/savetheinternet/Tinyboard/blob/master/js/download-original.js
*
* Makes image filenames clickable, allowing users to download and save files as their original filename.
* Only works in newer browsers. http://caniuse.com/#feat=download
*
* Released under the MIT license
* Copyright (c) 2012-2013 Michael Save <savetheinternet@tinyboard.org>
* Copyright (c) 2013-2014 Marcin Łabanowski <marcin@6irc.net>
*
* Usage:
* $config['additional_javascript'][] = 'js/jquery.min.js';
* $config['additional_javascript'][] = 'js/download-original.js';
*
*/
onready(function(){
var do_original_filename = function() {
var filename, truncated;
if ($(this).attr('title')) {
filename = $(this).attr('title');
truncated = true;
} else {
filename = $(this).text();
}
$(this).replaceWith(
$('<a></a>')
.attr('download', filename)
.append($(this).contents())
.attr('href', $(this).parent().parent().find('a').attr('href'))
.attr('title', _('Save as original filename') + (truncated ? ' (' + filename + ')' : ''))
);
};
$('.postfilename').each(do_original_filename);
$(document).on('new_post', function(e, post) {
$(post).find('.postfilename').each(do_original_filename);
});
});

4
js/expand-all-images.js

@ -18,7 +18,7 @@
if (active_page == 'ukko' || active_page == 'thread' || active_page == 'index')
onready(function(){
$('hr:first').before('<div id="expand-all-images" style="text-align:right"><a class="unimportant" href="javascript:void(0)"></a></div>');
$('hr').first().before('<div id="expand-all-images" style="text-align:right"><a class="unimportant" href="javascript:void(0)"></a></div>');
$('div#expand-all-images a')
.text(_('Expand all images'))
.click(function() {
@ -36,7 +36,7 @@ onready(function(){
});
if (!$('#shrink-all-images').length) {
$('hr:first').before('<div id="shrink-all-images" style="text-align:right"><a class="unimportant" href="javascript:void(0)"></a></div>');
$('hr').first().before('<div id="shrink-all-images" style="text-align:right"><a class="unimportant" href="javascript:void(0)"></a></div>');
}
$('div#shrink-all-images a')

66
js/file-selector.js

@ -104,43 +104,39 @@ $(document).on('ajax_after_post', function () {
});
var dragCounter = 0;
var dropHandlers = {
dragenter: function (e) {
e.stopPropagation();
e.preventDefault();
if (dragCounter === 0) $('.dropzone').addClass('dragover');
dragCounter++;
},
dragover: function (e) {
// needed for webkit to work
e.stopPropagation();
e.preventDefault();
},
dragleave: function (e) {
e.stopPropagation();
e.preventDefault();
dragCounter--;
if (dragCounter === 0) $('.dropzone').removeClass('dragover');
},
drop: function (e) {
e.stopPropagation();
e.preventDefault();
$('.dropzone').removeClass('dragover');
dragCounter = 0;
var fileList = e.originalEvent.dataTransfer.files;
for (var i=0; i<fileList.length; i++) {
addFile(fileList[i]);
}
}
};
// attach handlers
$(document).on(dropHandlers);
$(document).on('dragenter', '.dropzone', function (e) {
e.stopPropagation();
e.preventDefault();
if (dragCounter === 0) $('.dropzone').addClass('dragover');
dragCounter++;
});
$(document).on('dragover', '.dropzone', function (e) {
// needed for webkit to work
e.stopPropagation();
e.preventDefault();
});
$(document).on('dragleave', '.dropzone', function (e) {
e.stopPropagation();
e.preventDefault();
dragCounter--;
if (dragCounter === 0) $('.dropzone').removeClass('dragover');
});
$(document).on('drop', '.dropzone', function (e) {
e.stopPropagation();
e.preventDefault();
$('.dropzone').removeClass('dragover');
dragCounter = 0;
var fileList = e.originalEvent.dataTransfer.files;
for (var i=0; i<fileList.length; i++) {
addFile(fileList[i]);
}
});
$(document).on('click', '.dropzone .remove-btn', function (e) {
e.stopPropagation();

45
js/flag-preview.js

@ -0,0 +1,45 @@
/*
* flag-preview.js - Add preview of user flag.
*
* Usage:
* $config['additional_javascript'][] = 'js/jquery.min.js';
* $config['additional_javascript'][] = 'js/flag-preview.js';
* $config['flag_preview'] = true;
*/
function getFlagUrl(value){
// No flag or None flag
if(!value || value == "") {
return "/static/blank.gif"
} else {
return "/static/flags/"+value+".png"
}
}
function updatePreviewWithSelected(img, select) {
img.attr("src", getFlagUrl(select.find(":selected").val()));
}
$(document).ready(function(){
var flagImg = $('#flag_preview');
var flagSelect = $('#user_flag');
updatePreviewWithSelected(flagImg,flagSelect);
flagSelect.change(function() {
flagImg.attr("src", getFlagUrl($(this).find(":selected").val()));
});
});
$(window).on('quick-reply', function() {
var flagImg = $('#flag_preview');
var quickReplyFlagImg = $('form#quick-reply img[name="flag_preview"]')
updatePreviewWithSelected(quickReplyFlagImg,$('form#quick-reply select[name="user_flag"]'));
$('form#quick-reply select[name="user_flag"]').change(function() {
updatePreviewWithSelected(quickReplyFlagImg,$(this));
updatePreviewWithSelected(flagImg,$(this));
});
$('#user_flag').change(function() {
updatePreviewWithSelected(quickReplyFlagImg,$(this));
updatePreviewWithSelected(flagImg,$(this));
});
});

11
js/forced-anon.js

@ -70,13 +70,16 @@ $(document).ready(function() {
if (window.Options && Options.get_tab('general')) {
var s1 = '#hide-ids', s2 = '#forced-anon', e = 'change';
Options.extend_tab("general", "<label id='hide-ids'><input type='checkbox' /> "+_('Hide IDs')+"</label>");
Options.extend_tab("general", "<label id='forced-anon'><input type='checkbox' /> "+_('Forced anonymity')+"</label>");
Options.extend_tab("general",
"<fieldset><legend>Forced anonymity</legend>"
+ ("<label id='forced-anon'><input type='checkbox' /> "+_('Forced anonymity')+"</label>")
+ ("<label id='hide-ids'><input type='checkbox' /> "+_('Hide IDs')+"</label>")
+ "</fieldset>");
}
else {
var s1 = '#hide-ids', s2 = '#forced-anon', e = 'click';
$('hr:first').before('<div id="hide-ids" style="text-align:right"><a class="unimportant" href="javascript:void(0)">Hide IDs</a></div>');
$('hr:first').before('<div id="forced-anon" style="text-align:right"><a class="unimportant" href="javascript:void(0)">-</a></div>');
$('hr').first().before('<div id="hide-ids" style="text-align:right"><a class="unimportant" href="javascript:void(0)">Hide IDs</a></div>');
$('hr').first().before('<div id="forced-anon" style="text-align:right"><a class="unimportant" href="javascript:void(0)">-</a></div>');
$('div#forced-anon a').text(_('Forced anonymity')+' (' + (forced_anon ? _('enabled') : _('disabled')) + ')');
}

4
js/gallery-view.js

@ -3,7 +3,7 @@ $(function(){
var gallery_view = false;
$('hr:first').before('<div id="gallery-view" style="text-align:right"><a class="unimportant" href="javascript:void(0)">-</a></div>');
$('hr').first().before('<div id="gallery-view" style="text-align:right"><a class="unimportant" href="javascript:void(0)">-</a></div>');
$('#gallery-view a').html(gallery_view ? _("Disable gallery mode") : _("Enable gallery mode")).click(function() {
gallery_view = !gallery_view;
$(this).html(gallery_view ? _("Disable gallery mode") : _("Enable gallery mode"));
@ -116,7 +116,7 @@ $(function(){
if (img.match(/\.webm$|\.mp4$|\.ogv$/i)) { // We are handling video nao
i = $('<video>');
i.attr('src', img);
i.attr('autoplay', true);
i.attr('autoplay', false);
i.attr('controls', true);
i.appendTo(active);
i.hide();

2
js/id_colors.js

@ -9,7 +9,7 @@ if (active_page == 'thread' || active_page == 'index') {
else {
var selector = '#color-ids';
var e = 'click';
$('hr:first').before('<div id="color-ids" style="text-align:right"><a class="unimportant" href="javascript:void(0)">'+_('Color IDs')+'</a></div>')
$('hr').first().before('<div id="color-ids" style="text-align:right"><a class="unimportant" href="javascript:void(0)">'+_('Color IDs')+'</a></div>')
}
$(selector).on(e, function() {

64
js/image-hover.js

@ -6,7 +6,7 @@
*/
if (active_page === "catalog" || active_page === "thread" || active_page === "index" || active_page === "ukko") {
$(document).on('ready', function(){
$(document).ready(function(){
if (window.Options && Options.get_tab('general')) {
Options.extend_tab("general",
@ -34,6 +34,7 @@ if (getSetting('catalogImageHover')) $('#catalogImageHover>input').prop('checked
if (getSetting('imageHoverFollowCursor')) $('#imageHoverFollowCursor>input').prop('checked', 'checked');
function getFileExtension(filename) { //Pashe, WTFPL
if (filename == undefined) {return "unknown";} // catalog
if (filename.match(/\.([a-z0-9]+)(&loop.*)?$/i) !== null) {
return filename.match(/\.([a-z0-9]+)(&loop.*)?$/i)[1];
} else if (filename.match(/https?:\/\/(www\.)?youtube.com/)) {
@ -67,6 +68,33 @@ function getSetting(key) {
return (localStorage[key] == 'true');
}
function followCursor(e, hoverImage) {
var scrollTop = $(window).scrollTop();
var imgWidth = Number(hoverImage.css("max-width").slice(0,-2))
var imgHeight = Number(hoverImage.css("max-height").slice(0,-2))
var imgTop = e.pageY - (imgHeight/2);
var windowWidth = $(window).width();
var imgEnd = imgWidth + e.pageX;
if (imgTop < scrollTop + 15) {
imgTop = scrollTop + 15;
} else if (imgTop > scrollTop + $(window).height() - imgHeight - 15) {
imgTop = scrollTop + $(window).height() - imgHeight - 15;
}
if (imgEnd > windowWidth) {
hoverImage.css({
'left': (e.pageX + (windowWidth - imgEnd)),
'top' : imgTop,
});
} else {
hoverImage.css({
'left': e.pageX,
'top' : imgTop,
});
}
}
function initImageHover() { //Pashe, influenced by tux, et al, WTFPL
if (!getSetting("imageHover") && !getSetting("catalogImageHover")) {return;}
@ -101,30 +129,7 @@ function imageHoverStart(e) { //Pashe, anonish, WTFPL
if (hoverImage.length) {
if (getSetting("imageHoverFollowCursor")) {
var scrollTop = $(window).scrollTop();
var imgY = e.pageY;
var imgTop = imgY;
var windowWidth = $(window).width();
var imgWidth = hoverImage.width() + e.pageX;
if (imgY < scrollTop + 15) {
imgTop = scrollTop;
} else if (imgY > scrollTop + $(window).height() - hoverImage.height() - 15) {
imgTop = scrollTop + $(window).height() - hoverImage.height() - 15;
}
if (imgWidth > windowWidth) {
hoverImage.css({
'left': (e.pageX + (windowWidth - imgWidth)),
'top' : imgTop,
});
} else {
hoverImage.css({
'left': e.pageX,
'top' : imgTop,
});
}
followCursor(e, hoverImage);
hoverImage.appendTo($("body"));
}
@ -145,7 +150,7 @@ function imageHoverStart(e) { //Pashe, anonish, WTFPL
hoverImage = $('<img id="chx_hoverImage" src="'+fullUrl+'" />');
if (getSetting("imageHoverFollowCursor")) {
if (getSetting("imageHoverFollowCursor") && active_page !== "catalog") {
var size = $this.parents('.file').find('.unimportant').text().match(/\b(\d+)x(\d+)\b/),
maxWidth = $(window).width(),
maxHeight = $(window).height();
@ -158,9 +163,7 @@ function imageHoverStart(e) { //Pashe, anonish, WTFPL
"width" : size[1] + "px",
"height" : size[2] + "px",
"max-width" : (size[1] * scale) + "px",
"max-height" : (size[2] * scale) + "px",
'left' : e.pageX,
'top' : imgTop,
"max-height" : (size[2] * scale) + "px"
});
} else {
hoverImage.css({
@ -173,6 +176,9 @@ function imageHoverStart(e) { //Pashe, anonish, WTFPL
"max-height" : "100%",
});
}
if (getSetting("imageHoverFollowCursor")) {
followCursor(e, hoverImage);
}
hoverImage.appendTo($("body"));
if (isOnThread()) {$this.css("cursor", "none");}
}

2
js/infinite-scroll.js

@ -38,7 +38,7 @@ var load_next_page = function() {
if (loading) return;
loading = true;
var this_page = $(".pages a.selected:last");
var this_page = $(".pages a.selected").last();
var next_page = this_page.next();
var href = next_page.prop("href");

3
js/inline-expanding.js

@ -100,7 +100,8 @@ $(document).ready(function(){
for (var i = 0; i < link.length; i++) {
if (typeof link[i] == "object" && link[i].childNodes && typeof link[i].childNodes[0] !== 'undefined' &&
link[i].childNodes[0].src && link[i].childNodes[0].className.match(/post-image/) && !link[i].className.match(/file/)) {
link[i].childNodes[0].src && link[i].childNodes[0].className.match(/post-image/) && !link[i].className.match(/file/) &&
!link[i].href.match("\\.(pdf|djvu|djv)$") ) {
link[i].onclick = function(e) {
var img, post_body, still_open, canvas, scroll;
var thumb = this.childNodes[0];

8
js/jquery-ui.custom.min.js

File diff suppressed because one or more lines are too long

6
js/jquery.min.js

File diff suppressed because one or more lines are too long

2
js/keyboard-shortcuts.js

@ -2,7 +2,7 @@
// keyboard navigation
// v1.4
$(document).on("ready", function() {
$(document).ready(function() {
// adding keyboard navigation to options menu
if (window.Options && Options.get_tab('general')) {

4
js/live-index.js

@ -27,7 +27,7 @@ if (active_page == 'index' && (""+document.location).match(/\/(index\.html)?(\?|
};
$(function() {
$("hr:first").before("<hr /><div class='new-threads'>"+_("No new threads.")+"</div>");
$("hr").first().before("<hr /><div class='new-threads'>"+_("No new threads.")+"</div>");
$('div[id^="thread_"]').each(handle_one_thread);
@ -96,7 +96,7 @@ if (active_page == 'index' && (""+document.location).match(/\/(index\.html)?(\?|
// okay, the thread is there
}
else {
var thread = $(this).insertBefore('div[id^="thread_"]:first');
var thread = $(this).insertBefore('div[id^="thread_"]').first();
$(document).trigger("new_post", this);
}
});

6
js/local-time.js

@ -73,7 +73,7 @@ $(document).ready(function(){
times[i].setAttribute('data-local', 'true');
if (localStorage.show_relative_time === 'false') {
if (!localStorage.show_relative_time || localStorage.show_relative_time === 'false') {
times[i].innerHTML = dateformat(iso8601(t));
times[i].setAttribute('title', timeDifference(currentTime, postTime.getTime()));
} else {
@ -89,7 +89,7 @@ $(document).ready(function(){
Options.extend_tab('general', '<label id="show-relative-time"><input type="checkbox">' + _('Show relative time') + '</label>');
$('#show-relative-time>input').on('change', function() {
if (localStorage.show_relative_time !== 'false') {
if (localStorage.show_relative_time === 'true') {
localStorage.show_relative_time = 'false';
clearInterval(interval_id);
} else {
@ -100,7 +100,7 @@ $(document).ready(function(){
do_localtime(document);
});
if (localStorage.show_relative_time !== 'false') {
if (localStorage.show_relative_time === 'true') {
$('#show-relative-time>input').attr('checked','checked');
interval_id = setInterval(do_localtime, 30000, document);
}

9
js/mod/ban-list.js

@ -37,7 +37,7 @@ var banlist_init = function(token, my_boards, inMod) {
}
return pre+f.mask;
} },
reason: {name: _("Reason"), width: "calc(100% - 715px - 6 * 4px)", fmt: function(f) {
reason: {name: _("Reason"), width: "calc(100% - 770px - 6 * 4px)", fmt: function(f) {
var add = "", suf = '';
if (f.seen == 1) add += "<i class='fa fa-check' title='"+_("Seen")+"'></i>";
if (f.message) {
@ -73,7 +73,12 @@ var banlist_init = function(token, my_boards, inMod) {
un = "<em>"+_("system")+"</em>";
}
return pre + un + suf;
} }
} },
id: {
name: (inMod)?_("Edit"):"&nbsp;", width: (inMod)?"35px":"0px", fmt: function(f) {
if (!inMod) return '';
return "<a href='?/edit_ban/"+f.id+"'>Edit</a>";
} }
}, {}, t);
$("#select-all").click(function(e) {

274
js/mod/recent-posts-auto-reload.js

@ -0,0 +1,274 @@
/*
* recent-posts-auto-reload.js
*
* Hacked the regular auto-reload for the mod recent page.
*
* Released under the MIT license
* Copyright (c) 2012 Michael Save <savetheinternet@tinyboard.org>
* Copyright (c) 2013-2014 Marcin Łabanowski <marcin@6irc.net>
* Copyright (c) 2013 undido <firekid109@hotmail.com>
* Copyright (c) 2014-2015 Fredrick Brennan <admin@8chan.co>
*
* Usage:
* $config['additional_javascript'][] = 'js/jquery.min.js';
* //$config['additional_javascript'][] = 'js/titlebar-notifications.js';
*
*/
+function(){
var notify = false;
auto_reload_enabled = true; // for watch.js to interop
$(document).ready(function(){
// Adds Options panel item
if (typeof localStorage.auto_recent_update_mod === 'undefined') {
localStorage.auto_recent_update_mod = 'true'; //default value
}
if (window.Options && Options.get_tab('general')) {
Options.extend_tab("general", "<fieldset id='auto-update-fs'><legend>"+_("Auto update (recent)")+"</legend>"
+ ('<label id="auto-recent-update-mod"><input type="checkbox">' + _('Auto update recent (mod)') + '</label>')
+ ('<label id="auto_recent_desktop_notifications_all_mod"><input type="checkbox">' + _('Show desktop notifications on all replies (mod) (requires refresh)') + '</label>')
+ '</fieldset>');
$('#auto-recent-update-mod>input').on('click', function() {
if ($('#auto-recent-update-mod>input').is(':checked')) {
localStorage.auto_recent_update_mod = 'true';
} else {
localStorage.auto_recent_update_mod = 'false';
}
});
$('#auto_recent_desktop_notifications_all_mod>input').on('click', function() {
if (!("Notification" in window)) return;
var setting = $(this).parent().attr('id');
if ($(this).is(':checked')) {
Notification.requestPermission(function(permission){
if (permission === "granted") {
localStorage[setting] = 'true';
}
});
if (Notification.permission === "granted") {
localStorage[setting] = 'true';
}
} else {
localStorage[setting] = 'false';
}
});
if (localStorage.auto_recent_update_mod === 'true') {
$('#auto-recent-update-mod>input').prop('checked', true);
}
if (localStorage.auto_recent_desktop_notifications_all_mod === 'true') {
$('#auto_recent_desktop_notifications_all_mod>input').prop('checked', true);
notify = true;
}
}
var countdown_interval;
// Add an update link
$(".update-bar").append("<span id='updater'><a href='#' id='update_thread'>["+_("Update")+"]</a> (<input type='checkbox' id='auto_update_status'> "+_("Auto")+") <span id='update_secs'></span></span>");
// Set the updater checkbox according to user setting
if (localStorage.auto_recent_update_mod === 'true') {
$('#auto_update_status').prop('checked', true);
}
// Grab the settings
var settings = new script_settings('auto-reload-mod');
var poll_interval_mindelay = settings.get('min_delay_bottom', 5000);
var poll_interval_maxdelay = settings.get('max_delay', 10000);
var poll_interval_errordelay = settings.get('error_delay', 9000);
// number of ms to wait before reloading
var poll_interval_delay = poll_interval_mindelay;
var poll_current_time = poll_interval_delay;
var end_of_page = false;
var new_posts = 0;
var first_new_post = null;
var title = document.title;
if (typeof update_title == "undefined") {
var update_title = function() {
if (new_posts) {
document.title = "("+new_posts+") "+title;
} else {
document.title = title;
}
};
}
if (typeof add_title_collector != "undefined")
add_title_collector(function(){
return new_posts;
});
var window_active = true;
$(window).focus(function() {
window_active = true;
recheck_activated();
// Reset the delay if needed
if(settings.get('reset_focus', true)) {
poll_interval_delay = poll_interval_mindelay;
}
});
$(window).blur(function() {
window_active = false;
});
$('#auto_update_status').click(function() {
if($("#auto_update_status").is(':checked')) {
auto_update(poll_interval_mindelay);
} else {
stop_auto_update();
$('#update_secs').text("");
}
});
var decrement_timer = function() {
poll_current_time = poll_current_time - 1000;
$('#update_secs').text(poll_current_time/1000);
if (poll_current_time <= 0) {
poll(manualUpdate = false);
}
}
var recheck_activated = function() {
if (new_posts && window_active &&
$(window).scrollTop() <=
$('header').position().top + $('header').outerHeight(true)) {
new_posts = 0;
}
update_title();
first_new_post = null;
};
// automatically updates the thread after a specified delay
var auto_update = function(delay) {
clearInterval(countdown_interval);
poll_current_time = delay;
countdown_interval = setInterval(decrement_timer, 1000);
$('#update_secs').text(poll_current_time/1000);
}
var stop_auto_update = function() {
clearInterval(countdown_interval);
}
var epoch = (new Date).getTime();
var epochold = epoch;
var timeDiff = function (delay) {
if((epoch-epochold) > delay) {
epochold = epoch = (new Date).getTime();
return true;
}else{
epoch = (new Date).getTime();
return;
}
}
var poll = function(manualUpdate) {
stop_auto_update();
$('#update_secs').text(_("Updating..."));
$.ajax({
url: document.location,
success: function(data) {
var loaded_posts = 0; // the number of new posts loaded in this update
$($(data).find('div.post.reply,div.thread').get().reverse()).each(function(i) {
var id = $(this).attr('id');
// check that this post doesn't already exist
if($('#' + id).length == 0) {
if (notify) {
if ($(this).hasClass('thread')){
var body = $(this).children('.post').children('.body').html().replace(/<br\s*[\/]?>/gi, "\n");
var n = new Notification("New thread in "+ title, {body: $('<div/>').html(body).text()});
} else {
var body = $(this).children('.body').html().replace(/<br\s*[\/]?>/gi, "\n");
var n = new Notification("New reply to "+ title, {body: $('<div/>').html(body).text()});
}
}
$(this).parent().insertBefore($('.post-wrapper').first());
new_posts++;
loaded_posts++;
$(document).trigger('new_post', this);
recheck_activated();
}
});
time_loaded = Date.now(); // interop with watch.js
if ($('#auto_update_status').is(':checked')) {
// If there are no new posts, double the delay. Otherwise set it to the min.
if(loaded_posts == 0) {
// if the update was manual, don't increase the delay
if (manualUpdate == false) {
poll_interval_delay *= 2;
// Don't increase the delay beyond the maximum
if(poll_interval_delay > poll_interval_maxdelay) {
poll_interval_delay = poll_interval_maxdelay;
}
}
} else {
poll_interval_delay = poll_interval_mindelay;
}
auto_update(poll_interval_delay);
} else {
// Decide the message to show if auto update is disabled
if (loaded_posts > 0)
$('#update_secs').text(fmt(_("Posts updated with {0} new post(s)"), [loaded_posts]));
else
$('#update_secs').text(_("No new posts found"));
}
},
error: function(xhr, status_text, error_text) {
if (status_text == "error") {
if (error_text == "Not Found") {
$('#update_secs').text(_("Failed to load"));
$('#auto_update_status').prop('checked', false);
$('#auto_update_status').prop('disabled', true); // disable updates if thread is deleted
return;
} else {
$('#update_secs').text("Error: "+error_text);
}
} else if (status_text) {
$('#update_secs').text(_("Error: ")+status_text);
} else {
$('#update_secs').text(_("Unknown error"));
}
// Keep trying to update
if ($('#auto_update_status').is(':checked')) {
poll_interval_delay = poll_interval_errordelay;
auto_update(poll_interval_delay);
}
}
});
return false;
};
$(window).scrollStopped(function() {
recheck_activated();
});
$('#update_thread').on('click', function() { poll(manualUpdate = true); return false; });
if($("#auto_update_status").is(':checked')) {
auto_update(poll_interval_delay);
}
});
}();

2
js/mod/recent-posts.js

@ -49,7 +49,7 @@ $(document).ready(function(){
}
$('<a class="hide-post-link" href="javascript:void(0)"> Dismiss </a>')
.insertBefore(post_container.find('a.eita-link:first'))
.insertBefore(post_container.find('a.eita-link').first())
.click(function(){
hidden_data[board][id] = Math.round(Date.now() / 1000);
store_data_posts();

4
js/multi-image.js

@ -20,9 +20,9 @@ function multi_image() {
if (!(images_len >= max_images)) {
var new_file = '<br class="file_separator"/><input type="file" name="file'+(images_len+1)+'" id="upload_file'+(images_len+1)+'">';
$('[type=file]:last').after(new_file);
$('[type=file]').last().after(new_file);
if ($("#quick-reply").length) {
$('form:not(#quick-reply) [type=file]:last').after(new_file);
$('form:not(#quick-reply) [type=file]').last().after(new_file);
}
if (typeof setup_form !== 'undefined') setup_form($('form[name="post"]'));
}

2
js/no-animated-gif.js

@ -76,7 +76,7 @@ if (active_page == 'thread' || active_page == 'index' || active_page == 'ukko' |
else {
selector = '#no-animated-gif';
event = 'click';
$('hr:first').before('<div id="no-animated-gif" style="text-align:right"><a class="unimportant" href="javascript:void(0)">'+_('Unanimate GIFs')+'</a></div>')
$('hr').first().before('<div id="no-animated-gif" style="text-align:right"><a class="unimportant" href="javascript:void(0)">'+_('Unanimate GIFs')+'</a></div>')
}
$(selector).on(event, function() {

4
js/options.js

@ -109,8 +109,8 @@ $(function(){
options_button.addClass("cb-item cb-fa").html("<i class='fa fa-gear'></i>");
}
if ($(".boardlist:first").length) {
options_button.appendTo($(".boardlist:first"));
if ($(".boardlist").first().length) {
options_button.appendTo($(".boardlist").first());
}
else {
options_button.prependTo($(document.body));

2
js/options/user-css.js

@ -45,7 +45,7 @@ var update_textarea = function() {
textarea.text("/* "+_("Enter here your own CSS rules...")+" */\n" +
"/* "+_("If you want to make a redistributable style, be sure to\nhave a Yotsuba B theme selected.")+" */\n" +
"/* "+_("You can include CSS files from remote servers, for example:")+" */\n" +
'@import "http://example.com/style.css";');
'/* @import "https://example.com/style.css"; */');
}
else {
textarea.text(localStorage.user_css);

2
js/options/user-js.js

@ -54,7 +54,7 @@ var update_textarea = function() {
textarea.text("/* "+_("Enter here your own Javascript code...")+" */\n" +
"/* "+_("Have a backup of your storage somewhere, as messing here\nmay render you this website unusable.")+" */\n" +
"/* "+_("You can include JS files from remote servers, for example:")+" */\n" +
'load_js("http://example.com/script.js");');
'/* load_js("https://example.com/script.js"); */');
}
else {
textarea.text(localStorage.user_js);

25
js/post-filter.js

@ -375,7 +375,7 @@ if (active_page === 'thread' || active_page === 'index' || active_page === 'cata
var list = getList();
var postId = $post.find('.post_no').not('[id]').text();
var name, trip, uid, subject, comment;
var name, trip, uid, subject, comment, flag;
var i, length, array, rule, pattern; // temp variables
var boardId = $post.data('board');
@ -388,6 +388,7 @@ if (active_page === 'thread' || active_page === 'index' || active_page === 'cata
var hasTrip = ($post.find('.trip').length > 0);
var hasSub = ($post.find('.subject').length > 0);
var hasFlag = ($post.find('.flag').length > 0);
$post.data('hidden', false);
$post.data('hiddenByUid', false);
@ -396,6 +397,7 @@ if (active_page === 'thread' || active_page === 'index' || active_page === 'cata
$post.data('hiddenByTrip', false);
$post.data('hiddenBySubject', false);
$post.data('hiddenByComment', false);
$post.data('hiddenByFlag', false);
// add post with matched UID to localList
if (hasUID &&
@ -436,6 +438,8 @@ if (active_page === 'thread' || active_page === 'index' || active_page === 'cata
});
comment = array.join(' ');
if (hasFlag)
flag = $post.find('.flag').attr('title')
for (i = 0, length = list.generalFilter.length; i < length; i++) {
rule = list.generalFilter[i];
@ -467,6 +471,12 @@ if (active_page === 'thread' || active_page === 'index' || active_page === 'cata
hide(post);
}
break;
case 'flag':
if (hasFlag && pattern.test(flag)) {
$post.data('hiddenByFlag', true);
hide(post);
}
break;
}
} else {
switch (rule.type) {
@ -496,6 +506,13 @@ if (active_page === 'thread' || active_page === 'index' || active_page === 'cata
hide(post);
}
break;
case 'flag':
pattern = new RegExp('\\b'+ rule.value+ '\\b');
if (hasFlag && pattern.test(flag)) {
$post.data('hiddenByFlag', true);
hide(post);
}
break;
}
}
}
@ -603,7 +620,7 @@ if (active_page === 'thread' || active_page === 'index' || active_page === 'cata
'#filter-list th:nth-child(2) {text-align: left;}\n' +
'#filter-list th:nth-child(3) {text-align: center; width: 58px;}\n' +
'#filter-list tr:not(#header) {height: 22px;}\n' +
'#filter-list tr:nth-child(even) {background-color:rgba(255, 255, 255, 0.5);}\n' +
'#filter-list tr:nth-child(even) {background-color:rgba(255, 255, 255, 0.2);}\n' +
'#filter-list td:nth-child(1) {text-align: center; width: 70px;}\n' +
'#filter-list td:nth-child(3) {text-align: center; width: 58px;}\n' +
'#confirm {text-align: right; margin-bottom: -18px; padding-top: 2px; font-size: 14px; color: #FF0000;}';
@ -621,7 +638,8 @@ if (active_page === 'thread' || active_page === 'index' || active_page === 'cata
name: 'name',
trip: 'tripcode',
sub: 'subject',
com: 'comment'
com: 'comment',
flag: 'flag'
};
$ele.empty();
@ -660,6 +678,7 @@ if (active_page === 'thread' || active_page === 'index' || active_page === 'cata
'<option value="trip">'+_('Tripcode')+'</option>' +
'<option value="sub">'+_('Subject')+'</option>' +
'<option value="com">'+_('Comment')+'</option>' +
'<option value="flag">'+_('Flag')+'</option>' +
'</select>' +
'<input type="text">' +
'<input type="checkbox">' +

4
js/quick-post-controls.js

@ -39,10 +39,10 @@ $(document).ready(function(){
' <input type="submit" name="report" value="'+_('Report')+'">' +
'</div>' +
'</form>');
if($('form[name="post"]:first').length){
if($('form[name="post"]').first().length){
var board=$(this).parent().parent().parent().attr("data-board");
post_form
.attr('action', $('form[name="post"]:first').attr('action'))
.attr('action', $('form[name="post"]').first().attr('action'))
.append('<input type="hidden" value="'+board+'" name="board" />');
}else{
var board=$(this).parent().parent().parent().attr("data-board");

12
js/quick-reply.js

@ -125,7 +125,7 @@
};
var show_quick_reply = function(){
if($('div.banner').length == 0)
if(active_page != 'thread')
return;
if($('#quick-reply').length != 0)
return;
@ -139,8 +139,8 @@
$dummyStuff = $('<div class="nonsense"></div>').appendTo($postForm);
$postForm.find('table tr').each(function() {
var $th = $(this).children('th:first');
var $td = $(this).children('td:first');
var $th = $(this).children('th').first();
var $td = $(this).children('td').first();
if ($th.length && $td.length) {
$td.attr('colspan', 2);
@ -294,7 +294,7 @@
$postForm.attr('id', 'quick-reply');
$postForm.appendTo($('body')).hide();
$origPostForm = $('form[name="post"]:first');
$origPostForm = $('form[name="post"]').first();
// Synchronise body text with original post form
$origPostForm.find('textarea[name="body"]').on('change input propertychange', function() {
@ -421,7 +421,7 @@
if (settings.get('floating_link', false)) {
$(window).ready(function() {
if($('div.banner').length == 0)
if(active_page != 'thread')
return;
$('<style type="text/css">\
a.quick-reply-btn {\
@ -442,7 +442,7 @@
$(window).scroll(function() {
if ($(this).width() <= 400)
return;
if ($(this).scrollTop() < $('form[name="post"]:first').offset().top + $('form[name="post"]:first').height() - 100)
if ($(this).scrollTop() < $('form[name="post"]').first().offset().top + $('form[name="post"]').first().height() - 100)
$('.quick-reply-btn').fadeOut(100);
else
$('.quick-reply-btn').fadeIn(100);

2
js/save-user_flag.js

@ -1,5 +1,5 @@
function user_flag() {
var flagStorage = "flag_" + document.getElementsByName('board')[0].value;
var flagStorage = "flag_" + board_name;
var item = window.localStorage.getItem(flagStorage);
$('select[name=user_flag]').val(item);
$('select[name=user_flag]').change(function() {

2
js/show-op.js

@ -18,7 +18,7 @@ $(document).ready(function(){
var showOPLinks = function() {
var OP;
if ($('div.banner').length == 0) {
if (active_page = 'thread') {
OP = parseInt($(this).parent().find('div.post.op a.post_no:eq(1)').text());
} else {
OP = parseInt($('div.post.op a.post_no:eq(1)').text());

24
js/show-own-posts.js

@ -17,6 +17,28 @@
+function(){
if (typeof localStorage.own_posts_tracking === 'undefined') {
localStorage.own_posts_tracking = 'true'; // set default
}
const addgui = function () {
if (!(window.Options && Options.get_tab ('general'))) { return; }
const prefix = "showownposts_";
const gui = '<fieldset><legend>(You)s</legend>'
+ '<label><input type="checkbox" id="' + prefix + 'track">' + _('Track future (You)s') + '</label>'
+ ' <button id="' + prefix + 'clear" title="' + _('Reload the page to see the effect') + '">' + _('Clear all (You)s') + '</button>'
+ '</fieldset>';
Options.extend_tab ("general", gui);
const cb = Options.get_tab ('general').content.find ('#' + prefix + 'track');
cb.prop ('checked', localStorage.own_posts_tracking === 'true');
cb.on ("change", function (ev) {
const cb = ev.target;
localStorage.own_posts_tracking = cb.checked ? 'true' : 'false';
});
Options.get_tab ('general').content.find ('#' + prefix + 'clear').on ("click", function () {
localStorage.own_posts = '{}';
});
}
var update_own = function() {
if ($(this).is('.you')) return;
@ -61,9 +83,11 @@ $(function() {
board = $('input[name="board"]').first().val();
update_all();
addgui();
});
$(document).on('ajax_after_post', function(e, r) {
if (localStorage.own_posts_tracking !== 'true') { return; }
var posts = JSON.parse(localStorage.own_posts || '{}');
posts[board] = posts[board] || [];
posts[board].push(r.id);

4
js/thread-stats.js

@ -68,7 +68,7 @@ $(document).ready(function(){
$('#thread_stats_uids').text(size(ids));
}
var board_name = $('input[name="board"]').val();
$.getJSON('//'+ document.location.host +'/'+ board_name +'/threads.json').success(function(data){
$.getJSON('//'+ document.location.host +'/'+ board_name +'/threads.json').done(function(data){
var found, page = '???';
for (var i=0;data[i];i++){
var threads = data[i].threads;
@ -90,7 +90,7 @@ $(document).ready(function(){
// uses ajax call so it gets loaded on a delay (depending on network resources available)
var thread_stats_page_timer = setInterval(function(){
var board_name = $('input[name="board"]').val();
$.getJSON('//'+ document.location.host +'/'+ board_name +'/threads.json').success(function(data){
$.getJSON('//'+ document.location.host +'/'+ board_name +'/threads.json').done(function(data){
var found, page = '???';
for (var i=0;data[i];i++){
var threads = data[i].threads;

4
js/thread-watcher.js

@ -41,7 +41,7 @@ watchlist.render = function(reset) {
} else {
//If the watchlist has not yet been rendered, create it.
var menuStyle = getComputedStyle($('.boardlist')[0]);
$((active_page == 'ukko') ? 'hr:first' : (active_page == 'catalog') ? 'body>span:first' : 'form[name="post"]').before(
$('header').after(
$('<div id="watchlist">'+
'<div class="watchlist-controls">'+
'<span><a id="clearList">['+_('Clear List')+']</a></span>&nbsp'+
@ -151,7 +151,7 @@ $(document).ready(function(){
//Append the watchlist toggle button.
$('.boardlist, .compact-boardlist').append(' <span>[ <a class="watchlist-toggle" href="#">'+_('watchlist')+'</a> ]</span>');
//Append a watch thread button after every OP post number.
$('.op>.intro>.post_no:odd').after('<a class="watchThread" href="#">['+_('Watch Thread')+']</a>');
$('.op>.intro>.post_no').odd().after('<a class="watchThread" href="#">['+_('Watch Thread')+']</a>');
//Draw the watchlist, hidden.
watchlist.render();

2
js/toggle-images.js

@ -65,7 +65,7 @@ $(document).ready(function(){
else {
selector = '#toggle-images a';
event = 'click';
$('hr:first').before('<div id="toggle-images" style="text-align:right"><a class="unimportant" href="javascript:void(0)">-</a></div>');
$('hr').first().before('<div id="toggle-images" style="text-align:right"><a class="unimportant" href="javascript:void(0)">-</a></div>');
$('div#toggle-images a')
.text(hide_images ? _('Show images') : _('Hide images'));
}

2
js/toggle-locked-threads.js

@ -46,7 +46,7 @@ $(document).ready(function(){
else {
selector = '#toggle-locked-threads a';
event = 'click';
$('hr:first').before('<div id="toggle-locked-threads" style="text-align:right"><a class="unimportant" href="javascript:void(0)">-</a></div>');
$('hr').first().before('<div id="toggle-locked-threads" style="text-align:right"><a class="unimportant" href="javascript:void(0)">-</a></div>');
}
$('div#toggle-locked-threads a')

2
js/treeview.js

@ -65,7 +65,7 @@ $(function() {
}
}
$('hr:first').before('<div class="unimportant" style="text-align:right"><label for="treeview"><input type="checkbox" id="treeview"> '+_('Tree view')+'</label></div>');
$('hr').first().before('<div class="unimportant" style="text-align:right"><label for="treeview"><input type="checkbox" id="treeview"> '+_('Tree view')+'</label></div>');
$('input#treeview').on('change', function(e) { treeview($(this).is(':checked')); });
if (localStorage.treeview === 'true') {

162
js/watch.js

@ -14,7 +14,7 @@
* //$config['additional_javascript'][] = 'js/hide-threads.js';
* //$config['additional_javascript'][] = 'js/compact-boardlist.js';
* $config['additional_javascript'][] = 'js/watch.js';
*
*
*/
$(function(){
@ -125,7 +125,7 @@ $(function(){
var tag;
if (variant == 'desktop') {
tag = $("<a href='"+((storage()[board].slugs && storage()[board].slugs[tid]) || (modRoot+board+"/res/"+tid+".html"))+"'><span>#"+tid+"</span><span class='cb-uri watch-remove'>"+newposts+"</span>");
tag.find(".watch-remove").mouseenter(function() {
tag.find(".watch-remove").mouseenter(function() {
this.oldval = $(this).html();
$(this).css("min-width", $(this).width());
$(this).html("<i class='fa fa-minus'></i>");
@ -136,7 +136,7 @@ $(function(){
}
else if (variant == 'mobile') {
tag = $("<a href='"+((storage()[board].slugs && storage()[board].slugs[tid]) || (modRoot+board+"/res/"+tid+".html"))+"'><span>#"+tid+"</span><span class='cb-uri'>"+newposts+"</span>"
+"<span class='cb-uri watch-remove'><i class='fa fa-minus'></i></span>");
+"<span class='cb-uri watch-remove'><i class='fa fa-minus'></i></span>");
}
tag.attr('data-thread', tid)
@ -148,7 +148,7 @@ $(function(){
var t = $(this).parent().attr("data-thread");
toggle_threadwatched(b, t);
$(this).parent().parent().parent().mouseleave();
$(this).parent().remove();
$(this).parent().remove();
return false;
});
}
@ -174,48 +174,48 @@ $(function(){
var st = storage();
for (var i in st) {
if (is_pinned(st[i])) {
var link;
var link;
if (bl.find('[href*="'+modRoot+i+'/index.html"]:not(.cb-menuitem)').length) link = bl.find('[href*="'+modRoot+i+'/"]').first();
else link = $('<a href="'+modRoot+i+'/" class="cb-item cb-cat">/'+i+'/</a>').appendTo(pinned);
if (link[0].origtitle === undefined) {
link[0].origtitle = link.html();
}
else {
link.html(link[0].origtitle);
}
if (link[0].origtitle === undefined) {
link[0].origtitle = link.html();
}
else {
link.html(link[0].origtitle);
}
if (st[i].watched) {
link.css("font-weight", "bold");
if (status && status[i] && status[i].new_threads) {
link.html(link.html() + " (" + status[i].new_threads + ")");
}
}
else if (st[i].threads && osize(st[i].threads)) {
link.css("font-style", "italic");
if (st[i].watched) {
link.css("font-weight", "bold");
if (status && status[i] && status[i].new_threads) {
link.html(link.html() + " (" + status[i].new_threads + ")");
}
}
else if (st[i].threads && osize(st[i].threads)) {
link.css("font-style", "italic");
link.attr("data-board", i);
link.attr("data-board", i);
if (status && status[i] && status[i].threads) {
var new_posts = 0;
var new_posts = 0;
for (var tid in status[i].threads) {
if (status[i].threads[tid] > 0) {
new_posts += status[i].threads[tid];
}
}
if (new_posts > 0) {
new_posts += status[i].threads[tid];
}
}
if (new_posts > 0) {
link.html(link.html() + " (" + new_posts + ")");
}
}
}
if (device_type == "desktop")
link.off().mouseenter(function() {
$('.cb-menu').remove();
if (device_type == "desktop")
link.off().mouseenter(function() {
$('.cb-menu').remove();
var board = $(this).attr("data-board");
var board = $(this).attr("data-board");
var wl = construct_watchlist_for(board, "desktop").appendTo($(this))
var wl = construct_watchlist_for(board, "desktop").appendTo($(this))
.css("top", $(this).position().top
+ ($(this).css('padding-top').replace('px', '')|0)
+ ($(this).css('padding-bottom').replace('px', '')|0)
@ -225,12 +225,12 @@ $(function(){
.css("font-style", "normal");
if (typeof init_hover != "undefined")
wl.find("a.cb-menuitem").each(init_hover);
wl.find("a.cb-menuitem").each(init_hover);
}).mouseleave(function() {
$('.boardlist .cb-menu').remove();
});
}
}).mouseleave(function() {
$('.boardlist .cb-menu').remove();
});
}
}
}
@ -252,7 +252,7 @@ $(function(){
for (var i in st) {
if (st[i].watched) {
(function(i) {
(function(i) {
setTimeout(function() {
var r = $.getJSON(configRoot+i+"/threads.json", function(j, x, r) {
handle_board_json(r.board, j);
@ -260,24 +260,24 @@ $(function(){
r.board = i;
}, sched);
sched += sched_diff;
})(i);
})(i);
}
else if (st[i].threads) {
for (var j in st[i].threads) {
(function(i,j) {
setTimeout(function() {
var r = $.getJSON(configRoot+i+"/res/"+j+".json", function(k, x, r) {
handle_thread_json(r.board, r.thread, k);
}).error(function(r) {
if(r.status == 404) handle_thread_404(r.board, r.thread);
});
r.board = i;
r.thread = j;
handle_thread_json(r.board, r.thread, k);
}).fail(function(r) {
if(r.status == 404) handle_thread_404(r.board, r.thread);
});
r.board = i;
r.thread = j;
}, sched);
})(i,j);
sched += sched_diff;
}
}
}
}
@ -298,22 +298,22 @@ $(function(){
for (var j in json[i].threads) {
var thread = json[i].threads[j];
if (hidden_data[board]) { // hide threads integration
var cont = false;
for (var k in hidden_data[board]) {
if (parseInt(k) == thread.no) {
cont = true;
break;
}
}
if (cont) continue;
}
if (thread.last_modified > storage()[board].watched / 1000) {
last_thread = thread.no;
new_threads++;
}
if (hidden_data[board]) { // hide threads integration
var cont = false;
for (var k in hidden_data[board]) {
if (parseInt(k) == thread.no) {
cont = true;
break;
}
}
if (cont) continue;
}
if (thread.last_modified > storage()[board].watched / 1000) {
last_thread = thread.no;
new_threads++;
}
}
}
@ -331,7 +331,7 @@ $(function(){
var post = json.posts[i];
if (post.time > storage()[board].threads[threadid] / 1000) {
new_posts++;
new_posts++;
}
}
@ -360,7 +360,7 @@ $(function(){
var boardconfig = storage()[board] || {};
$('hr:first').before('<div id="watch-thread" style="text-align:right"><a class="unimportant" href="javascript:void(0)">-</a></div>');
$('hr').first().before('<div id="watch-thread" style="text-align:right"><a class="unimportant" href="javascript:void(0)">-</a></div>');
$('#watch-thread a').html(is_threadwatched(boardconfig, thread) ? _("Stop watching this thread") : _("Watch this thread")).click(function() {
$(this).html(toggle_threadwatched(board, thread) ? _("Stop watching this thread") : _("Watch this thread"));
update_pinned();
@ -371,14 +371,14 @@ $(function(){
var boardconfig = storage()[board] || {};
$('hr:first').before('<div id="watch-pin" style="text-align:right"><a class="unimportant" href="javascript:void(0)">-</a></div>');
$('hr').first().before('<div id="watch-pin" style="text-align:right"><a class="unimportant" href="javascript:void(0)">-</a></div>');
$('#watch-pin a').html(is_pinned(boardconfig) ? _("Unpin this board") : _("Pin this board")).click(function() {
$(this).html(toggle_pinned(board) ? _("Unpin this board") : _("Pin this board"));
$('#watch-board a').html(is_boardwatched(boardconfig) ? _("Stop watching this board") : _("Watch this board"));
update_pinned();
});
$('hr:first').before('<div id="watch-board" style="text-align:right"><a class="unimportant" href="javascript:void(0)">-</a></div>');
$('hr').first().before('<div id="watch-board" style="text-align:right"><a class="unimportant" href="javascript:void(0)">-</a></div>');
$('#watch-board a').html(is_boardwatched(boardconfig) ? _("Stop watching this board") : _("Watch this board")).click(function() {
$(this).html(toggle_boardwatched(board) ? _("Stop watching this board") : _("Watch this board"));
$('#watch-pin a').html(is_pinned(boardconfig) ? _("Unpin this board") : _("Pin this board"));
@ -398,20 +398,20 @@ $(function(){
for(var bid in status) {
if (((status[bid].new_threads && (active_page == "ukko" || active_page == "index")) || status[bid].new_threads == 1)
&& check_post(this, $('[data-board="'+bid+'"]#thread_'+status[bid].last_thread))) {
var st = storage()
st[bid].watched = time_loaded;
storage_save(st);
refresh = true;
var st = storage()
st[bid].watched = time_loaded;
storage_save(st);
refresh = true;
}
if (!status[bid].threads) continue;
for (var tid in status[bid].threads) {
if(status[bid].threads[tid] && check_post(this, $('[data-board="'+bid+'"]#thread_'+tid))) {
var st = storage();
st[bid].threads[tid] = time_loaded;
storage_save(st);
refresh = true;
}
if(status[bid].threads[tid] && check_post(this, $('[data-board="'+bid+'"]#thread_'+tid))) {
var st = storage();
st[bid].threads[tid] = time_loaded;
storage_save(st);
refresh = true;
}
}
}
return refresh;
@ -431,19 +431,19 @@ $(function(){
var sum = 0;
for (var bid in status) {
if (status[bid].new_threads) {
sum += status[bid].new_threads;
sum += status[bid].new_threads;
if (!status[bid].threads) continue;
for (var tid in status[bid].threads) {
if (status[bid].threads[tid] > 0) {
if (status[bid].threads[tid] > 0) {
if (auto_reload_enabled && active_page == "thread") {
var board = $('form[name="post"] input[name="board"]').val();
var thread = $('form[name="post"] input[name="thread"]').val();
if (board == bid && thread == tid) continue;
}
sum += status[bid].threads[tid];
}
}
sum += status[bid].threads[tid];
}
}
}
}
return sum;

2
js/webm-settings.js

@ -8,7 +8,7 @@ if (typeof _ == 'undefined') {
var defaultSettings = {
"videoexpand": true,
"videohover": false,
"videovolume": 1.0
"videovolume": 0.75
};
// Non-persistent settings for when localStorage is absent/disabled

82
js/youtube.js

@ -23,23 +23,75 @@
*/
onready(function(){
var do_embed_yt = function(tag) {
$('div.video-container a', tag).click(function() {
var videoID = $(this.parentNode).data('video');
$(this.parentNode).html('<iframe style="float:left;margin: 10px 20px" type="text/html" '+
'width="360" height="270" src="//www.youtube.com/embed/' + videoID +
$(document).ready(function(){
// Adds Options panel item
if (typeof localStorage.youtube_embed_proxy === 'undefined') {
if (location.hostname.includes(".onion")){
localStorage.youtube_embed_proxy = 'tuberyps2pn6dor6h47brof3w2asmauahhk4ei42krugybzzzo55klad.onion';
} else {
localStorage.youtube_embed_proxy = 'incogtube.com'; //default value
}
}
if (window.Options && Options.get_tab('general')) {
Options.extend_tab("general", "<fieldset id='media-proxy-fs'><legend>"+_("Media Proxy (requires refresh)")+"</legend>"
+ ('<label id="youtube-embed-proxy-url">' + _('YouTube embed proxy url&nbsp;&nbsp;')+'<input type="text" size=30></label>')
+ '</fieldset>');
$('#youtube-embed-proxy-url>input').val(localStorage.youtube_embed_proxy);
$('#youtube-embed-proxy-url>input').on('input', function() {
localStorage.youtube_embed_proxy = $('#youtube-embed-proxy-url>input').val();
});
}
const ON = "[Remove]";
const OFF = "[Embed]";
const YOUTUBE = 'www.youtube.com';
const PROXY = localStorage.youtube_embed_proxy;
function addEmbedButton(index, videoNode) {
videoNode = $(videoNode);
var contents = videoNode.contents();
var videoId = videoNode.data('video');
var span = $("<span>[Embed]</span>");
var spanProxy = $("<span>[Proxy]</span>");
var defaultEmbed = location.hostname.includes(".onion") ? PROXY : YOUTUBE;
var embedNode = function(embedHost) {
$('<iframe style="float:left;margin: 10px 20px" type="text/html" '+
'width="360" height="270" src="//' + embedHost + '/embed/' + videoId +
'?autoplay=1&html5=1" allowfullscreen frameborder="0"/>');
}
videoNode.click(function(e) {
e.preventDefault();
if (span.text() == ON){
videoNode.append(contents);
embedNode.remove();
span.text(OFF);
spanProxy.hidden = false;
} else{
contents.detach();
videoNode.append(embedNode(defaultEmbed));
span.text(ON);
}
});
return false;
spanProxy.click(function(e) {
e.preventDefault();
contents.detach();
videoNode.append(embedNode(PROXY));
span.text(ON);
spanProxy.hidden = true;
});
};
do_embed_yt(document);
// allow to work with auto-reload.js, etc.
$(document).on('new_post', function(e, post) {
do_embed_yt(post);
});
});
videoNode.append(span);
videoNode.append(spanProxy);
}
$('div.video-container', document).each(addEmbedButton);
// allow to work with auto-reload.js, etc.
$(document).on('new_post', function(e, post) {
$('div.video-container', post).each(addEmbedButton);
});
});

1
log.php

@ -1,5 +1,6 @@
<?php
require 'inc/bootstrap.php';
require_once 'inc/mod/pages.php';
if (!isset($_GET['board']) || !preg_match("/{$config['board_regex']}/u", $_GET['board'])) {
http_response_code(400);

29
mod.php

@ -62,29 +62,41 @@ $pages = array(
'/IP/([\w.:]+)' => 'secure_POST ip', // view ip address
'/IP/([\w.:]+)/remove_note/(\d+)' => 'secure ip_remove_note', // remove note from ip address
'/IP/([\w.:-]+)/remove_telegram/(\d+)' => 'secure ip_remove_telegram', // remove telegram from ip address
'/ban' => 'secure_POST ban', // new ban
'/bans' => 'secure_POST bans', // ban list
'/bans.json' => 'secure bans_json', // ban list JSON
'/edit_ban/(\d+)' => 'secure_POST edit_ban',
'/ban-appeals' => 'secure_POST ban_appeals', // view ban appeals
'/recent/(\d+)' => 'recent_posts', // view recent posts
'/recent/(\d+)/([\w,]+?)' => 'recent_posts', // view recent posts
'/recent/(\d+)/([\w,]+?)/(json)?' => 'recent_posts', // view recent posts JSON
'/search' => 'search_redirect', // search
'/search/(posts|IP_notes|bans|log)/(.+)/(\d+)' => 'search', // search
'/search/(posts|IP_notes|bans|log)/(.+)' => 'search', // search
'/(\%b)/archive/' => 'secure_POST view_archive', // view archive
'/(\%b)/featured/' => 'secure_POST view_archive_featured', // view featured Archive
'/(\%b)/warning/(\d+)' => 'secure_POST warning_post', // issue warning for post
'/(\%b)/mod_archive/' => 'secure_POST view_archive_mod_archive', // View mod archive
'/(\%b)/featured/' => 'secure_POST view_archive_featured', // view featured archive
'/shadow_recent_post/(\d+)' => 'recent_shadow_posts', // view recent posts shadow deleted
'/(\%b)/shadow_view/(\d+)' => 'view_shadow_thread', // view shadow deleted thread
'/(\%b)/shadow_restore/(\d+)' => 'secure_POST shadow_restore_post', // restore shadow deleted post
'/(\%b)/shadow_delete/(\d+)' => 'secure_POST shadow_delete_post', // permanent delete shadow deleted post
'/(\%b)/shadow_purge/(\d+)' => 'secure_POST shadow_purge', // permanent delete all shadow deleted post that have timed out
'/statistics' => 'view_statistics', // view site post statistics
'/(\%b)/statistics' => 'view_board_statistics', // view post statistics for a given board
'/(\%b)/warning(&delete)?/(\d+)' => 'secure_POST warning_post', // issue warning for post
'/(\%b)/ban(&delete)?/(\d+)' => 'secure_POST ban_post', // ban poster
'/(\%b)/move/(\d+)' => 'secure_POST move', // move thread
'/(\%b)/move_reply/(\d+)' => 'secure_POST move_reply', // move reply
'/(\%b)/merge/(\d+)' => 'secure_POST merge', // merge thread
'/(\%b)/edit(_raw)?/(\d+)' => 'secure_POST edit_post', // edit post
'/(\%b)/delete/(\d+)' => 'secure delete', // delete post
'/(\%b)/delete(_shadow)?/(\d+)' => 'secure delete', // delete post
'/(\%b)/deletefile/(\d+)/(\d+)' => 'secure deletefile', // delete file from post
'/(\%b)/deletefilepermaban/(\d+)/(\d+)' => 'secure deletefilepermaban', // delete file from post and permaban it
'/(\%b+)/spoiler/(\d+)/(\d+)' => 'secure spoiler_image', // spoiler file
@ -93,7 +105,8 @@ $pages = array(
'/(\%b)/(un)?sticky/(\d+)' => 'secure sticky', // sticky thread
'/(\%b)/(un)?cycle/(\d+)' => 'secure cycle', // cycle thread
'/(\%b)/bump(un)?lock/(\d+)' => 'secure bumplock', // "bumplock" thread
'/(\%b)/archive_thread/(\d+)' => 'secure archive_thread', // send thread to archive
'/themes' => 'themes_list', // manage themes
'/themes/(\w+)' => 'secure_POST theme_configure', // configure/reconfigure theme
'/themes/(\w+)/rebuild' => 'secure theme_rebuild', // rebuild theme
@ -111,6 +124,7 @@ $pages = array(
// This should always be at the end:
'/(\%b)/' => 'view_board',
'/(\%b)/' . preg_quote($config['file_index'], '!') => 'view_board',
'/(\%b)/' . preg_quote($config['file_catalog'], '!') => 'view_catalog',
'/(\%b)/' . str_replace('%d', '(\d+)', preg_quote($config['file_page'], '!')) => 'view_board',
'/(\%b)/' . preg_quote($config['dir']['res'], '!') .
str_replace('%d', '(\d+)', preg_quote($config['file_page50'], '!')) => 'view_thread50',
@ -140,7 +154,7 @@ foreach ($pages as $key => $callback) {
if (is_string($callback) && preg_match('/^secure /', $callback))
$key .= '(/(?P<token>[a-f0-9]{8}))?';
$key = str_replace('\%b', '?P<board>' . sprintf(substr($config['board_path'], 0, -1), $config['board_regex']), $key);
$new_pages[@$key[0] == '!' ? $key : '!^' . $key . '(?:&[^&=]+=[^&]*)*$!u'] = $callback;
$new_pages[(!empty($key) and $key[0] == '!') ? $key : '!^' . $key . '(?:&[^&=]+=[^&]*)*$!u'] = $callback;
}
$pages = $new_pages;
@ -161,6 +175,7 @@ foreach ($pages as $uri => $handler) {
$secure_post_only = isset($m[1]);
if (!$secure_post_only || $_SERVER['REQUEST_METHOD'] == 'POST') {
$token = isset($matches['token']) ? $matches['token'] : (isset($_POST['token']) ? $_POST['token'] : false);
unset($matches['token']);
if ($token === false) {
if ($secure_post_only)

375
post.php

@ -169,9 +169,12 @@ elseif (isset($_GET['Newsgroups'])) {
error("NNTPChan: NNTPChan support is disabled");
}
session_start();
if (!isset($_POST['captcha_cookie']) && isset($_SESSION['captcha_cookie'])) {
$_POST['captcha_cookie'] = $_SESSION['captcha_cookie'];
// Create session cookie if not already done and captchas are enabled
if ($config['captcha']['enabled'] || $config['report_captcha']) {
session_start();
if (!isset($_POST['captcha_cookie']) && isset($_SESSION['captcha_cookie'])) {
$_POST['captcha_cookie'] = $_SESSION['captcha_cookie'];
}
}
if (isset($_POST['delete'])) {
@ -230,18 +233,33 @@ if (isset($_POST['delete'])) {
if ($password != '' && $post['password'] != $password && (!$thread || $thread['password'] != $password))
error($config['error']['invalidpassword']);
if ($post['time'] > time() - $config['delete_time'] && (!$thread || $thread['password'] != $password)) {
error(sprintf($config['error']['delete_too_soon'], until($post['time'] + $config['delete_time'])));
if ($post['thread'] == NULL) {
if ($post['time'] > time() - $config['delete_time_op'] && (!$thread || $thread['password'] != $password)) {
error(sprintf($config['error']['delete_too_soon'], until($post['time'] + $config['delete_time_op'])));
}
}
else {
if ($post['time'] > time() - $config['delete_time_reply'] && (!$thread || $thread['password'] != $password)) {
error(sprintf($config['error']['delete_too_soon'], until($post['time'] + $config['delete_time_reply'])));
}
}
if (isset($_POST['file'])) {
// Delete just the file
deleteFile($id);
modLog("User deleted file from his own post #$id");
modLog("User deleted file from their own post #$id");
} else {
// Check if thread, and that the delete cutoff post count hasn't been reached
if($config['allow_delete_cutoff'] && $post['thread'] === NULL) {
$count_query = query(sprintf("SELECT COUNT(*) FROM ``posts_%s`` WHERE `thread` = %d", $board['uri'], (int)$id));
if($count_query->fetchColumn(0) >= $config['allow_delete_cutoff'])
error($config['error']['delete_post_cutoff']);
}
// Delete entire post
deletePost($id);
modLog("User deleted his own post #$id");
deletePostPermanent($id);
modLog("User deleted their own post #$id");
}
_syslog(LOG_INFO, 'Deleted post: ' .
@ -256,7 +274,8 @@ if (isset($_POST['delete'])) {
$root = $is_mod ? $config['root'] . $config['file_mod'] . '?/' : $config['root'];
if (!isset($_POST['json_response'])) {
header('Location: ' . $root . $board['dir'] . $config['file_index'], true, $config['redirect_http']);
// If only deleting a post in a thread, redirect to the current thread
header('Location: ' . $root . $board['dir'] . ($post['thread'] ? $config['dir']['res'] . sprintf($config['file_page'], $post['thread']) : $config['file_index']), true, $config['redirect_http']);
} else {
header('Content-Type: text/json');
echo json_encode(array('success' => true));
@ -295,9 +314,9 @@ if (isset($_POST['delete'])) {
if (empty($report))
error($config['error']['noreport']);
if (strlen($report) > 30)
error($config['error']['invalidreport']);
if (strlen($_POST['reason']) > $config['report_max_length'])
error($config['error']['toolongreport']);
if (count($report) > $config['report_limit'])
error($config['error']['toomanyreports']);
@ -306,17 +325,38 @@ if (isset($_POST['delete'])) {
}
if ($config['report_captcha']) {
$ch = curl_init($config['domain'].'/'.$config['captcha']['provider_check'] . "?" . http_build_query([
'mode' => 'check',
'text' => $_POST['captcha_text'],
'extra' => $config['captcha']['extra'],
'cookie' => $_POST['captcha_cookie']
]));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$resp = curl_exec($ch);
if ($resp !== '1') {
error($config['error']['captcha']);
if ($config['captcha']['local']) {
require_once '.' . $config['captcha']['provider_check'];
$code = captcha_check($_POST['captcha_cookie'], $config['captcha']['extra'], $_POST['captcha_text']);
if ($code !== 1) {
if ($code === 2) {
error($config['error']['captcha_incorrect']);
} else if ($code === 3) {
error($config['error']['captcha_expired']);
} else {
error($config['error']['captcha']);
}
}
} else {
$ch = curl_init($config['domain'].$config['captcha']['provider_check'] . "?" . http_build_query([
'mode' => 'check',
'text' => $_POST['captcha_text'],
'extra' => $config['captcha']['extra'],
'cookie' => $_POST['captcha_cookie']
]));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$resp = curl_exec($ch);
if ($resp !== '1') {
if ($resp === '2') {
error($config['error']['captcha_incorrect']);
} else if ($resp === '3') {
error($config['error']['captcha_expired']);
} else {
error($config['error']['captcha']);
}
}
}
}
@ -355,7 +395,7 @@ if (isset($_POST['delete'])) {
if (!isset($_POST['json_response'])) {
$index = $root . $board['dir'] . $config['file_index'];
echo Element('page.html', array('config' => $config, 'body' => '<div style="text-align:center"><a href="javascript:window.close()">[ ' . _('Close window') ." ]</a> <a href='$index'>[ " . _('Return') . ' ]</a></div>', 'title' => _('Report submitted!')));
echo Element($config['file_page_template'], array('config' => $config, 'body' => '<div style="text-align:center"><a href="javascript:window.close()">[ ' . _('Close window') ." ]</a> <a href='$index'>[ " . _('Return') . ' ]</a></div>', 'title' => _('Report submitted!')));
} else {
header('Content-Type: text/json');
echo json_encode(array('success' => true));
@ -404,7 +444,7 @@ if (isset($_POST['delete'])) {
error($config['error']['bot']);
// Check what reCAPTCHA has to say...
$resp = json_decode(file_get_contents(sprintf('https://www.google.com/recaptcha/api/siteverify?secret=%s&response=%s&remoteip=%s',
$resp = json_decode(file_get_contents(sprintf('https://www.recaptcha.net/recaptcha/api/siteverify?secret=%s&response=%s&remoteip=%s',
$config['recaptcha_private'],
urlencode($_POST['g-recaptcha-response']),
$_SERVER['REMOTE_ADDR'])), true);
@ -415,20 +455,40 @@ if (isset($_POST['delete'])) {
}
// Same, but now with our custom captcha provider
if (($config['captcha']['enabled']) || (($post['op']) && ($config['new_thread_capt'])) ) {
$ch = curl_init($config['domain'].'/'.$config['captcha']['provider_check'] . "?" . http_build_query([
'mode' => 'check',
'text' => $_POST['captcha_text'],
'extra' => $config['captcha']['extra'],
'cookie' => $_POST['captcha_cookie']
]));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$resp = curl_exec($ch);
if ($resp !== '1') {
error($config['error']['captcha'] .
'<script>if (actually_load_captcha !== undefined) actually_load_captcha("'.$config['captcha']['provider_get'].'", "'.$config['captcha']['extra'].'");</script>');
if ($config['captcha']['local']) {
require_once '.' . $config['captcha']['provider_check'];
$code = captcha_check($_POST['captcha_cookie'], $config['captcha']['extra'], $_POST['captcha_text']);
if ($code !== 1) {
if ($code === 2) {
error($config['error']['captcha_incorrect']);
} else if ($code === 3) {
error($config['error']['captcha_expired']);
} else {
error($config['error']['captcha']);
}
}
} else {
$ch = curl_init($config['domain'].$config['captcha']['provider_check'] . "?" . http_build_query([
'mode' => 'check',
'text' => $_POST['captcha_text'],
'extra' => $config['captcha']['extra'],
'cookie' => $_POST['captcha_cookie']
]));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$resp = curl_exec($ch);
if ($resp !== '1') {
if ($resp === '2') {
error($config['error']['captcha_incorrect']);
} else if ($resp === '3') {
error($config['error']['captcha_expired']);
} else {
error($config['error']['captcha']);
}
}
}
}
}
if (!(($post['op'] && $_POST['post'] == $config['button_newtopic']) ||
(!$post['op'] && $_POST['post'] == $config['button_reply'])))
@ -489,7 +549,6 @@ if (isset($_POST['delete'])) {
else {
$thread = false;
}
// Check for an embed field
if ($config['enable_embedding'] && isset($_POST['embed']) && !empty($_POST['embed'])) {
@ -548,7 +607,7 @@ if (isset($_POST['delete'])) {
else if (!in_array($post['extension'], $config['allowed_ext']) && !in_array($post['extension'], $config['allowed_ext_files']))
error($config['error']['unknownext']);
$post['file_tmp'] = tempnam($config['tmp'], 'url');
$post['file_tmp'] = @tempnam($config['tmp'], 'url');
register_shutdown_function('unlink_tmp_file', $post['file_tmp']);
$fp = fopen($post['file_tmp'], 'w');
@ -589,6 +648,28 @@ if (isset($_POST['delete'])) {
}
// Convert multiple upload format to array of files. This makes the following code
// work the same whether we used the JS or HTML multiple file upload techniques.
if (array_key_exists('file_multiple', $_FILES)) {
$file_array = $_FILES['file_multiple'];
$_FILES = [];
// If more than 0 files were uploaded
if (!empty($file_array['tmp_name'][0])) {
$i = 0;
$n = count($file_array['tmp_name']);
while ($i < $n) {
$_FILES[strval($i+1)] = array(
'name' => $file_array['name'][$i],
'tmp_name' => $file_array['tmp_name'][$i],
'type' => $file_array['type'][$i],
'error' => $file_array['error'][$i],
'size' => $file_array['size'][$i]
);
$i++;
}
}
}
$post['name'] = $_POST['name'] != '' ? $_POST['name'] : $config['anonymous'];
$post['subject'] = $_POST['subject'];
$post['email'] = str_replace(' ', '%20', htmlspecialchars($_POST['email']));
@ -604,7 +685,11 @@ if (isset($_POST['delete'])) {
error($config['error']['no_body']);
}
}
if ($post['op'] && $config['force_subject_op'] && $post['subject'] == '') {
error($config['error']['nosubject']);
}
if (!$post['op']) {
// Check if thread is locked
// but allow mods to post
@ -687,27 +772,14 @@ if (isset($_POST['delete'])) {
if ($post['has_file']) {
$i = 0;
foreach ($_FILES as $key => $file) {
if (!in_array($file['error'], array(UPLOAD_ERR_NO_FILE, UPLOAD_ERR_OK))) {
error(sprintf3($config['error']['phpfileserror'], array(
'index' => $i+1,
'code' => $file['error']
)));
}
if ($file['size'] && $file['tmp_name']) {
$file['filename'] = urldecode($file['name']);
$file['extension'] = strtolower(mb_substr($file['filename'], mb_strrpos($file['filename'], '.') + 1));
if (isset($config['filename_func']))
$file['file_id'] = $config['filename_func']($file);
else
$file['file_id'] = time() . substr(microtime(), 2, 3);
if (sizeof($_FILES) > 1)
$file['file_id'] .= "-$i";
$file['file'] = $board['dir'] . $config['dir']['img'] . $file['file_id'] . '.' . $file['extension'];
$file['thumb'] = $board['dir'] . $config['dir']['thumb'] . $file['file_id'] . '.' . ($config['thumb_ext'] ? $config['thumb_ext'] : $file['extension']);
$post['files'][] = $file;
if (!in_array($file['error'], array(UPLOAD_ERR_NO_FILE, UPLOAD_ERR_OK))) {
error(sprintf3($config['error']['phpfileserror'], array(
'index' => $i+1,
'code' => $file['error']
)));
}
$post['files'][] = process_filenames($file, $board['dir'], (sizeof($_FILES) > 1), $i);
$i++;
}
}
@ -738,7 +810,7 @@ if (isset($_POST['delete'])) {
// Check string lengths
if (mb_strlen($post['name']) > 35)
error(sprintf($config['error']['toolong'], 'name'));
if (mb_strlen($post['email']) > 40)
if (mb_strlen($post['email']) > 30)
error(sprintf($config['error']['toolong'], 'email'));
if (mb_strlen($post['subject']) > 100)
error(sprintf($config['error']['toolong'], 'subject'));
@ -806,22 +878,7 @@ if (isset($_POST['delete'])) {
$post['body'] .= "\n<tinyboard proxy>".$proxy."</tinyboard>";
}
if (mysql_version() >= 50503) {
$post['body_nomarkup'] = $post['body']; // Assume we're using the utf8mb4 charset
} else {
// MySQL's `utf8` charset only supports up to 3-byte symbols
// Remove anything >= 0x010000
$chars = preg_split('//u', $post['body'], -1, PREG_SPLIT_NO_EMPTY);
$post['body_nomarkup'] = '';
foreach ($chars as $char) {
$o = 0;
$ord = ordutf8($char, $o);
if ($ord >= 0x010000)
continue;
$post['body_nomarkup'] .= $char;
}
}
$post['body_nomarkup'] = $post['body']; // Assume we're using the utf8mb4 charset
$post['tracked_cites'] = markup($post['body'], true);
@ -971,7 +1028,7 @@ if (isset($_POST['delete'])) {
$file['extension'] == ($config['thumb_ext'] ? $config['thumb_ext'] : $file['extension'])) {
// Copy, because there's nothing to resize
copy($file['tmp_name'], $file['thumb']);
copy($file['tmp_name'], $file['thumb_path']);
$file['thumbwidth'] = $image->size->width;
$file['thumbheight'] = $image->size->height;
@ -982,21 +1039,27 @@ if (isset($_POST['delete'])) {
$post['op'] ? $config['thumb_op_height'] : $config['thumb_height']
);
$thumb->to($file['thumb']);
$thumb->to($file['thumb_path']);
$file['thumbwidth'] = $thumb->width;
$file['thumbheight'] = $thumb->height;
$thumb->_destroy();
}
$dont_copy_file = false;
if ($config['redraw_image'] || (!@$file['exif_stripped'] && $config['strip_exif'] && ($file['extension'] == 'jpg' || $file['extension'] == 'jpeg'))) {
if ($config['redraw_image'] || (!array_key_exists('exif_stripped', $file) && $config['strip_exif'] && ($file['extension'] == 'jpg' || $file['extension'] == 'jpeg'))) {
if (!$config['redraw_image'] && $config['use_exiftool']) {
if($error = shell_exec_error('exiftool -overwrite_original -ignoreMinorErrors -q -q -all= ' .
escapeshellarg($file['tmp_name'])))
if($error = shell_exec_error('exiftool -overwrite_original -ignoreMinorErrors -q -q -all= ' . escapeshellarg($file['tmp_name']))) {
error(_('Could not strip EXIF metadata!'), null, $error);
} else {
clearstatcache(true, $file['tmp_name']);
if (($newfilesize = filesize($file['tmp_name'])) !== false)
$file['size'] = $newfilesize;
}
} else {
$image->to($file['file']);
$image->to($file['file_path']);
$dont_copy_file = true;
}
}
@ -1004,7 +1067,7 @@ if (isset($_POST['delete'])) {
} else {
if ($file['extension'] == "pdf" && $config['pdf_file_thumbnail']){
$path = $file['thumb'];
$error = shell_exec_error( 'convert -thumbnail x300 -background white -alpha remove ' .
$error = shell_exec_error( 'convert -size '.$config['thumb_width'].'x'.$config['thumb_height'].' -thumbnail '.$config['thumb_width'].'x'.$config['thumb_height'].' -background white -alpha remove ' .
escapeshellarg($file['tmp_name']. '[0]') . ' ' .
escapeshellarg($file['thumb']));
@ -1018,35 +1081,34 @@ if (isset($_POST['delete'])) {
$file['thumbheight'] = $size[1];
$file['width'] = $size[0];
$file['height'] = $size[1];
}
else {
// not an image
//copy($config['file_thumb'], $post['thumb']);
$file['thumb'] = 'file';
$size = @getimagesize(sprintf($config['file_thumb'],
} else {
// not an image
$file['thumb'] = 'file';
$size = @getimagesize(sprintf($config['file_thumb'],
isset($config['file_icons'][$file['extension']]) ?
$config['file_icons'][$file['extension']] : $config['file_icons']['default']));
$file['thumbwidth'] = $size[0];
$file['thumbheight'] = $size[1];
$file['thumbwidth'] = $size[0];
$file['thumbheight'] = $size[1];
$dont_copy_file = false;
}
}
if ($config['tesseract_ocr'] && $file['thumb'] != 'file') { // Let's OCR it!
$fname = $file['tmp_name'];
if ($config['tesseract_ocr'] && $file['is_an_image']) { // Let's OCR it!
$fname = '';
if ($file['height'] > 500 || $file['width'] > 500) {
$fname = $file['thumb'];
if ($file['thumb'] != 'spoiler') {
$fname = $file['thumb_path'];
}
}
else
$fname = $file['tmp_name'];
if ($fname == 'spoiler') { // We don't have that much CPU time, do we?
}
else {
$tmpname = __DIR__ . "/tmp/tesseract/".rand(0,10000000);
if ($fname != '') {
$tmpname = "tmp/tesseract/".rand(0,10000000);
// Preprocess command is an ImageMagick b/w quantization
$error = shell_exec_error(sprintf($config['tesseract_preprocess_command'], escapeshellarg($fname)) . " | " .
'tesseract stdin '.escapeshellarg($tmpname).' '.$config['tesseract_params']);
'tesseract stdin '.escapeshellarg($tmpname).' '.$config['tesseract_params']);
$tmpname .= ".txt";
$value = @file_get_contents($tmpname);
@ -1060,12 +1122,12 @@ if (isset($_POST['delete'])) {
}
}
if (!isset($dont_copy_file) || !$dont_copy_file) {
if (!$dont_copy_file) {
if (isset($file['file_tmp'])) {
if (!@rename($file['tmp_name'], $file['file']))
if (!@rename($file['tmp_name'], $file['file_path']))
error($config['error']['nomove']);
chmod($file['file'], 0644);
} elseif (!@move_uploaded_file($file['tmp_name'], $file['file']))
chmod($file['file_path'], 0644);
} elseif (!@move_uploaded_file($file['tmp_name'], $file['file_path']))
error($config['error']['nomove']);
}
}
@ -1148,11 +1210,9 @@ if (isset($_POST['delete'])) {
// Remove board directories before inserting them into the database.
if ($post['has_file']) {
foreach ($post['files'] as $key => &$file) {
$file['file_path'] = $file['file'];
$file['thumb_path'] = $file['thumb'];
$file['file'] = mb_substr($file['file'], mb_strlen($board['dir'] . $config['dir']['img']));
if ($file['is_an_image'] && $file['thumb'] != 'spoiler')
$file['thumb'] = mb_substr($file['thumb'], mb_strlen($board['dir'] . $config['dir']['thumb']));
$file['file'] = mb_substr($file['file_path'], mb_strlen($board['dir'] . $config['dir']['img']));
if (!isset($file['thumb']))
$file['thumb'] = mb_substr($file['thumb_path'], mb_strlen($board['dir'] . $config['dir']['thumb']));
}
}
@ -1291,36 +1351,15 @@ if (isset($_POST['delete'])) {
$query->execute() or error(db_error($query));
$telegrams = $query->fetchAll(PDO::FETCH_ASSOC);
if (count($telegrams) > 0)
goto skip_redirect;
if (!isset($_POST['json_response'])) {
header('Location: ' . $redirect, true, $config['redirect_http']);
} else {
header('Content-Type: text/json; charset=utf-8');
echo json_encode(array(
'redirect' => $redirect,
'noko' => $noko,
'id' => $id
));
}
skip_redirect:
if ($config['try_smarter'] && $post['op'])
$build_pages = range(1, $config['max_pages']);
if ($post['op'])
clean($id);
event('post-after', $post);
buildIndex();
if (count($telegrams) > 0) {
$ids = implode(', ', array_map(function($x) { return (int)$x['id']; }, $telegrams));
query("UPDATE ``telegrams`` SET ``seen`` = 1 WHERE ``id`` IN({$ids})") or error(db_error());
die(Element('page.html', array(
// Give JS users a redirect instead of an error
if (isset($_POST['json_response'])) {
header('Content-Type: text/json');
echo json_encode(array('error' => true, 'telegram' => true));
} else {
echo Element('page.html', array(
'title' => _('Important message from Moderation'),
'config' => $config,
'body' => Element('important.html', array(
@ -1328,18 +1367,47 @@ if (isset($_POST['delete'])) {
'redirect' => $redirect,
'telegrams' => $telegrams,
))
)));
));
}
} else {
if (!isset($_POST['json_response'])) {
header('Location: ' . $redirect, true, $config['redirect_http']);
} else {
header('Content-Type: text/json; charset=utf-8');
echo json_encode(array(
'redirect' => $redirect,
'noko' => $noko,
'id' => $id
));
}
}
if ($post['op']) {
clean($id);
if ($config['try_smarter'])
$build_pages = range(1, $config['max_pages']);
}
event('post-after', $post);
// If this is a new thread or the poster is returning to the index, build it before they redirect
if ($post['op'] || !$noko)
buildIndex();
// We are already done, let's continue our heavy-lifting work in the background (if we run off FastCGI)
if (function_exists('fastcgi_finish_request'))
if (function_exists('fastcgi_finish_request')) {
@fastcgi_finish_request();
}
if (!$post['op'] && $noko)
buildIndex();
if ($post['op'])
if ($post['op']) {
rebuildThemes('post-thread', $board['uri']);
else
} else {
rebuildThemes('post', $board['uri']);
}
} elseif (isset($_POST['appeal'])) {
if (!isset($_POST['ban_id']))
error($config['error']['bot']);
@ -1361,6 +1429,9 @@ if (isset($_POST['delete'])) {
if ($ban['expires'] && $ban['expires'] - $ban['created'] <= $config['ban_appeals_min_length']) {
error(_("You cannot appeal a ban of this length."));
}
if (strlen($_POST['appeal']) > $config['appeal_text_max_length'])
error(_("Your appeal is too long."));
$query = query("SELECT `denied` FROM ``ban_appeals`` WHERE `ban_id` = $ban_id") or error(db_error());
$ban_appeals = $query->fetchAll(PDO::FETCH_COLUMN);
@ -1381,6 +1452,20 @@ if (isset($_POST['delete'])) {
$query->execute() or error(db_error($query));
displayBan($ban);
} elseif (isset($_POST['archive_vote'])) {
if (!isset($_POST['board'], $_POST['thread_id']))
error($config['error']['bot']);
// Check if board exists
if (!openBoard($_POST['board']))
error($config['error']['noboard']);
// Add Vote
Archive::addVote($_POST['board'], $_POST['thread_id']);
// Return user to archive
header('Location: ' . $config['root'] . sprintf($config['board_path'], $_POST['board']) . $config['dir']['archive'], true, $config['redirect_http']);
} else {
if (!file_exists($config['has_installed'])) {
header('Location: install.php', true, $config['redirect_http']);

4
report.php

@ -15,5 +15,5 @@ if ($config['report_captcha']) {
$captcha = null;
}
$body = Element('report.html', ['global' => $global, 'post' => $post, 'board' => $board, 'captcha' => $captcha, 'config' => $config]);
echo Element('page.html', ['config' => $config, 'body' => $body]);
$body = Element($config['file_report'], ['global' => $global, 'post' => $post, 'board' => $board, 'captcha' => $captcha, 'config' => $config]);
echo Element($config['file_page_template'], ['config' => $config, 'body' => $body]);

6
search.php

@ -72,7 +72,7 @@
if(!preg_match('/[^*^\s]/', $phrase) && empty($filters)) {
_syslog(LOG_WARNING, 'Query too broad.');
$body .= '<p class="unimportant" style="text-align:center">(Query too broad.)</p>';
echo Element('page.html', Array(
echo Element($config['file_page_template'], Array(
'config'=>$config,
'title'=>'Search',
'body'=>$body,
@ -133,7 +133,7 @@
if($query->rowCount() == $search_limit) {
_syslog(LOG_WARNING, 'Query too broad.');
$body .= '<p class="unimportant" style="text-align:center">('._('Query too broad.').')</p>';
echo Element('page.html', Array(
echo Element($config['file_page_template'], Array(
'config'=>$config,
'title'=>'Search',
'body'=>$body,
@ -167,7 +167,7 @@
$body .= '<p style="text-align:center" class="unimportant">('._('No results.').')</p>';
}
echo Element('page.html', Array(
echo Element($config['file_page_template'], Array(
'config'=>$config,
'title'=>_('Search'),
'boardlist'=>createBoardlist(),

42
securimage.php

@ -15,6 +15,26 @@ function cleanup() {
prepare("DELETE FROM `captchas` WHERE `created_at` < ?")->execute([time() - $expires_in]);
}
// Checks captcha and returns a code
// 1 = success
// 2 = incorrect
// 3 = expired
function captcha_check($cookie, $extra, $text) {
cleanup();
$query = prepare("SELECT * FROM `captchas` WHERE `cookie` = ? AND `extra` = ?");
$query->execute([$cookie, $extra]);
$ary = $query->fetchAll();
if (!$ary) {
return 3;
} else {
$query = prepare("DELETE FROM `captchas` WHERE `cookie` = ? AND `extra` = ?");
$query->execute([$cookie, $extra]);
}
return ($ary[0]['text'] === $text ? 1 : 2);
}
$mode = @$_GET['mode'];
switch ($mode) {
@ -26,7 +46,7 @@ switch ($mode) {
header("Content-type: application/json");
$extra = $_GET['extra'];
$cookie = rand_string(20, "abcdefghijklmnopqrstuvwxyz");
$i = new Securimage(['send_headers' => false, 'no_exit' => true]);
$i = new Securimage($config['captcha']['securimage_options']);
$i->createCode();
ob_start();
$i->show();
@ -46,27 +66,9 @@ switch ($mode) {
}
break;
case 'check':
cleanup();
if (!isset ($_GET['mode']) || !isset ($_GET['cookie']) || !isset ($_GET['extra']) || !isset ($_GET['text'])) {
die();
}
$query = prepare("SELECT * FROM `captchas` WHERE `cookie` = ? AND `extra` = ?");
$query->execute([$_GET['cookie'], $_GET['extra']]);
$ary = $query->fetchAll();
if (!$ary) {
echo "0";
} else {
$query = prepare("DELETE FROM `captchas` WHERE `cookie` = ? AND `extra` = ?");
$query->execute([$_GET['cookie'], $_GET['extra']]);
}
if ($ary[0]['text'] !== $_GET['text']) {
echo "0";
} else {
echo "1";
}
echo strval(captcha_check($_GET['cookie'], $_GET['extra'], $_GET['text']));
break;
}

BIN
static/banners/defaultbanner.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

BIN
static/banners/placeholder.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

60
status.php

@ -0,0 +1,60 @@
<?php
require_once 'inc/bootstrap.php';
// Boards that are nsfw
$nsfw_boards = $config['api']['status']['nsfw_boards'];
// Boards where posts are not allowed to be created
$readonly_boards = $config['api']['status']['locked_boards'];
// Allowed boards
$whitelist = $config['api']['status']['whitelist'];
foreach ($config['boards'] as $boards) {
foreach ($boards as $board) {
$whitelist[] = $board;
}
}
foreach (array_keys($config['overboards']) as $board_uri) {
$whitelist[] = $board_uri;
$readonly_boards[] = $board_uri;
}
$board_list = listBoards();
// Add objects that are not boards but are treated as such
foreach (array_keys($config['overboards']) as $uri => $overboard) {
$board_list[] = array(
'uri' => $uri,
'title' => $config['overboards'][$overboard]['title']
);
}
/**
* Allowed fields for the board object:
* - code<string>: The board code ('b', 'tech', ...)
* - name<string>: The board user-readable name ('Siberia', ...)
* - sfw<boolean>: Is this board sfw?
* - posting_enabled<boolean>: Can new posts be created belonging to this board?
*/
$boards = [];
foreach ($board_list as $board) {
// Skip non-whitelisted boards
if (!in_array($board['uri'], $whitelist)) {
continue;
}
$boards[] = [
'code' => $board['uri'],
'name' => $board['title'],
'sfw' => !in_array($board['uri'], $nsfw_boards),
'posting_enabled' => !in_array($board['uri'], $readonly_boards),
];
}
header('Content-Type: application/json');
echo json_encode([
'captcha' => $config['captcha']['enabled'],
'recaptcha' => $config['recaptcha'],
'flags' => $config['user_flags'],
'boards' => $boards,
]);

371
stylesheets/500px.css

@ -0,0 +1,371 @@
/**
* 500px.css
* kalyx is terrible at css
*/
body
{
display:block;
padding-top: 26px;
background: #0d1010;
color: #d2e7e8;
font-family: sans-serif;
font-size: 13px;
border: solid 1px #803474;
max-width: 568px;
}
html {
/* Size of largest container or bigger */
background-image:url('img/starry.png');
max-width: 568px;
margin-left: auto;
margin-right: auto;
border: double 0px #93e0e3;
box-shadow: 2px 3px #803474;
}
.board_image{
display: none;
}
div.sidearrows{
display: none;
}
span.quote
{
color:#6fc58a;
}
@font-face {
font-family: 'lain';
src: url('./fonts/lain.woff') format('woff'),
url('./fonts/lain.TTF') format('truetype');
}
h1
{
display: none;
font-family: 'lain', tahoma;
letter-spacing: -2px;
font-size: 42pt;
text-align: center;
color: #d2e7e8;
}
header div.subtitle
{
display: none;
color: #d2e7e8;
font-size: 13px;
margin-left: auto;
margin-right: auto;
max-width:385px;
}
div.title
{
display: none;
color: #ce54ba;
font-family: Arial, Helvetica, sans-serif;
}
div.title p
{
font-size: 8px;
color: #ce54ba;
}
a, a:link, a:visited, p.intro a.email span.name
{
color: #d2e7e8;
text-transform: uppercase;
font-size: 10px;
text-decoration: none;
font-family: sans-serif;
}
a, a:link, a:visited, p.intro a.email span.name
{
-moz-transition: 0.15s text-shadow, 0.15s color;
-webkit-transition: 0.15s text-shadow, 0.15s color;
-khtml-transition: 0.15s text-shadow, 0.15s color;
-o-transition: 0.15s text-shadow, 0.15s color;
-ms-transition: 0.15s text-shadow, 0.15s color;
transition: 0.15s text-shadow, 0.15s color;
}
input[type="text"], textarea
{
-moz-transition: 0.15s border-color;
-webkit-transition: 0.15s border-color;
-khtml-transition: 0.15s border-color;
-o-transition: 0.15s border-color;
-ms-transition: 0.15s border-color;
transition: 0.15s border-color;
}
input[type="submit"]
{
-moz-transition: 0.15s border-color, 0.15s background-color, 0.15s color;
-webkit-transition: 0.15s border-color, 0.15s background-color, 0.15s color;
-khtml-transition: 0.15s border-color, 0.15s background-color, 0.15s color;
-o-transition: 0.15s border-color, 0.15s background-color, 0.15s color;
-ms-transition: 0.15s border-color, 0.15s background-color, 0.15s color;
transition: 0.15s border-color, 0.15s background-color, 0.15s color;
}
a:hover, a:link:hover, a:visited:hover
{
color: #ce54ba;
font-family: sans-serif;
text-decoration: none;
text-shadow: 0px 0px 5px #ce54ba;
}
a.post_no
{
color: #ce54ba;
text-decoration: none;
}
p.intro a.post_no:hover
{
color: #32DD72!important;
}
div.post.reply
{
background: #0d1010;
align: center;
max-width:96% !important;
min-width: 96%!important;
border: solid 1px #ce54ba;
box-shadow: 2px 2px #803474;
}
div.postcontainer
{
max-width:100% !important;
min-width: 100%!important;
}
div.post.reply.highlighted
{
background: #1e2324;
border: solid 1px #93e0e3;
box-shadow: 3px 5px #5c8c8e;
margin-left: auto;
margin-right: auto;
}
div.post.reply div.body a:link, div.post.reply div.body a:visited
{
color: #CCCCCC;
}
div.post.reply div.body a:link:hover, div.post.reply div.body a:visited:hover
{
color: #ce54ba;
}
p.intro span.subject
{
font-size: 12px;
font-family: sans-serif;
color: #ce54ba;
font-weight: 800;
}
p.intro span.name
{
color: #32DD72;
font-weight: 800;
}
p.intro a.capcode, p.intro a.nametag
{
color: magenta;
margin-left: 0;
}
p.intro a.email, p.intro a.email span.name, p.intro a.email:hover, p.intro a.email:hover span.name
{
color: #32ddaf;
}
input[type="text"], textarea, select
{
background: #0d1010!important;
color: #CCCCCC!important;
border: solid 1px #ce54ba;
box-shadow: 1px 1px #803474;
margin-left: auto;
margin-right: auto;
}
input[type="password"]
{
background: #0d1010!important;
color: #CCCCCC!important;
border: #d2e7e8 1px double!important;
}
form table tr th
{
background: #0d1010!important;
color: #d2e7e8!important;
border: solid 1px #ce54ba;
box-shadow: 1px 1px #803474;
text-align: right;
}
div.banner
{
background: #E04000;
border: 0px solid hsl(17, 100%, 60%);
color: #EEE;
text-align: center;
height: 15px;
padding: 1px 1px 4px 1px;
margin-left: auto;
margin-right: auto;
}
div.banner a
{
color:#000;
}
input[type="submit"]
{
background: #333333;
border: #666 1px solid;
color: #CCCCCC;
}
input[type="submit"]:hover
{
background: #555;
border: #888 1px solid;
color: #32DD72;
}
input[type="text"]:focus, textarea:focus
{
border:#888 1px solid!important;
}
p.fileinfo a:hover
{
text-decoration: underline;
}
span.trip
{
color: #AAA;
}
.bar
{
background: #0c0c0c!important;
-moz-box-shadow: 0 0 40px #000;
-webkit-box-shadow: 0 0 40px #000;
}
.bar.top
{
border: solid 1px #803474;
box-shadow: 6px 2px #803474;
}
.bar.bottom
{
border-top: 0px solid #666;
border: solid 1px #803474;
}
div.pages
{
color: #d2e7e8 ;
background: #333;
border: #666 0px solid;
font-family: sans-serif;
font-size: 10pt;
}
div.pages a.selected
{
color: #d2e7e8 ;
}
hr
{
height: 0px;
border: #d2e7e8 2px solid;
}
div.boardlist
{
color: #d2e7e8;
}
div.ban
{
background-color: #0d1010;
border: 0px solid #555;
-moz-border-radius: 2px;
-webkit-border-radius: 2px;
border-radius: 2px;
text-align: left!important;
font-size: 13px;
}
div.ban h2
{
background: #333;
color: #d2e7e8;
padding: 3px 7px;
font-size: 12pt;
border-bottom: 1px solid #555;
}
div.ban h2:not(:nth-child(1))
{
border-top: 0px solid #555;
}
table.modlog tr th
{
background: #333;
color: #AAA;
}
div.report
{
color: #666;
}
.pages, .board_image, input, .reply, form table th, textarea, a img, select, .banner
{
-webkit-border-radius: 2px;
-khtml-border-radius: 2px;
-moz-border-radius: 2px;
-o-border-radius: 2px;
-ms-border-radius: 2px;
border-radius: 2px;
}
.blur
{
filter: blur(20px);
-webkit-filter: blur(23px);
-moz-filter: blur(23px);
-o-filter: blur(23px);
-ms-filter: blur(23px);
filter: url(svg/blur.svg#blur);
}
/* options.js */
#options_div
{
background: #333333;
}
.options_tab_icon
{
color: #AAAAAA;
}
.options_tab_icon.active
{
color: #FFFFFF;
}
.blotter
{
color: #DCA1F5!important;
}

482
stylesheets/8ch.cyber.css

@ -0,0 +1,482 @@
DIV.announcement.apr212015.board-owners-or-users-feel-free-to-hide
{color: #fff;}
form table tr td {
background-color: rgb(43, 43, 43);}
/*monoelains ez css for VIP quality imageboards. steal this .css and edit it to your liking. */
/*Bottom bar settings for lainchan.org CSS theft by kalyx*/
.bar {
-moz-box-shadow: 0 0 0px #d1d5ee;
-webkit-box-shadow: 0 0 0px #d1d5ee;
box-shadow: 0 0 0px #d1d5ee;
display: table;
position: fixed;
width: 100%;
left: 0px;
z-index: 3;
background-color: #35363b;
border-color: #39d958;
}
.bar.bottom {
bottom: 0px;
border-top: 1px solid #333333;
background-color: #1A1A1A !important;
}
/* OPTION HIDING */
/*removes footer (STI pls dont sue)
footer {
display:none;
color: #ffffff;
}
/* hides AA in catalog */
.theme-catalog span.aa {
display: none !important;
}
/* hides expand all images */
#expand-all-images {
display: none;
}
/* hides hints*/
.hint {
display: none;
}
/* hides horizontal bar */
hr {
display: none;
}
/*banner stuff*/
img.banner, img.board_image {
border: 1px solid #989898;
background-color: #B2A50F;
display: ;
box-shadow: 3px 5px #989898;
margin: 30px auto 0px;
margin-top: 50px;
}
/* hides delete options (obsolete) */
div.delete {
display: none;
}
/*makes files align vertically (hw pls fix this shit)*/
.file {
display: block;
margin-right: 11px;
}
/*expanded image border/shadow*/
.full-image {
border-top-right-radius: 0;
box-shadow: 3px 5px #2e8b57;
border: solid 1px #2e8b57;
border-bottom-right-radius: 0;
border-bottom-left-radius: 0;
border-top-left-radius: 0;
padding: 5px;
margin: 0px 0px 0px;
border-radius: 0;
max-width: 80% !important;
float: none;
background-color: #2b2b2b;
}
/*gives quick reply its border/shadow*/
#quick-reply table {
background: none repeat scroll 0 0 #2b2b2b;
border: 1px solid #989898;
margin: 0;
box-shadow: 3px 5px #989898;
width: auto !important;
}
/*makes posting form maroon*/
form table tr th {
background-color: #2e8b57;
color: #2b2b2b;
font-family: "Lucida Console", Monaco, monospace;
}
/*gives images border/shadow*/
div.files img.post-image {
box-shadow: 3px 5px #4D4D4D;
background-color: #2b2b2b;
border: solid 1px #4D4D4D;
padding: 0px;
border-radius: 0;
margin-bottom: 5px;
}
/* makes Youtube thumbs not obscenely big */
div.video-container {
width: auto !important;
height: auto !important;
max-height: 50% !important;
max-width: 50% !important;
}
/* Image thumbs size limit (make as big as you want)*/
.post-image {
border: solid 1px #2e8b57;
height: auto !important;
width: auto !important;
max-height: 60%;
max-width: 60%;
box-shadow: 3px 5px #2e8b57;
padding: 5px;
background-color: #2b2b2b;
}
/* Keep small thumbnails in replies */
.post.reply a:not([data-expanded="true"]) .post-image {
width: auto !important;
height: auto !important;
max-height: 60%;
max-width: 60%;
}
/* reply box size */
.input[type="text"], textarea {
min-height: 117px;
min-width: 240px;
}
/*boardwide font stuff goes here also background image*/
body {
padding: 0;
font-family: "Lucida Console", Monaco, monospace;
font-size: 14px;
background: url("img/cyber_dot_overlay.png") fixed repeat top #000000;
float: left;
margin-left: 10px;
margin-right: 35px;
}
/*saves one pixel of space*/
header {
margin-bottom: 0px;
}
/*gives posting table its border/shadow*/
form table {
max-width: 500px;
min-width: 100%
display: inline-block;
margin: 5px auto;
background-color: #2b2b2b;
border: solid 1px #989898;
color: #61CE3C;
padding: 5px;
box-shadow: 3px 3px #989898 !important;
}
/*gives threads its border/shadow*/
div[id^="thread_"] {
background-color: #1A1A1A;
color: #61CE3C;
margin-bottom: 20px;
padding: 10px;
border: solid 1px #7b68ee;
width: 100%;
box-shadow: 3px 5px #7b68ee;
}
/*adds horizontal bar below subject*/
p.intro {
background-image: "img/line_diagonal.png";
background-repeat: repeat-x;
background-position: bottom;
padding-bottom: 7px;
}
/*gives replies border/shadow*/
div.post.reply {
max-width: 95% !important;
box-shadow: 3px 5px #2e8b57;
padding: 4px;
margin-top: 9px;
border: solid 1px #2e8b57;
background-color: #2b2b2b;
}
/*unfucks highlighting replies and gives border/shadow*/
div.post.reply.highlighted {
background-color: #2b2b2b;
border: solid 1px #93e0e3;
box-shadow: 3px 5px #93e0e3;
}
/*dont touch this*/
div.body {
display: block;
clear: both;
padding-top: 10px;
}
/*makes page bar have border/shadow*/
div.pages {
box-shadow: 3px 5px #2e8b57;
width: 100%;
display: block;
clear: both;
border: solid 1px #2e8b57;
/*background-color: #2b2b2b;*/
}
/*hovered links have underline*/
a:hover {
text-decoration: underline;
}
/*stops textbox resizing shenanigans*/
.input[type="text"], textarea {
font-size: 14px;
max-width: 9000px;
font-family: "Lucida Console", Monaco, monospace;
min-height: 115px;
min-width: 270px;
background-color: #1A1A1A;
color: #61CE3C;
}
input {
background-color: #1A1A1A;
color: #61CE3C;
}
select {
background-color: #1A1A1A;
color: #61CE3C;
}
/*boardname nice font. replace with something classy someday*/
header div.subtitle, h1 {
font-family: "Lucida Console", Monaco, monospace;
color: #cc0000;
}
/*announcement font and color*/
div.blotter {
padding: 15px;
color: #cc0000;
font-family: "Lucida Console", Monaco, monospace;
}
/*makes links red*/
a {
color: #c80b63;
text-decoration: none;
}
/*top boardlist stuff. change position to "fixed" to make it behave like normal 8chan*/
div.boardlist:not(.bottom) {
background-color: #1A1A1A !important;
text-align: center;
padding: 5px 1px;
letter-spacing: -2px;
color: #989898;
font-size: 11px;
border-bottom: double 3px #989898 !important;
position:fixed;
}
/*favorites in a new line*/
div.boardlist span.favorite-boards {
display: ;
}
@-moz-document url-prefix() {
div.boardlist {
word-spacing:-1px !important;
}
}
/*subject color*/
p.intro span.subject {
color: #7b68ee;
}
/*bottom boardlist centered*/
div.boardlist.bottom {
text-align: center;
background-color: #1A1A1A;
border: solid 1px #989898;
box-shadow: 3px 7px #989898;
width: 100%;
margin: 15px;
}
/*colors name*/
p.intro span.name {
color: #989898;
font-family: ;
}
/*catalog stuff*/
.theme-catalog div.thread {
background-color: #2b2b2b;
color: #61CE3C;
border: solid 1px #2e8b57;
}
.theme-catalog div.thread:hover {
background-color: #383838;
border: solid 1px #2e8b57;
}
span.trip {
color: rgb(34, 136, 84);
}
#updater, #thread-links {
background-color: #2b2b2b;
border:solid 1px #989898;
padding: 5px;
margin: 5px;
box-shadow: 3px 5px #989898;
color: #989898;
}
A#update_thread
{color: #ffffff;}
A#thread-catalog
{color: #ffffff;}
A#thread-top
{color: #ffffff;}
A#thread-return
{color: #ffffff;}
div.banner {
width: 380px;
display:block;
margin-left:auto;
margin-right:auto;
}
#quick-reply th .close-btn{
color:#ffffff;
}
div.post-hover {
border: solid 1px #2e8b57;
box-shadow: 3px 5px #2e8b57;
max-width: 50%;
}
span.subject:after { content: '\A'; white-space:pre; }
SPAN.controls.op:before { content: '\A'; white-space:pre; }
a:visited {color: #97094b}
.quote {color: #93e0e3;}
a:hover,p.intro a.post_no:hover {
color: #ff0000 !important;
}
p.intro {color: #989898}
p.intro a {color: #ffffff !important}
A.post_no {color:#989898}
p.fileinfo
{color: #989898 !important}
p.fileinfo a:link{
color: #989898;
} !important;
max-width: 50%;
}
span.subject:after { content: '\A'; white-space:pre; }
SPAN.controls.op:before { content: '\A'; white-space:pre; }
a:visited {color: #97094b}
p.intro {color: #989898}
p.intro a {color: #ffffff !important}
A.post_no {color:#989898}
p.fileinfo
{color: #989898 !important}
p.fileinfo a:link{
color: #989898;
}
DIV#thread_stats
{
border: solid 1px #989898;
box-shadow: 3px 5px #989898;
color: #989898;
}
DIV.file-hint
{background-color: #383838;}
div.dropzone
{background-color: #383838;
color: #ffffff}
#options_div
{background-color: #383838; color: #61CE3C;
}
div.options_tab_icon
{color: #61CE3C;}
div.options_tab_icon.active
{color: #ffffff;}
#filter-container
{background-color: #707070;
color: #ffffff;}
#treeview
{display:none}
span.heading
{color: #FF6400}
body > footer:nth-child(19)
{color: #ffffff;}
div.boardlist a {color: #ffffff;}
form table tr th {
color: #ffffff !important;
}
p.intro a.email span.name{color: #c80b63}
p.intro span.capcode {color: #FBDE2D}
p.intro span.capcode:before {font-family: MS PGothic; content: "( ´∀`) 🔰"}
label {display:none}
div.post-menu {color: #ffffff}
@media only screen and (max-width: 480px), only screen and (max-device-width: 480px) {
div.boardlist:not(.bottom) {position: static;}
body { min-width: 100%;}
}

175
stylesheets/bunker_like.css

@ -0,0 +1,175 @@
body {
background: #1D1F21;
color: #ACACAC;
font-family: Courier, monospace;
font-size: 13px;
}
/* LINKS */
a, a:link, a:visited, .intro a.email span.name {
color: #FFB300;
text-decoration: none;
}
a:link:hover, a:visited:hover {
color: #FFB300;
text-shadow: 0px 0px 5px #117743;
}
div.pages a.selected {
color: #FFB300;
}
/* INTRO */
h1, div.title, header div.subtitle {
color: #663E11;
font-family: Courier, monospace;
}
h1 {
font-size: 24pt;
font-weight: normal;
letter-spacing: 0px;
}
header div.subtitle {
font-size: 12pt;
}
/* FORMS AND BUTTONS */
div.banner {
background-color: inherit;
color: #ACACAC;
}
form table {
border: 1px dashed #117743;
padding-right: 1px;
}
form table tr th {
background: #282A2E;
border: 1px solid #117743;
border-radius: 5px;
}
input[type="text"], input[type="password"], textarea, select {
border: 1px double #07371F;
border-radius: 5px;
background: #282A2E;
color: #ACACAC;
font-family: Courier, monospace;
}
input[type="text"]:focus, input[type="password"]:focus, textarea:focus {
box-shadow: 0px 0px 5px 2px #117743;
}
input[type="submit"] {
border: 3px double #07371F;
border-radius: 5px;
background: #16171A;
color: #ACACAC;
font-family: Courier, monospace;
font-weight: bold;
}
.dropzone {
background: #16171A;
border: 3px double #07371F;
color: #ACACAC;
}
.dropzone .file-hint {
color: #ACACAC;
font-weight: bold;
}
#quick-reply table {
background: #1D1F21 !important;
}
fieldset {
border: 1px dashed #117743;
}
/* POST IDENTIFIERS */
.intro span.subject {
color: #34ED3A;
}
.intro span.name {
color: #117743;
}
.intro span.trip {
color: #117743;
}
.intro a.capcode, p.intro a.nametag {
color: #FF0000;
font-weight: bold;
}
.intro a.email, p.intro a.email span.name, p.intro a.email:hover, p.intro a.email:hover span.name {
color: #34ED97;
}
.intro time {
font-weight: bold;
}
.intro a.post_no {
color: #ACACAC;
font-weight: bold;
}
/* POST BOXES */
div.post.reply {
background: #282A2E;
border: 1px solid #117743;
border-radius: 5px;
}
div.post.reply.highlighted {
background: rgba(59, 22, 43, 0.4);
border: 1px solid #117743;
border-radius: 5px;
}
/* POST CONTENT */
div.post.reply div.body a {
color: #FFB300;
}
.quote {
color: #789922;
}
/* BARS */
.bar {
background-color: #151515;
}
.bar.top {
border-bottom: 1px solid #B0790A;
}
.bar.bottom {
border-top: 1px solid #B0790A;
}
div.boardlist {
color: #ACACAC;
}
hr {
border: none;
border-top: 1pt solid #117743;
}
/* CATALOG */
.theme-catalog h1 {
color: #ACACAC;
font-size: 18pt;
font-weight: bold;
}
.theme-catalog h1 a {
font-weight: normal;
}
.theme-catalog div.thread, .theme-catalog div.thread:hover {
background: #282A2E;
border: 1px solid #117743;
border-radius: 5px;
font-size: 10pt;
}
/* OPTIONS */
#options_div, #alert_div {
background: #1D1F21;
border: 1px dashed #117743;
}
#options_tablist {
border-right: 1px dashed #117743;
}
.options_tab_icon {
color: #ACACAC;
}
.options_tab_icon.active {
color: #FFB300;
}

1
stylesheets/chartist/chartist.min.css

File diff suppressed because one or more lines are too long

96
stylesheets/cupcake.css

@ -0,0 +1,96 @@
/**
* volafile.css fuck you
color change by kalyx
*/
@import url("/stylesheets/dark.css");
@font-face
{
font-family: "DejaVuSansMono";
src: url("/stylesheets/fonts/DejaVuSansMono.ttf") format("truetype");
}
@font-face
{
font-family: 'lain';
src: url('./fonts/lain.woff') format('woff'),
url('./fonts/lain.TTF') format('truetype');
}
body {
background: #000000;
color: rgb(193, 180, 146);
font-family: monospace;
font-size: 11px;
}
.boardlist
{
text-shadow: none!important;
}
header h1, header div.subtitle, form table tr th, form table tr td input
{
color: #F12D67!important;
}
header h1
{
font-family: "DejaVuSansMono",sans-serif;
font-style: normal;
font-weight: 400;
font-size: 32px;
letter-spacing: 0px;
margin-top: calc(-16px / 3 - 6.5px);
}
p.intro span.subject {
color: #32BFAB;
}
div.topbar, input[type="text"], select, form table tr th, form table tr td input[type="text"], form table tr td textarea, div.post.reply, [type="submit"], input[type="password"], div.pages
{
background: #303030!important;
border-color: #5A5A5A!important;
color: #F12D67!important;
}
div.post.reply.highlighted{
background-color: #303030!important;
}
form table tr td input[type="text"]:focus, form table tr td textarea:focus
{
border-color: #5A5A5A!important;
}
input[type="submit"][name="post"]
{
color: #3A4040!important;
background-color: #303030!important;
border-color: #5A5A5A!important;
}
.blotter
{
color: #303030!important;
}
hr {
border-color: #5A5A5A;
}
a
{
color: #32BFAB!important;
}
p.intro span.name
{
color: #F12D67;
}
span.quote {
color: #E6E1E9;
}
span[style="color:orange;font-weight:bold"]
{
color: #D880FC!important;
}
a:hover, p.intro a.post_no:hover
{
color: #EDEDED!important;
text-shadow: none!important;
}
i.fa
{
color: #303030;
}

277
stylesheets/cyberpunk.css

@ -0,0 +1,277 @@
/*cyberpunk mod of ferus by kalyx
B332E6 = dark purp
33cccc = teal
00ff00 = green
FF46D2 = pink
dark blue = 00080C
*/
body {
background: #0C0001;
color: #FF46D2;
font-family: monospace;
font-size: 11px;
/*text-shadow: 0px 0px 3px #FF46D2;*/
}
.bar
{
background-color: #0C0001;
-webkit-box-shadow: none;
-moz-box-shadow: none;
box-shadow: none;
}
div.boardlist{
background-color: #0C0001!important;
}
.bar.top
{
border-bottom: 2px solid #3CC!important;
background-color: #0C0001;
}
.bar.bottom
{
border-top: 2px solid #3CC!important;
background-color: #0C0001;
}
@font-face
{
font-family: 'lain';
src: url('./fonts/lain.woff') format('woff'),
url('./fonts/lain.TTF') format('truetype');
}
h1 {
font-family: monospace, Arial;
font-size: 18pt;
text-align: center;
letter-spacing: 0px;
}
div.title, h1 {
color: #FF46D2;
font-family: lain, Helvetica, sans-serif;
}
header div.subtitle {
color: #00ff00;
text-align: center;
text-shadow: 0px 0px 3px #00ff00;
}
div.title p {
font-size: 13px;
}
a, a:link, a:visited, p.intro a.email span.name {
color: #00ff00;
text-shadow: 0px 0px 10px #00ff00;
font-family: monospace;
}
pre.prettyprint {
background: #00080C;
font-size: 12px;
line-height: 1.5;
border: 1px solid #33cccc;
padding: 10px;
}
a, a:link, a:visited, p.intro a.email span.name
{
-moz-transition: 0.15s text-shadow, 0.15s color;
-webkit-transition: 0.15s text-shadow, 0.15s color;
-khtml-transition: 0.15s text-shadow, 0.15s color;
-o-transition: 0.15s text-shadow, 0.15s color;
-ms-transition: 0.15s text-shadow, 0.15s color;
transition: 0.15s text-shadow, 0.15s color;
}
input[type="text"], textarea
{
-moz-transition: 0.15s border-color;
-webkit-transition: 0.15s border-color;
-khtml-transition: 0.15s border-color;
-o-transition: 0.15s border-color;
-ms-transition: 0.15s border-color;
transition: 0.15s border-color;
}
input[type="submit"]
{
-moz-transition: 0.15s border-color, 0.15s background-color, 0.15s color;
-webkit-transition: 0.15s border-color, 0.15s background-color, 0.15s color;
-khtml-transition: 0.15s border-color, 0.15s background-color, 0.15s color;
-o-transition: 0.15s border-color, 0.15s background-color, 0.15s color;
-ms-transition: 0.15s border-color, 0.15s background-color, 0.15s color;
transition: 0.15s border-color, 0.15s background-color, 0.15s color;
}
a:hover, a:link:hover, a:visited:hover {
color: #00ff00;
font-family: monospace;;
text-shadow: 0px 0px 10px #00ff00;
}
strong {
color: #00ff00;
}
a.post_no {
color: #33cccc;
text-decoration: none;
}
span.quote
{
color:#00ff00;
}
a.post_no:hover {
color: maroon;
/*text-decoration: underline overline;*/
}
div.post.reply {
background: #0C0001;
border: #33cccc 2px solid;
border-radius: 5px;
}
.de-pview {
background: rgba(14, 14, 14, 0.84) !important;
}
div.post.reply.highlighted {
background: transparent;
border: #1A6666 2px solid;
}
div.post.reply div.body a:link, div.post.reply div.body a:visited {
color: #646464;
}
div.post.reply div.body a:link:hover, div.post.reply div.body a:visited:hover {
color: #00ff00;
}
p.intro span.subject {
font-size: 12px;
font-family: monospace;
color: #00ff00;
font-weight: 800;
text-shadow: 0px 0px 3px #00ff00;
}
p.intro span.name {
color: #00ff00;
font-weight: 900;
text-shadow: 0px 0px 3px #00ff00;
}
p.intro a.capcode, p.intro a.nametag {
color: #00ff00;
margin-left: 0;
}
p.intro a.email, p.intro a.email span.name, p.intro a.email:hover, p.intro a.email:hover span.name {
color: #00ff00;
font-family: monospace;
}
input[type="text"], textarea, select {
background: #00080C;
color: #CCCCCC;
border: #33cccc 1px solid;
padding-left: 5px;
padding-right: -5px;
font-family: monospace;
font-size: 10pt;
border-radius: 5px;
}
input[type="password"] {
background: #00080C;
color: #CCCCCC;
border: #33cccc 1px solid;
border-radius: 5px;
}
form table tr th {
background: #00080C;
color: #00ff00;
border: #33cccc 1px solid;
font-weight: 800;
text-align: left;
padding: 0;
border-radius: 5px;
text-shadow: 0px 0px 3px #00ff00;
}
div.sidearrows{display:none;}
div.banner {
background: #0C0001;
color: #00ff00;
text-align: center;
width: 250px;
padding: 4px;
padding-left: 12px;
padding-right: 12px;
margin-left: auto;
margin-right: auto;
font-size: 12px;
text-shadow: 0px 0px 3px #00ff00;
}
input[type="submit"] {
background: #00080C;
color: #00ff00;
border: #33cccc 1px solid;
border-radius: 5px;
}
input[type="submit"]:hover {
background: #00080C;
border: #33cccc 1px solid;
color: #FF46D2;
border-radius: 5px;
}
p.fileinfo a:hover {
text-decoration: underline;
}
span.trip {
color: #AAAAAA;
}
.topbar {
background-color: black;
border-bottom: 1px solid #33cccc;
}
div.pages {
color: #AAAAAA;
font-family: sans-serif;
font-size: 10pt;
}
div.pages a.selected {
color: #8C8C8C;
}
hr {
height: 0px;
border: #33cccc 1px solid;
}
div.boardlist {
font-size: 9pt;
color: #999999;
}
div.ban {
background-color: transparent;
border: #33cccc 1px solid;
}
div.ban h2 {
background: transparent;
color: #00ff00;
font-size: 11px;
text-shadow: 0px 0px 3px #00ff00;
}
table.modlog tr th {
background: #FF46D2;
color: #AAAAAA;
/*text-shadow: 0px 0px 3px #00ff00;*/
}
.options_tab_icon {
color: #FF46D2;
}
.options_tab_icon.active {
color: #00ff00;
text-shadow: 0px 0px 10px #00ff00;
}
#options_div {
background-color: #0C0001;
border: #33cccc 2px solid;
border-radius: 5px;
}
#options_tablist {
border: #33cccc 1px solid;
}

107
stylesheets/dark.css

@ -1,34 +1,43 @@
/**
/*
* dark.css
* For AwsumChan by Circlepuller
*/
body {
background: #1E1E1E;
color: #999999;
font-family: sans-serif;
font-size: 12px;
font-family: Verdana, sans-serif;
font-size: 14px;
}
span.quote {
.quote {
color:#B8D962;
}
h1 {
@font-face {
font-family: 'lain';
src: url('./fonts/lain.woff') format('woff'),
url('./fonts/lain.TTF') format('truetype');
}
h1
{
letter-spacing: -2px;
font-size: 20pt;
text-align: center;
letter-spacing: 0px;
color: #32DD72;
}
div.title, h1 {
color: #32DD72;
font-family: Arial, Helvetica, sans-serif;
}
div.title p {
font-size: 10px;
}
a:link, a:visited, .intro a.email span.name {
a, a:link, a:visited, .intro a.email span.name {
color: #CCCCCC;
text-decoration: none;
font-family: sans-serif;
}
a:link:hover, a:visited:hover {
a:hover, a:link:hover, a:visited:hover {
color: #fff;
font-family: sans-serif;
text-decoration: none;
@ -45,10 +54,8 @@ a.post_no:hover {
div.post.reply {
background: #333333;
border: #555555 1px solid;
-webkit-border-radius: 10px;
-khtml-border-radius: 10px;
-moz-border-radius: 10px;
border-radius: 10px;
}
div.post.reply.highlighted {
@ -134,11 +141,16 @@ span.trip {
color: #AAAAAA;
}
div.pages {
color: #AAAAAA;
background: #333333;
border: #666666 1px solid;
background: #1E1E1E;
font-family: sans-serif;
font-size: 10pt;
}
.bar.bottom {
bottom: 0px;
border-top: 1px solid #333333;
background-color: #1E1E1E;
}
div.pages a.selected {
color: #CCCCCC;
@ -148,8 +160,9 @@ hr {
border: #333333 1px solid;
}
div.boardlist {
text-align: center;
color: #999999;
background-color: rgba(12%, 12%, 12%, 0.10);
/*background-color: rgba(12%, 12%, 12%, 0.10);*/
}
div.ban {
@ -166,15 +179,20 @@ table.modlog tr th {
color: #AAAAAA;
}
div.boardlist:not(.bottom) {
background-color: #1E1E1E;
}
.desktop-style div.boardlist:not(.bottom) {
text-shadow: black 1px 1px 1px, black -1px -1px 1px, black -1px 1px 1px, black 1px -1px 1px;
background-color: #666666;
/* position:static; */
text-shadow: black 1px 1px 1px, black -1px -1px 1px, black -1px 1px 1px, black 1px -1px 1px;
color: #999999;
background-color: #1E1E1E;
}
.desktop-style div.boardlist:not(.bottom):hover, .desktop-style div.boardlist:not(.bottom).cb-menu {
background-color: rgba(30%, 30%, 30%, 0.65);
}
div.report {
color: #666666;
@ -194,3 +212,46 @@ div.report {
#quick-reply table {
background: none repeat scroll 0% 0% #333 !important;
}
.modlog tr:nth-child(even), .modlog th {
background-color: #282A2E;
}
/*
.box {
background: #333333;
border-color: #555555;
color: #C5C8C6;
border-radius: 10px;
}
.box-title {
background: transparent;
color: #32DD72;
}
table thead th {
background: #333333;
border-color: #555555;
color: #C5C8C6;
border-radius: 4px;
}
table tbody tr:nth-of-type( even ) {
background-color: #333333;
}
table.board-list-table .board-uri .board-sfw {
color: #CCCCCC;
}
tbody.board-list-omitted td {
background: #333333;
border-color: #555555;
}
table.board-list-table .board-tags .board-cell:hover {
background: #1e1e1e;
}
table.board-list-table tr:nth-of-type( even ) .board-tags .board-cell {
background: #333333;
}
*/

242
stylesheets/dark_red.css

@ -0,0 +1,242 @@
/**
* dark.css, red accent
*/
/*dark.css has been prepended (2021-11-11) instead of @import'd for performance*/
body {
background: #1E1E1E;
color: #999999;
font-family: Verdana, sans-serif;
font-size: 14px;
}
.quote {
color:#B8D962;
}
@font-face {
font-family: 'lain';
src: url('./fonts/lain.woff') format('woff'),
url('./fonts/lain.TTF') format('truetype');
}
h1
{
letter-spacing: -2px;
font-size: 20pt;
text-align: center;
color: #32DD72;
}
div.title, h1 {
color: #32DD72;
}
div.title p {
font-size: 10px;
}
a, a:link, a:visited, .intro a.email span.name {
color: #CCCCCC;
text-decoration: none;
font-family: sans-serif;
}
a:hover, a:link:hover, a:visited:hover {
color: #fff;
font-family: sans-serif;
text-decoration: none;
}
a.post_no {
color: #AAAAAA;
text-decoration: none;
}
a.post_no:hover {
color: #32DD72 !important;
text-decoration: underline overline;
}
div.post.reply {
background: #333333;
border: #555555 1px solid;
}
div.post.reply.highlighted {
background: #555;
border: transparent 1px solid;
}
div.post.reply div.body a:link, div.post.reply div.body a:visited {
color: #CCCCCC;
}
div.post.reply div.body a:link:hover, div.post.reply div.body a:visited:hover {
color: #32DD72;
}
.intro span.subject {
font-size: 12px;
font-family: sans-serif;
color: #446655;
font-weight: 800;
}
.intro span.name {
color: #32DD72;
font-weight: 800;
}
.intro a.capcode, p.intro a.nametag {
color: magenta;
margin-left: 0;
}
.intro a.email, p.intro a.email span.name, p.intro a.email:hover, p.intro a.email:hover span.name {
color: #32ddaf;
}
input[type="text"], textarea, select {
background: #333333;
color: #CCCCCC;
border: #666666 1px solid;
padding-left: 5px;
padding-right: -5px;
font-family: sans-serif;
font-size: 10pt;
}
input[type="password"] {
background: #333333;
color: #CCCCCC;
border: #666666 1px solid;
}
form table tr th {
background: #333333;
color: #AAAAAA;
font-weight: 800;
text-align: left;
padding: 0;
}
div.banner {
background: #32DD72;
color: #000;
text-align: center;
width: 250px;
padding: 4px;
padding-left: 12px;
padding-right: 12px;
margin-left: auto;
margin-right: auto;
font-size: 12px;
}
div.banner a {
color:#000;
}
input[type="submit"] {
background: #333333;
border: #888888 1px solid;
color: #CCCCCC;
}
input[type="submit"]:hover {
background: #555555;
border: #888888 1px solid;
color: #32DD72;
}
input[type="text"]:focus {
border:#aaa 1px solid;
}
p.fileinfo a:hover {
text-decoration: underline;
}
span.trip {
color: #AAAAAA;
}
div.pages {
background: #1E1E1E;
font-family: sans-serif;
}
.bar.bottom {
bottom: 0px;
border-top: 1px solid #333333;
background-color: #1E1E1E;
}
div.pages a.selected {
color: #CCCCCC;
}
hr {
height: 1px;
border: #333333 1px solid;
}
div.boardlist {
text-align: center;
color: #999999;
}
div.ban {
background-color: transparent;
border: transparent 0px solid;
}
div.ban h2 {
background: transparent;
color: lime;
font-size: 12px;
}
table.modlog tr th {
background: #333333;
color: #AAAAAA;
}
div.boardlist:not(.bottom) {
background-color: #1E1E1E;
}
.desktop-style div.boardlist:not(.bottom) {
/* position:static; */
text-shadow: black 1px 1px 1px, black -1px -1px 1px, black -1px 1px 1px, black 1px -1px 1px;
color: #999999;
background-color: #1E1E1E;
}
div.report {
color: #666666;
}
#options_div, #alert_div {
background: #333333;
}
.options_tab_icon {
color: #AAAAAA;
}
.options_tab_icon.active {
color: #FFFFFF;
}
#quick-reply table {
background: none repeat scroll 0% 0% #333 !important;
}
.modlog tr:nth-child(even), .modlog th {
background-color: #282A2E;
}
/*
.box {
background: #333333;
border-color: #555555;
color: #C5C8C6;
border-radius: 10px;
}
.box-title {
background: transparent;
color: #32DD72;
}
table thead th {
background: #333333;
border-color: #555555;
color: #C5C8C6;
border-radius: 4px;
}
table tbody tr:nth-of-type( even ) {
background-color: #333333;
}
table.board-list-table .board-uri .board-sfw {
color: #CCCCCC;
}
tbody.board-list-omitted td {
background: #333333;
border-color: #555555;
}
table.board-list-table .board-tags .board-cell:hover {
background: #1e1e1e;
}
table.board-list-table tr:nth-of-type( even ) .board-tags .board-cell {
background: #333333;
}
*/
/* red accents */
div.blotter, h1, h2, header div.subtitle, div.title, a:link:hover, a:visited:hover p.intro a.post_no:hover,
div.post.reply div.body a:link:hover, div.post.reply div.body a:visited:hover, p.intro span.name,
p.intro a.email, p.intro a.email span.name, p.intro a.email:hover, p.intro a.email:hover span.name,
input[type="submit"]:hover, div.ban h2 {
color: #DD3232;
}
p.intro span.subject {
color: #962C22;
}

10
stylesheets/dark_roach.css

@ -1,4 +1,12 @@
@import url(//fonts.googleapis.com/css?family=Ubuntu);
/*@import url(//fonts.googleapis.com/css?family=Ubuntu);*/
/* latin */
@font-face {
font-family: 'Ubuntu';
font-style: normal;
font-weight: 400;
src: url('fonts/Ubuntu.woff2') format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
body {
background: #000112;

398
stylesheets/dark_solarized.css

@ -0,0 +1,398 @@
/**
* dark.css, solar extension
* copypasta from oneechan css/Colors.css, and UbuntuSolarized colors
*/
@import url("/stylesheets/dark.css");
/* Text Colors */
/* General */
html,
body,
div.boardBanner,
#menu,
input:not(.jsColor),
textarea,
#qr-filename-container,
#post-preview,
.post-hover,
.post-last,
.pln,
select,
.captcha-root,
.tegaki-label,
.dd-menu ul,
.boxbar {
color: #93a1a1 !important;
}
/* Names */
.name {
color: #586e75 !important;
}
.capcode {
color: #ca00ca !important;
}
/* Tripcodes */
.tripcode,
.tag {
color: #2aa198 !important;
}
/* Links */
a,
.typ,
.atn,
body.is_catalog .button,
:root.catalog-mode .button,
.options-button,
.tegaki-tb-btn {
color: #696bba !important;
}
a.summary,
.pages strong>a {
color: #93a1a1 !important;
}
.blotter #notifications a {
color: #ffffff !important;
}
a:hover,
body.is_catalog .button:hover,
:root.catalog-mode .button:hover,
.lit,
.blotter #notifications a:hover,
.tegaki-tb-btn:hover {
color: #d33682 !important;
}
/* Header */
.blotter, a.current {
color: #93a1a1 !important;
}
.blotter a:not(.current) {
color: #696bba !important;
}
.blotter a:hover {
color: #d33682 !important;
}
#custom-board-list .current {
border-bottom: 1px solid rgba(105,107,186,1) !important;;
}
#custom-board-list .current:hover {
border-bottom: 1px solid rgba(211,54,130,1) !important;;
}
/* Greentext */
.postMessage>.quote,
s:hover .quote,
.str,
.atv,
.new,
.catalog-thread > .comment > .quote {
color: #859900 !important;
}
/* Headers */
.heading {
color: #bec2c4 !important;
}
/* Subject and Option labels */
.subject,
.replytitle,
.teaser b,
.post-subject,
.option.header .option-title,
.kwd {
color: #bec2c4 !important;
}
.option.header {
font-size: 140%;
}
/* Board Title */
h1, h2 {
color: #93a1a1 !important;
text-shadow: none !important;
}
.subtitle {
color: #93a1a1 !important;
text-shadow: none !important;
}
/* Backlinks */
.backlink {
color: #4f5f8f !important;
}
.quotelink {
color: #696bba !important;
}
/* Code Tags */
.prettyprint,
.catalog-code {
background: none repeat scroll 0 0 rgba(" + $SS.theme.codeBackground + ") !important;
border: 1px solid rgba(" + $SS.theme.codeBorder + ") !important;
}
.pun {
color: rgba(147,161,161,.4) !important;
}
::-webkit-input-placeholder {
color:rgba(147,161,161,.4) !important;
}
#qr .field::-moz-placeholder,
::-moz-placeholder,
#qr-no-file {
color: rgba(147,161,161,.4) !important;
}
/* Backgrounds */
body {
background: #073642 !important;
}
.reply,
body.is_catalog .panel,
:root.catalog-mode .panel,
.dialog,
.tab-label,
#post-preview,
#tegaki,
#options_div,
#alert_div,
.bar,
.boardlist,
.pages,
:root.op-background .postContainer.opContainer,
.flashListing tr:nth-of-type(2n+1),
.dd-menu ul,
:root.catalog-hover-expand .catalog-container:hover > .post {
background: rgba(3,43,54,1)!important;
}
:root.recolor-even .thread>.replyContainer:nth-of-type(even):not(.hidden) .post {
background: rgb(0,33,44) !important;
}
:root:not(.header-gradient) .blotter {
background: rgba(3,43,54,.9) !important;
}
:root.header-gradient .blotter {
background: linear-gradient(rgb(18,58,69),rgba(3,43,54,.9)) !important;
}
:root.header-shadow .blotter {
box-shadow: none !important;
}
:root:not(.fixed) .blotter {
background: none !important;
}
.options-button,
.qr-link,
.pages.cataloglink,
.pages strong>a {
background:linear-gradient(rgb(18,58,69),rgb(3,43,54)) !important;
}
.options-button:hover,
.import-input:hover + .options-button,
.pages strong>a:hover,
.dd-menu li:hover {
background:rgb(18,58,69);
}
.focused.entry {
background:rgb(13,53,64) !important;
}
.qr-link:hover,
:root.vertical-qr #qr .move {
background:rgb\(3,43,54);
}
input:not(.jsColor),
textarea,
.riceCheck,
#qr-filename-container,
select,
.captcha-root {
background: #073642 !important;
transition: background .2s;
}
input[type=checkbox],
.riceCheck {
background: rgb(32,79,91) !important;
}
input:not(.jsColor):hover,
.riceCheck:hover,
#qr-filename-container:hover,
textarea:hover,
select:hover,
.captcha-root:hover {
background: #0d272e !important;
transition: background .2s;
}
hr {
background-image: linear-gradient(to left, rgba(211,54,130,0), rgb(211,54,130), rgba(211,54,130,0));
}
#unread-line {
background-image: linear-gradient(to left, rgba(105,111,192,0), rgb(105,111,192), rgba(105,111,192,0));
}
.inline {
background:rgba(0,27,38,.8)!important;
}
:root.post-info .reply>.postInfo {
background: rgba(0,27,38,.2);
border-bottom:1px solid rgb(3,47,58);
}
form table tr th {background: #073642;}
tr, td {background: #073642;}
#quick-reply table {background: #073642 !important;}
div.banner {background-color: rgba(200, 200, 200, 0.5);}
/* Borders */
.reply,
:root.op-background .postContainer.opContainer,
.dd-menu ul {
border-width: 0 1px 1px 0;
border-style: solid;
}
:root.borders-all .reply,
:root.borders-all.op-background .postContainer.opContainer {
border-width: 1px !important;
}
:root.borders-none .reply,
:root.borders-none.op-background .postContainer.opContainer {
border: none;
}
#menu,
.catalog-thumb {
border-radius: 0 !important;
}
:root.rounded-corners .reply,
:root.rounded-corners.op-background .postContainer.opContainer,
:root.rounded-corners .dialog:not(.blotter),
:root.rounded-corners .inline,
:root.rounded-corners #thread-stats
:root.rounded-corners #updater,
:root.rounded-corners #menu,
:root.rounded-corners .thumb,
:root.rounded-corners .fileThumb img:not(.full-image),
:root.rounded-corners .catalog-thumb,
:root.rounded-corners .dd-menu ul,
:root.rounded-corners.werkTyme .catalog-thread:not(:hover),
:root.rounded-corners.werkTyme:not(.catalog-hover-expand) .catalog-thread,
:root.rounded-corners.catalog-hover-expand .catalog-container:hover > .post,
:root.rounded-corners.catalog-hover-expand .catalog-container:hover .catalog-reply {
border-radius: 3px !important;
}
:root.post-info.rounded-corners .reply>.postInfo,
:root.rounded-corners #qr,
:root.rounded-corners:root.vertical-qr #qr > .move {
border-radius: 3px 3px 0 0 !important;
}
:root:not(.rounded-corners) #post-preview {
border-radius: 0 !important;
}
.reply,
:root.op-background .postContainer.opContainer,
.dialog,
.entry,
.inline,
fieldset,
#post-preview,
.flashListing td:not(:last-of-type):not(.postblock),
:root.vertical-qr #qr .move,
#qr select,
select {
border-color: #133942 !important;
}
.dd-menu li {
border-bottom: #133942 !important;
}
input,
textarea,
.riceCheck,
#qr-filename-container,
#search-box,
#index-search,
.captcha-img,
:root.vertical-qr #qr .move,
#qr select,
select,
#post-preview,
.captcha-root,
.dd-menu ul,
:root.werkTyme .catalog-thread:not(:hover),
:root.werkTyme:not(.catalog-hover-expand) .catalog-thread,
:root.catalog-hover-expand .catalog-container:hover > .post,
:root.catalog-hover-expand .catalog-container:hover .catalog-reply {
border: 1px solid #0d272e !important;
}
.options-button,
.qr-link,
.pages.cataloglink,
.pages strong>a {
border-style: solid;
border-width: 1px;
border-color: rgb(0,28,39) rgb(0,28,39) rgb(0,13,24) !important;
}
a.quotelink.forwardlink, a.backlink.forwardlink {
border-bottom: 1px dashed;
}
input:focus,
textarea:focus,
#qr-filename-container:focus,
#qr-filename-container.focus,
select:focus,
.captcha-root:focus {
border: 1px solid #696bba !important;
}
#search-box:hover,
#index-search:hover,
.captcha-img:hover {
border-color: #696bba !important;
}
.blotter {
border: none !important;
}
.flashListing td:not(:last-of-type):not(.postblock) {
border-width: 1px;
border-style: solid;
}
:root.header-highlight #custom-board-list .current:hover,
:root.header-highlight #custom-board-list .current {
border-bottom: none !important;
}
/* 4chan X Menu */
.suboption-list > div:last-of-type {
background: rgba(3,43,54,1)!important;
}
.suboption-list > div::before,
.suboption-list::before {
border-color: #133942 !important;
left: 0.5em !important;
}
/* Shadows */
#navlinks a {
text-shadow:#032b36 -1px -1px,
#032b36 1px -1px,
#032b36 -1px 1px,
#032b36 1px 1px,
#032b36 -1px 0,
#032b36 1px 0,
#032b36 0 -1px,
#032b36 0 1px,
rgba(0,0,0,.6) 0 2px 4px,
rgba(0,0,0,.6) 0 0 2px;
}
.thumb {
box-shadow: 0 0 5px rgba(0, 0, 0, 0.25);
}
#qr,
#thread-watcher {
box-shadow: 1px 1px 3px rgba(0, 0, 0, 0.1) !important;
}
.highlighted {
border-color: #6c71c4 !important;
}
/* Thread Stats */
:root.style-stats #thread-stats,
:root.style-stats #updater,
:root.style-stats #stats {
box-shadow: 0 1px 2px rgba(0, 0, 0, .15);
padding: 3px 6px !important;
border: 1px solid;
}
/* Mascots */
:root.mascot-grayscale #mascot img {
filter: url('data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 height=%220%22 color-interpolation-filters=%22sRGB%22><filter id=%22grayscale%22><feColorMatrix id=%22color%22 type=%22saturate%22 values=%220%22 /></filter></svg>#grayscale');
-webkit-filter: grayscale(100%);
}
/* Closed Threads */
.closed {
margin-top: 10px;
color: red;
}

59
stylesheets/dead.css

@ -0,0 +1,59 @@
/**
* dead.css, gray extension
* Clumps all rules into a few rules to determine the new accent color
*/
@import url("/stylesheets/dark.css");
div.blotter, h1, h2, header div.subtitle, div.title, a:link:hover, a:visited:hover p.intro a.post_no:hover,
div.post.reply div.body a:link:hover, div.post.reply div.body a:visited:hover, p.intro span.name,
p.intro a.email, p.intro a.email span.name, p.intro a.email:hover, p.intro a.email:hover span.name,
input[type="submit"]:hover, div.ban h2 {
font-family: 'Times';
color: #888888;
}
p.intro span.subject {
color: #5C5C5C;
}
.quote, span.orangeQuote, span.heading {
color:#CDCDCD;
}
input[type="submit"]:hover {
background: #555555;
border: #888888 1px solid;
color: #CCCCCC;
}
div.banner {
background: #DDDDDD;
}
a:link {
font-family: 'Times';
}
body, div.pages {
font-family: 'Times';
}
button, input, input[type="submit"], input[type="text"], textarea, select {
font-family: 'Times';
}
input, textarea {
-webkit-filter: grayscale(100%) contrast(1.25); /* Chrome, Safari, Opera */
filter: grayscale(100%) contrast(1.25);
}
img, .file-tmb {
-webkit-filter: grayscale(100%) contrast(1.25); /* Chrome, Safari, Opera */
filter: grayscale(100%) contrast(1.25);
}
video {
-webkit-filter: grayscale(100%) contrast(1.25); /* Chrome, Safari, Opera */
filter: grayscale(100%) contrast(1.25);
}

BIN
stylesheets/delete.css

Binary file not shown.

212
stylesheets/demain.css

@ -0,0 +1,212 @@
/* Demain_light theme for leftypol.org adapted from "Tomorrow" theme for 4chan, with large font size for wide screens */
/* Work in progress*/
/* General */
/* Main page */
legend {
color: indianred
}
div.module, div.ban {
background: #1d1f21;;
border: 1px solid #373b41;
max-width: 700px;
margin: 30px auto;
}
/* Text */
body {
background: #1d1f21;
color: #ACACAC;
font-size: 11pt;
font-family: "LiberationSansRegular";
}
/* Link colors */
a, a:link, a:visited, .intro a.email span.name {
color: #81a2be;
}
a:hover,
.intro a.post_no:hover {
color: #cc6666;
}
/* Board title and subtitle */
header div.subtitle,
h1 {
color: indianred;
font-size: 20pt;
font-family: "LiberationSansRegular";
}
header div.subtitle {
font-size: 10pt;
}
/* Post number*/
a.post_no {
color: #c5c8c6;
}
/* Replies */
/* Background color and border */
div.post.reply {
background: #282A2E;
border-color: #373b41;
border-style: solid;
border-width: 0.8px;
border-radius: 5px;
}
div.post.reply.highlighted {
background: #1b1c21;
border-color: #5f89ac;
border-style: solid;
border-width: 0.8px;
border-radius: 5px;
}
div.post.reply div.body a {
color: #5f89ac;
}
.intro span.subject {
color: #b294bb;
}
/* Greentext */
span.quote {
color: #b5bd68
}
/* Orangetext */
span.orangeQuote {
color: #b294bb
}
/* Catalog grids */
/* Background color and green border */
.thread.grid-li.grid-size-vsmall,
.thread.grid-li.grid-size-small,
.thread.grid-li.grid-size-large {
background-color: #282A2E;
border: 1px solid #373b41;
}
.thread.grid-li.grid-size-vsmall:hover,
.thread.grid-li.grid-size-small:hover,
.thread.grid-li.grid-size-large:hover {
background: #1b1c21;
border-color: #5f89ac;
border-style: solid;
border-width: 0.8px;
border-radius: 5px;
}
/* Posting form */
/* Field names */
form table tr th {
background: #282A2E;
color: #ACACAC;
border: 1px solid #373b41;
}
/* Input fields */
textarea, input:not([type="file"]):not([type="checkbox"]), [type="submit"], select {
color: #ACACAC;
background: #282A2E;
border: 1px double #1d1f21;
border-radius: 5px;
}
/* Input fields, focused */
textarea:focus, input:not([type="file"]):not([type="checkbox"]):focus, [type="submit"]:hover {
border: 1px solid #1d1f21;
-moz-box-shadow: 0 0 10px #1d1f21;
-webkit-box-shadow: 0 0 10px #1d1f21;
box-shadow: 0 0 10px #1d1f21;
}
/* Top bar */
/* Background, border and fade */
.bar {
background-color: #151515;
border-color: #81a2be!important;;
/* comment these out to remove the fade */
/*
-moz-box-shadow: 0 0 40px #FFB300;
-webkit-box-shadow: 0 0 40px #FFB300;
box-shadow: 0 0 40px #FFB300;
*/
}
/* Bottom bar, not visible in catalog mode */
div.pages {
background: #151515;
border-color: #81a2be;
}
div.pages a.selected {
color: #cc6666;
}
hr {
border-color: #373b41;
}
div.boardlist {
color: #81a2be;
font-size: 11pt;
}
div.boardlist a {
color: #cc6666;
}
table.modlog tr th {
background: #1d1f21;
}
/* options.js */
#options_div, #alert_div {
background: #151515;
}
.options_tab_icon {
color: gray;
}
.desktop-style div.boardlist:nth-child(1) {
background-color: #151515;
}
/* Red text */
span.heading {
color: indianred;
font-size: 11pt;
}
/* Buggy shit */
div.post.reply div.body a {
color: #5f89ac;
text-decoration: none;
}
/* OP */
/* Subject */
.intro span.subject {
color: #b294bb;
}
/* name */
.intro span.name {
color: #5f89ac;
}
.desktop-style div.boardlist:nth-child(1) {
background-color: #151515;
}
/* Upper part of a post */
div.post p {
display: block;
margin: 0;
line-height: 1.16em;
min-height: 1.16em;
}
/* Buggy shit */
div.post.reply div.body a {
color: #5f89ac;
text-decoration: none;
}
/* Watchlist options */
#watchlist-toggle, .watchThread, .watchlist-remove, #clearList, #clearGhosts {
color: indianred
}
/* Mod things */
div.report {
color: grey;
}
.banlist-opts .checkboxes label {
display: block;
color: grey;
}
tr.tblhead > th {
color: grey;
}
.desktop-style div.boardlist:not(.bottom) {
background-color: #151515;
}
.banlist-opts .checkboxes label {
display: block;
color: grey;
}
tr.tblhead > th {
color: indianred
}

213
stylesheets/demain_large.css

@ -0,0 +1,213 @@
/* Demain_light theme for leftypol.org adapted from "Tomorrow" theme for 4chan, with large font size for wide screens */
/* Work in progress*/
/* General */
/* Main page */
legend {
color: indianred
}
div.module, div.ban {
background: #1d1f21;;
border: 1px solid #373b41;
max-width: 700px;
margin: 30px auto;
}
/* Text */
body {
background: #1d1f21;
color: #ACACAC;
font-size: 14pt;
font-family: "LiberationSansRegular";
}
/* Link colors */
a:link, a:visited, .intro a.email span.name {
color: #81a2be;
}
a:hover,
.intro a.post_no:hover {
color: #cc6666;
}
/* Board title and subtitle */
header div.subtitle,
h1 {
color: indianred;
font-size: 20pt;
font-family: "LiberationSansRegular";
}
header div.subtitle {
font-size: 10pt;
}
/* Post number*/
a.post_no {
color: #c5c8c6;
}
/* Replies */
/* Background color and border */
div.post.reply {
background: #282A2E;
border-color: #373b41;
border-style: solid;
border-width: 0.8px;
border-radius: 5px;
}
div.post.reply.highlighted {
background: #1b1c21;
border-color: #5f89ac;
border-style: solid;
border-width: 0.8px;
border-radius: 5px;
}
div.post.reply div.body a {
color: #5f89ac;
}
.intro span.subject {
color: #b294bb;
}
/* Greentext */
span.quote {
color: #b5bd68
}
/* Orangetext */
span.orangeQuote {
color: #b294bb
}
/* Catalog grids */
/* Background color and green border */
.thread.grid-li.grid-size-vsmall,
.thread.grid-li.grid-size-small,
.thread.grid-li.grid-size-large {
background-color: #282A2E;
border: 1px solid #373b41;
}
.thread.grid-li.grid-size-vsmall:hover,
.thread.grid-li.grid-size-small:hover,
.thread.grid-li.grid-size-large:hover {
background: #1b1c21;
border-color: #5f89ac;
border-style: solid;
border-width: 0.8px;
border-radius: 5px;
}
/* Posting form */
/* Field names */
form table tr th {
background: #282A2E;
color: #ACACAC;
border: 1px solid #373b41;
}
/* Input fields */
textarea, input:not([type="file"]):not([type="checkbox"]), [type="submit"], select {
color: #ACACAC;
background: #282A2E;
border: 1px double #1d1f21;
border-radius: 5px;
}
/* Input fields, focused */
textarea:focus, input:not([type="file"]):not([type="checkbox"]):focus, [type="submit"]:hover {
border: 1px solid #1d1f21;
-moz-box-shadow: 0 0 10px #1d1f21;
-webkit-box-shadow: 0 0 10px #1d1f21;
box-shadow: 0 0 10px #1d1f21;
}
/* Top bar */
/* Background, border and fade */
.bar {
background-color: #151515;
border-color: #81a2be!important;;
/* comment these out to remove the fade */
/*
-moz-box-shadow: 0 0 40px #FFB300;
-webkit-box-shadow: 0 0 40px #FFB300;
box-shadow: 0 0 40px #FFB300;
*/
}
/* Bottom bar, not visible in catalog mode */
div.pages {
background: #151515;
border-color: #81a2be;
}
div.pages a.selected {
color: #cc6666;
}
hr {
border-color: #373b41;
}
div.boardlist {
color: #81a2be;
font-size: 14pt;
}
div.boardlist a {
color: #cc6666;
}
table.modlog tr th {
background: #1d1f21;
}
/* options.js */
#options_div, #alert_div {
background: #151515;
}
.options_tab_icon {
color: gray;
}
.desktop-style div.boardlist:nth-child(1) {
background-color: #151515;
}
/* Red text */
span.heading {
color: indianred;
font-size: 14pt;
}
/* Buggy shit */
div.post.reply div.body a {
color: #5f89ac;
text-decoration: none;
}
/* OP */
/* Subject */
.intro span.subject {
color: #b294bb;
}
/* name */
.intro span.name {
color: #5f89ac;
}
.desktop-style div.boardlist:nth-child(1) {
background-color: #151515;
}
/* Upper part of a post */
div.post p {
display: block;
margin: 0;
line-height: 1.16em;
font-size: 13pt;
min-height: 1.16em;
}
/* Buggy shit */
div.post.reply div.body a {
color: #5f89ac;
text-decoration: none;
}
/* Watchlist options */
#watchlist-toggle, .watchThread, .watchlist-remove, #clearList, #clearGhosts {
color: indianred
}
/* Mod things */
div.report {
color: grey;
}
.banlist-opts .checkboxes label {
display: block;
color: grey;
}
tr.tblhead > th {
color: grey;
}
.desktop-style div.boardlist:not(.bottom) {
background-color: #151515;
}
.banlist-opts .checkboxes label {
display: block;
color: grey;
}
tr.tblhead > th {
color: indianred
}

226
stylesheets/fauux.css

@ -0,0 +1,226 @@
/*fauux.neocities.org mod of ferus by kalyx
B332E6 = dark purp#d2738a = teal #d2738a = green
rgb(193, 180, 146) = pink
dark blue = 00080C
*/
body {
background: #000000;
color: rgb(193, 180, 146);
font-family: monospace;
font-size: 11px;
}
.bar
{
background-color: #000;
border-color: #D2738A!important;
-moz-box-shadow: none;
-webkit-box-shadow: none;
box-shadow: none;
background-color: #000!important;
}
div.boardlist{
background-color: #000!important;
}
@font-face
{
font-family: 'lain';
src: url('./fonts/lain.woff') format('woff'),
url('./fonts/lain.TTF') format('truetype');
}
h1 {
font-family: monospace, Arial;
font-size: 18pt;
text-align: center;
letter-spacing: 0px;
}
div.title, h1 {
color: rgb(193, 180, 146);
font-family: lain, Helvetica, sans-serif;
}
header div.subtitle {
color: #d2738a;
text-align: center;
}
div.title p {
font-size: 13px;
}
a, a:link, a:visited, p.intro a.email span.name {
color: #d2738a;
text-decoration: underline;
font-family: monospace;
}
pre.prettyprint {
background: #00080C;
font-size: 12px;
line-height: 1.5;
border: 1px solid #d2738a;
padding: 10px;
}
a:hover, a:link:hover, a:visited:hover {
color: #d2738a;
font-family: monospace;;
text-decoration: underline overline;
}
strong {
color: #d2738a;
}
a.post_no {
color: #d2738a;
text-decoration: none;
}
span.quote
{
color:#d2738a;
}
a.post_no:hover {
color: maroon;
text-decoration: underline overline;
}
div.post.reply {
background: #000000;
border: #d2738a 1px solid;
/*border-radius: 5px;*/
}
.de-pview {
background: rgba(14, 14, 14, 0.84) !important;
}
div.post.reply.highlighted {
background: transparent;
border: #EDC7D0 1px solid;
}
div.post.reply div.body a:link, div.post.reply div.body a:visited {
color: #646464;
}
div.post.reply div.body a:link:hover, div.post.reply div.body a:visited:hover {
color: #d2738a;
}
p.intro span.subject {
font-size: 10px;
font-family: monospace;
color: #d2738a;
font-weight: 800;
}
p.intro span.name {
color: #d2738a;
font-weight: 900;
}
p.intro a.capcode, p.intro a.nametag {
color: #d2738a;
margin-left: 0;
}
p.intro a.email, p.intro a.email span.name, p.intro a.email:hover, p.intro a.email:hover span.name {
color: #d2738a;
font-family: monospace;
}
input[type="text"], textarea, select {
background: #00080C;
color: #CCCCCC;
border: #d2738a 1px solid;
padding-left: 5px;
padding-right: -5px;
font-family: monospace;
font-size: 10pt;
/*border-radius: 5px;*/
}
input[type="password"] {
background: #00080C;
color: #CCCCCC;
border: #d2738a 1px solid;
/*border-radius: 5px;*/
}
form table tr th {
background: #00080C;
color: #d2738a;
border: #d2738a 1px solid;
font-weight: 800;
text-align: left;
padding: 0;
/*border-radius: 5px;*/
}
div.sidearrows{display:none;}
div.banner {
background: #000000;
color: #d2738a;
text-align: center;
width: 250px;
padding: 4px;
padding-left: 12px;
padding-right: 12px;
margin-left: auto;
margin-right: auto;
font-size: 12px;
}
input[type="submit"] {
background: #00080C;
color: #d2738a;
border: #d2738a 1px solid;
/*border-radius: 5px;*/
}
input[type="submit"]:hover {
background: #00080C;
border: #d2738a 1px solid;
color: rgb(193, 180, 146);
/*border-radius: 5px;*/
}
p.fileinfo a:hover {
text-decoration: underline;
}
span.trip {
color: #AAAAAA;
}
.topbar {
background-color: black;
border-bottom: 1px solid #d2738a;
}
div.pages {
color: #AAAAAA;
/*background: #333333;
border: #d2738a 1px solid;*/
font-family: sans-serif;
font-size: 10pt;
}
div.pages a.selected {
color: #8C8C8C;
}
hr {
height: 1px;
border: #d2738a 1px solid;
}
div.boardlist {
font-size: 10pt;
color: #999999;
}
div.ban {
background-color: transparent;
border: transparent 0px solid;
}
div.ban h2 {
background: transparent;
color: lime;
font-size: 11px;
}
table.modlog tr th {
background: rgb(193, 180, 146);
color: #AAAAAA;
}
#options_div {
background-color: #000000;
}
.options_tab_icon {
color: #c1b492;
}
.options_tab_icon.active {
color: #d2738a;
}

4
stylesheets/ferus.css

@ -16,12 +16,12 @@ div.title, h1 {
div.title p {
font-size: 13px;
}
a:link, a:visited, .intro a.email span.name {
a, a:link, a:visited, .intro a.email span.name {
color: #16C816;
text-decoration: underline;
font-family: monospace;
}
a:link:hover, a:visited:hover {
a:hover, a:link:hover, a:visited:hover {
color: #003C00;
font-family: monospace;;
text-decoration: underline overline;

BIN
stylesheets/fonts/DejaVuSansMono.ttf

Binary file not shown.

BIN
stylesheets/fonts/OpenSans-Light-webfont.eot

Binary file not shown.

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save