Compare commits

...

772 Commits

Author SHA1 Message Date
Lorenzo Yario
83b644f0f5
Merge pull request #920 from vichan-devel/5.2.1
5.2.1 Release
2025-04-02 04:45:22 -05:00
Lorenzo Yario
953635f807
Delete static/banners/.gitkeep 2025-04-02 04:44:51 -05:00
Lorenzo Yario
5968118231
bugfix 2025-04-02 04:39:20 -05:00
Lorenzo Yario
03da3b8db0
bug fix 2025-04-02 04:27:38 -05:00
Lorenzo Yario
f27e5a6989
ensure premade ban reasons are passed 2025-04-02 04:04:08 -05:00
Lorenzo Yario
0866adc89f
styling for predefined ban reasons 2025-04-02 03:56:03 -05:00
Lorenzo Yario
5259f88262
Update config.php 2025-04-02 03:46:07 -05:00
Lorenzo Yario
87420966b2
kick doesn't seem all too bad anymore. Removed X/Bluesky test 2025-04-02 03:03:58 -05:00
Lorenzo Yario
8350645a70
we don't need kick it doesn't embed nicely 2025-04-02 02:52:44 -05:00
Lorenzo Yario
16a6112b5b
new embed stuff 2025-04-02 02:45:04 -05:00
Lorenzo Yario
9468c9d372
Add files via upload 2025-04-02 02:22:44 -05:00
Lorenzo Yario
8aa965b77f
Create .gitkeep 2025-04-02 02:22:26 -05:00
Lorenzo Yario
b08dbcff14
Delete static/banners 2025-04-02 02:22:15 -05:00
Lorenzo Yario
f90291faf0
Create banners 2025-04-02 02:21:31 -05:00
Lorenzo Yario
e67f3f94bf
Delete static/banners/defaultbanner.png 2025-04-02 02:20:16 -05:00
Lorenzo Yario
4244b0a86c
tidied slightly 2025-04-02 02:14:46 -05:00
Lorenzo Yario
ac3f69a7eb
jank? maybe but it fixes our current config.php 2025-04-02 02:12:50 -05:00
Lorenzo Yario
7bc1f67b4e
X 2025-04-02 00:34:45 -05:00
Lorenzo Yario
54f32bc75d
X 2025-03-30 21:54:28 -05:00
Lorenzo Yario
5f1598367c
Merge pull request #783 from perdedora/fix-poster-id
fix: wrong poster id for op in api
2025-03-16 02:59:08 -05:00
Weav
3987b2a25d
Merge branch 'master' into fix-poster-id 2025-03-15 17:18:22 +00:00
Lorenzo Yario
6ec0c17ac9
Update login.html #890 2025-02-08 22:28:53 -06:00
Lorenzo Yario
2c2d476cea
5.2.1 2025-02-08 22:27:21 -06:00
Lorenzo Yario
717795ddff
Update fileinfo.html #891 2025-02-08 22:26:17 -06:00
Lorenzo Yario
d1e57290e0
Update bug_report.yml 2025-02-02 23:07:01 -06:00
Lorenzo Yario
0c92315837
Create bug_report.yml 2025-02-02 23:03:47 -06:00
Lorenzo Yario
c026e85b3c
Delete .github/ISSUE_TEMPLATE/bug_report.md
yml
2025-02-02 23:03:35 -06:00
Lorenzo Yario
8d76087710
what did I break? 2025-02-02 23:02:41 -06:00
Lorenzo Yario
50d57a4464
Update bug_report.md 2025-02-02 23:01:48 -06:00
Lorenzo Yario
96099945b9
Update issue templates 2025-02-02 22:51:55 -06:00
Lorenzo Yario
8c6cf17a97
Delete .github/ISSUE_TEMPLATE/bug_report.md
failed
2025-02-02 22:43:03 -06:00
Lorenzo Yario
b3f0658594
Update issue templates 2025-02-02 22:42:08 -06:00
Lorenzo Yario
33efeda647
Merge pull request #886 from vichan-devel/RealAngeleno-patch-18
adding in perdedora's PR to the master branch
2025-02-02 22:24:36 -06:00
Lorenzo Yario
92096b43b8
adding in perdedora's PR to the master branch 2025-02-02 22:24:06 -06:00
Lorenzo Yario
f3cb2552ce
modify default captcha config to work out of the box 2025-01-03 00:25:31 -06:00
Lorenzo Yario
440e3126c9
Merge pull request #872 from vichan-devel/RealAngeleno-patch-17
modify themes to use numeric array #871
2024-12-30 17:20:08 -06:00
Lorenzo Yario
47cbbbc972
modify themes to use numeric array #871 2024-12-30 17:19:42 -06:00
Lorenzo Yario
74959aaed9
Merge pull request #868 from vichan-devel/RealAngeleno-captchafix
fix native captcha implementation
2024-12-26 22:30:48 -06:00
Lorenzo Yario
2d9e5009f8
fix typo 2024-12-26 22:28:21 -06:00
Lorenzo Yario
372784ecd2
Merge pull request #866 from vichan-devel/7.4-compatibility3
7.4 compatibility3
2024-12-23 12:18:35 -06:00
Lorenzo Yario
0f8a5fa926
continued 7.4 compatibility 2024-12-22 15:17:22 -06:00
Lorenzo Yario
0a870ebdb3
7.4 2024-12-22 15:05:16 -06:00
Lorenzo Yario
3b9b23035e
7.4 compatibility (updated) 2024-12-22 15:02:58 -06:00
Lorenzo Yario
40571f2001
7.4 compatibility 2024-12-22 14:59:12 -06:00
Lorenzo Yario
c4cd4d3c12
Update HttpDriver.php 2024-12-22 14:55:57 -06:00
Lorenzo Yario
00fc21322c
7.4 compatibility 2024-12-22 14:54:48 -06:00
Lorenzo Yario
6606c182b5
7.4 compatibility 2024-12-22 14:51:07 -06:00
Lorenzo Yario
58c2f095dc
7.4 compatibility 2024-12-22 14:49:27 -06:00
Lorenzo Yario
8cd4cae56e
Merge pull request #863 from vichan-devel/RealAngeleno-patch-16
continued php 7.4 compatibility
2024-12-22 14:44:20 -06:00
Lorenzo Yario
762a0edefd
continued 7.4 compatibility 2024-12-22 14:42:16 -06:00
Lorenzo Yario
c94ab113de
continued 7.4 backwards compatibility 2024-12-22 14:37:22 -06:00
Lorenzo Yario
32f0cb3a5f
Merge pull request #862 from vichan-devel/5.2.0-74compatibility
5.2.0 PHP 7.4 compatibility
2024-12-22 14:23:08 -06:00
Lorenzo Yario
2466a3d859
7.4 compatibility 2024-12-22 14:15:40 -06:00
Lorenzo Yario
f3f7c0c75c
7.4 compatibility 2024-12-22 14:13:37 -06:00
Lorenzo Yario
29ee5aeb1d
For 7.4 compatibility 2024-12-22 14:05:42 -06:00
Lorenzo Yario
6a22a51a72
Merge pull request #860 from vichan-devel/5.2.0-release
5.2.0 release
2024-12-22 11:11:31 -06:00
Lorenzo Yario
a9059fab66
Merge branch 'master' into 5.2.0-release 2024-12-20 17:40:59 -06:00
Lorenzo Yario
9d590eed2a
typo? 2024-12-20 15:08:24 -06:00
Lorenzo Yario
e7aa695775
2025 2024-12-20 15:04:30 -06:00
Lorenzo Yario
f419ae046c
replace captcha click me image 2024-12-20 14:59:46 -06:00
Lorenzo Yario
0ed3513e66
add youtu.be and voca.ro 2024-12-20 14:43:13 -06:00
Lorenzo Yario
a376a5a2e3
finish stripping current antispam system 2024-12-20 02:31:26 -06:00
Lorenzo Yario
b2ca26dba5
Merge pull request #858 from vichan-devel/removecurrentantibot
Remove current antibot system
2024-12-20 02:27:40 -06:00
Lorenzo Yario
6f5b0ae6d5
strip old antibot system 2024-12-20 02:24:18 -06:00
Lorenzo Yario
5f2f653993
Update functions.php 2024-12-20 02:16:59 -06:00
Lorenzo Yario
0ff0e707d6
remove vichan's current antibot system 2024-12-20 01:56:57 -06:00
Lorenzo Yario
26ffd1aa72
remove anti-bot functions 2024-12-20 01:08:41 -06:00
Lorenzo Yario
2e91c1ed3d
Update FileLogDriver.php 2024-12-20 00:24:46 -06:00
Lorenzo Yario
efd4810e83
Merge pull request #856 from vichan-devel/RealAngeleno-patch-15
skip captcha or log settings in mod.php config
2024-12-16 23:16:18 -06:00
Lorenzo Yario
9dacdf59b1
skip captcha or log settings in mod.php config
relates to multidimensional arrays
2024-12-16 23:15:23 -06:00
Lorenzo Yario
b889b10626
Merge pull request #834 from Zankaria/patch-2
Fix #830 remove multiple return types for PHP 7.4 support.
2024-10-31 13:52:09 -07:00
Zankaria
b5a9dc4d1a
bans.php: remove multiple return types for PHP 7.4 support 2024-10-31 14:47:11 +01:00
Lorenzo Yario
d6677bb90c
Merge pull request #825 from Zankaria/context-fix
context.php: fix missing include file
2024-10-20 12:03:51 -07:00
Lorenzo Yario
c88acfc4f0
Merge pull request #826 from vichan-devel/RealAngeleno-patch-13
minor bugfix relating to auth when changing your own username
2024-10-20 12:03:42 -07:00
Lorenzo Yario
58f7302936
minor bugfix relating to auth when changing your own username 2024-10-20 12:03:15 -07:00
Zankaria
76f6c721e9 context.php: fix missing include file 2024-10-16 22:49:19 +02:00
Lorenzo Yario
c1307feeb5
Merge pull request #818 from Zankaria/dep-inj-cache-wrap
Dependency injected cache
2024-10-15 20:40:34 -07:00
Lorenzo Yario
e65bfa87c4
Merge pull request #824 from Zankaria/inline-dice
Inline dices via markup
2024-10-15 20:40:21 -07:00
Zankaria
b67ff982e2 composer: add dice.php to autoload 2024-10-15 21:14:47 +02:00
Zankaria
fba88643ec config.php: add inline dice rolling markup support to the default configuration 2024-10-15 21:14:47 +02:00
Zankaria
ad1d56d092 dice.php: handle inline dice rolling markup 2024-10-15 21:14:44 +02:00
Zankaria
ceccbfc5b7 config.php: limit the number of dicerolls 2024-10-14 17:46:29 +02:00
Zankaria
b8c53fbbcd dice.php: extract email dice function from functions.php 2024-10-14 12:31:36 +02:00
Zankaria
27e4bd833a config.php: use op-cache friend array syntax for markup config 2024-10-14 12:18:04 +02:00
Zankaria
fe7a667441 style.css: add diceroll styling 2024-10-14 12:13:21 +02:00
Zankaria
115f28807a FsCacheDriver.php: collect expired cache items before operating on the cache 2024-10-10 23:10:05 +02:00
Zankaria
003e8f6d3b maintenance.php: delete expired filesystem cache 2024-10-10 23:10:05 +02:00
Zankaria
243e4894fa Use CacheDriver and Context for mod.php and mod pages 2024-10-10 23:10:05 +02:00
Zankaria
589435b667 context.php: use shared cache driver 2024-10-10 23:10:05 +02:00
Zankaria
f138b4b887 cache.php: wrap new cache drivers 2024-10-10 23:10:05 +02:00
Zankaria
ace2f2e83b driver: break up cache drivers 2024-10-10 23:10:05 +02:00
Zankaria
3d406aeab2 cache-driver.php: move to Data 2024-10-10 23:10:05 +02:00
Zankaria
66d2f90171 cache-driver.php: filesystem handle expired values. 2024-10-10 23:09:55 +02:00
Zankaria
5ea42fa0e2 config.php: update cache documentation 2024-10-10 22:56:56 +02:00
Zankaria
b57d9bfbb3 cache: implement cache locking for filesystem cache and give it multiprocess support 2024-10-10 22:56:56 +02:00
Zankaria
82ea1815fd Refactor cache driver 2024-10-10 22:56:56 +02:00
Lorenzo Yario
7c305f58bf
Merge pull request #822 from Zankaria/fix-captcha-call
post.php: fix invocation of native captcha
2024-10-08 20:12:25 -07:00
Lorenzo Yario
59d0dd9083
Merge pull request #821 from Zankaria/refactor-log
Break up the log subsystem and move it into Data
2024-10-08 20:12:13 -07:00
Lorenzo Yario
f60b4d190f
Merge pull request #789 from perdedora/expand-filename
Original filename changes
2024-10-08 20:11:49 -07:00
Lorenzo Yario
88a81a6d74
Merge pull request #819 from Zankaria/minor-format
Assorted PHP refactoring
2024-10-08 20:11:41 -07:00
Zankaria
3fe44653f2 post.php: fix invocation of native captcha 2024-10-06 13:08:29 +02:00
Zankaria
0e8909ac4e pages.php: fix whitespace indentation 2024-10-06 11:33:20 +02:00
Zankaria
767b8fd8c3 mod.php: add missing context parameter 2024-10-06 11:25:57 +02:00
Zankaria
f9127dd478 mod.php: $matches should always be an array 2024-10-06 11:25:11 +02:00
Zankaria
6c6ec65b02 pages.php: extract $mod array 2024-10-06 11:23:27 +02:00
Zankaria
23f3d15a52 extract create_pm_header 2024-10-06 10:41:32 +02:00
Zankaria
d88d6c814a docker: use shorter name for instance 2024-10-04 20:16:06 +02:00
Zankaria
65a668d3a8 composer.json: use classmap for LogDriver 2024-10-04 20:16:06 +02:00
Zankaria
b501852ea4 config.php: update LogDriver configuration 2024-10-04 20:16:06 +02:00
Zankaria
927a837216 post.php: update LogDriver 2024-10-04 20:16:06 +02:00
Zankaria
79af4b34dd context.php: update LogDriver 2024-10-04 20:16:06 +02:00
Zankaria
36d48951c1 log-driver.php: split up log driver 2024-10-04 20:16:04 +02:00
Zankaria
5dab17e5f4 log-driver.php: move to Data 2024-10-04 13:05:06 +02:00
Lorenzo Yario
c97c61aeca
Merge pull request #820 from Zankaria/http-refactor
Move HttpDriver to Data
2024-10-04 00:00:06 -07:00
Zankaria
59551a2042 composer.json: rely on classmap for HttpDriver 2024-10-04 01:21:21 +02:00
Zankaria
1682272f75 http-driver.php: rename to HttpDriver 2024-10-04 01:13:15 +02:00
Zankaria
3eed312b6b http-driver.php: minor refactor 2024-10-04 01:13:15 +02:00
Zankaria
cae85a6a0c http-driver.php: move to Data 2024-10-04 01:13:12 +02:00
Zankaria
25b2b6bc6e display.php: trim 2024-10-02 22:19:44 +02:00
Lorenzo Yario
7377885de9
Merge pull request #815 from Zankaria/remove-extensions
style.css: remove deprecated extensions
2024-10-02 08:33:02 -07:00
Lorenzo Yario
f421e25e63
Merge pull request #816 from Zankaria/fix-dir-config
Assorted php fixes
2024-10-02 08:32:52 -07:00
Zankaria
3e9ad58e97 config.php: fix mod config array 2024-10-01 23:54:16 +02:00
Zankaria
f4ff39c876 post.php, mod.php: pass the context to check_login 2024-10-01 22:16:54 +02:00
Zankaria
e6133ef00f auth.php: passthrough the context 2024-10-01 22:16:54 +02:00
Zankaria
13ca053e06 config.php: fix default 'dir' configuration 2024-10-01 22:16:54 +02:00
Zankaria
44b31eff0b style.css: remove deprecated extensions 2024-10-01 21:13:36 +02:00
Lorenzo Yario
785643a3bd
Merge pull request #814 from Zankaria/patch-1
docker: use composer LTS
2024-10-01 09:38:02 -07:00
Zankaria
e70c087f5f
docker: use composer LTS 2024-10-01 15:01:03 +02:00
Lorenzo Yario
428a686f47
fix minor exploit with banning 2024-09-21 14:39:13 -07:00
Lorenzo Yario
c95877bdcb
Merge pull request #803 from Zankaria/more-busting
More cache busting with resource_version
2024-09-20 18:45:14 -07:00
Lorenzo Yario
380ae8c675
Merge pull request #811 from Zankaria/main-js-let
Minor JS update
2024-09-20 18:42:01 -07:00
Lorenzo Yario
2f37b3ce51
Merge pull request #812 from Zankaria/anti-bot-format
Format anti-bot.php
2024-09-20 18:41:48 -07:00
Zankaria
65008dec98 anti-bot.php: remove unused variable 2024-09-19 23:13:42 +02:00
Zankaria
0e8aeca4af anti-bot.php: format 2024-09-19 23:09:02 +02:00
Zankaria
bdd7090e75 quote-selection.js: format 2024-09-19 22:58:23 +02:00
Zankaria
fd309443ea js: drop IE support 2024-09-19 22:54:04 +02:00
Zankaria
4332b70363 quote-selection.js: trim 2024-09-19 22:51:31 +02:00
Zankaria
cb6d6f13dd main.js: use let 2024-09-19 22:51:01 +02:00
Zankaria
6b60f841d4 template: supply data-resource-version to main.js 2024-09-19 22:28:55 +02:00
Zankaria
3c5484a7c2 main.js: load styles with dyanamically provided resource version.
This is done because:
 - If the version is updated before the rebuild, someone might cache the old version in the meanwhile.
 - One could not rebuild javascript before updating the version. Now it's possible.
2024-09-19 22:28:52 +02:00
Zankaria
fcf5c3d73a templates: bust all css caches with resource_version 2024-09-19 22:01:51 +02:00
Zankaria
9fbc816205 templates: bust all js caches with resource_version 2024-09-19 22:01:51 +02:00
Lorenzo Yario
bd5c2c61b9
Merge pull request #810 from vichan-devel/revert-809-howwwwww
i jinxed the previous commit
2024-09-19 01:33:18 -07:00
Lorenzo Yario
c1788d0792
Revert "revert trimming and this somehow fixes the antibot issues??????" 2024-09-19 01:32:49 -07:00
Lorenzo Yario
ccfcd03c95
Merge pull request #809 from vichan-devel/howwwwww
revert trimming and this somehow fixes the antibot issues??????
2024-09-19 01:28:18 -07:00
Lorenzo Yario
e1a4ae5336
revert trimming and this somehow fixes the antibot issues??????
idk but it works somehow??????
2024-09-19 01:26:50 -07:00
Lorenzo Yario
1effe1648b
add some spaces 2024-09-17 23:28:21 -07:00
Lorenzo Yario
36737b77a8
made https flag make sense 2024-09-17 22:20:36 -07:00
Lorenzo Yario
a457b905bf
from master branch 2024-09-17 21:44:06 -07:00
Lorenzo Yario
3291dc27f9
Merge pull request #788 from perdedora/patch-747
fixes #747
2024-09-17 21:40:04 -07:00
Lorenzo Yario
8c27b5261c
lazy loading commit 2024-09-17 21:18:09 -07:00
Lorenzo Yario
16bb704154
lazy loading commit 2024-09-17 21:15:43 -07:00
Lorenzo Yario
1db5c788dd
lazy loading 2024-09-17 21:03:05 -07:00
Lorenzo Yario
2b3eae89f1
Merge pull request #805 from perdedora/fix-oversights
Fix oversights
2024-09-17 21:01:53 -07:00
Lorenzo Yario
ce844b9270
Merge pull request #807 from seisatsu/dev
Fix typo in post.php that breaks Tesseract OCR
2024-09-17 21:00:51 -07:00
seisatsu
43b926c41b Fix typo in post.php that breaks Tesseract OCR
Fixes a typo in post.php that causes an uninitialized variable "$value"
to be referenced instead of the correct variable "$txt". This caused all
attempts to use the Tesseract OCR feature to fail with an error.
2024-09-06 15:23:37 -05:00
fowr
eeb55133eb
add missing context file to composer autoload 2024-08-25 17:01:31 -03:00
fowr
cd5c57f717
fix oversights introduced in captcha; 2024-08-25 17:01:06 -03:00
Lorenzo Yario
1672646213
Merge pull request #802 from Zankaria/resouce-ver-fix
Fix a couple of things with PR #801
2024-08-24 22:16:39 -07:00
Zankaria
6ea8fd5bf3 config.php: add missing default configuration, remove db settings 2024-08-21 14:05:29 +02:00
Lorenzo Yario
bf32a24b96
Merge pull request #798 from Zankaria/link-fallback
Add a fallback if link() fails
2024-08-20 23:36:56 -07:00
Lorenzo Yario
187f16693c
Merge pull request #801 from perdedora/resource_version
Resource version
2024-08-20 23:36:38 -07:00
fowr
13b587cf62
add resource_version to avoid cache. 2024-08-20 21:27:22 -03:00
Zankaria
1191dfb193 pages.php: add copy fallback in case of already existing file when moving thread. 2024-08-18 01:06:45 +02:00
Lorenzo Yario
5ee48c5865
Merge pull request #793 from Zankaria/captcha-rework-vichan
Refactor (again) the captcha backend + add support for dynamic captchas
2024-08-16 21:43:31 -07:00
Lorenzo Yario
60135bbb89
Merge pull request #794 from Zankaria/fix-di
Forgot to convert a method call in #784
2024-08-16 21:43:24 -07:00
Lorenzo Yario
39876f3cc7
Merge pull request #795 from Zankaria/api-modern
Modernize (a bit) the Api class
2024-08-16 21:43:17 -07:00
Lorenzo Yario
84a3bedd18
Merge pull request #796 from Zankaria/mod-use-di
Add the context dependency injection container to the mod pages
2024-08-16 21:43:07 -07:00
Lorenzo Yario
90dead0394
Merge pull request #797 from Zankaria/function-themes
Split the theme functions from functions.php
2024-08-16 21:42:43 -07:00
fowr
4f68166870 api.php: using depedency injection instead of globals 2024-08-16 19:26:19 +02:00
Zankaria
d408ed0413 theme.php: rename functions to snake_case 2024-08-16 18:33:02 +02:00
Zankaria
453ae795f5 composer.json: autoload theme.php 2024-08-16 18:24:48 +02:00
Zankaria
b2df2ab2a5 theme.php: extract theme functions from functions.php 2024-08-16 18:24:26 +02:00
Zankaria
81aebef2f4 pages.php: use modern array syntax for new empty arrays 2024-08-15 17:21:41 +02:00
Zankaria
b64bac5eb8 pages.php: use the context to access the configuration array 2024-08-15 17:13:43 +02:00
Zankaria
524ae94624 mod.php: pass the context to the mod pages 2024-08-15 17:13:34 +02:00
Zankaria
19082aec56 mod.php: use modern array syntax 2024-08-15 17:13:27 +02:00
Zankaria
e640217a8f post.php: fix DI method call 2024-08-15 16:39:12 +02:00
Zankaria
165ea5308a config.php: ops, wrong name for the native captcha 2024-08-15 16:33:56 +02:00
Zankaria
f7073d5d7e post.php: do not verify the poster IP if the captcha is dynamic 2024-08-15 16:22:58 +02:00
Zankaria
cb5fb68c5e header.html: format 2024-08-15 16:21:02 +02:00
Zankaria
fb92e5fb68 config.php: rework captcha configuration 2024-08-15 16:20:58 +02:00
Zankaria
a275d04efa captcha-queries.php: refactor NativeCaptchaQuery to use DI 2024-08-15 15:17:54 +02:00
Zankaria
933594194c captcha-queries.php: refactor remote captcha queries to use DI and abstract the implementation better 2024-08-15 15:11:32 +02:00
Zankaria
e825e7aac5 Add dynamic captcha support 2024-08-15 14:37:59 +02:00
fowr
d4b4cf5825
script to expand original filename if truncated 2024-08-12 15:35:16 -03:00
fowr
3e72171889
download original filename without javascript. 2024-08-12 15:34:48 -03:00
fowr
d8391eb34a
fixes #747 and also fix when loading next page on ukko wrong value. e.g >>thread_<cite_id> 2024-08-12 15:23:19 -03:00
Lorenzo Yario
e16dc142b7
Merge pull request #786 from Zankaria/config-opcache
Make configuration sub-arrays more opcache friendly
2024-08-11 17:03:17 -07:00
Lorenzo Yario
420ec4a852
Merge pull request #787 from Zankaria/mod-page
Unify mod page handlers
2024-08-11 17:02:57 -07:00
Zankaria
4d8a4db338 api.php: partially modernize the Api class 2024-08-11 16:14:03 +02:00
Zankaria
d1b06acbe9 mod.php: remove last mod_page_* handler, use only mod_* for mod pages 2024-08-11 15:45:24 +02:00
Zankaria
80be41f47a config.php: make config sub-arrays more opcache friendly 2024-08-11 15:39:21 +02:00
Lorenzo Yario
f3e81c80d9
Merge pull request #784 from Zankaria/micro-di
(What should be a) much better Dependency Injection implementation
2024-08-11 03:05:47 -07:00
Lorenzo Yario
b3ae38da57
Merge pull request #785 from Zankaria/batch-maintenance
Batch maintenance
2024-08-11 03:05:31 -07:00
Zankaria
51e0616eb8 bans.php: fix forgot to extract the mask 2024-08-11 12:04:12 +02:00
Zankaria
7e4acbb6d2 bans.php: FIX every IP matching to any ban that was going to expire eventually 2024-08-11 12:04:12 +02:00
Zankaria
ffe855222e bans.php: do not deserialize post that does not exist 2024-08-11 12:04:12 +02:00
Zankaria
1e0a95ce83 maintenance.php: fix and update logs 2024-08-11 12:04:12 +02:00
Zankaria
c4e3541b15 config.php: purge_bans configuration into the proper section 2024-08-11 12:04:12 +02:00
Zankaria
ede7591702 bans.php: refactor to expose the moratorium on ban deletion from the database.
Also fixes the 'purge_bans' configuration for non-cache deployments.
2024-08-11 12:04:12 +02:00
Zankaria
c057c6df29 functions.php: skip ban deletion on auto_maintenance disabled 2024-08-11 12:04:12 +02:00
Zankaria
4d97e69620 maintenance.php: add purging antispam to the tool 2024-08-11 12:04:12 +02:00
Zankaria
e5bbdb9d28 functions.php: make automated antispam puring optional 2024-08-11 12:04:12 +02:00
Zankaria
cc5e96eb9d bans.php: use modern array syntax 2024-08-11 12:04:12 +02:00
Zankaria
82b8eb1e74 bans.php: group deletion policy 2024-08-11 12:04:12 +02:00
Zankaria
2298d4433f bans.php: use modify inplace 2024-08-11 12:04:12 +02:00
Zankaria
accca93084 Add maintenance.php to the tools 2024-08-11 12:04:12 +02:00
Zankaria
cbaf19cb7a bans.php: make the purge configurable 2024-08-11 12:04:12 +02:00
Zankaria
75714505a0 config.php: add auto_maintenance configuration option 2024-08-11 12:04:10 +02:00
Zankaria
36476f6341 bans.php: split find implementations 2024-08-11 11:53:27 +02:00
Zankaria
980b2ef7bf bans.php: split findSingle implementations 2024-08-11 10:28:08 +02:00
Zankaria
e4707ee2a8 Delete stale unreferenced ban appeals via foreign key constrain 2024-08-11 10:20:02 +02:00
Zankaria
00cc1f434d anti-bot.php: add comments to _create_antibot 2024-08-11 10:17:14 +02:00
Zankaria
ee20bf574a functions.php: format _create_antibot 2024-08-11 10:14:54 +02:00
Zankaria
609da43548 anti-bot.php: trim 2024-08-11 10:13:44 +02:00
Zankaria
0c074016e7 context.php: much better Dependency Injection implementation 2024-08-11 10:06:20 +02:00
Weav
562ad74a12
Merge branch 'vichan-devel:master' into fix-poster-id 2024-08-09 14:16:42 -03:00
fowr
73fce5e571
fix: wrong poster id for op; 2024-08-09 14:10:18 -03:00
Lorenzo Yario
e5d423e595
revert column behavior 2024-08-09 00:43:43 -07:00
Lorenzo Yario
230cc252c5
add changes from #782 2024-08-09 00:39:08 -07:00
Lorenzo Yario
0fbf2f6f77
Merge pull request #781 from Zankaria/main-js-refactor
Refactor main.js
2024-08-08 23:41:29 -07:00
Lorenzo Yario
db20a350a1
Merge pull request #782 from perdedora/fix-cycle
fix: proper delete posts in a cyclical thread
2024-08-08 23:40:49 -07:00
fowr
7f45f31aa8
fix: proper delete posts in a cyclical thread 2024-08-06 11:49:51 -03:00
Zankaria
4445254b00 expand-video.js: onReady 2024-08-05 19:21:09 +02:00
Zankaria
85b03c0fb0 expand-video.js: format 2024-08-05 19:20:55 +02:00
Zankaria
d9a333a69f expand-all-images.js: onReady 2024-08-05 19:15:42 +02:00
Zankaria
e92e9469a8 expand-all-images.js: format 2024-08-05 19:15:26 +02:00
Zankaria
41f9aed606 inline-expanding-filename.js: onReady 2024-08-05 19:14:06 +02:00
Zankaria
5306f1d1f9 inline-expanding-filename.js: format 2024-08-05 19:14:04 +02:00
Zankaria
2749567c3f download-original.js: onReply 2024-08-05 19:11:32 +02:00
Zankaria
44e9a5aa57 download-original.js: format 2024-08-05 19:11:23 +02:00
Zankaria
5550bc4212 catalog-search.js: onReady 2024-08-05 19:09:05 +02:00
Zankaria
d9d05ddbf5 catalog-search.js: format 2024-08-05 19:08:55 +02:00
Zankaria
d9feb5cfa7 catalog.html: onReady 2024-08-05 19:03:24 +02:00
Zankaria
b7f46a239d show-backlinks.js: onReady 2024-08-05 19:01:27 +02:00
Zankaria
2728966c1c show-backlinks.js: format 2024-08-05 19:01:08 +02:00
Zankaria
ed46907a6c post-hover.js: onReady 2024-08-05 19:00:12 +02:00
Zankaria
31444d654a post-hover.js: format 2024-08-05 18:59:44 +02:00
Zankaria
6bea01b00b youtube.js: onReady 2024-08-05 18:50:35 +02:00
Zankaria
023e59d88f youtube.js: format 2024-08-05 18:50:23 +02:00
Zankaria
b822a76b23 style-select.js: onReply 2024-08-05 18:48:04 +02:00
Zankaria
902c558237 style-select.js: format 2024-08-05 18:47:53 +02:00
Zankaria
a018772267 show-backlinks.js: onReady 2024-08-05 18:38:38 +02:00
Zankaria
fa341b29d0 smartphone-spoiler.js: format 2024-08-05 18:38:35 +02:00
Zankaria
6daae3ec92 show-backlinks.js: onReady 2024-08-05 18:35:21 +02:00
Zankaria
da0f26485a show-backlinks.js: trim 2024-08-05 18:34:55 +02:00
Zankaria
8b773b124e main.js: rename onready to camelCase 2024-08-05 18:33:15 +02:00
Zankaria
c327a0439e main.js: rename init_stylechooser to camelCase 2024-08-04 15:52:15 +02:00
Zankaria
4b5e40f575 main.js: format 2024-08-04 15:50:04 +02:00
Zankaria
eb67076e60 main.js: rename dopost to camelCase 2024-08-04 15:47:38 +02:00
Zankaria
990f27e80b main.js: format getCookie 2024-08-04 15:44:12 +02:00
Zankaria
8d3bfedc72 main.js: trim 2024-08-04 15:42:02 +02:00
Lorenzo Yario
d78f865645
Merge pull request #779 from Zankaria/ban-query-truncate
Fix new_ban failing due to the message body being too big
2024-08-03 23:16:11 -07:00
Lorenzo Yario
283973c141
Merge pull request #780 from seisatsu/dev
Add a spam filter that unshortens urls (resubmission)
2024-08-03 23:15:56 -07:00
seisatsu
098edb9cd7 Add a spam filter that unshortens urls 2024-07-31 12:44:39 -05:00
Zankaria
4bc69be4bc bans.php: fix new_ban failing due to the message body being too big 2024-07-27 15:27:44 +02:00
Lorenzo Yario
a20f618d80
Create uboachan-gray.css
#777
2024-07-19 11:30:08 -07:00
Lorenzo Yario
d7468bb93b
Merge pull request #772 from vichan-devel/RealAngeleno-templateupdates
Update catalog and the index: Wildcard and excluding boards
2024-07-14 04:40:09 -07:00
Lorenzo Yario
541f31f4d1
allow excluding boards from the boardlist 2024-07-14 04:38:11 -07:00
Lorenzo Yario
c223b1c55d
allow board exclusion for the boardlist 2024-07-14 04:37:28 -07:00
Lorenzo Yario
409f571955
switch to columns of two. Make it optional for a video and other things. 2024-07-14 04:21:21 -07:00
Lorenzo Yario
d23d1526e8
forgot to remove implode() 2024-07-14 03:41:04 -07:00
Lorenzo Yario
9a80ae2434
make the wildcard the default 2024-07-14 03:38:09 -07:00
Lorenzo Yario
dba38b10d4
allow wildcard for catalog theme 2024-07-14 03:26:06 -07:00
Lorenzo Yario
89a31794d9
fixing and standardizing something i did at like 4am with tripcode disabling 2024-07-04 11:29:18 -07:00
Lorenzo Yario
1a780ce9cb
move are you sure prompt to the js file 2024-06-27 02:18:06 -07:00
Lorenzo Yario
fe8fa0da8a
move to the js file 2024-06-27 02:13:54 -07:00
Lorenzo Yario
e12cbf6d80
Merge pull request #767 from Zankaria/patch-1
docker: remove PEAR leftovers
2024-06-24 17:02:10 -07:00
Lorenzo Yario
0df33b4956
Merge pull request #768 from Zankaria/docker-patch
Improve docker composer
2024-06-24 17:01:56 -07:00
Zankaria
ef1939500c docker: use modern compose file naming scheme 2024-06-23 15:54:21 +02:00
Zankaria
555d14b7ae docker: make the compose local instance folder file parametrizable 2024-06-23 15:53:42 +02:00
Zankaria
281f391205
docker: remove PEAR leftovers 2024-06-23 13:13:45 +02:00
Lorenzo Yario
b21865853b
fix the rest of local-time.js somehow 2024-06-20 20:38:35 -07:00
Lorenzo Yario
5b0a7fb975
Merge pull request #765 from perdedora/hash-passwords
Hash poster passwords
2024-06-20 16:05:55 -07:00
fowr
e9f1d59209
posts.sql: update column password 2024-06-20 10:33:51 -03:00
Weav
fee67b6719
Merge branch 'dev' into hash-passwords 2024-06-20 13:21:05 +00:00
fowr
fff9b88c6d
hash poster passwords 2024-06-20 10:11:47 -03:00
Lorenzo Yario
8d37f1dd2c
Merge pull request #764 from Zankaria/patch-1
install.php: fix https check
2024-06-20 03:40:13 -07:00
Zankaria
991ed657fb
install.php: fix https check 2024-06-15 00:53:49 +02:00
Lorenzo Yario
ad532d1d2d
Merge pull request #762 from vichan-devel/RealAngeleno-patch-12
fix local-time somehow
2024-06-06 00:02:35 -07:00
Lorenzo Yario
d2f1b7e0e0
fix local-time somehow
this fixes it. i don't know how. don't ask. i'm not a javascript guy much.
2024-06-06 00:02:06 -07:00
Lorenzo Yario
2d6b599b26
Merge pull request #761 from vichan-devel/parsedown
Parsedown
2024-06-05 18:49:27 -07:00
Lorenzo Yario
bd7eb130ea
use current website 2024-06-05 18:08:23 -07:00
Lorenzo Yario
f852172e9b
no they're not 2024-06-05 18:05:43 -07:00
Lorenzo Yario
809ab99c9b
allow images for markdown 2024-06-05 18:02:08 -07:00
Lorenzo Yario
5c99c8395e
add parsedown 2024-06-05 18:00:24 -07:00
Lorenzo Yario
878a67389a
Merge pull request #758 from Zankaria/refactor-ban-log
Refactor ban log
2024-05-22 23:15:30 -07:00
Lorenzo Yario
d990c344d4
Merge pull request #759 from Zankaria/fake-copy
Use hardlinks instead of copying the file
2024-05-22 23:14:49 -07:00
Zankaria
88a48befd4 pages.php: use link to create hardlinks instead of full file copy for thread moving 2024-05-23 00:05:43 +02:00
Zankaria
7e4dd5567b bans.php: always print modLog 2024-05-22 22:14:38 +02:00
Zankaria
8963ebfce9 bans.php: simplify modLog string 2024-05-22 22:14:08 +02:00
Lorenzo Yario
9236e10f37
add ) 2024-05-20 04:32:30 -07:00
Lorenzo Yario
9aaed32c57
Merge pull request #752 from vichan-devel/RealAngeleno-cattychanges
remove redundant second catalog button on catalog-link.js
2024-05-18 03:26:03 -07:00
Lorenzo Yario
055d31d2db
Merge pull request #749 from vichan-devel/RealAngeleno-datetime
fix bug with datetime
2024-05-17 19:03:51 -07:00
Lorenzo Yario
de39780194
Merge pull request #753 from Zankaria/fix-install
Fix HTTPS check
2024-05-17 15:05:48 -07:00
Zankaria
37658f1817 install.php: fix HTTPS check 2024-05-17 14:13:40 +02:00
Lorenzo Yario
00be5e6ced
remove redundant second catalog button 2024-05-17 00:51:24 -07:00
Lorenzo Yario
605f198d8c
fix bug with datetime 2024-05-16 22:51:20 -07:00
Lorenzo Yario
3d797c95ca
The https check doesn't work properly if we run it inside the array. 2024-05-16 22:12:12 -07:00
Lorenzo Yario
566b04f94e
add a message for those not using https 2024-05-16 21:26:36 -07:00
Lorenzo Yario
7899476323
Merge pull request #746 from Zankaria/secure-login-triple
Rework secure_login_only configuration option to allow secure default and header checking
2024-05-16 00:55:16 -07:00
Zankaria
d700aa0522 Rework secure_login_only configuration option to allow secure default and header checking 2024-05-11 16:02:15 +02:00
Lorenzo Yario
feb2860d9e
Merge pull request #745 from Zankaria/fix-deprecations
Fix more deprecations
2024-05-11 06:57:43 -07:00
Zankaria
9f46f0fdd4 functions.php: do not fail fail_unlink if the file does not exist 2024-05-11 15:38:17 +02:00
fowr
ba2daa88e6 fix conversion of false to array 2024-05-11 15:38:17 +02:00
Lorenzo Yario
519dd27221
but we're using datetime 2024-05-11 05:03:35 -07:00
Lorenzo Yario
cc55563411
let's continue to use current date/time formatting 2024-05-11 04:58:59 -07:00
Lorenzo Yario
de91423a9f
Merge pull request #744 from vichan-devel/RealAngeleno-httpsonly
modify how https only works, disabling by default and allowing cloudflare.
2024-05-11 04:46:58 -07:00
Lorenzo Yario
f9c54dbbbe
removed redundant message 2024-05-11 04:46:08 -07:00
Lorenzo Yario
273722dc7e
set to false by default. this'll probably become true eventually though 2024-05-11 04:45:39 -07:00
Lorenzo Yario
091c0442e8
allow cloudflare and other proxies to still count as https 2024-05-11 04:42:45 -07:00
Lorenzo Yario
b2f246abb0
Merge pull request #743 from Zankaria/fix-deprecations
Fix deprecations
2024-05-11 04:38:59 -07:00
Zankaria
96bebe8c79 post.php: fix broken JS cookie setting 2024-05-11 12:44:16 +02:00
Zankaria
827373819f docker: add compose documentation notes 2024-05-11 12:34:41 +02:00
fowr
ffa5c018e7 functions.php: add missing global 2024-05-11 12:24:50 +02:00
fowr
1a59e663c6 anti-bot.php: fix implicit conversion from float 2024-05-11 12:21:59 +02:00
fowr
010ab2bf62 post.php: add default exif_stripped 2024-05-11 12:19:42 +02:00
Zankaria
faa43cb8a6 image.php: do not delete moved images 2024-05-11 12:05:10 +02:00
Zankaria
dd224cea58 functions.php: handle null body in remove_modifiers 2024-05-11 11:48:01 +02:00
Zankaria
4c731ba241 auth.php: check if cookie exists 2024-05-11 00:51:02 +02:00
Zankaria
ed05049777 functions.php: fix null parameter 2024-05-11 00:51:02 +02:00
Lorenzo Yario
49d815c2ee
Merge pull request #742 from Zankaria/config-reenable-syslog
config.php: reenable syslog default
2024-05-10 15:41:18 -07:00
Lorenzo Yario
e5dc2cd5ce
Merge pull request #741 from Zankaria/fix-login
Fix the login
2024-05-10 15:41:02 -07:00
Zankaria
44fd0dfb82 config.php: reenable syslog default 2024-05-11 00:16:18 +02:00
Zankaria
b90d6f5680 Fix broken login 2024-05-11 00:04:20 +02:00
Lorenzo Yario
8170e226ec
we don't need vichan to require two versions of php. removed redundant 5.6. 2024-05-09 23:30:07 -07:00
Lorenzo Yario
a7cb62349c
Merge pull request #739 from vichan-devel/RealAngeleno-searchexclusion
Allow board exclusion for search
2024-05-09 23:22:55 -07:00
Lorenzo Yario
21cbdfef04
change the search exclusion error to just be a blacklist via server side verification 2024-05-09 23:19:50 -07:00
Lorenzo Yario
ad653af082
allow excluding searches from boards 2024-05-09 23:06:45 -07:00
Lorenzo Yario
2fa3b3c93e
allow exclusion of boards 2024-05-09 23:00:51 -07:00
Lorenzo Yario
021e20f373
Merge pull request #714 from Zankaria/fix-string-interning
Fix: substitute deprecated string interning syntax
2024-05-09 22:48:10 -07:00
Lorenzo Yario
34a3dcac24
Merge pull request #713 from Zankaria/update-template-datetime
Update template datetime
2024-05-09 22:47:52 -07:00
Lorenzo Yario
b1bdb1222a
Add files via upload 2024-05-08 18:53:29 -07:00
Lorenzo Yario
b5182a1864
Merge pull request #738 from vichan-devel/disabletripcodes
Disabletripcodes
2024-05-08 18:48:09 -07:00
Lorenzo Yario
82881fe512
disabling tripcodes 2024-05-08 18:34:35 -07:00
Lorenzo Yario
b0e6580845
Merge pull request #736 from papereth/patch-1
Fix Exif leak in JPEG orientation conversion code
2024-05-08 17:44:48 -07:00
papereth
0dd064b2ea
Fix Exif leak in JPEG orientation conversion code
https://github.com/vichan-devel/vichan/issues/735
2024-05-04 15:01:17 +01:00
Lorenzo Yario
a7658fe3c2
allow tripcodes to be disabled 2024-05-03 15:54:09 -07:00
Lorenzo Yario
ae72ed42ad
Merge pull request #733 from Zankaria/improve-auth
Improve authentication security
2024-05-03 14:00:59 -07:00
Lorenzo Yario
12bcc63753
Merge branch 'dev' into improve-auth 2024-05-03 14:00:50 -07:00
Lorenzo Yario
67fbd90f0d
Merge pull request #734 from Zankaria/refactor-functions2
(Partial) refactor of functions.php
2024-05-03 13:57:16 -07:00
Zankaria
1b6d6f38f1 auth.php: add typing 2024-04-30 19:47:31 +02:00
Zankaria
da4842eb7b auth.php: disallow unencrypted logins by default 2024-04-30 19:45:15 +02:00
Zankaria
0c51d46cdf auth.php: check if the cookie is set before deletion 2024-04-30 19:45:15 +02:00
Zankaria
9db8444c3c auth.php: use secured names and directives for mod cookies 2024-04-30 19:44:11 +02:00
Zankaria
abdf82e1c8 auth.php: remove obsolete code 2024-04-30 19:44:11 +02:00
Zankaria
5a378dd605 config.php: reduce default login cookie expire timeout 2024-04-30 19:42:01 +02:00
Zankaria
5bb1202def Add num.php to autoloader 2024-04-30 15:57:02 +02:00
Zankaria
f6960b8b3a functions.php: format and trim 2024-04-30 15:57:02 +02:00
Zankaria
7041cd13df functions: split off numeric functions 2024-04-30 15:56:59 +02:00
Zankaria
687e8e078a Add format.php to autoload 2024-04-30 15:56:56 +02:00
Zankaria
b7eed34b83 functions: split off time formatting functions 2024-04-30 15:56:53 +02:00
Zankaria
39ce0e7dfc auth.php: trim 2024-04-30 11:36:11 +02:00
Lorenzo Yario
6cd11d546f
undid to do a pr for it 2024-04-29 15:20:17 -07:00
Lorenzo Yario
4674f44327
soyjak upstream for api changes 2024-04-29 15:18:54 -07:00
Lorenzo Yario
4cfb1ec576
Merge pull request #729 from Zankaria/improve-social-media-cards
Improve social media cards
2024-04-28 13:34:53 -07:00
Lorenzo Yario
1df9d2d84f
Merge pull request #730 from Zankaria/move-twig-cache
Move twig template cache
2024-04-28 13:34:24 -07:00
Lorenzo Yario
51aa6e3813
Merge pull request #731 from Zankaria/improve-mobile-user-options
Limit user options panel width on mobile
2024-04-28 13:34:14 -07:00
Zankaria
af06d84094 style.css: limit user options panel width on mobile 2024-04-27 22:21:20 +02:00
Zankaria
ebbb5fce53 Move twig template cache 2024-04-26 14:03:57 +02:00
discomrade
735180cf54 Improve social media cards 2024-04-24 17:22:39 +02:00
Zankaria
4bb0f40cd1 index.html: trim 2024-04-24 17:01:38 +02:00
Lorenzo Yario
7025035376
Merge pull request #728 from vichan-devel/RealAngeleno-patch-5
bugfix for ban appeals page
2024-04-19 12:04:09 -07:00
Lorenzo Yario
85471e007e
vichan likes to error on the ban appeals page if this isn't set this way 2024-04-19 12:03:49 -07:00
Lorenzo Yario
19fa804116
Merge pull request #721 from Zankaria/dockerize
Dockerize Vichan
2024-04-19 01:04:16 -07:00
Zankaria
2836ace551 readme: add basic docker documentation in the README 2024-04-18 23:44:09 +02:00
Zankaria
9d7ddd46c5 dokcer: cache webp images files in nginx 2024-04-18 23:33:47 +02:00
Zankaria
cbb1571063 docker: remove duplicated nginx configuration 2024-04-18 23:32:48 +02:00
Lorenzo Yario
0278ce16be
Merge pull request #720 from Zankaria/fix-installation
Better error SQL reporting on installation
2024-04-18 14:11:10 -07:00
Lorenzo Yario
004481b920
Merge pull request #723 from Zankaria/improve-context-init
Improve Context initialization
2024-04-18 14:10:59 -07:00
Lorenzo Yario
73a83e5dc9
Merge pull request #725 from Zankaria/banners-redirect
Make banner access redirect instead of serving the image directly
2024-04-18 14:10:48 -07:00
Lorenzo Yario
a19e4cc755
Merge pull request #726 from Zankaria/post-filter-by-flag
Filter posts by flag
2024-04-18 14:10:23 -07:00
Lorenzo Yario
3175f3627d
Merge pull request #724 from Zankaria/fix-hcaptcha
Fix HCaptcha
2024-04-18 14:09:55 -07:00
discomrade
df07515397 Enable filtering by flag name 2024-04-17 10:16:27 +02:00
Zankaria
0428c35684 b.php: make banners redirect instead of serving the image directly 2024-04-17 10:15:55 +02:00
Zankaria
b86b9e375d post.php: fix hcaptcha reading wrong post field 2024-04-16 22:39:32 +02:00
Zankaria
c3619c49fb docker: prepare compose for multiple test instances 2024-04-16 22:32:41 +02:00
Zankaria
024f955338 docker: remove special handling of instance-config.php from build 2024-04-16 22:32:41 +02:00
Zankaria
bf4f388a04 docker: make non-profile the default compose setting 2024-04-16 22:32:41 +02:00
Zankaria
f161de3d57 docker: format compose file 2024-04-16 22:32:41 +02:00
Zankaria
107592f70c docker: simplify composer file 2024-04-16 22:32:41 +02:00
Zankaria
fbbdb5afd6 docker: fix variable checking in bootstrapping script 2024-04-16 22:32:41 +02:00
Zankaria
5c99b0f4f3 docker: enable profiling with xdebug 2024-04-16 22:32:41 +02:00
Zankaria
fb191a0ffd docker: change compose database root password 2024-04-16 22:32:41 +02:00
Zankaria
575b265c74 docker: adjust php-fpm pool log config on compose 2024-04-16 22:32:41 +02:00
Zankaria
050bc59588 docker: enable JIT by default on compose 2024-04-16 22:32:41 +02:00
Zankaria
f8ea32376e docker: remove leftchan references from compose 2024-04-16 22:32:41 +02:00
Zankaria
5a5d315330 docker: remove leftypol branding from nginx compose 2024-04-16 22:32:41 +02:00
Zankaria
729219a3c4 docker: bootstrap remove leftypol specific files 2024-04-16 22:32:41 +02:00
Zankaria
d117619ce6 docker: boostrap script handle secrets.php, copy static files 2024-04-16 22:32:41 +02:00
Zankaria
5bdbe49f38 docker: move image to alpine linux 2024-04-16 22:32:41 +02:00
Zankaria
711e824153 docker: remove lainchan branding from compose file 2024-04-16 22:32:41 +02:00
Zankaria
9d9804db13 docker: compose mount local-www as root directory in nginx 2024-04-16 22:32:41 +02:00
Zankaria
79183ae8e6 docker: handle secrets.php 2024-04-16 22:32:41 +02:00
Zankaria
eb01768191 docker: use less used port for compose 2024-04-16 22:32:41 +02:00
Zankaria
c058ec12f9 Remove template/cache directory 2024-04-16 22:32:41 +02:00
Zankaria
1fed05c5ee docker: ignore empty gitkeep directories 2024-04-16 22:32:41 +02:00
Zankaria
2af07d006b docker: create empty robots.txt 2024-04-16 22:32:41 +02:00
Zankaria
8799c142b0 install.php: check that secrets.php is writable 2024-04-16 22:32:41 +02:00
Zankaria
3de9fa24dd template.php install.php: handle cache directory being a symlink 2024-04-16 22:32:41 +02:00
Zankaria
efdf93e3dd template.php: trim 2024-04-16 22:32:41 +02:00
Zankaria
7aca69125f docker: extract the vichan directory and make it optionally exposable 2024-04-16 22:32:41 +02:00
Zankaria
b1b28dcb90 docker-compose: use local www root 2024-04-16 22:32:41 +02:00
Zankaria
bd3bf7e4f8 docker: reduce file permissions 2024-04-16 22:32:41 +02:00
Zankaria
7979404c1b docker: change user to www-data 2024-04-16 22:32:41 +02:00
Zankaria
d55961995c docker: change the work directory to /var/www 2024-04-16 22:32:41 +02:00
Zankaria
482962844a docker: fix missing tmp directory 2024-04-16 22:32:41 +02:00
Zankaria
cc8e458906 docker: split up application and dependency layers 2024-04-16 22:32:41 +02:00
Zankaria
1d385ad6aa docker: remove "meaningless" bits from nginx configuration 2024-04-16 22:32:41 +02:00
Zankaria
62f0e9894c docker: trim PHP configuration 2024-04-16 22:32:41 +02:00
zeke
7ac0460b0a Apply suggested changes 2024-04-16 22:32:41 +02:00
zeke
460aee0dad Remove unused Dockerfile 2024-04-16 22:32:41 +02:00
zeke
03228eb87d Fix user and group creation for docker containers 2024-04-16 22:32:41 +02:00
zeke
f3c7bad9cd Remove extension lines (added by docker-php-ext-enable) 2024-04-16 22:32:41 +02:00
zeke
fc63de4ed4 Use two Dockerfiles, move Docker stuff to /docker 2024-04-16 22:32:41 +02:00
zeke
5ca2d194b4 Update dependencies for Docker image 2024-04-16 22:32:41 +02:00
Zankaria
90235b2bab docker: remove unused configuration 2024-04-16 22:32:41 +02:00
Barbara Pitt
3a769b437f adding docker items 2024-04-16 22:32:41 +02:00
Zankaria
8389c399bd Context: shorter lazy initialization 2024-04-16 22:30:37 +02:00
Lorenzo Yario
73f2e3222b
Merge pull request #722 from Zankaria/dep-inj-factory
Splits the dependecy container from the dependency building
2024-04-09 05:37:21 -05:00
Zankaria
a83bcf14a4 Context: simplify lazy initialization 2024-04-09 11:43:46 +02:00
Zankaria
f93dd1fae5 Context: extract dependency building from the container 2024-04-09 11:33:06 +02:00
Zankaria
fc716fd0a8 install.php: better SQL error reporting 2024-04-07 19:59:08 +02:00
Lorenzo Yario
3c49645af0
Merge pull request #717 from Zankaria/dep-inj-logging
Add logger by dependency injection
2024-04-04 19:43:46 -07:00
Lorenzo Yario
1109ef3ba6
Merge pull request #718 from Zankaria/fix-deprecation
Fix deprecations in post.php
2024-04-04 19:43:33 -07:00
Zankaria
710f6aa6c2 post.php: use logger 2024-04-04 12:44:37 +02:00
Zankaria
55cb6bc400 context.php: add log driver 2024-04-04 12:44:37 +02:00
Zankaria
f05c290b67 log-driver.php: autoload the logging dirver 2024-04-04 12:44:37 +02:00
Zankaria
28395d55e5 Refactor the logging system 2024-04-04 12:44:37 +02:00
Zankaria
4d9d68b550 Substitute deprecated php functions calls 2024-04-04 11:09:43 +02:00
Lorenzo Yario
5680d73f08
Merge pull request #716 from Zankaria/adjust-image-stripping
Enhance image metadata ops
2024-04-03 14:23:39 -07:00
Zankaria
eaacf27199 post.php: do not strip orientation metadata 2024-04-03 21:38:16 +02:00
Zankaria
6ceee8261e post.php: strip and reorient png and webp images 2024-04-03 21:34:51 +02:00
Zankaria
2bb1b0b9d4 post.php: remove double check 2024-04-03 21:24:10 +02:00
Zankaria
9072ce5992 post.php: strip metadata from png and webp image file types 2024-04-03 21:23:48 +02:00
Lorenzo Yario
98650ec2e7
Merge pull request #677 from Zankaria/refactor-queue
Refactor queue
2024-04-03 12:17:50 -07:00
Lorenzo Yario
99d55ca2b5
Merge pull request #711 from Zankaria/add-dep-injection
Fixed share REST call code and separate components via dependency injection
2024-04-03 12:17:33 -07:00
Lorenzo Yario
59b763fd53
Merge pull request #715 from vichan-devel/revert-668-remove-check-updates
leno merges another commit while half asleep
2024-04-03 12:16:56 -07:00
Lorenzo Yario
429ae8e352
Revert "Remove check-updates functionality" 2024-04-03 12:15:06 -07:00
Lorenzo Yario
e811a9deeb
Merge pull request #668 from Zankaria/remove-check-updates
Remove check-updates functionality
2024-04-03 12:14:51 -07:00
Zankaria
c91c58ed07 Fix: substitute deprecated string interning syntax 2024-04-03 19:18:41 +02:00
Zankaria
99133c90fa config.php: fix date time diplay documentation 2024-04-03 17:38:12 +02:00
Zankaria
4ffe91e384 templates: substitute deprecated functions 2024-04-03 17:33:53 +02:00
Zankaria
1fa60e7386 index.hmtl: trim template 2024-04-03 17:33:53 +02:00
Zankaria
7479360aad catalog.html: format template 2024-04-03 17:33:48 +02:00
Zankaria
9a014e8557 template.php: fix deprecated string interning syntax 2024-04-03 17:33:48 +02:00
Zankaria
542c9f3342 Remove check-updates functionality.
It relies on the defunct vichan.net website, no point keeping it around.
2024-04-03 14:38:26 +02:00
Zankaria
f47332cdff Allow queue push to fail gracefully 2024-04-03 14:34:43 +02:00
Zankaria
09dc44ec40 functions.php: trim 2024-04-03 14:34:42 +02:00
Zankaria
e61ed35aa0 Refactor queue.php 2024-04-03 14:33:51 +02:00
Zankaria
55034762b0 Format queue.php 2024-04-03 14:33:51 +02:00
Zankaria
760431606d Refactor lock.php 2024-04-03 14:33:51 +02:00
Zankaria
00b05099f3 Format lock.php 2024-04-03 14:33:51 +02:00
Zankaria
8120c42440 Format injected javascript on failed native captcha 2024-04-03 14:33:38 +02:00
Zankaria
650ef8bcc2 Fix crash if no captcha is enabled 2024-04-03 14:33:38 +02:00
Zankaria
3016d69428 Share REST call code and separate components via dependency injection 2024-04-03 14:33:38 +02:00
Lorenzo Yario
c3de90075e
Merge pull request #712 from Zankaria/fix-bad-report
Handle reporting non-existing posts.
2024-04-02 03:41:21 -07:00
Zankaria
03a875f663 Handle reporting non-existing posts. 2024-04-01 19:55:32 +02:00
Lorenzo Yario
74c2aec338
Merge pull request #708 from vichan-devel/revert-707-revert-704-revert-688-add-dep-injection
reverting one last time because of errors in my test env
2024-03-30 22:47:55 -07:00
Lorenzo Yario
95153233eb
Revert "Revert "Revert "Share REST call code and separate components via dependency injection""" 2024-03-30 22:46:35 -07:00
Lorenzo Yario
be3437e452
Merge pull request #707 from vichan-devel/revert-704-revert-688-add-dep-injection
Revert "Revert "Share REST call code and separate components via dependency injection""
2024-03-30 22:34:42 -07:00
Lorenzo Yario
67475c2c2f
Revert "Revert "Share REST call code and separate components via dependency injection"" 2024-03-30 22:34:30 -07:00
Lorenzo Yario
4094437464
Merge pull request #706 from vichan-devel/revert-705-revert-666-fix-composer
Revert "Revert "Fix composer.json platform""
2024-03-30 22:33:44 -07:00
Lorenzo Yario
d0c0d272af
Revert "Revert "Fix composer.json platform"" 2024-03-30 22:32:45 -07:00
Lorenzo Yario
2fa28510b9
Merge pull request #705 from vichan-devel/revert-666-fix-composer
Revert "Fix composer.json platform"
2024-03-30 22:31:35 -07:00
Lorenzo Yario
24708dec7e
Revert "Fix composer.json platform" 2024-03-30 22:31:04 -07:00
Lorenzo Yario
06ea4898d4
Merge pull request #704 from vichan-devel/revert-688-add-dep-injection
Revert "Share REST call code and separate components via dependency injection"
2024-03-30 22:25:25 -07:00
Lorenzo Yario
f5328a436c
Revert "Share REST call code and separate components via dependency injection" 2024-03-30 22:24:20 -07:00
Lorenzo Yario
3d2f0b6a1b
Merge pull request #688 from Zankaria/add-dep-injection
Share REST call code and separate components via dependency injection
2024-03-30 22:15:44 -07:00
Lorenzo Yario
45f476084e
Merge pull request #693 from Zankaria/premade-ban-reasons
Add premade ban reasons
2024-03-30 22:15:20 -07:00
Lorenzo Yario
38b47a0844
Merge pull request #698 from Zankaria/calc-file-size-after-strip
Calculate the file size after stripping the metadata off an image.
2024-03-30 22:15:10 -07:00
Lorenzo Yario
afe7c73195
Merge pull request #699 from Zankaria/remove-no-op
Remove no-op lines from post.php
2024-03-30 22:14:58 -07:00
Lorenzo Yario
55984b4260
Merge pull request #700 from Zankaria/optimize-ban-queries
Optimize Bans queries
2024-03-30 22:14:47 -07:00
Lorenzo Yario
b10ca57da1
Merge pull request #666 from Zankaria/fix-composer
Fix composer.json platform
2024-03-30 21:21:30 -07:00
Lorenzo Yario
c5ca05185f
Merge pull request #702 from Zankaria/show-ban-id2
Show ban id on ban page.
2024-03-27 18:10:52 -07:00
Zankaria
d4bcc34d6d Add ban id to ban page. 2024-03-26 15:29:20 +01:00
Zankaria
b739a8e846 banned.html: trim 2024-03-26 15:26:55 +01:00
Zankaria
018da09ac5 Fix CaptchaQueries naming 2024-03-26 14:16:19 +01:00
Zankaria
3e3fa057d5 bans.php: rebuild themes only if bans are deleted 2024-03-22 10:59:22 +01:00
Zankaria
67b88ec78f bans.php: group deletions 2024-03-22 10:59:13 +01:00
Zankaria
a2eb3d9053 bans.php: trim 2024-03-22 10:57:10 +01:00
Zankaria
27ddd5158d psot.php: remove no-op lines 2024-03-21 16:15:49 +01:00
Zankaria
e455080d42 post.php: refactor image metadata stripping into function 2024-03-21 16:02:09 +01:00
discomrade
0cefa9353b Recalculate filesize after stripping metadata 2024-03-21 15:42:48 +01:00
Zankaria
af8240e166 Autoload CaptchaQueries 2024-03-21 15:14:13 +01:00
Zankaria
dc903017ca Add CaptchaQueries 2024-03-21 15:14:13 +01:00
Zankaria
819ce71c55 Autload HttpDriver 2024-03-21 15:14:13 +01:00
Zankaria
2cad97775a post.php: use HttpDriver for remote file download 2024-03-21 15:14:13 +01:00
Zankaria
c20b0ba6f0 Add context dependency container 2024-03-21 15:14:13 +01:00
Zankaria
3dc239e204 http-driver: refactor 2024-03-21 15:14:13 +01:00
Zankaria
65cebe9bd5 http-driver: add file size limit and timeout support 2024-03-21 15:14:13 +01:00
Zankaria
2a228e8ee4 Add http curl driver 2024-03-21 15:14:13 +01:00
Lorenzo Yario
0c3159673d
Merge pull request #692 from Zankaria/simple-antispam
Prettier simple-antispam documentation
2024-03-16 15:39:57 -07:00
Lorenzo Yario
70410e5e61
Merge pull request #695 from Zankaria/post-delete-ip-modlog
Report in the modlog the IP that deleted their own post
2024-03-15 13:23:08 -07:00
Lorenzo Yario
32d3573e56
kept since it sounds better 2024-03-15 13:22:45 -07:00
Lorenzo Yario
3c23e01dab
Merge pull request #696 from Zankaria/ban-appeals-limits
Add maximum length of ban appeal
2024-03-15 09:48:25 -07:00
Lorenzo Yario
44684496c7
Merge pull request #697 from Zankaria/report-limits
Limit the report length
2024-03-15 09:47:06 -07:00
Zankaria
2a8b69fa77 Add maximum report length 2024-03-15 16:37:59 +01:00
vholmes
61cdca7207 Prevents reports with too many characters 2024-03-15 16:34:05 +01:00
Zankaria
d5109426d9 Prettier simple-antispam 2024-03-15 16:08:45 +01:00
discomrade
e80549a881 Enforce maximum length of ban appeal 2024-03-15 16:02:35 +01:00
Zankaria
caa05e41a2 Add IPs of users deleting their own post to the mod log 2024-03-15 12:02:33 +01:00
discomrade
43973862dd Added Feature - Premade Ban Reasons
see 13ac9172e7
2024-03-14 15:55:49 +01:00
Lorenzo Yario
3ac86a074a
Merge pull request #687 from Zankaria/trim-functions
Trim the whitespace in functions.php
2024-03-14 06:12:42 -07:00
Lorenzo Yario
9841102494
Merge pull request #691 from vichan-devel/simplespamfix
Simplespamfix
2024-03-14 06:11:58 -07:00
Lorenzo Yario
f48cf068d6
make case-insensitive 2024-03-14 06:10:18 -07:00
Lorenzo Yario
954bb08762
disable by default, like other captchas 2024-03-14 06:06:28 -07:00
Lorenzo Yario
28df28de26
add some more info about this as this is basically questycaptcha 2024-03-14 06:05:43 -07:00
Lorenzo Yario
33534b5856
fix critical typo for simple spam 2024-03-14 05:58:23 -07:00
Lorenzo Yario
e3ae4aee80
Merge pull request #689 from Zankaria/faster-delete
Speed up page builds on delete
2024-03-14 05:22:34 -07:00
Lorenzo Yario
032a6f42e3
Merge pull request #690 from Zankaria/simple-antispam
Add simple antispam
2024-03-14 05:22:22 -07:00
Zankaria
e2d771239c Critically enhance the simple antispam mechanism 2024-03-14 12:50:19 +01:00
Your Name
6dabfa08bc Simple anti-spam mechanism 2024-03-14 12:50:19 +01:00
nonmakina
fff3e11c48 use more cache. Speeds up page builds (#72)
use more cache. Speeds up page builds.
Tested as much as reasonably possible. Works well.
cuts self delete from ~8 seconds to ~1 second. and mod delete from 10-14 seconds to ~4 seconds.
Co-authored-by: nonmakina <nonmakina@leftypol.org>
Co-committed-by: nonmakina <nonmakina@leftypol.org>
2024-03-14 12:28:02 +01:00
Zankaria
b25cffe12e functions.php: trim 2024-03-14 10:01:13 +01:00
Lorenzo Yario
0296301fff
use the current domain 2024-03-11 18:54:20 -07:00
Lorenzo Yario
e855d5213b
Update README.md 2024-03-11 16:17:02 -07:00
Lorenzo Yario
ab06e62266
Merge pull request #684 from Zankaria/fix-bad-download-parameter
post.php: fix bad call to download_file_into with wrong argument
2024-03-11 16:13:37 -07:00
Lorenzo Yario
9aba8f35b6
Merge pull request #686 from Zankaria/refactor-post-ocr
post.php: refactor image OCR into function
2024-03-11 16:13:26 -07:00
Lorenzo Yario
4e707ab9ae
Merge pull request #685 from Zankaria/remove-cache-backends
Remove cache backends
2024-03-11 15:58:35 -07:00
Zankaria
2f88d0809f Remove APC support 2024-03-11 10:24:35 +01:00
Zankaria
0154e54f85 Remove xcache support 2024-03-11 10:24:35 +01:00
Zankaria
8cebc7f80c mod.php: trim 2024-03-11 10:24:35 +01:00
Zankaria
575cb856a5 install.php: trim 2024-03-11 10:24:35 +01:00
Zankaria
6715991cdd README: trim 2024-03-11 10:24:35 +01:00
Zankaria
58134f8cde cache.php: trim 2024-03-11 10:24:35 +01:00
Zankaria
f683c72c39 config.php: trim 2024-03-11 10:24:30 +01:00
Lorenzo Yario
557b183d36
Merge pull request #675 from Zankaria/remove-manual-orient-2
Remove 'convert_manual_orient' functionality.
2024-03-11 00:00:58 -07:00
Zankaria
9ae988dd6f post.php: refactor image OCR into function 2024-03-08 10:24:02 +01:00
Zankaria
8978bb5442 post.php: fix bad call to download_file_into with wrong argument 2024-03-07 12:37:17 +01:00
Lorenzo Yario
4439f1736a
Merge pull request #673 from Zankaria/remove-remote
Remove remote server support
2024-03-05 15:04:08 -08:00
Lorenzo Yario
59c9e1771a
Merge pull request #682 from Zankaria/appeal-count
Show ban appeal count in bold on mod dashboard
2024-02-26 19:41:40 -08:00
discomrade
1ed75a58c2 Show ban appeal count on mod dashboard 2024-02-23 22:59:32 +01:00
Zankaria
f7a8cb258f dashboard template: trim 2024-02-23 22:59:32 +01:00
Zankaria
f317691dcd mod pages: trim 2024-02-23 22:59:32 +01:00
Lorenzo Yario
350060e7a6
Merge pull request #681 from vichan-devel/innodb
Innodb
2024-02-23 10:31:46 -08:00
Lorenzo Yario
7e19026be0
we don't need myisam 2024-02-23 10:31:25 -08:00
Lorenzo Yario
1b539ae40e
innodb 2024-02-23 09:19:54 -08:00
Lorenzo Yario
3f529db5e4
wrong branch. oops. 2024-02-23 09:19:23 -08:00
Lorenzo Yario
0c6f7c8237
innodb 2024-02-23 09:18:50 -08:00
Lorenzo Yario
492da02b53
change to innodb 2024-02-23 09:17:25 -08:00
Lorenzo Yario
3d05ba0247
Merge pull request #678 from Zankaria/format-filter
Format filter
2024-02-23 09:15:13 -08:00
Lorenzo Yario
543d5e1757
make it harder to accidentally unban everyone 2024-02-22 18:40:37 -08:00
Lorenzo Yario
4bda535c66
Merge pull request #679 from Zankaria/remove-captcha-sql
Remove obsolete dbschema.sql
2024-02-20 01:26:00 -08:00
Zankaria
cb7a6ecf45 Remove obsolete dbschema.sql 2024-02-19 15:48:56 +01:00
Zankaria
db7d51670e Format filter 2024-02-16 16:01:01 +01:00
Zankaria
24ebabb976 Remove 'convert_manual_orient' functionality.
It was supposed to help in case the user had an unspecified imagemagik version that was old in 2013...
2024-02-14 23:34:08 +01:00
Zankaria
dd5c25f99c Remove remote server support
The functionality was marked as "I'm not even sure if this code works anymore" 11 years ago...
2024-02-14 23:00:52 +01:00
Lorenzo Yario
e52c996a56
Merge pull request #669 from Zankaria/remove-ban-php
Remove ban.php
2024-02-14 03:44:38 -08:00
Lorenzo Yario
421e7ff283
Merge pull request #671 from vichan-devel/webpsupport
add webp support to vichan

exact changes are on soyjak.party and work
2024-02-14 03:19:19 -08:00
Lorenzo Yario
2a0c495b59
webp support 2024-02-14 03:15:21 -08:00
Lorenzo Yario
acc284004f
Merge pull request #670 from Zankaria/remove-polyfill
Remove polyfills for php 5.4 and 5.6
2024-02-14 03:13:55 -08:00
Lorenzo Yario
23670bae4f
webp support by default 2024-02-14 03:12:59 -08:00
Zankaria
6cf8c393b5 Remove polyfills for php 5.4 and 5.6 2024-02-14 11:24:40 +01:00
Zankaria
fde425111c Remove bans.php 2024-02-14 11:12:42 +01:00
Lorenzo Yario
77bec421d9
webp 2024-02-14 01:18:45 -08:00
Lorenzo Yario
0e2795d355
Merge pull request #665 from Zankaria/remove-bmp
Remove the custom BMP polyfill.
2024-02-12 18:47:12 -08:00
Zankaria
b35990e1cd Fix composer.json platform 2024-02-09 14:49:12 +01:00
Zankaria
fb3843efd5 Remove the custom BMP polyfill.
It was previously required to support PHP < 7.2, but now the minimum supported version is PHP 7.4
2024-02-07 23:24:34 +01:00
Lorenzo Yario
5867963747
Merge pull request #662 from vichan-devel/revert-661-refactor-functions
Revert "Initial break up of functions.php"
2024-02-04 05:53:52 -08:00
Lorenzo Yario
ec515621eb
Revert "Initial break up of functions.php" 2024-02-04 05:53:06 -08:00
Lorenzo Yario
4bd3fdb8a9
Merge pull request #658 from Zankaria/post-initial-refactor
Initial post refactor
2024-02-04 05:09:03 -08:00
Lorenzo Yario
1b33080214
Merge pull request #661 from Zankaria/refactor-functions
Initial break up of functions.php
2024-02-04 05:08:37 -08:00
Zankaria
3baa68c7b6 Initial refactor of functions.php 2024-02-03 17:17:14 +01:00
Zankaria
d3cd289511 Refactor post.php: break up file upload 2024-01-30 14:37:08 +01:00
Zankaria
c43229af8e Refactor post.php: extract symbol post body stripping 2024-01-30 14:36:31 +01:00
Zankaria
d02c4eb31d Refactor post.php: split upload file hashing to a function 2024-01-30 10:35:11 +01:00
Zankaria
15cce3a31f Fix leading whitespace in post.php 2024-01-30 10:35:07 +01:00
Lorenzo Yario
f45bc768fe
get duckroll'd 2024-01-23 14:18:20 -08:00
Lorenzo Yario
2ab6a3919b
It's 2024 2024-01-14 05:25:45 -08:00
Lorenzo Yario
8dec1f4798
Merge pull request #650 from x9a/master
Update flags
2023-12-10 14:12:48 -08:00
x9a
6af53795f5 Update flags
Update flags with their newer variants
2023-12-10 19:55:10 +03:00
RealAngeleno
e80cc95858
Merge pull request #645 from vichan-devel/RealAngeleno-patch-13
Allow prohibition of deleting old posts
2023-10-31 22:05:06 -07:00
RealAngeleno
d057c6d660
logic for preventing deletion of old posts 2023-10-31 22:03:09 -07:00
RealAngeleno
fe9573a385
allow disabling post deletion for old posts 2023-10-31 21:59:12 -07:00
RealAngeleno
f3c6093bf6
Merge pull request #643 from vichan-devel/RealAngeleno-installqol
Make templates/cache and secrets.php exist by default.
2023-10-21 23:24:55 -07:00
RealAngeleno
fa6e240c1c
QOL for mass vichan installation 2023-10-21 23:02:20 -07:00
RealAngeleno
bc05223362
Make secrets.php already exist as a QOL. 2023-10-21 22:59:45 -07:00
RealAngeleno
4545d33b74
added missing change from commit for hcaptcha 2023-10-10 13:45:23 -07:00
RealAngeleno
e9ed9acd30
fixed oversight. oops. 2023-10-04 22:50:06 -07:00
RealAngeleno
c9b05e5bfc
Merge pull request #609 from vichan-devel/discontinuation
After deliberation and discussions with perdedora and others, this can be merged.
2023-10-04 22:36:14 -07:00
RealAngeleno
2ade0b0c8a
Update composer.json 2023-10-04 21:33:37 -07:00
RealAngeleno
e5d6960e4a
Merge pull request #629 from vichan-devel/hcaptcha
Add hCaptcha support
2023-09-16 10:35:59 -07:00
RealAngeleno
5e26d5f09e
add hcaptcha support 2023-09-16 01:32:01 -07:00
RealAngeleno
f396204e44
add hcaptcha support 2023-09-16 01:31:02 -07:00
RealAngeleno
ad3731ec67
add hCaptcha support 2023-09-16 01:27:20 -07:00
RealAngeleno
cca659bd1f
Merge pull request #625 from frozenpandaman/patch-1
fix float division deprecation warning breaking `captcha.php` json
2023-09-10 21:46:19 -07:00
RealAngeleno
9c34ee778a
Merge pull request #626 from vichan-devel/RealAngeleno-patch-11
very small QOL change for ban appeals
2023-09-10 10:55:24 -07:00
RealAngeleno
eda383cbb3
very small QOL change for ban appeals 2023-09-10 10:54:49 -07:00
eli
c3c7bc4fc3
fix float division deprecation warning breaking captcha.php json 2023-09-07 05:45:14 -10:00
RealAngeleno
fbc2464c96
Merge pull request #622 from vichan-devel/RealAngeleno-patch-5
Drop-in replacement for rc4-40 encryption with aes-256-ctr

When I think about it, the above can be done later.
2023-09-05 21:33:24 -07:00
RealAngeleno
1b14830dfd
drop-in replacement for rc4-40 2023-09-01 22:16:05 -07:00
RealAngeleno
2094155b6c
Merge pull request #618 from vichan-devel/RealAngeleno-hourlylimit
Add hourly thread limit
2023-08-13 23:15:49 -07:00
RealAngeleno
997babeee0
add hourly thread limit (from npfchan) 2023-08-13 23:14:41 -07:00
RealAngeleno
503e4e39d0
hourly thread limit. Mostly from NPFchan 2023-08-13 23:13:29 -07:00
RealAngeleno
42a473ae8b
maintainers changed 2023-08-06 08:59:23 -07:00
RealAngeleno
063275c4c8
honestly the latest development version is less buggy than "vichan stable" 2023-08-05 18:28:15 -07:00
RealAngeleno
237c5c4d4b
Merge pull request #615 from vichan-devel/stickyfix
Allow image for cyclical posts to be set independently // Add mod.php logic to catalog links
2023-08-05 15:43:45 -07:00
RealAngeleno
dd5d8830d7
Add image for cyclical posts
From Wikimedia (Public domain)
2023-08-05 15:41:24 -07:00
RealAngeleno
b8190dfd06
allow cyclical images to be set independently of stickies 2023-08-05 15:38:31 -07:00
RealAngeleno
4d3ca11b91
allow cyclical images to be set independently of stickies 2023-08-05 15:35:28 -07:00
RealAngeleno
ea08c250fa
Add mod.php logic 2023-08-05 15:32:46 -07:00
RealAngeleno
4a151acb18
allow cyclical images to be set separately from stickies 2023-08-05 15:11:21 -07:00
RealAngeleno
285bb035da
Merge pull request #612 from vichan-devel/RealAngeleno-patch-9
Remove ENT_QUOTES?
2023-08-04 20:51:30 -07:00
RealAngeleno
29e02b705a
Merge pull request #613 from vichan-devel/RealAngeleno-patch-11
prohibit file-selector.js from running on ios
2023-08-03 22:33:53 -07:00
RealAngeleno
b79731c548
prohibit script from running on ios 2023-08-03 22:32:42 -07:00
RealAngeleno
fb8f1d04d0
Update functions.php 2023-08-02 18:04:49 -07:00
RealAngeleno
bee2d07499
Merge pull request #610 from vichan-devel/RealAngeleno-reportfix
Fix multi-report bug #567
2023-07-28 14:33:30 -04:00
RealAngeleno
aa0f6607cf
Fix multi-report bug #567 2023-07-28 14:32:42 -04:00
RealAngeleno
14ae2d7d06
discontinue php versions before 7.4 2023-07-26 20:59:37 -05:00
RealAngeleno
3413e74f24
discontinue older versions of php 2023-07-26 19:37:57 -05:00
RealAngeleno
9f7205a27e
drop support for anything lower than 7.4. 2023-07-26 19:36:33 -05:00
RealAngeleno
3b659732d6
no irc channel currently 2023-07-20 21:52:34 -05:00
RealAngeleno
b22b56d58f
Update README.md 2023-07-20 19:45:36 -05:00
RealAngeleno
a0da06b46b
Update README.md 2023-07-20 19:45:18 -05:00
RealAngeleno
dfbf5df0a3
current standings 2023-07-20 19:44:36 -05:00
RealAngeleno
2ef0d2796c
Merge pull request #601 from vichan-devel/revert-600-revert-597-remove_telegram
Re-apply Remove telegrams
2023-07-18 02:36:34 -07:00
RealAngeleno
70b5c80cea
fix bug with removal of telegrams 2023-07-18 02:35:56 -07:00
RealAngeleno
1a5b3c8480
Revert "Temporarily Revert "Remove telegrams" due to critical bug with posting" 2023-07-15 23:10:39 -07:00
RealAngeleno
ab2d29c8d1
Merge pull request #600 from vichan-devel/revert-597-remove_telegram
Temporarily Revert "Remove telegrams" due to critical bug with posting
2023-07-15 23:09:12 -07:00
RealAngeleno
af6bb09031
Revert "Remove telegrams" 2023-07-15 23:03:13 -07:00
RealAngeleno
7feb101bbf
Merge pull request #597 from perdedora/remove_telegram
Telegrams are basically a broken version of a warn system. Agreed.
2023-07-14 20:39:35 -07:00
fowr
5ef004c7ad remove telegrams 2023-07-15 00:24:04 -03:00
RealAngeleno
715e73ae90
link to stable download 2023-07-13 22:29:52 -07:00
RealAngeleno
972bc855b8
Merge pull request #596 from vichan-devel/RealAngeleno-patch-8
Fix oversight in hide_filename
2023-07-13 22:22:59 -07:00
RealAngeleno
899cfe1aaa
Fix oversight in hide_filename 2023-07-13 22:11:59 -07:00
RealAngeleno
485862acaa
Merge pull request #593 from vichan-devel/RealAngeleno-patch-5
Modify email field and selectbox functionality
2023-07-10 04:05:45 -07:00
RealAngeleno
f48872d5a4
prohibit emails from showing up in api if hide_email is true 2023-07-10 04:02:51 -07:00
RealAngeleno
694351baa8
fix email logic and add nonoko setting to selectbox
Changes it so that if the user is mod and they can bypass fields, "email" will show up just fine. If not, "email" will become an options field, like on 4chan. The options field will show up for anybody if hide_email is set to true.
Nonoko will be in the selectbox if always_noko is set to true.
2023-07-10 03:39:34 -07:00
RealAngeleno
e2a42c67f6
Merge pull request #592 from vichan-devel/RealAngeleno-patch-4
prohibit original filename from showing up in api when disabled
2023-07-10 02:47:10 -07:00
RealAngeleno
ed627325ad
prohibit original filename from showing up in api when disabled
Makes it so that if $config['show_filename'] = false, it will show $apiPost['tim'] as the filename instead.
2023-07-10 02:46:28 -07:00
RealAngeleno
33397fa1cf
That Rizon chat was KIA 2023-07-09 17:38:31 -07:00
RealAngeleno
3c2f1800ec
Merge pull request #587 from vichan-devel/RealAngeleno-patch-8
Add hide-form.js.
2023-06-26 15:06:22 -07:00
RealAngeleno
eaf7c83e15
Create hide-form.js 2023-06-26 15:05:51 -07:00
RealAngeleno
e912ee24ec
Merge pull request #585 from vichan-devel/RealAngeleno-patch-5
Change variable for max lines to prevent conflict
2023-06-25 05:27:10 -07:00
RealAngeleno
5df3ae6606
Update post.php 2023-06-25 05:26:29 -07:00
RealAngeleno
728476ce99
Update config.php
prevent conflict
2023-06-25 05:25:32 -07:00
RealAngeleno
0dd904aec5
Merge pull request #583 from vichan-devel/RealAngeleno-spaceless
(twig) Remove spaceless for post and file descriptions
2023-06-23 01:21:27 -07:00
RealAngeleno
744b025137
Merge pull request #584 from vichan-devel/RealAngeleno-patch-4
add missing break in switch/case for dismiss post
2023-06-22 04:50:31 -07:00
RealAngeleno
691ce02cbd
add missing break in switch/case for dismiss post
fixes bug in which Dismiss Post worked identically to Dismiss+
2023-06-22 04:49:22 -07:00
RealAngeleno
222de51247
remove spaceless filter for post description 2023-06-21 15:57:39 -07:00
RealAngeleno
0470064ea6
Update post_thread.html 2023-06-20 16:38:59 -07:00
RealAngeleno
07f998f9eb
Merge pull request #582 from ogjamesfranco/ogjamesfranco-patch-1
fix incorrect function call in body length check
2023-06-20 16:00:30 -07:00
James Franco
d8732d3d30
fix incorrect function call in body length check 2023-06-17 22:20:05 -05:00
RealAngeleno
1a39025162
Merge pull request #578 from vichan-devel/RealAngeleno-patch-3
make it so that appeals always go through post.php
2023-06-01 23:24:27 -07:00
RealAngeleno
fbee2dfbe0
make it so that appeals always go through post.php
Originally, appeals would not go through if made through banned.php, so routing everything through one place.
2023-06-01 23:24:09 -07:00
RealAngeleno
81d56bb784
Merge pull request #577 from vichan-devel/RealAngeleno-patch-8
changed language in audit log
2023-05-28 19:30:20 -07:00
RealAngeleno
2cd1ea4095
changed "his" to "their"
also inadvertently made vichan woke
2023-05-28 19:29:26 -07:00
RealAngeleno
39f4f5cd0b
Merge pull request #576 from syogu/patch-1
A small typo
2023-05-27 19:32:23 -07:00
mikka
6a13e1053d
A small typo 2023-05-26 22:54:32 +02:00
RealAngeleno
1504a30e45
Merge pull request #575 from vichan-devel/revert-574-RealAngeleno-patch-5-1
Revert "update Yotsuba B for parity with 4chan"
2023-05-23 01:29:52 -07:00
RealAngeleno
7d95959735
Revert "update Yotsuba B for parity with 4chan" 2023-05-23 01:28:51 -07:00
RealAngeleno
b112201d2b
Merge pull request #574 from vichan-devel/RealAngeleno-patch-5-1
update Yotsuba B for parity with 4chan
2023-05-23 01:20:07 -07:00
RealAngeleno
0799b5399b
Merge pull request #573 from vichan-devel/RealAngeleno-patch-5
Fix parity issues with the yotsuba theme
2023-05-21 10:09:54 -07:00
RealAngeleno
ec8bb62764
update Yotsuba B for parity with 4chan 2023-05-21 00:19:18 -07:00
RealAngeleno
5d1d52295a
parity 2023-05-21 00:10:09 -07:00
RealAngeleno
77f4684a6b
Merge pull request #572 from vichan-devel/RealAngeleno-patch-4
allow posts to be discarded for containing too many lines
2023-05-20 15:40:20 -07:00
RealAngeleno
bb59e907b1
allow posts to be discarded for too many lines 2023-05-20 15:38:40 -07:00
RealAngeleno
727e8723f0
add too many lines 2023-05-20 15:37:25 -07:00
RealAngeleno
d04e0d2caa
Update config.php 2023-05-20 14:45:26 -07:00
RealAngeleno
1db892fa6d
undid the undo 2023-05-20 11:31:07 -07:00
RealAngeleno
bdfd967554
revert README until all issues are sorted out 2023-05-19 23:40:23 -07:00
basedgentoo
ef32749986
No longer Kolyma-Affiliated. 2023-05-19 20:47:33 -04:00
RealAngeleno
d636076597
Merge pull request #569 from vichan-devel/RealAngeleno-patch-3
add some information about the requirement for boardlinks in auto-reload
2023-04-28 22:37:06 -07:00
RealAngeleno
380c3ba000
add some information about the requirement for boardlinks to be enabled for the script to work 2023-04-28 22:36:11 -07:00
RealAngeleno
a2d9493602
undid since I wanted to do it with a PR 2023-04-28 22:35:25 -07:00
RealAngeleno
639785cd43
Added some small information about the requirement for boardlinks for script to load 2023-04-28 22:33:52 -07:00
basedgentoo
d1d324fc81
README update & delete unapplied patches directory. (#566)
* We merged this.

No need for this anymore.

* Update README

No more unapplied patches, we also are doing releases now.
2023-04-25 04:36:28 +00:00
RealAngeleno
b0445d8b61
Merge pull request #565 from vichan-devel/RealAngeleno-patch-2
fix error with the newer version of twig
2023-04-24 16:29:59 -07:00
RealAngeleno
144f0e1a3d
fix error with this version of twig
Caught fatal error: Uncaught Twig\Error\SyntaxError: Unexpected token "name" of value "group_name" ("name" expected with value "from").

done on
PHP 7.2
Apache
2023-04-24 16:28:49 -07:00
RealAngeleno
d1724d0da6
Merge pull request #563 from vichan-devel/revert-561-revert-560-dismiss_report
Undo temporary revert of a merge
2023-04-23 19:17:05 -07:00
RealAngeleno
66be6de2fd
Revert "Temporarily Revert "feature: dismiss all reports for a post" (misclick)" 2023-04-23 19:15:44 -07:00
basedgentoo
8012cc5f05
Merge pull request #527 from discomrade/regex-open-bracket
Allow open parentheses before cite: "(>>1"
2023-04-23 22:13:18 -04:00
basedgentoo
417406d970
Merge pull request #553 from perdedora/update_twig
Update twig to 2.9
2023-04-23 22:12:49 -04:00
RealAngeleno
f926d6c399
Merge pull request #561 from vichan-devel/revert-560-dismiss_report (misclick)
Temporarily Revert "feature: dismiss all reports for a post" (misclick)
2023-04-23 19:04:32 -07:00
RealAngeleno
aee8288942
Revert "feature: dismiss all reports for a post" 2023-04-23 19:04:01 -07:00
RealAngeleno
b3c6896e1b
Merge pull request #560 from perdedora/dismiss_report
feature: dismiss all reports for a post
2023-04-23 19:03:55 -07:00
fowr
d825c36b8d feature: dismiss all reports for a post 2023-04-23 22:07:16 -03:00
RealAngeleno
f8c8d57d64
Merge pull request #550 from f89s/master
Fix combining character set, allow limit for combining characters
2023-04-22 09:31:06 -07:00
fowr
87f99715c9 Merge branch 'master' of github.com:perdedora/vichan into update_twig 2023-04-21 00:05:20 -03:00
RealAngeleno
c2eaa69013
Merge pull request #557 from vichan-devel/RealAngeleno-patch-1
change copyright to current year
2023-04-12 13:53:52 -07:00
RealAngeleno
b1e428bed7
change copyright to current year 2023-04-12 13:51:29 -07:00
fowr
b6c8b05cf7 change the default of twig_auto_reload to false 2023-03-30 17:52:38 -03:00
Yuri Kuznetsov
05851f950e
Merge pull request #552 from perdedora/fix_confirm_page
Add global to mod_confirm
2023-03-30 16:31:44 -04:00
basedgentoo
76c5f77932
Merge pull request #555 from vichan-devel/timeline
[timeline] Actually wait, it's even more complicated than that.
2023-03-29 21:21:35 -04:00
Fredrick Brennan
ddae472dd1 [timeline] Make 4chan the head of the tree 2023-03-29 21:17:25 -04:00
Fredrick Brennan
db68297f58 [timeline] Actually wait, it's even more complicated than that. 2023-03-29 21:00:24 -04:00
basedgentoo
4e383426bf
Merge pull request #554 from vichan-devel/timeline
[README] Add timeline
2023-03-29 20:36:56 -04:00
Fredrick Brennan
fc00b08c80
[README] Add timeline 2023-03-29 20:26:38 -04:00
fowr
427f3f8a0a delete remove_whitespace filter 2023-03-29 19:13:28 -03:00
fowr
d2bb450b1a update twig to 2.9 2023-03-29 18:59:28 -03:00
fowr
2c847a6413 add global to mod_confirm 2023-03-29 18:20:10 -03:00
f89s
90bf50fa30 Fix combining character set, allow limit for combining characters 2023-03-28 19:01:12 +04:00
Yuri Kuznetsov
08c9929eb0
Merge pull request #548 from basedgentoo/master
Add Twemoji submodule.
2023-03-25 01:20:30 -04:00
basedgentoo
19c15325b7 Add Twemoji submodule. To use: git submodule init && git submodule update 2023-03-25 01:15:47 -04:00
Yuri Kuznetsov
448d506135
Merge pull request #547 from basedgentoo/basedgentoo-twemoji-issue
This should've been a submodule.
2023-03-25 00:45:50 -04:00
basedgentoo
a93a2dc869
This should've been a submodule. 2023-03-25 00:42:34 -04:00
runit
573dfa84e3 Add Twemoji JavaScript Library 2023-03-25 00:21:06 -04:00
kuz-sysadmin
f9db8e05de
Fixed maintainer list
To look more like past entries, removed unecessary note. 

We will keep e politics out of the github.
2023-03-24 04:30:04 -04:00
basedgentoo
dc7668df64
URL error 2023-03-24 04:22:43 -04:00
basedgentoo
686dc80644
Hopefully final update to the readme for now. 2023-03-24 04:21:43 -04:00
basedgentoo
95d73ae6f9
Update README.md 2023-03-24 03:53:05 -04:00
basedgentoo
324e2f6af4
Update README.md 2023-03-24 03:03:09 -04:00
basedgentoo
e0e8ea50a9
Update README.md 2023-03-24 01:41:49 -04:00
basedgentoo
f3f6d2aa2d
Professionalism! 2023-03-24 01:37:12 -04:00
basedgentoo
9d460d5b0a
Update README 2023-03-24 01:36:25 -04:00
RealAngeleno
f631b83fb1 fix issues with the banner script not working
PHP 7.4
Notice: Trying to access array offset on value of type resource in on line 13

Notice: Trying to access array offset on value of type resource in on line 14

Notice: fpassthru(): read of 8192 bytes failed with errno=21 Is a directory in  on line 17
2023-03-09 01:17:09 -05:00
Fred Brennan
a69337e243 Apply inc/display.php@0a2dfed from vichan/infinity 2023-01-24 02:35:15 -05:00
Fredrick Brennan
491f71ce3a
[README] +webservers/PHP exp needed to use in 2023 2023-01-20 22:39:24 -05:00
discomrade
85b5dee77d Fix post number on dismissed reports log, show id
Previously the report dismiss logs would show the report id instead of the post number.
2023-01-20 22:36:36 -05:00
fowr
2d708984c4 fix: vichan ban length post variable is different than npfchan. causing the ban to be always permanent 2023-01-14 19:22:04 -05:00
perdedora
b02a1fc0db
feat: edit pre-existing bans (#528)
* feat: edit pre-existing bans

* change default permission

* theres no need for elseif here. related: d34f083a6b33185927c6b79a38477f4ea4ce49b5

* quote href param

* changes made
2023-01-12 20:38:23 -05:00
fowr
46b29de7ff remove requires since we are using autoload and its broken 2023-01-12 15:33:33 -05:00
Fred Brennan
fe3e4997b6 +static/unapplied patches 2023-01-12 05:37:21 -05:00
Fredrick Brennan
3256049ca9
[README] + § Unapplied patches 2023-01-12 05:30:08 -05:00
discomrade
5d31f3bab7
Add open parentheses to cite regex
Allows cite regex to match a post number after an opening parenthesis. "I like the comfy threads (>>1 and >>2) because they're comfy"
From d78254b41d
2023-01-12 10:16:15 +00:00
discomrade
2317c0adee Add default value for flood_cache
The original code left flood_cache undefined, leading to errors in newer versions of PHP. See https://github.com/vichan-devel/vichan/issues/525
2023-01-12 03:30:23 -05:00
Majin Bejitto
23163ae59c fix "scandir should not need to sort in b.php (banner code)"
scandir by default sorts files in ascending order. this is unnecessary when you're picking a random file anyway. it's just wasting CPU cycles and increasing latency as more files are added.

currently it is

    $files = scandir($dir);

it should be

    $files = scandir($dir, SCANDIR_SORT_NONE);
2022-12-21 03:36:45 -05:00
Majin Bejitto
a0f699d49f fix "anchor/bumplock icon won't show up without fixing variable name"
mistake in /templates/post_thread.html

it should be post.sage not post.bumplocked, there is no bumplocked field in posts_X tables
2022-12-21 03:36:35 -05:00
Fredrick Brennan
70222e8c2d Revert "When moving Reply from Thread to Thread: Remove re-direct to error page "Thread does not exist"" 2022-12-14 03:18:00 -05:00
27chan
5961373dda
Merge pull request #518 from smolten/master
When moving Reply from Thread to Thread: Remove re-direct to error page "Thread does not exist"
2022-12-13 17:31:36 -03:00
smolten
812a111107 When moving a post, add condition (post is Thread) before trying to make Thread for post. Removes "Thread Does not exist" page that showed up even when a post was successfully moved between threads. 2022-12-13 00:56:23 -07:00
Fred Brennan
191dbd3be0 [SECURITY] [REGRESSION] Fix config editor UI
Closes #516.

Co-Authored-By: Scotty Molt <smolt@asu.edu>
2022-12-10 10:27:59 -05:00
27chan
cd444a1c1b Update footer
Remove duplicate code footer in all pages
Updated the link to the project on github
2022-12-06 16:55:26 -03:00
perdedora
7571df84c8
break to avoid undefined error (#514) 2022-11-28 17:10:20 -05:00
deysu
c8f88c14a8
Add banners as default functionality & display them on mod login / dashboard when enabled (#513)
* Add banner support as a default, integrated option.
Used lainchan's original banner script, authored by barrucadu

* Display banners on moderator login and dashboard

* Remove memes & better directory structure
2022-11-17 01:48:24 -05:00
runit
f62acd5c1a banned.php now shows a pretty page instead of an ugly one. credits to JamesJDillon @ Lainchan 2022-10-15 10:09:10 -04:00
runit
4e5fecebd2 I didn't need to actually touch this. 2022-10-15 10:09:10 -04:00
runit
63b71bf1fc We're not Tinyboard anymore. We've been vichan for 8 years. We should reflect that.
Also use Github wiki so new enhancements can be reflected. It's better than linking to 10-year-old archived documentation.
I'll update the wiki eventually.
2022-10-15 10:09:10 -04:00
runit
b6d411c5e4 I'd just like to interject for a moment. 2022-10-15 10:09:10 -04:00
清靈語
5780e78975 Replace Google reCAPTCHA API's domain (#507)
This makes it accessible in mainland China.

Sources:
google/recaptcha#227
https://developers.google.com/recaptcha/docs/faq#can-i-use-recaptcha-globally
https://meta.stackoverflow.com/questions/402339/please-change-the-recaptcha-link-to-ensure-the-site-is-accessible-in-china

Co-Authored-By: Fredrick Brennan <copypaste@kittens.ph>
2022-10-15 10:07:10 -04:00
Fred Brennan
4c6a695a6f Use ENT_QUOTES when converting UTF-8 to HTML (#448)
Closes #448.
2022-09-15 14:46:05 -04:00
sshscp15
e42a1b04b1 fixes boardlist in catalog when in mod mode 2022-09-15 13:03:49 -04:00
sshscp15
30e7574649
Fix display warning/errors (#496)
* prevent filling debug param when debug is off

* fix php warning: "$item" must be passed by reference
2022-09-15 13:03:32 -04:00
sshscp15
401b506691 fix mod.php. check if key is there and is valid 2022-09-15 13:03:06 -04:00
27chan
70a06116e5
Update DEMO
Update version PHP
2022-09-12 20:18:18 -03:00
27chan
ec8ae76d43
Merge pull request #499 from vichan-devel/xss-embed
Removed regex with possibiblity of XSS
2022-09-05 13:59:45 -03:00
27chan
76fad44de4
Removed regex with possibiblity of XSS
An anonymous user reported the issue to me
2022-09-05 13:58:41 -03:00
Fred Brennan
572a11dba7 Flip insane default for non-developers 2022-08-29 11:48:38 -04:00
Fred Brennan
efd54a20e8 Install config to secrets.php by default 2022-08-29 11:47:47 -04:00
Fred Brennan
a99d7c7c80 Add support for APC(u) 2022-08-29 11:45:59 -04:00
C Hatfield
ae2d91c534
Moved hardcoded html filepaths into config file for extensibility (#354)
Co-authored-by: chatfield <chatfield@creatuity.com>
Co-authored-by: Fred Brennan <copypaste@kittens.ph>
2022-08-29 10:50:45 -04:00
haruhianon609
0f94915fdf
Add yandex images as image identification option (#430)
* Add yandex images as image identification option

* Update image_identification.html

* Fix indentation

Co-authored-by: Fred Brennan <copypaste@kittens.ph>
2022-08-29 10:37:40 -04:00
Fredrick Brennan
1e19e75bf5
Change illogical default of $config[force_body]
Makes JS and non-JS enforcement equivalent while providing a downgrade path.

JS may not allow empty bodies even when $config[force_body] false. Don't care enough to fix. PR welcome.

Close #493.
2022-08-29 10:30:50 -04:00
27chan
dd7c63b65f
Merge pull request #491 from vichan-devel/issue№441
fix mysql_version() for mariadb
2022-08-24 11:46:28 -03:00
Fredrick Brennan
9e63116338
fix mysql_version() for mariadb 2022-08-20 12:45:56 -04:00
Łiźnier Hełam Łabej
d6ea8db5ef
remove named parameters from call_user_func_array calls (#402) 2022-08-20 12:25:52 -04:00
sshscp15
4db4ab9cf0 simple catalog support for moderators 2022-08-20 12:25:10 -04:00
370chan
c170bc7ef2
Fix files not being saved in certain cases (#483) 2022-08-20 12:24:46 -04:00
PVNFU-28
b5eee58b96
Update theme.php (#409)
fixed hardcoded root that broke spoilered images on the catalog if vichan was not installed on the web root.
2022-08-20 12:21:42 -04:00
PVNFU-28
2d1420b763 Update style.css
Made CSS formatting more consistent, really minor stuff
2022-08-20 12:21:14 -04:00
PVNFU-28
75236d6ae5 Update functions.php
current regex consumes the space after a post quote, instead of merely checking that it's there. As a result textually consecutive post quotes, which the source calls cites, cannot be separated by a single space. This fixes that
2022-08-20 12:20:53 -04:00
ben2613
72f38933c2 Fix mobile browser hang issue when auto-reload by reducing DOM modification in forloop 2022-08-20 12:19:41 -04:00
bebyx
c128a37160
Fix editing global pages for 7.4 (#487) 2022-08-20 12:17:04 -04:00
Junicchi
23ebde7883 fix Undefined index ip problem, fixes #434 2022-08-20 12:13:02 -04:00
27chan
ae3b5b194c
Merge pull request #466 from discomrade/patch-5
Fix multiple issues in anti bump flood
2022-06-21 12:56:11 -03:00
27chan
1d8a577029
Merge pull request #440 from discomrade/patch-1
Fix custom thumb_ext when using ImageMagick convert
2022-06-06 22:06:16 -03:00
27chan
bfd0cc0bc2 Adjust css theme
Fix responsive
2022-06-01 11:46:28 -03:00
27chan
6577a0d9d8
Fix typo 2022-06-01 01:11:53 -03:00
27chan
023d6c763b
Updating information
Adding Redis in the recommendations section.
Inserting a demo section.
2022-06-01 01:09:05 -03:00
27chan
fe5b8b7863
Merge pull request #464 from discomrade/master
Fixes an error reporting typo in move thread.
2022-05-31 08:55:21 -03:00
rosemash
52321abc7b Fix 404 caused by recent_posts.html loading wrong JS filename 2022-01-22 13:55:08 -05:00
discomrade
77bab66293
Fix multiple issues in anti bump flood
- saged posts aren't ignored when finding last bump
- bumplocked thread with one reply, delete the reply and no post matches the query
- bumplocked threads should be ignored
2022-01-22 03:31:52 +00:00
jove
847c5d130c Fixes an error reporting typo. 2022-01-18 07:17:34 -01:00
discomrade
8512321669 Fix redis 'delete' deprecation error
Redis deprecated the 'delete' alias for 'del'. Posting while using redis cache would return an error, the post would still be posted.
2022-01-16 23:50:25 -05:00
Łiźnier Hełam Łabej
617f2a68da
Merge pull request #455 from vichan-devel/securimage
securimage captcha
2021-11-13 01:31:22 +01:00
h00j
a3dc72bddb securimage captcha 2021-11-13 01:24:34 +01:00
Fredrick Brennan
9745e9d854 Ignore recently added graphicsmagick check
Closes #445.
2021-11-03 22:55:55 -04:00
Fredrick Brennan
f1b43e5fb2 {% (end)?raw %}{% (end)?verbatim %}
```
rg '\{%\s*raw' -l | xargs -I{} sed -i -e 's/{%\(\s*\)raw/{%\1verbatim/g; s/endraw\(\s*\)%}/endverbatim\1%}/g' {}
```

Close vichan-devel/vichan#452.
2021-11-03 22:49:49 -04:00
discomrade
54cd4d41f2
Fix custom thumb_ext for ImageMagick convert
Fixed breaking typo in previous commit
2021-06-23 12:40:27 +00:00
discomrade
a752a3930c
Fix custom thumb_ext for ImageMagick convert
When using ImageMagick's convert tool, the output defaults to the input format if no file extension or format is specified.
The temp file currently has no extension, so a $config['thumb_ext'] value has no effect on the image.
By appending the thumb_ext to the temp output file, it will convert the image to the intended format.

You can see this issue present on lainchan, where thumbnails have a .png filename but are not really PNG files when the input is a .jpg, for example.
2021-06-23 11:52:29 +00:00
215 changed files with 8911 additions and 5669 deletions

4
.dockerignore Normal file
View File

@ -0,0 +1,4 @@
**/.git
**/.gitignore
/local-instances
**/.gitkeep

69
.github/ISSUE_TEMPLATE/bug_report.yml vendored Normal file
View File

@ -0,0 +1,69 @@
name: Bug Report
description: File a bug report for Vichan
title: "[BUG] "
labels: ["bug"]
assignees: []
body:
- type: markdown
attributes:
value: |
**Thank you for reporting a bug! Please provide as much detail as possible.**
Before submitting, check the [Vichan Wiki](https://vichan.info) to see if there's already a solution to your problem.
- type: textarea
id: bug_description
attributes:
label: "Describe the bug"
description: "A clear and concise description of what the bug is."
placeholder: "Posting doesn't go through and displays a collation error. The exact error message given is the text below and I've attached a screenshot..."
validations:
required: true
- type: textarea
id: steps_to_reproduce
attributes:
label: "Steps to Reproduce"
description: "Provide step-by-step instructions to reproduce the issue. If you're unsure on how, that is alright, just try and explain as well as you can."
placeholder: |
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
render: markdown
validations:
required: true
- type: textarea
id: expected_behavior
attributes:
label: "Expected Behavior"
description: "What did you expect to happen?"
placeholder: "Expected behavior here..."
render: markdown
validations:
required: true
- type: textarea
id: server_specs
attributes:
label: "Server Specifications"
description: "Provide details about your server environment. If you're unsure about any of this, you might be using shared hosting (Hostinger, HostGator, Serv00, etc). If so, put the name of your hosting provider here."
placeholder: |
- OS: (Ubuntu, CentOS, Windows Server 2025, etc.)
- PHP Version: (e.g., 7.4, 8.0, 8.4)
- Web Server: (Apache, NGINX, etc.)
- Database: (MySQL, MariaDB, etc.)
- Vichan Version: (5.2.0, 5.3.0 (dev branch), etc)
render: markdown
validations:
required: true
- type: textarea
id: additional_context
attributes:
label: "Additional Context"
description: "Any other details we should know?"
placeholder: "Add any additional context here..."
render: markdown

View File

@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

3
.gitignore vendored
View File

@ -12,7 +12,7 @@
!/inc/config.php
# instance-config
/inc/instance-config.php
/inc/secrets.php
# .installed
/.installed
@ -44,5 +44,6 @@ Thumbs.db
#vichan custom
favicon.ico
/static/spoiler.png
/local-instances
/vendor/

3
.gitmodules vendored
View File

@ -7,3 +7,6 @@
path = inc/lib/parsedown
url = https://github.com/vichan-devel/parsedown.git
branch = master
[submodule "js/twemoji"]
path = js/twemoji
url = https://github.com/basedgentoo/twemoji

View File

@ -1,20 +1,15 @@
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-->.**
**Please do not contact Fredrick Brennan in regards to vichan issues.**
As of 29 August 2022 it supports PHP8.1.
About
------------
vichan is a free light-weight, fast, highly configurable and user-friendly
imageboard software package. It is written in PHP and has few dependencies.
*Security problems can be reported to the development team: DEVELOPMENT \<AT\> VICHAN \<DOT\> NET.*
While there is currently no active development besides fixing security problems, we don't exclude the possibility to refactor the code in order to meet today's standards and continue our work from the point where [@czaks](https://github.com/czaks) retired in 2017.
Before this milestone is achieved though, we strongly urge you to consider other imageboard packages. It is the opinion of the vichan development team that no new vichan imageboards should be deployed at the moment, and other imageboard packages used instead.
For support, feel free to join our [IRC channel](https://webchat.6an.org/?channels=vichan-dev) at irc.6an.org.
Some documentation may be found on our [wiki](https://github.com/vichan-devel/vichan/wiki). (feel free to contribute)
History
@ -23,30 +18,33 @@ vichan is a fork of (now defunc'd) [Tinyboard](http://github.com/savetheinternet
a great imageboard package, actively building on it and adding a lot of features and other
improvements.
![](static/doc/timeline.svg)
### Maintainer timeline
1. [@h00j](https://github.com/h00j) (2021 - present)
2. [@ctrlcctrlv](https://github.com/ctrlcctrlv) (2017 - 2021)
3. [@czaks](https://github.com/czaks) (2014 - 2017) (The author of vichan fork)
4. [@savetheinternet](https://github.com/savetheinternet) (2010 - 2014) (The creator of Tinyboard)
1. [@perdedora](https://github.com/perdedora) and [@RealAngeleno](https://github.com/RealAngeleno) - 2023-Present.
2. Development Commission lead by [@basedgentoo](https://github.com/basedgentoo), [@kuz-sysadmin](https://github.com/kuz-sysadmin), and [@RealAngeleno](https://github.com/RealAngeleno). (2023 - 2023)
3. [@h00j](https://github.com/h00j) (2021 - ???)
4. [@ctrlcctrlv](https://github.com/ctrlcctrlv) (2017 - 2021)
5. [@czaks](https://github.com/czaks) (2014 - 2017) (The author of vichan fork)
6. [@savetheinternet](https://github.com/savetheinternet) (2010 - 2014) (The creator of Tinyboard)
Requirements
------------
1. PHP >= 5.4 (we still try to keep compatibility with php 5.3 as much as possible)
PHP 7.0 is explicitly supported. PHP 7.2 works as well, but may cause as yet unreported bugs.
1. PHP >= 7.4
2. MySQL/MariaDB server
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. [APCu (Alternative PHP Cache)](http://php.net/manual/en/book.apcu.php),
[Memcached](http://www.php.net/manual/en/intro.memcached.php) or
[Redis](https://redis.io/docs/about/)
Contributing
------------
@ -57,8 +55,7 @@ You can contribute to vichan by:
Installation
-------------
1. Download and extract vichan to your web directory or get the latest
development version with:
1. Get the latest development version with:
git clone git://github.com/vichan-devel/vichan.git
@ -83,6 +80,15 @@ finally run ```install.php```.
To migrate from a Kusaba X board, use http://github.com/vichan-devel/Tinyboard-Migration
Demo
--------
Demo with the most updated version of [Vichan](https://vichan.27chan.org).
1. PHP 8.1
2. MySQL 5.7
3. KeyDB 6.2.1 (Redis)
4. NGINX 1.14.0
Support
--------
vichan is still beta software -- there are bound to be bugs. If you find a
@ -113,6 +119,11 @@ WebM support
------------
Read `inc/lib/webm/README.md` for information about enabling webm.
Docker
------------
Vichan comes with a Dockerfile and docker-compose configuration, the latter aimed primarily at development and testing.
See the `docker/doc.md` file for more information.
vichan API
----------
vichan provides by default a 4chan-compatible JSON API. For documentation on this, see:
@ -121,4 +132,3 @@ https://github.com/vichan-devel/vichan-API/ .
License
--------
See [LICENSE.md](http://github.com/vichan-devel/vichan/blob/master/LICENSE.md).

8
b.php Normal file
View File

@ -0,0 +1,8 @@
<?php
$files = scandir('static/banners/', SCANDIR_SORT_NONE);
$files = array_diff($files, ['.', '..']);
$name = $files[array_rand($files)];
header("Location: /static/banners/$name", true, 307);
header('Cache-Control: no-cache');

View File

@ -1,7 +1,14 @@
<?php
require_once 'inc/bootstrap.php';
checkBan();
print "<!doctype html><html><head><meta charset='utf-8'><title>"._("Banned?")."</title></head><body>";
print "<h1>"._("You are not banned.")."</h1>";
print "</body></html>";
//If the user is not banned, show the "not banned" page.
die(
Element('page.html', array(
'title' => _('Not banned!'),
'config' => $config,
'nojavascript' => true,
'body' => Element('notbanned.html', array()
))
));
?>

40
compose.yml Normal file
View File

@ -0,0 +1,40 @@
services:
#nginx webserver + php 8.x
web:
build:
context: .
dockerfile: ./docker/nginx/Dockerfile
ports:
- "9090:80"
depends_on:
- db
volumes:
- ./local-instances/${INSTANCE:-0}/www:/var/www/html
- ./docker/nginx/vichan.conf:/etc/nginx/conf.d/default.conf
- ./docker/nginx/nginx.conf:/etc/nginx/nginx.conf
- ./docker/nginx/proxy.conf:/etc/nginx/conf.d/proxy.conf
links:
- php
php:
build:
context: .
dockerfile: ./docker/php/Dockerfile
volumes:
- ./local-instances/${INSTANCE:-0}/www:/var/www
- ./docker/php/www.conf:/usr/local/etc/php-fpm.d/www.conf
- ./docker/php/jit.ini:/usr/local/etc/php/conf.d/jit.ini
#MySQL Service
db:
image: mysql:8.0.35
container_name: db
restart: unless-stopped
tty: true
ports:
- "3306:3306"
environment:
MYSQL_DATABASE: vichan
MYSQL_ROOT_PASSWORD: password
volumes:
- ./local-instances/${INSTANCE:-0}/mysql:/var/lib/mysql

View File

@ -2,15 +2,24 @@
"name": "vichan-devel/vichan",
"description": "vichan imageboard",
"type": "project",
"config": {
"platform": {
"php": "7.4"
}
},
"require": {
"ext-mbstring": ">=5.4",
"ext-gd": ">=5.4",
"ext-pdo": ">=5.4",
"twig/twig": "^1.44.2",
"php": ">=7.4",
"ext-mbstring": ">=7.4",
"ext-gd": ">=7.4",
"ext-pdo": ">=7.4",
"twig/twig": "^2.9",
"phpmyadmin/twig-i18n-extension": "^4.0",
"lifo/ip": "^1.0",
"gettext/gettext": "^1.0",
"gettext/gettext": "^5.5",
"mrclay/minify": "^2.1.6",
"geoip/geoip": "^1.17"
"geoip/geoip": "^1.17",
"dapphp/securimage": "^4.0",
"erusev/parsedown": "^1.7.4"
},
"autoload": {
"classmap": ["inc/"],
@ -24,8 +33,14 @@
"inc/mod/auth.php",
"inc/lock.php",
"inc/queue.php",
"inc/polyfill.php",
"inc/functions.php"
"inc/functions.php",
"inc/functions/dice.php",
"inc/functions/format.php",
"inc/functions/net.php",
"inc/functions/num.php",
"inc/functions/theme.php",
"inc/service/captcha-queries.php",
"inc/context.php"
]
},
"license": "Tinyboard + vichan",

481
composer.lock generated
View File

@ -4,8 +4,65 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "e78104dfc51d725ae15a17e7a70e7ca3",
"content-hash": "72e79f203581eea6e6b0455147b25878",
"packages": [
{
"name": "dapphp/securimage",
"version": "4.0.2",
"source": {
"type": "git",
"url": "https://github.com/dapphp/securimage.git",
"reference": "aabde76d839d75a238970661187f83312c2eeda7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/dapphp/securimage/zipball/aabde76d839d75a238970661187f83312c2eeda7",
"reference": "aabde76d839d75a238970661187f83312c2eeda7",
"shasum": ""
},
"require": {
"ext-gd": "*",
"php": ">=5.4"
},
"suggest": {
"ext-pdo": "For database storage support",
"ext-pdo_mysql": "For MySQL database support",
"ext-pdo_sqlite": "For SQLite3 database support"
},
"type": "library",
"autoload": {
"psr-4": {
"Securimage\\": "./"
},
"classmap": [
"securimage.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Drew Phillips",
"email": "drew@drew-phillips.com"
}
],
"description": "PHP CAPTCHA Library",
"homepage": "https://www.phpcaptcha.org",
"keywords": [
"Forms",
"anti-spam",
"captcha",
"security"
],
"support": {
"issues": "https://github.com/dapphp/securimage/issues",
"source": "https://github.com/dapphp/securimage/tree/4.0.2"
},
"abandoned": true,
"time": "2020-05-30T10:05:48+00:00"
},
{
"name": "geoip/geoip",
"version": "v1.17",
@ -57,37 +114,43 @@
"issues": "https://github.com/maxmind/geoip-api-php/issues",
"source": "https://github.com/maxmind/geoip-api-php/tree/master"
},
"abandoned": "geoip2/geoip2",
"time": "2016-05-16T19:06:50+00:00"
},
{
"name": "gettext/gettext",
"version": "v1.1.5",
"version": "v5.7.0",
"source": {
"type": "git",
"url": "https://github.com/php-gettext/Gettext.git",
"reference": "1bdf755a1b49f0614d6fc29f446df567eb62cd5c"
"reference": "8657e580747bb3baacccdcebe69cac094661e404"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-gettext/Gettext/zipball/1bdf755a1b49f0614d6fc29f446df567eb62cd5c",
"reference": "1bdf755a1b49f0614d6fc29f446df567eb62cd5c",
"url": "https://api.github.com/repos/php-gettext/Gettext/zipball/8657e580747bb3baacccdcebe69cac094661e404",
"reference": "8657e580747bb3baacccdcebe69cac094661e404",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
"gettext/languages": "^2.3",
"php": "^7.2|^8.0"
},
"require-dev": {
"brick/varexporter": "^0.3.5",
"friendsofphp/php-cs-fixer": "^3.2",
"oscarotero/php-cs-fixer-config": "^2.0",
"phpunit/phpunit": "^8.0|^9.0",
"squizlabs/php_codesniffer": "^3.0"
},
"type": "library",
"autoload": {
"psr-0": {
"Gettext": ""
},
"files": [
"Gettext/translator_functions.php"
]
"psr-4": {
"Gettext\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"AGPL-3.0"
"MIT"
],
"authors": [
{
@ -97,33 +160,123 @@
"role": "Developer"
}
],
"description": "PHP - JS gettext conversor",
"homepage": "https://github.com/oscarotero/Gettext",
"description": "PHP gettext manager",
"homepage": "https://github.com/php-gettext/Gettext",
"keywords": [
"JS",
"gettext",
"i18n",
"mo",
"po",
"translation"
],
"support": {
"email": "oom@oscarotero.com",
"issues": "https://github.com/oscarotero/Gettext/issues",
"source": "https://github.com/php-gettext/Gettext/tree/v1.1.5"
"issues": "https://github.com/php-gettext/Gettext/issues",
"source": "https://github.com/php-gettext/Gettext/tree/v5.7.0"
},
"time": "2014-10-22T15:53:45+00:00"
"funding": [
{
"url": "https://paypal.me/oscarotero",
"type": "custom"
},
{
"name": "lifo/ip",
"version": "v1.1",
"url": "https://github.com/oscarotero",
"type": "github"
},
{
"url": "https://www.patreon.com/misteroom",
"type": "patreon"
}
],
"time": "2022-07-27T19:54:55+00:00"
},
{
"name": "gettext/languages",
"version": "2.10.0",
"source": {
"type": "git",
"url": "https://github.com/lifo101/ip.git",
"reference": "b6a36dab288d7aea155698808bfc6649799fe413"
"url": "https://github.com/php-gettext/Languages.git",
"reference": "4d61d67fe83a2ad85959fe6133d6d9ba7dddd1ab"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/lifo101/ip/zipball/b6a36dab288d7aea155698808bfc6649799fe413",
"reference": "b6a36dab288d7aea155698808bfc6649799fe413",
"url": "https://api.github.com/repos/php-gettext/Languages/zipball/4d61d67fe83a2ad85959fe6133d6d9ba7dddd1ab",
"reference": "4d61d67fe83a2ad85959fe6133d6d9ba7dddd1ab",
"shasum": ""
},
"require": {
"php": ">=5.3"
},
"require-dev": {
"phpunit/phpunit": "^4.8 || ^5.7 || ^6.5 || ^7.5 || ^8.4"
},
"bin": [
"bin/export-plural-rules"
],
"type": "library",
"autoload": {
"psr-4": {
"Gettext\\Languages\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Michele Locati",
"email": "mlocati@gmail.com",
"role": "Developer"
}
],
"description": "gettext languages with plural rules",
"homepage": "https://github.com/php-gettext/Languages",
"keywords": [
"cldr",
"i18n",
"internationalization",
"l10n",
"language",
"languages",
"localization",
"php",
"plural",
"plural rules",
"plurals",
"translate",
"translations",
"unicode"
],
"support": {
"issues": "https://github.com/php-gettext/Languages/issues",
"source": "https://github.com/php-gettext/Languages/tree/2.10.0"
},
"funding": [
{
"url": "https://paypal.me/mlocati",
"type": "custom"
},
{
"url": "https://github.com/mlocati",
"type": "github"
}
],
"time": "2022-10-18T15:00:10+00:00"
},
{
"name": "lifo/ip",
"version": "v1.1.1",
"source": {
"type": "git",
"url": "https://github.com/lifo101/ip.git",
"reference": "4c4cf5b554884be93f1d0422eaec8d6426993229"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/lifo101/ip/zipball/4c4cf5b554884be93f1d0422eaec8d6426993229",
"reference": "4c4cf5b554884be93f1d0422eaec8d6426993229",
"shasum": ""
},
"require": {
@ -155,9 +308,9 @@
],
"support": {
"issues": "https://github.com/lifo101/ip/issues",
"source": "https://github.com/lifo101/ip/tree/master"
"source": "https://github.com/lifo101/ip/tree/v1.1.1"
},
"time": "2020-04-02T11:09:10+00:00"
"time": "2022-07-12T15:45:54+00:00"
},
{
"name": "mrclay/minify",
@ -211,29 +364,88 @@
"time": "2017-11-03T21:04:01+00:00"
},
{
"name": "symfony/polyfill-ctype",
"version": "v1.22.0",
"name": "phpmyadmin/twig-i18n-extension",
"version": "v4.0.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-ctype.git",
"reference": "c6c942b1ac76c82448322025e084cadc56048b4e"
"url": "https://github.com/phpmyadmin/twig-i18n-extension.git",
"reference": "c0d0dd171cd1c7733bf152fd44b61055843df052"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/c6c942b1ac76c82448322025e084cadc56048b4e",
"reference": "c6c942b1ac76c82448322025e084cadc56048b4e",
"url": "https://api.github.com/repos/phpmyadmin/twig-i18n-extension/zipball/c0d0dd171cd1c7733bf152fd44b61055843df052",
"reference": "c0d0dd171cd1c7733bf152fd44b61055843df052",
"shasum": ""
},
"require": {
"php": "^7.1 || ^8.0",
"twig/twig": "^1.42.3|^2.0|^3.0"
},
"require-dev": {
"phpmyadmin/coding-standard": "^3.0.0",
"phpmyadmin/motranslator": "^5.2",
"phpstan/phpstan": "^0.12.66",
"phpunit/phpunit": "^7 || ^8 || ^9"
},
"type": "library",
"autoload": {
"psr-4": {
"PhpMyAdmin\\Twig\\Extensions\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "The phpMyAdmin Team",
"email": "developers@phpmyadmin.net",
"homepage": "https://www.phpmyadmin.net/team/"
}
],
"description": "Internationalization support for Twig via the gettext library",
"keywords": [
"gettext",
"i18n"
],
"support": {
"issues": "https://github.com/phpmyadmin/twig-i18n-extension/issues",
"source": "https://github.com/phpmyadmin/twig-i18n-extension"
},
"time": "2021-06-10T15:53:38+00:00"
},
{
"name": "symfony/polyfill-ctype",
"version": "v1.27.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-ctype.git",
"reference": "5bbc823adecdae860bb64756d639ecfec17b050a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/5bbc823adecdae860bb64756d639ecfec17b050a",
"reference": "5bbc823adecdae860bb64756d639ecfec17b050a",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
"provide": {
"ext-ctype": "*"
},
"suggest": {
"ext-ctype": "For best performance"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.22-dev"
"dev-main": "1.27-dev"
},
"thanks": {
"name": "symfony/polyfill",
@ -241,12 +453,12 @@
}
},
"autoload": {
"psr-4": {
"Symfony\\Polyfill\\Ctype\\": ""
},
"files": [
"bootstrap.php"
]
],
"psr-4": {
"Symfony\\Polyfill\\Ctype\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
@ -271,7 +483,7 @@
"portable"
],
"support": {
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.22.0"
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.27.0"
},
"funding": [
{
@ -287,34 +499,195 @@
"type": "tidelift"
}
],
"time": "2021-01-07T16:49:33+00:00"
"time": "2022-11-03T14:55:06+00:00"
},
{
"name": "twig/twig",
"version": "v1.44.2",
"name": "symfony/polyfill-mbstring",
"version": "v1.27.0",
"source": {
"type": "git",
"url": "https://github.com/twigphp/Twig.git",
"reference": "138c493c5b8ee7cff3821f80b8896d371366b5fe"
"url": "https://github.com/symfony/polyfill-mbstring.git",
"reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/twigphp/Twig/zipball/138c493c5b8ee7cff3821f80b8896d371366b5fe",
"reference": "138c493c5b8ee7cff3821f80b8896d371366b5fe",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/8ad114f6b39e2c98a8b0e3bd907732c207c2b534",
"reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534",
"shasum": ""
},
"require": {
"php": ">=7.2.5",
"symfony/polyfill-ctype": "^1.8"
"php": ">=7.1"
},
"require-dev": {
"psr/container": "^1.0",
"symfony/phpunit-bridge": "^4.4.9|^5.0.9"
"provide": {
"ext-mbstring": "*"
},
"suggest": {
"ext-mbstring": "For best performance"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.44-dev"
"dev-main": "1.27-dev"
},
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
"autoload": {
"files": [
"bootstrap.php"
],
"psr-4": {
"Symfony\\Polyfill\\Mbstring\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill for the Mbstring extension",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"mbstring",
"polyfill",
"portable",
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.27.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2022-11-03T14:55:06+00:00"
},
{
"name": "symfony/polyfill-php72",
"version": "v1.27.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php72.git",
"reference": "869329b1e9894268a8a61dabb69153029b7a8c97"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/869329b1e9894268a8a61dabb69153029b7a8c97",
"reference": "869329b1e9894268a8a61dabb69153029b7a8c97",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.27-dev"
},
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
"autoload": {
"files": [
"bootstrap.php"
],
"psr-4": {
"Symfony\\Polyfill\\Php72\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"polyfill",
"portable",
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-php72/tree/v1.27.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2022-11-03T14:55:06+00:00"
},
{
"name": "twig/twig",
"version": "v2.15.4",
"source": {
"type": "git",
"url": "https://github.com/twigphp/Twig.git",
"reference": "3e059001d6d597dd50ea7c74dd2464b4adea48d3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/twigphp/Twig/zipball/3e059001d6d597dd50ea7c74dd2464b4adea48d3",
"reference": "3e059001d6d597dd50ea7c74dd2464b4adea48d3",
"shasum": ""
},
"require": {
"php": ">=7.1.3",
"symfony/polyfill-ctype": "^1.8",
"symfony/polyfill-mbstring": "^1.3",
"symfony/polyfill-php72": "^1.8"
},
"require-dev": {
"psr/container": "^1.0",
"symfony/phpunit-bridge": "^4.4.9|^5.0.9|^6.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.15-dev"
}
},
"autoload": {
@ -353,7 +726,7 @@
],
"support": {
"issues": "https://github.com/twigphp/Twig/issues",
"source": "https://github.com/twigphp/Twig/tree/v1.44.2"
"source": "https://github.com/twigphp/Twig/tree/v2.15.4"
},
"funding": [
{
@ -365,7 +738,7 @@
"type": "tidelift"
}
],
"time": "2021-01-05T10:10:05+00:00"
"time": "2022-12-27T12:26:20+00:00"
}
],
"packages-dev": [],
@ -380,5 +753,5 @@
"ext-pdo": ">=5.4"
},
"platform-dev": [],
"plugin-api-version": "2.0.0"
"plugin-api-version": "2.3.0"
}

20
docker/doc.md Normal file
View File

@ -0,0 +1,20 @@
The `php-fpm` process runs containerized.
The php application always uses `/var/www` as it's work directory and home folder, and if `/var/www` is bind mounted it
is necessary to adjust the path passed via FastCGI to `php-fpm` by changing the root directory to `/var/www`.
This can achieved in nginx by setting the `fastcgi_param SCRIPT_FILENAME` to `/var/www/$fastcgi_script_name;`
The default docker compose settings are intended for development and testing purposes.
The folder structure expected by compose is as follows
```
<vichan-project>
└── local-instances
└── 1
├── mysql
└── www
```
The vichan container is by itself much less rigid.
Use `docker compose up --build` to start the docker compose.
Use `docker compose up --build -d php` to rebuild just the vichan container while the compose is running. Useful for development.

8
docker/nginx/Dockerfile Normal file
View File

@ -0,0 +1,8 @@
FROM nginx:1.25.3-alpine
COPY . /code
RUN adduser --system www-data \
&& adduser www-data www-data
CMD [ "nginx", "-g", "daemon off;" ]
EXPOSE 80

34
docker/nginx/nginx.conf Normal file
View File

@ -0,0 +1,34 @@
# This and proxy.conf are based on
# https://github.com/dead-guru/devichan/blob/master/nginx/nginx.conf
user www-data;
worker_processes auto;
error_log /dev/stdout warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
# Switch logging to console out to view via Docker
access_log /dev/stdout;
error_log /dev/stdout warn;
sendfile on;
keepalive_timeout 5;
gzip on;
gzip_http_version 1.0;
gzip_vary on;
gzip_comp_level 6;
gzip_types text/xml text/plain text/css application/xhtml+xml application/xml application/rss+xml application/atom_xml application/x-javascript application/x-httpd-php;
gzip_disable "MSIE [1-6]\.";
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-available/*.conf;
}

40
docker/nginx/proxy.conf Normal file
View File

@ -0,0 +1,40 @@
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=czone:4m max_size=50m inactive=120m;
proxy_temp_path /var/tmp/nginx;
proxy_cache_key "$scheme://$host$request_uri";
map $http_forwarded_request_id $x_request_id {
"" $request_id;
default $http_forwarded_request_id;
}
map $http_forwarded_forwarded_host $forwardedhost {
"" $host;
default $http_forwarded_forwarded_host;
}
map $http_x_forwarded_proto $fcgi_https {
default "";
https on;
}
map $http_x_forwarded_proto $real_scheme {
default $scheme;
https https;
}
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Server $host;
real_ip_header X-Forwarded-For;
set_real_ip_from 10.0.0.0/8;
set_real_ip_from 172.16.0.0/12;
set_real_ip_from 172.18.0.0;
set_real_ip_from 192.168.0.0/24;
set_real_ip_from 127.0.0.0/8;
real_ip_recursive on;

66
docker/nginx/vichan.conf Normal file
View File

@ -0,0 +1,66 @@
upstream php-upstream {
server php:9000;
}
server {
listen 80 default_server;
listen [::]:80 default_server ipv6only=on;
server_name vichan;
root /var/www/html;
add_header X-Frame-Options "SAMEORIGIN";
add_header X-Content-Type-Options "nosniff";
index index.html index.php;
charset utf-8;
location ~ ^([^.\?]*[^\/])$ {
try_files $uri @addslash;
}
# Expire rules for static content
# Media: images, icons, video, audio, HTC
location ~* \.(?:jpg|jpeg|gif|png|webp|ico|cur|gz|svg|svgz|mp4|ogg|ogv|webm|htc)$ {
expires 1M;
access_log off;
log_not_found off;
add_header Cache-Control "public";
}
# CSS and Javascript
location ~* \.(?:css|js)$ {
expires 1y;
access_log off;
log_not_found off;
add_header Cache-Control "public";
}
location ~* \.(html)$ {
expires -1;
}
location @addslash {
return 301 $uri/;
}
location / {
try_files $uri $uri/ /index.php$is_args$args;
}
client_max_body_size 2G;
location ~ \.php$ {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Request-Id $x_request_id;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header Forwarded-Request-Id $x_request_id;
fastcgi_pass php-upstream;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME /var/www/$fastcgi_script_name;
fastcgi_read_timeout 600;
include fastcgi_params;
}
location = /favicon.ico { access_log off; log_not_found off; }
location = /robots.txt { access_log off; log_not_found off; }
}

88
docker/php/Dockerfile Normal file
View File

@ -0,0 +1,88 @@
# Based on https://github.com/dead-guru/devichan/blob/master/php-fpm/Dockerfile
FROM composer:lts AS composer
FROM php:8.1-fpm-alpine
RUN apk add --no-cache \
zlib \
zlib-dev \
libpng \
libpng-dev \
libjpeg-turbo \
libjpeg-turbo-dev \
libwebp \
libwebp-dev \
libcurl \
curl-dev \
imagemagick \
graphicsmagick \
gifsicle \
ffmpeg \
bind-tools \
gettext \
gettext-dev \
icu-dev \
oniguruma \
oniguruma-dev \
libmcrypt \
libmcrypt-dev \
lz4-libs \
lz4-dev \
imagemagick-dev \
pcre-dev \
$PHPIZE_DEPS \
&& docker-php-ext-configure gd \
--with-webp=/usr/include/webp \
--with-jpeg=/usr/include \
&& docker-php-ext-install -j$(nproc) \
gd \
curl \
bcmath \
opcache \
pdo_mysql \
gettext \
intl \
mbstring \
&& pecl update-channels \
&& pecl install -o -f igbinary \
&& pecl install redis \
&& pecl install imagick \
$$ docker-php-ext-enable \
igbinary \
redis \
imagick \
&& apk del \
zlib-dev \
libpng-dev \
libjpeg-turbo-dev \
libwebp-dev \
curl-dev \
gettext-dev \
oniguruma-dev \
libmcrypt-dev \
lz4-dev \
imagemagick-dev \
pcre-dev \
$PHPIZE_DEPS \
&& rm -rf /var/cache/* \
&& rm -rf /tmp/pear
RUN rmdir /var/www/html \
&& install -d -m 744 -o www-data -g www-data /var/www \
&& install -d -m 700 -o www-data -g www-data /var/tmp/vichan \
&& install -d -m 700 -o www-data -g www-data /var/cache/gen-cache \
&& install -d -m 700 -o www-data -g www-data /var/cache/template-cache
# Copy the bootstrap script.
COPY ./docker/php/bootstrap.sh /usr/local/bin/bootstrap.sh
COPY --from=composer /usr/bin/composer /usr/local/bin/composer
# Copy the actual project (use .dockerignore to exclude stuff).
COPY . /code
# Install the compose depedencies.
RUN cd /code && composer install
WORKDIR "/var/www"
CMD [ "bootstrap.sh" ]
EXPOSE 9000

View File

@ -0,0 +1,16 @@
# syntax = devthefuture/dockerfile-x
INCLUDE ./docker/php/Dockerfile
RUN apk add --no-cache \
linux-headers \
$PHPIZE_DEPS \
&& pecl update-channels \
&& pecl install xdebug \
&& docker-php-ext-enable xdebug \
&& apk del \
linux-headers \
$PHPIZE_DEPS \
&& rm -rf /var/cache/*
ENV XDEBUG_OUT_DIR=/var/www/xdebug_out
CMD [ "bootstrap.sh" ]

87
docker/php/bootstrap.sh Executable file
View File

@ -0,0 +1,87 @@
#!/bin/sh
set -eu
function set_cfg() {
if [ -L "/var/www/inc/$1" ]; then
echo "INFO: Resetting $1"
rm "/var/www/inc/$1"
cp "/code/inc/$1" "/var/www/inc/$1"
chown www-data "/var/www/inc/$1"
chgrp www-data "/var/www/inc/$1"
chmod 600 "/var/www/inc/$1"
else
echo "INFO: Using existing $1"
fi
}
if ! mountpoint -q /var/www; then
echo "WARNING: '/var/www' is not a mountpoint. All the data will remain inside the container!"
fi
if [ ! -w /var/www ] ; then
echo "ERROR: '/var/www' is not writable. Closing."
exit 1
fi
if [ -z "${XDEBUG_OUT_DIR:-''}" ] ; then
echo "INFO: Initializing xdebug out directory at $XDEBUG_OUT_DIR"
mkdir -p "$XDEBUG_OUT_DIR"
chown www-data "$XDEBUG_OUT_DIR"
chgrp www-data "$XDEBUG_OUT_DIR"
chmod 755 "$XDEBUG_OUT_DIR"
fi
# Link the entrypoints from the exposed directory.
ln -nfs \
/code/tools/ \
/code/*.php \
/code/LICENSE.* \
/code/install.sql \
/var/www/
# Static files accessible from the webserver must be copied.
cp -ur /code/static /var/www/
cp -ur /code/stylesheets /var/www/
# Ensure correct permissions are set, since this might be bind mount.
chown www-data /var/www
chgrp www-data /var/www
# Initialize an empty robots.txt with the default if it doesn't exist.
touch /var/www/robots.txt
# Link the cache and tmp files directory.
ln -nfs /var/tmp/vichan /var/www/tmp
# Link the javascript directory.
ln -nfs /code/js /var/www/
# Link the html templates directory and it's cache.
ln -nfs /code/templates /var/www/
ln -nfs -T /var/cache/template-cache /var/www/templates/cache
chown -h www-data /var/www/templates/cache
chgrp -h www-data /var/www/templates/cache
# Link the generic cache.
ln -nfs -T /var/cache/gen-cache /var/www/tmp/cache
chown -h www-data /var/www/tmp/cache
chgrp -h www-data /var/www/tmp/cache
# Create the included files directory and link them
install -d -m 700 -o www-data -g www-data /var/www/inc
for file in /code/inc/*; do
file="${file##*/}"
if [ ! -e /var/www/inc/$file ]; then
ln -s /code/inc/$file /var/www/inc/
fi
done
# Copy an empty instance configuration if the file is a link (it was linked because it did not exist before).
set_cfg 'instance-config.php'
set_cfg 'secrets.php'
# Link the composer dependencies.
ln -nfs /code/vendor /var/www/
# Start the php-fpm server.
exec php-fpm

2
docker/php/jit.ini Normal file
View File

@ -0,0 +1,2 @@
opcache.jit_buffer_size=192M
opcache.jit=tracing

13
docker/php/www.conf Normal file
View File

@ -0,0 +1,13 @@
[www]
access.log = /proc/self/fd/2
; Ensure worker stdout and stderr are sent to the main error log.
catch_workers_output = yes
decorate_workers_output = no
user = www-data
group = www-data
listen = 127.0.0.1:9000
pm = static
pm.max_children = 16

View File

@ -0,0 +1,7 @@
zend_extension=xdebug
[xdebug]
xdebug.mode = profile
xdebug.start_with_request = start
error_reporting = E_ALL
xdebug.output_dir = /var/www/xdebug_out

View File

@ -0,0 +1,28 @@
<?php
namespace Vichan\Data\Driver;
defined('TINYBOARD') or exit;
class ApcuCacheDriver implements CacheDriver {
public function get(string $key): mixed {
$success = false;
$ret = \apcu_fetch($key, $success);
if ($success === false) {
return null;
}
return $ret;
}
public function set(string $key, mixed $value, mixed $expires = false): void {
\apcu_store($key, $value, (int)$expires);
}
public function delete(string $key): void {
\apcu_delete($key);
}
public function flush(): void {
\apcu_clear_cache();
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace Vichan\Data\Driver;
defined('TINYBOARD') or exit;
/**
* A simple process-wide PHP array.
*/
class ArrayCacheDriver implements CacheDriver {
private static $inner = [];
public function get(string $key) {
return isset(self::$inner[$key]) ? self::$inner[$key] : null;
}
public function set(string $key, $value, $expires = false): void {
self::$inner[$key] = $value;
}
public function delete(string $key): void {
unset(self::$inner[$key]);
}
public function flush(): void {
self::$inner = [];
}
}

View File

@ -0,0 +1,38 @@
<?php
namespace Vichan\Data\Driver;
defined('TINYBOARD') or exit;
interface CacheDriver {
/**
* Get the value of associated with the key.
*
* @param string $key The key of the value.
* @return mixed|null The value associated with the key, or null if there is none.
*/
public function get(string $key);
/**
* Set a key-value pair.
*
* @param string $key The key.
* @param mixed $value The value.
* @param int|false $expires After how many seconds the pair will expire. Use false or ignore this parameter to keep
* the value until it gets evicted to make space for more items. Some drivers will always
* ignore this parameter and store the pair until it's removed.
*/
public function set(string $key, $value, $expires = false);
/**
* Delete a key-value pair.
*
* @param string $key The key.
*/
public function delete(string $key);
/**
* Delete all the key-value pairs.
*/
public function flush();
}

View File

@ -0,0 +1,28 @@
<?php
namespace Vichan\Data\Driver;
defined('TINYBOARD') or exit;
/**
* Log via the php function error_log.
*/
class ErrorLogLogDriver implements LogDriver {
use LogTrait;
private string $name;
private int $level;
public function __construct(string $name, int $level) {
$this->name = $name;
$this->level = $level;
}
public function log(int $level, string $message): void {
if ($level <= $this->level) {
$lv = $this->levelToString($level);
$line = "{$this->name} $lv: $message";
\error_log($line, 0, null, null);
}
}
}

View File

@ -0,0 +1,61 @@
<?php
namespace Vichan\Data\Driver;
defined('TINYBOARD') or exit;
/**
* Log to a file.
*/
class FileLogDriver implements LogDriver {
use LogTrait;
private string $name;
private int $level;
private mixed $fd;
public function __construct(string $name, int $level, string $file_path) {
/*
* error_log is slow as hell in it's 3rd mode, so use fopen + file locking instead.
* https://grobmeier.solutions/performance-ofnonblocking-write-to-files-via-php-21082009.html
*
* Whatever file appending is atomic is contentious:
* - There are no POSIX guarantees: https://stackoverflow.com/a/7237901
* - But linus suggested they are on linux, on some filesystems: https://web.archive.org/web/20151201111541/http://article.gmane.org/gmane.linux.kernel/43445
* - But it doesn't seem to be always the case: https://www.notthewizard.com/2014/06/17/are-files-appends-really-atomic/
*
* So we just use file locking to be sure.
*/
$this->fd = \fopen($file_path, 'a');
if ($this->fd === false) {
throw new \RuntimeException("Unable to open log file at $file_path");
}
$this->name = $name;
$this->level = $level;
// In some cases PHP does not run the destructor.
\register_shutdown_function([$this, 'close']);
}
public function __destruct() {
$this->close();
}
public function log(int $level, string $message): void {
if ($level <= $this->level) {
$lv = $this->levelToString($level);
$line = "{$this->name} $lv: $message\n";
\flock($this->fd, LOCK_EX);
\fwrite($this->fd, $line);
\fflush($this->fd);
\flock($this->fd, LOCK_UN);
}
}
public function close() {
\flock($this->fd, LOCK_UN);
\fclose($this->fd);
}
}

View File

@ -0,0 +1,155 @@
<?php
namespace Vichan\Data\Driver;
defined('TINYBOARD') or exit;
class FsCacheDriver implements CacheDriver {
private string $prefix;
private string $base_path;
private mixed $lock_fd;
private int|false $collect_chance_den;
private function prepareKey(string $key): string {
$key = \str_replace('/', '::', $key);
$key = \str_replace("\0", '', $key);
return $this->prefix . $key;
}
private function sharedLockCache(): void {
\flock($this->lock_fd, LOCK_SH);
}
private function exclusiveLockCache(): void {
\flock($this->lock_fd, LOCK_EX);
}
private function unlockCache(): void {
\flock($this->lock_fd, LOCK_UN);
}
private function collectImpl(): int {
/*
* A read lock is ok, since it's alright if we delete expired items from under the feet of other processes, and
* no other process add new cache items or refresh existing ones.
*/
$files = \glob($this->base_path . $this->prefix . '*', \GLOB_NOSORT);
$count = 0;
foreach ($files as $file) {
$data = \file_get_contents($file);
$wrapped = \json_decode($data, true, 512, \JSON_THROW_ON_ERROR);
if ($wrapped['expires'] !== false && $wrapped['expires'] <= \time()) {
if (@\unlink($file)) {
$count++;
}
}
}
return $count;
}
private function maybeCollect(): void {
if ($this->collect_chance_den !== false && \mt_rand(0, $this->collect_chance_den - 1) === 0) {
$this->collect_chance_den = false; // Collect only once per instance (aka process).
$this->collectImpl();
}
}
public function __construct(string $prefix, string $base_path, string $lock_file, int|false $collect_chance_den) {
if ($base_path[\strlen($base_path) - 1] !== '/') {
$base_path = "$base_path/";
}
if (!\is_dir($base_path)) {
throw new \RuntimeException("$base_path is not a directory!");
}
if (!\is_writable($base_path)) {
throw new \RuntimeException("$base_path is not writable!");
}
$this->lock_fd = \fopen($base_path . $lock_file, 'w');
if ($this->lock_fd === false) {
throw new \RuntimeException('Unable to open the lock file!');
}
$this->prefix = $prefix;
$this->base_path = $base_path;
$this->collect_chance_den = $collect_chance_den;
}
public function __destruct() {
$this->close();
}
public function get(string $key): mixed {
$key = $this->prepareKey($key);
$this->sharedLockCache();
// Collect expired items first so if the target key is expired we shortcut to failure in the next lines.
$this->maybeCollect();
$fd = \fopen($this->base_path . $key, 'r');
if ($fd === false) {
$this->unlockCache();
return null;
}
$data = \stream_get_contents($fd);
\fclose($fd);
$this->unlockCache();
$wrapped = \json_decode($data, true, 512, \JSON_THROW_ON_ERROR);
if ($wrapped['expires'] !== false && $wrapped['expires'] <= \time()) {
// Already expired, leave it there since we already released the lock and pretend it doesn't exist.
return null;
} else {
return $wrapped['inner'];
}
}
public function set(string $key, mixed $value, mixed $expires = false): void {
$key = $this->prepareKey($key);
$wrapped = [
'expires' => $expires ? \time() + $expires : false,
'inner' => $value
];
$data = \json_encode($wrapped);
$this->exclusiveLockCache();
$this->maybeCollect();
\file_put_contents($this->base_path . $key, $data);
$this->unlockCache();
}
public function delete(string $key): void {
$key = $this->prepareKey($key);
$this->exclusiveLockCache();
@\unlink($this->base_path . $key);
$this->maybeCollect();
$this->unlockCache();
}
public function collect(): int {
$this->sharedLockCache();
$count = $this->collectImpl();
$this->unlockCache();
return $count;
}
public function flush(): void {
$this->exclusiveLockCache();
$files = \glob($this->base_path . $this->prefix . '*', \GLOB_NOSORT);
foreach ($files as $file) {
@\unlink($file);
}
$this->unlockCache();
}
public function close(): void {
\fclose($this->lock_fd);
}
}

View File

@ -0,0 +1,131 @@
<?php
namespace Vichan\Data\Driver;
defined('TINYBOARD') or exit;
/**
* Honestly this is just a wrapper for cURL. Still useful to mock it and have an OOP API on PHP 7.
*/
class HttpDriver {
private $inner;
private int $timeout;
private int $max_file_size;
private function resetTowards(string $url, int $timeout): void {
\curl_reset($this->inner);
\curl_setopt_array($this->inner, [
\CURLOPT_URL => $url,
\CURLOPT_TIMEOUT => $timeout,
\CURLOPT_USERAGENT => 'Tinyboard',
\CURLOPT_PROTOCOLS => \CURLPROTO_HTTP | \CURLPROTO_HTTPS,
]);
}
public function __construct(int $timeout, int $max_file_size) {
$this->inner = \curl_init();
$this->timeout = $timeout;
$this->max_file_size = $max_file_size;
}
public function __destruct() {
\curl_close($this->inner);
}
/**
* Execute a GET request.
*
* @param string $endpoint Uri endpoint.
* @param ?array $data Optional GET parameters.
* @param int $timeout Optional request timeout in seconds. Use the default timeout if 0.
* @return string Returns the body of the response.
* @throws RuntimeException Throws on IO error.
*/
public function requestGet(string $endpoint, ?array $data, int $timeout = 0): string {
if (!empty($data)) {
$endpoint .= '?' . \http_build_query($data);
}
if ($timeout == 0) {
$timeout = $this->timeout;
}
$this->resetTowards($endpoint, $timeout);
\curl_setopt($this->inner, \CURLOPT_RETURNTRANSFER, true);
$ret = \curl_exec($this->inner);
if ($ret === false) {
throw new \RuntimeException(\curl_error($this->inner));
}
return $ret;
}
/**
* Execute a POST request.
*
* @param string $endpoint Uri endpoint.
* @param ?array $data Optional POST parameters.
* @param int $timeout Optional request timeout in seconds. Use the default timeout if 0.
* @return string Returns the body of the response.
* @throws RuntimeException Throws on IO error.
*/
public function requestPost(string $endpoint, ?array $data, int $timeout = 0): string {
if ($timeout == 0) {
$timeout = $this->timeout;
}
$this->resetTowards($endpoint, $timeout);
\curl_setopt($this->inner, \CURLOPT_POST, true);
if (!empty($data)) {
\curl_setopt($this->inner, \CURLOPT_POSTFIELDS, \http_build_query($data));
}
\curl_setopt($this->inner, \CURLOPT_RETURNTRANSFER, true);
$ret = \curl_exec($this->inner);
if ($ret === false) {
throw new \RuntimeException(\curl_error($this->inner));
}
return $ret;
}
/**
* Download the url's target with curl.
*
* @param string $url Url to the file to download.
* @param ?array $data Optional GET parameters.
* @param resource $fd File descriptor to save the content to.
* @param int $timeout Optional request timeout in seconds. Use the default timeout if 0.
* @return bool Returns true on success, false if the file was too large.
* @throws RuntimeException Throws on IO error.
*/
public function requestGetInto(string $endpoint, ?array $data, $fd, int $timeout = 0): bool {
if (!empty($data)) {
$endpoint .= '?' . \http_build_query($data);
}
if ($timeout == 0) {
$timeout = $this->timeout;
}
$this->resetTowards($endpoint, $timeout);
// Adapted from: https://stackoverflow.com/a/17642638
$opt = (\PHP_MAJOR_VERSION >= 8 && \PHP_MINOR_VERSION >= 2) ? \CURLOPT_XFERINFOFUNCTION : \CURLOPT_PROGRESSFUNCTION;
\curl_setopt_array($this->inner, [
\CURLOPT_NOPROGRESS => false,
$opt => fn($res, $next_dl, $dl, $next_up, $up) => (int)($dl <= $this->max_file_size),
\CURLOPT_FAILONERROR => true,
\CURLOPT_FOLLOWLOCATION => false,
\CURLOPT_FILE => $fd,
\CURLOPT_IPRESOLVE => CURL_IPRESOLVE_V4,
]);
$ret = \curl_exec($this->inner);
if ($ret === false) {
if (\curl_errno($this->inner) === CURLE_ABORTED_BY_CALLBACK) {
return false;
}
throw new \RuntimeException(\curl_error($this->inner));
}
return true;
}
}

View File

@ -0,0 +1,22 @@
<?php
namespace Vichan\Data\Driver;
defined('TINYBOARD') or exit;
interface LogDriver {
public const EMERG = \LOG_EMERG;
public const ERROR = \LOG_ERR;
public const WARNING = \LOG_WARNING;
public const NOTICE = \LOG_NOTICE;
public const INFO = \LOG_INFO;
public const DEBUG = \LOG_DEBUG;
/**
* Log a message if the level of relevancy is at least the minimum.
*
* @param int $level Message level. Use Log interface constants.
* @param string $message The message to log.
*/
public function log(int $level, string $message): void;
}

View File

@ -0,0 +1,26 @@
<?php
namespace Vichan\Data\Driver;
defined('TINYBOARD') or exit;
trait LogTrait {
public static function levelToString(int $level): string {
switch ($level) {
case LogDriver::EMERG:
return 'EMERG';
case LogDriver::ERROR:
return 'ERROR';
case LogDriver::WARNING:
return 'WARNING';
case LogDriver::NOTICE:
return 'NOTICE';
case LogDriver::INFO:
return 'INFO';
case LogDriver::DEBUG:
return 'DEBUG';
default:
throw new \InvalidArgumentException('Not a logging level');
}
}
}

View File

@ -0,0 +1,43 @@
<?php
namespace Vichan\Data\Driver;
defined('TINYBOARD') or exit;
class MemcachedCacheDriver implements CacheDriver {
private \Memcached $inner;
public function __construct(string $prefix, string $memcached_server) {
$this->inner = new \Memcached();
if (!$this->inner->setOption(\Memcached::OPT_BINARY_PROTOCOL, true)) {
throw new \RuntimeException('Unable to set the memcached protocol!');
}
if (!$this->inner->setOption(\Memcached::OPT_PREFIX_KEY, $prefix)) {
throw new \RuntimeException('Unable to set the memcached prefix!');
}
if (!$this->inner->addServers($memcached_server)) {
throw new \RuntimeException('Unable to add the memcached server!');
}
}
public function get(string $key): mixed {
$ret = $this->inner->get($key);
// If the returned value is false but the retrival was a success, then the value stored was a boolean false.
if ($ret === false && $this->inner->getResultCode() !== \Memcached::RES_SUCCESS) {
return null;
}
return $ret;
}
public function set(string $key, mixed $value, mixed $expires = false): void {
$this->inner->set($key, $value, (int)$expires);
}
public function delete(string $key): void {
$this->inner->delete($key);
}
public function flush(): void {
$this->inner->flush();
}
}

View File

@ -0,0 +1,26 @@
<?php
namespace Vichan\Data\Driver;
defined('TINYBOARD') or exit;
/**
* No-op cache. Useful for testing.
*/
class NoneCacheDriver implements CacheDriver {
public function get(string $key): mixed {
return null;
}
public function set(string $key, mixed $value, mixed $expires = false): void {
// No-op.
}
public function delete(string $key): void {
// No-op.
}
public function flush(): void {
// No-op.
}
}

View File

@ -0,0 +1,48 @@
<?php
namespace Vichan\Data\Driver;
defined('TINYBOARD') or exit;
class RedisCacheDriver implements CacheDriver {
private string $prefix;
private \Redis $inner;
public function __construct(string $prefix, string $host, int $port, ?string $password, string $database) {
$this->inner = new \Redis();
$this->inner->connect($host, $port);
if ($password) {
$this->inner->auth($password);
}
if (!$this->inner->select($database)) {
throw new \RuntimeException('Unable to connect to Redis!');
}
$$this->prefix = $prefix;
}
public function get(string $key): mixed {
$ret = $this->inner->get($this->prefix . $key);
if ($ret === false) {
return null;
}
return \json_decode($ret, true);
}
public function set(string $key, mixed $value, mixed $expires = false): void {
if ($expires === false) {
$this->inner->set($this->prefix . $key, \json_encode($value));
} else {
$expires = $expires * 1000; // Seconds to milliseconds.
$this->inner->setex($this->prefix . $key, $expires, \json_encode($value));
}
}
public function delete(string $key): void {
$this->inner->del($this->prefix . $key);
}
public function flush(): void {
$this->inner->flushDB();
}
}

View File

@ -0,0 +1,27 @@
<?php
namespace Vichan\Data\Driver;
defined('TINYBOARD') or exit;
/**
* Log to php's standard error file stream.
*/
class StderrLogDriver implements LogDriver {
use LogTrait;
private string $name;
private int $level;
public function __construct(string $name, int $level) {
$this->name = $name;
$this->level = $level;
}
public function log(int $level, string $message): void {
if ($level <= $this->level) {
$lv = $this->levelToString($level);
\fwrite(\STDERR, "{$this->name} $lv: $message\n");
}
}
}

View File

@ -0,0 +1,35 @@
<?php
namespace Vichan\Data\Driver;
defined('TINYBOARD') or exit;
/**
* Log to syslog.
*/
class SyslogLogDriver implements LogDriver {
private int $level;
public function __construct(string $name, int $level, bool $print_stderr) {
$flags = \LOG_ODELAY;
if ($print_stderr) {
$flags |= \LOG_PERROR;
}
if (!\openlog($name, $flags, \LOG_USER)) {
throw new \RuntimeException('Unable to open syslog');
}
$this->level = $level;
}
public function log(int $level, string $message): void {
if ($level <= $this->level) {
if (isset($_SERVER['REMOTE_ADDR'], $_SERVER['REQUEST_METHOD'], $_SERVER['REQUEST_URI'])) {
// CGI
\syslog($level, "$message - client: {$_SERVER['REMOTE_ADDR']}, request: \"{$_SERVER['REQUEST_METHOD']} {$_SERVER['REQUEST_URI']}\"");
} else {
\syslog($level, $message);
}
}
}
}

View File

@ -1,191 +1,5 @@
<?php
/*
* Copyright (c) 2010-2013 Tinyboard Development Group
* Anti-bot.php has been deprecated and removed due to its functions not being necessary and being easily bypassable, by both customized and uncustomized spambots.
*/
defined('TINYBOARD') or exit;
$hidden_inputs_twig = array();
class AntiBot {
public $salt, $inputs = array(), $index = 0;
public static function randomString($length, $uppercase = false, $special_chars = false, $unicode_chars = false) {
$chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
if ($uppercase)
$chars .= 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
if ($special_chars)
$chars .= ' ~!@#$%^&*()_+,./;\'[]\\{}|:<>?=-` ';
if ($unicode_chars) {
$len = strlen($chars) / 10;
for ($n = 0; $n < $len; $n++)
$chars .= mb_convert_encoding('&#' . mt_rand(0x2600, 0x26FF) . ';', 'UTF-8', 'HTML-ENTITIES');
}
$chars = preg_split('//u', $chars, -1, PREG_SPLIT_NO_EMPTY);
$ch = array();
// fill up $ch until we reach $length
while (count($ch) < $length) {
$n = $length - count($ch);
$keys = array_rand($chars, $n > count($chars) ? count($chars) : $n);
if ($n == 1) {
$ch[] = $chars[$keys];
break;
}
shuffle($keys);
foreach ($keys as $key)
$ch[] = $chars[$key];
}
$chars = $ch;
return implode('', $chars);
}
public static function make_confusing($string) {
$chars = preg_split('//u', $string, -1, PREG_SPLIT_NO_EMPTY);
foreach ($chars as &$c) {
if (mt_rand(0, 3) != 0)
$c = utf8tohtml($c);
else
$c = mb_encode_numericentity($c, array(0, 0xffff, 0, 0xffff), 'UTF-8');
}
return implode('', $chars);
}
public function __construct(array $salt = array()) {
global $config;
if (!empty($salt)) {
// create a salted hash of the "extra salt"
$this->salt = implode(':', $salt);
} else {
$this->salt = '';
}
shuffle($config['spam']['hidden_input_names']);
$input_count = mt_rand($config['spam']['hidden_inputs_min'], $config['spam']['hidden_inputs_max']);
$hidden_input_names_x = 0;
for ($x = 0; $x < $input_count ; $x++) {
if ($hidden_input_names_x === false || mt_rand(0, 2) == 0) {
// Use an obscure name
$name = $this->randomString(mt_rand(10, 40), false, false, $config['spam']['unicode']);
} else {
// Use a pre-defined confusing name
$name = $config['spam']['hidden_input_names'][$hidden_input_names_x++];
if ($hidden_input_names_x >= count($config['spam']['hidden_input_names']))
$hidden_input_names_x = false;
}
if (mt_rand(0, 2) == 0) {
// Value must be null
$this->inputs[$name] = '';
} elseif (mt_rand(0, 4) == 0) {
// Numeric value
$this->inputs[$name] = (string)mt_rand(0, 100000);
} else {
// Obscure value
$this->inputs[$name] = $this->randomString(mt_rand(5, 100), true, true, $config['spam']['unicode']);
}
}
}
public static function space() {
if (mt_rand(0, 3) != 0)
return ' ';
return str_repeat(' ', mt_rand(1, 3));
}
public function html($count = false) {
global $config;
$elements = array(
'<input type="hidden" name="%name%" value="%value%">',
'<input type="hidden" value="%value%" name="%name%">',
'<input name="%name%" value="%value%" type="hidden">',
'<input value="%value%" name="%name%" type="hidden">',
'<input style="display:none" type="text" name="%name%" value="%value%">',
'<input style="display:none" type="text" value="%value%" name="%name%">',
'<span style="display:none"><input type="text" name="%name%" value="%value%"></span>',
'<div style="display:none"><input type="text" name="%name%" value="%value%"></div>',
'<div style="display:none"><input type="text" name="%name%" value="%value%"></div>',
'<textarea style="display:none" name="%name%">%value%</textarea>',
'<textarea name="%name%" style="display:none">%value%</textarea>'
);
$html = '';
if ($count === false) {
$count = mt_rand(1, abs(count($this->inputs) / 15) + 1);
}
if ($count === true) {
// all elements
$inputs = array_slice($this->inputs, $this->index);
} else {
$inputs = array_slice($this->inputs, $this->index, $count);
}
$this->index += count($inputs);
foreach ($inputs as $name => $value) {
$element = false;
while (!$element) {
$element = $elements[array_rand($elements)];
$element = str_replace(' ', self::space(), $element);
if (mt_rand(0, 5) == 0)
$element = str_replace('>', self::space() . '>', $element);
if (strpos($element, 'textarea') !== false && $value == '') {
// There have been some issues with mobile web browsers and empty <textarea>'s.
$element = false;
}
}
$element = str_replace('%name%', utf8tohtml($name), $element);
if (mt_rand(0, 2) == 0)
$value = $this->make_confusing($value);
else
$value = utf8tohtml($value);
if (strpos($element, 'textarea') === false)
$value = str_replace('"', '&quot;', $value);
$element = str_replace('%value%', $value, $element);
$html .= $element;
}
return $html;
}
public function reset() {
$this->index = 0;
}
public function hash() {
global $config;
// This is the tricky part: create a hash to validate it after
// First, sort the keys in alphabetical order (A-Z)
$inputs = $this->inputs;
ksort($inputs);
$hash = '';
// Iterate through each input
foreach ($inputs as $name => $value) {
$hash .= $name . '=' . $value;
}
// Add a salt to the hash
$hash .= $config['cookies']['salt'];
// Use SHA1 for the hash
return sha1($hash . $this->salt);
}
}

View File

@ -9,14 +9,49 @@ defined('TINYBOARD') or exit;
* Class for generating json API compatible with 4chan API
*/
class Api {
function __construct(){
global $config;
/**
* Translation from local fields to fields in 4chan-style API
*/
$this->config = $config;
private bool $show_filename;
private bool $hide_email;
private bool $country_flags;
private array $postFields;
$this->postFields = array(
private const INTS = [
'no' => 1,
'resto' => 1,
'time' => 1,
'tn_w' => 1,
'tn_h' => 1,
'w' => 1,
'h' => 1,
'fsize' => 1,
'omitted_posts' => 1,
'omitted_images' => 1,
'replies' => 1,
'images' => 1,
'sticky' => 1,
'locked' => 1,
'last_modified' => 1
];
private const THREADS_PAGE_FIELDS = [
'id' => 'no',
'bump' => 'last_modified'
];
private const FILE_FIELDS = [
'thumbheight' => 'tn_h',
'thumbwidth' => 'tn_w',
'height' => 'h',
'width' => 'w',
'size' => 'fsize'
];
public function __construct(bool $show_filename, bool $hide_email, bool $country_flags) {
// Translation from local fields to fields in 4chan-style API
$this->show_filename = $show_filename;
$this->hide_email = $hide_email;
$this->country_flags = $country_flags;
$this->postFields = [
'id' => 'no',
'thread' => 'resto',
'subject' => 'sub',
@ -35,83 +70,65 @@ class Api {
'cycle' => 'cyclical',
'bump' => 'last_modified',
'embed' => 'embed',
);
$this->threadsPageFields = array(
'id' => 'no',
'bump' => 'last_modified'
);
$this->fileFields = array(
'thumbheight' => 'tn_h',
'thumbwidth' => 'tn_w',
'height' => 'h',
'width' => 'w',
'size' => 'fsize',
);
];
if (isset($config['api']['extra_fields']) && gettype($config['api']['extra_fields']) == 'array'){
$this->postFields = array_merge($this->postFields, $config['api']['extra_fields']);
}
}
private static $ints = array(
'no' => 1,
'resto' => 1,
'time' => 1,
'tn_w' => 1,
'tn_h' => 1,
'w' => 1,
'h' => 1,
'fsize' => 1,
'omitted_posts' => 1,
'omitted_images' => 1,
'replies' => 1,
'images' => 1,
'sticky' => 1,
'locked' => 1,
'last_modified' => 1
);
private function translateFields($fields, $object, &$apiPost) {
foreach ($fields as $local => $translated) {
if (!isset($object->$local))
if (!isset($object->$local)) {
continue;
}
$toInt = isset(self::$ints[$translated]);
$toInt = isset(self::INTS[$translated]);
$val = $object->$local;
if ($this->hide_email && $local === 'email') {
$val = '';
}
if ($val !== null && $val !== '') {
$apiPost[$translated] = $toInt ? (int) $val : $val;
}
}
}
private function translateFile($file, $post, &$apiPost) {
$this->translateFields($this->fileFields, $file, $apiPost);
$apiPost['filename'] = @substr($file->name, 0, strrpos($file->name, '.'));
$this->translateFields(self::FILE_FIELDS, $file, $apiPost);
$dotPos = strrpos($file->file, '.');
$apiPost['ext'] = substr($file->file, $dotPos);
$apiPost['tim'] = substr($file->file, 0, $dotPos);
if ($this->show_filename) {
$apiPost['filename'] = @substr($file->name, 0, strrpos($file->name, '.'));
} else {
$apiPost['filename'] = substr($file->file, 0, $dotPos);
}
if (isset ($file->hash) && $file->hash) {
$apiPost['md5'] = base64_encode(hex2bin($file->hash));
}
else if (isset ($post->filehash) && $post->filehash) {
} elseif (isset ($post->filehash) && $post->filehash) {
$apiPost['md5'] = base64_encode(hex2bin($post->filehash));
}
}
private function translatePost($post, $threadsPage = false) {
private function translatePost($post, bool $threadsPage = false) {
global $config, $board;
$apiPost = array();
$fields = $threadsPage ? $this->threadsPageFields : $this->postFields;
$apiPost = [];
$fields = $threadsPage ? self::THREADS_PAGE_FIELDS : $this->postFields;
$this->translateFields($fields, $post, $apiPost);
if (isset($config['poster_ids']) && $config['poster_ids']) $apiPost['id'] = poster_id($post->ip, $post->thread, $board['uri']);
if ($threadsPage) return $apiPost;
if (isset($config['poster_ids']) && $config['poster_ids']) {
$apiPost['id'] = poster_id($post->ip, $post->thread ?? $post->id);
}
if ($threadsPage) {
return $apiPost;
}
// Handle country field
if (isset($post->body_nomarkup) && $this->config['country_flags']) {
if (isset($post->body_nomarkup) && $this->country_flags) {
$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']);
@ -131,12 +148,15 @@ class 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();
if (sizeof($post->files) > 1) {
$extra_files = [];
foreach ($post->files as $i => $f) {
if ($i == 0) {
continue;
}
$extra_file = [];
$this->translateFile($f, $post, $extra_file);
$extra_files[] = $extra_file;
@ -148,8 +168,8 @@ class Api {
return $apiPost;
}
function translateThread(Thread $thread, $threadsPage = false) {
$apiPosts = array();
public function translateThread(Thread $thread, bool $threadsPage = false) {
$apiPosts = [];
$op = $this->translatePost($thread, $threadsPage);
if (!$threadsPage) $op['resto'] = 0;
$apiPosts['posts'][] = $op;
@ -161,16 +181,16 @@ class Api {
return $apiPosts;
}
function translatePage(array $threads) {
$apiPage = array();
public function translatePage(array $threads) {
$apiPage = [];
foreach ($threads as $thread) {
$apiPage['threads'][] = $this->translateThread($thread);
}
return $apiPage;
}
function translateCatalogPage(array $threads, $threadsPage = false) {
$apiPage = array();
public function translateCatalogPage(array $threads, bool $threadsPage = false) {
$apiPage = [];
foreach ($threads as $thread) {
$ts = $this->translateThread($thread, $threadsPage);
$apiPage['threads'][] = current($ts['posts']);
@ -178,8 +198,8 @@ class Api {
return $apiPage;
}
function translateCatalog($catalog, $threadsPage = false) {
$apiCatalog = array();
public function translateCatalog($catalog, bool $threadsPage = false) {
$apiCatalog = [];
foreach ($catalog as $page => $threads) {
$apiPage = $this->translateCatalogPage($threads, $threadsPage);
$apiPage['page'] = $page;

View File

@ -1,8 +1,163 @@
<?php
use Vichan\Functions\Format;
use Lifo\IP\CIDR;
class Bans {
static private function shouldDelete(array $ban, bool $require_ban_view) {
return $ban['expires'] && ($ban['seen'] || !$require_ban_view) && $ban['expires'] < time();
}
static private function deleteBans(array $ban_ids) {
$len = count($ban_ids);
if ($len === 1) {
$query = prepare('DELETE FROM ``bans`` WHERE `id` = :id');
$query->bindValue(':id', $ban_ids[0], PDO::PARAM_INT);
$query->execute() or error(db_error());
Vichan\Functions\Theme\rebuild_themes('bans');
} elseif ($len >= 1) {
// Build the query.
$query = 'DELETE FROM ``bans`` WHERE `id` IN (';
for ($i = 0; $i < $len; $i++) {
$query .= ":id{$i},";
}
// Substitute the last comma with a parenthesis.
substr_replace($query, ')', strlen($query) - 1);
// Bind the params
$query = prepare($query);
for ($i = 0; $i < $len; $i++) {
$query->bindValue(":id{$i}", (int)$ban_ids[$i], PDO::PARAM_INT);
}
$query->execute() or error(db_error());
Vichan\Functions\Theme\rebuild_themes('bans');
}
}
static private function findSingleAutoGc(string $ip, int $ban_id, bool $require_ban_view) {
// Use OR in the query to also garbage collect bans.
$query = prepare(
'SELECT ``bans``.* FROM ``bans``
WHERE ((`ipstart` = :ip OR (:ip >= `ipstart` AND :ip <= `ipend`)) OR (``bans``.id = :id))
ORDER BY `expires` IS NULL, `expires` DESC'
);
$query->bindValue(':id', $ban_id);
$query->bindValue(':ip', inet_pton($ip));
$query->execute() or error(db_error($query));
$found_ban = null;
$to_delete_list = [];
while ($ban = $query->fetch(PDO::FETCH_ASSOC)) {
if (self::shouldDelete($ban, $require_ban_view)) {
$to_delete_list[] = $ban['id'];
} elseif ($ban['id'] === $ban_id) {
if ($ban['post']) {
$ban['post'] = json_decode($ban['post'], true);
}
$ban['mask'] = self::range_to_string([$ban['ipstart'], $ban['ipend']]);
$found_ban = $ban;
}
}
self::deleteBans($to_delete_list);
return $found_ban;
}
static private function findSingleNoGc(int $ban_id) {
$query = prepare(
'SELECT ``bans``.* FROM ``bans``
WHERE ``bans``.id = :id
ORDER BY `expires` IS NULL, `expires` DESC
LIMIT 1'
);
$query->bindValue(':id', $ban_id);
$query->execute() or error(db_error($query));
$ret = $query->fetch(PDO::FETCH_ASSOC);
if ($query->rowCount() == 0) {
return null;
} else {
if ($ret['post']) {
$ret['post'] = json_decode($ret['post'], true);
}
$ret['mask'] = self::range_to_string([$ret['ipstart'], $ret['ipend']]);
return $ret;
}
}
static private function findAutoGc(?string $ip, $board, bool $get_mod_info, bool $require_ban_view, ?int $ban_id): array {
$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`)) OR (``bans``.id = :id))
ORDER BY `expires` IS NULL, `expires` DESC');
if ($board !== false) {
$query->bindValue(':board', $board, PDO::PARAM_STR);
}
$query->bindValue(':id', $ban_id);
$query->bindValue(':ip', inet_pton($ip));
$query->execute() or error(db_error($query));
$ban_list = [];
$to_delete_list = [];
while ($ban = $query->fetch(PDO::FETCH_ASSOC)) {
if (self::shouldDelete($ban, $require_ban_view)) {
$to_delete_list[] = $ban['id'];
} else {
if ($ban['post']) {
$ban['post'] = json_decode($ban['post'], true);
}
$ban['mask'] = self::range_to_string([$ban['ipstart'], $ban['ipend']]);
$ban_list[] = $ban;
}
}
self::deleteBans($to_delete_list);
return $ban_list;
}
static private function findNoGc(?string $ip, string $board, bool $get_mod_info, ?int $ban_id): array {
$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`)) OR (``bans``.id = :id))
AND (`expires` IS NULL OR `expires` >= :curr_time)
ORDER BY `expires` IS NULL, `expires` DESC');
if ($board !== false) {
$query->bindValue(':board', $board, PDO::PARAM_STR);
}
$query->bindValue(':id', $ban_id);
$query->bindValue(':ip', inet_pton($ip));
$query->bindValue(':curr_time', time());
$query->execute() or error(db_error($query));
$ban_list = $query->fetchAll(PDO::FETCH_ASSOC);
array_walk($ban_list, function (&$ban, $_index) {
if ($ban['post']) {
$ban['post'] = json_decode($ban['post'], true);
}
$ban['mask'] = self::range_to_string([$ban['ipstart'], $ban['ipend']]);
});
return $ban_list;
}
static public function range_to_string($mask) {
list($ipstart, $ipend) = $mask;
@ -26,7 +181,7 @@ class Bans {
$cidr = new CIDR($mask);
$range = $cidr->getRange();
return array(inet_pton($range[0]), inet_pton($range[1]));
return [ inet_pton($range[0]), inet_pton($range[1]) ];
}
public static function parse_time($str) {
@ -110,42 +265,27 @@ class Bans {
return false;
}
return array($ipstart, $ipend);
return [$ipstart, $ipend];
}
static public function find($ip, $board = false, $get_mod_info = false) {
static public function findSingle(string $ip, int $ban_id, bool $require_ban_view, bool $auto_gc) {
if ($auto_gc) {
return self::findSingleAutoGc($ip, $ban_id, $require_ban_view);
} else {
return self::findSingleNoGc($ban_id);
}
}
static public function find(?string $ip, $board = false, bool $get_mod_info = false, ?int $ban_id = null, bool $auto_gc = true) {
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`)))
ORDER BY `expires` IS NULL, `expires` DESC');
if ($board !== false)
$query->bindValue(':board', $board, PDO::PARAM_STR);
$query->bindValue(':ip', inet_pton($ip));
$query->execute() or error(db_error($query));
$ban_list = array();
while ($ban = $query->fetch(PDO::FETCH_ASSOC)) {
if ($ban['expires'] && ($ban['seen'] || !$config['require_ban_view']) && $ban['expires'] < time()) {
self::delete($ban['id']);
if ($auto_gc) {
return self::findAutoGc($ip, $board, $get_mod_info, $config['require_ban_view'], $ban_id);
} else {
if ($ban['post'])
$ban['post'] = json_decode($ban['post'], true);
$ban['mask'] = self::range_to_string(array($ban['ipstart'], $ban['ipend']));
$ban['cmask'] = cloak_mask($ban['mask']);
$ban_list[] = $ban;
return self::findNoGc($ip, $board, $get_mod_info, $ban_id);
}
}
return $ban_list;
}
static public function stream_json($out = false, $filter_ips = false, $filter_staff = false, $board_access = false) {
$query = query("SELECT ``bans``.*, `username` FROM ``bans``
LEFT JOIN ``mods`` ON ``mods``.`id` = `creator`
@ -159,8 +299,7 @@ class Bans {
$end = end($bans);
foreach ($bans as &$ban) {
$uncloaked_mask = self::range_to_string(array($ban['ipstart'], $ban['ipend']));
$ban['mask'] = cloak_mask($uncloaked_mask);
$ban['mask'] = self::range_to_string([$ban['ipstart'], $ban['ipend']]);
if ($ban['post']) {
$post = json_decode($ban['post']);
@ -204,12 +343,24 @@ class Bans {
static public function seen($ban_id) {
$query = query("UPDATE ``bans`` SET `seen` = 1 WHERE `id` = " . (int)$ban_id) or error(db_error());
rebuildThemes('bans');
Vichan\Functions\Theme\rebuild_themes('bans');
}
static public function purge() {
$query = query("DELETE FROM ``bans`` WHERE `expires` IS NOT NULL AND `expires` < " . time() . " AND `seen` = 1") or error(db_error());
rebuildThemes('bans');
static public function purge($require_seen, $moratorium) {
if ($require_seen) {
$query = prepare("DELETE FROM ``bans`` WHERE `expires` IS NOT NULL AND `expires` + :moratorium < :curr_time AND `seen` = 1");
} else {
$query = prepare("DELETE FROM ``bans`` WHERE `expires` IS NOT NULL AND `expires` + :moratorium < :curr_time");
}
$query->bindValue(':moratorium', $moratorium);
$query->bindValue(':curr_time', time());
$query->execute() or error(db_error($query));
$affected = $query->rowCount();
if ($affected > 0) {
Vichan\Functions\Theme\rebuild_themes('bans');
}
return $affected;
}
static public function delete($ban_id, $modlog = false, $boards = false, $dont_rebuild = false) {
@ -227,8 +378,7 @@ class Bans {
if ($boards !== false && !in_array($ban['board'], $boards))
error($config['error']['noaccess']);
$mask = self::range_to_string(array($ban['ipstart'], $ban['ipend']));
$cloaked_mask = cloak_mask($mask);
$mask = self::range_to_string([$ban['ipstart'], $ban['ipend']]);
modLog("Removed ban #{$ban_id} for " .
(filter_var($mask, FILTER_VALIDATE_IP) !== false ? "<a href=\"?/IP/$cloaked_mask\">$cloaked_mask</a>" : $cloaked_mask));
@ -236,7 +386,7 @@ class Bans {
query("DELETE FROM ``bans`` WHERE `id` = " . (int)$ban_id) or error(db_error());
if (!$dont_rebuild) rebuildThemes('bans');
if (!$dont_rebuild) Vichan\Functions\Theme\rebuild_themes('bans');
return true;
}
@ -289,24 +439,33 @@ class Bans {
$query->bindValue(':board', null, PDO::PARAM_NULL);
if ($post) {
if (!isset($board['uri']))
openBoard($post['board']);
$post['board'] = $board['uri'];
/*
* The body can be so long to make the json longer than 64KBs, causing the query to fail.
* Truncate it to a safe length (32KBs). It could probably be longer, but if the deleted body is THAT big
* already, the likelihood of it being just assorted spam/garbage is about 101%.
*/
// We're on UTF-8 only, right...?
$post['body'] = mb_strcut($post['body'], 0, 32768);
$query->bindValue(':post', json_encode($post));
} else
$query->bindValue(':post', null, PDO::PARAM_NULL);
$query->execute() or error(db_error($query));
if (isset($mod['id']) && $mod['id'] == $mod_id) {
modLog('Created a new ' .
($length > 0 ? preg_replace('/^(\d+) (\w+?)s?$/', '$1-$2', until($length)) : 'permanent') .
' ban on ' .
($ban_board ? '/' . $ban_board . '/' : 'all boards') .
' for ' .
(filter_var($mask, FILTER_VALIDATE_IP) !== false ? "<a href=\"?/IP/$cloaked_mask\">$cloaked_mask</a>" : $cloaked_mask) .
' (<small>#' . $pdo->lastInsertId() . '</small>)' .
' with ' . ($reason ? 'reason: ' . utf8tohtml($reason) . '' : 'no reason'));
}
rebuildThemes('bans');
$ban_len = $length > 0 ? preg_replace('/^(\d+) (\w+?)s?$/', '$1-$2', Format\until($length)) : 'permanent';
$ban_board = $ban_board ? "/$ban_board/" : 'all boards';
$ban_ip = filter_var($mask, FILTER_VALIDATE_IP) !== false ? "<a href=\"?/IP/$cloaked_mask\">$cloaked_mask</a>" : $cloaked_mask;
$ban_id = $pdo->lastInsertId();
$ban_reason = $reason ? 'reason: ' . utf8tohtml($reason) : 'no reason';
modLog("Created a new $ban_len ban on $ban_board for $ban_ip (<small># $ban_id </small>) with $ban_reason");
Vichan\Functions\Theme\rebuild_themes('bans');
return $pdo->lastInsertId();
}

View File

@ -4,170 +4,89 @@
* Copyright (c) 2010-2013 Tinyboard Development Group
*/
use Vichan\Data\Driver\{CacheDriver, ApcuCacheDriver, ArrayCacheDriver, FsCacheDriver, MemcachedCacheDriver, NoneCacheDriver, RedisCacheDriver};
defined('TINYBOARD') or exit;
class Cache {
private static $cache;
public static function init() {
private static function buildCache(): CacheDriver {
global $config;
switch ($config['cache']['enabled']) {
case 'memcached':
self::$cache = new Memcached();
self::$cache->addServers($config['cache']['memcached']);
break;
return new MemcachedCacheDriver(
$config['cache']['prefix'],
$config['cache']['memcached']
);
case 'redis':
self::$cache = new Redis();
self::$cache->connect($config['cache']['redis'][0], $config['cache']['redis'][1]);
if ($config['cache']['redis'][2]) {
self::$cache->auth($config['cache']['redis'][2]);
}
self::$cache->select($config['cache']['redis'][3]) or die('cache select failure');
break;
return new RedisCacheDriver(
$config['cache']['prefix'],
$config['cache']['redis'][0],
$config['cache']['redis'][1],
$config['cache']['redis'][2],
$config['cache']['redis'][3]
);
case 'apcu':
return new ApcuCacheDriver;
case 'fs':
return new FsCacheDriver(
$config['cache']['prefix'],
"tmp/cache/{$config['cache']['prefix']}",
'.lock',
$config['auto_maintenance'] ? 1000 : false
);
case 'none':
return new NoneCacheDriver();
case 'php':
self::$cache = array();
break;
default:
return new ArrayCacheDriver();
}
}
public static function getCache(): CacheDriver {
static $cache;
return $cache ??= self::buildCache();
}
public static function get($key) {
global $config, $debug;
$key = $config['cache']['prefix'] . $key;
$data = false;
switch ($config['cache']['enabled']) {
case 'memcached':
if (!self::$cache)
self::init();
$data = self::$cache->get($key);
break;
case 'apc':
$data = apc_fetch($key);
break;
case 'xcache':
$data = xcache_get($key);
break;
case 'php':
$data = isset(self::$cache[$key]) ? self::$cache[$key] : false;
break;
case 'fs':
$key = str_replace('/', '::', $key);
$key = str_replace("\0", '', $key);
if (!file_exists('tmp/cache/'.$key)) {
$data = false;
}
else {
$data = file_get_contents('tmp/cache/'.$key);
$data = json_decode($data, true);
}
break;
case 'redis':
if (!self::$cache)
self::init();
$data = json_decode(self::$cache->get($key), true);
break;
$ret = self::getCache()->get($key);
if ($ret === null) {
$ret = false;
}
if ($config['debug'])
$debug['cached'][] = $key . ($data === false ? ' (miss)' : ' (hit)');
if ($config['debug']) {
$debug['cached'][] = $config['cache']['prefix'] . $key . ($ret === false ? ' (miss)' : ' (hit)');
}
return $data;
return $ret;
}
public static function set($key, $value, $expires = false) {
global $config, $debug;
$key = $config['cache']['prefix'] . $key;
if (!$expires)
if (!$expires) {
$expires = $config['cache']['timeout'];
switch ($config['cache']['enabled']) {
case 'memcached':
if (!self::$cache)
self::init();
self::$cache->set($key, $value, $expires);
break;
case 'redis':
if (!self::$cache)
self::init();
self::$cache->setex($key, $expires, json_encode($value));
break;
case 'apc':
apc_store($key, $value, $expires);
break;
case 'xcache':
xcache_set($key, $value, $expires);
break;
case 'fs':
$key = str_replace('/', '::', $key);
$key = str_replace("\0", '', $key);
file_put_contents('tmp/cache/'.$key, json_encode($value));
break;
case 'php':
self::$cache[$key] = $value;
break;
}
if ($config['debug'])
$debug['cached'][] = $key . ' (set)';
self::getCache()->set($key, $value, $expires);
if ($config['debug']) {
$debug['cached'][] = $config['cache']['prefix'] . $key . ' (set)';
}
}
public static function delete($key) {
global $config, $debug;
$key = $config['cache']['prefix'] . $key;
self::getCache()->delete($key);
switch ($config['cache']['enabled']) {
case 'memcached':
case 'redis':
if (!self::$cache)
self::init();
self::$cache->delete($key);
break;
case 'apc':
apc_delete($key);
break;
case 'xcache':
xcache_unset($key);
break;
case 'fs':
$key = str_replace('/', '::', $key);
$key = str_replace("\0", '', $key);
@unlink('tmp/cache/'.$key);
break;
case 'php':
unset(self::$cache[$key]);
break;
if ($config['debug']) {
$debug['cached'][] = $config['cache']['prefix'] . $key . ' (deleted)';
}
if ($config['debug'])
$debug['cached'][] = $key . ' (deleted)';
}
public static function flush() {
global $config;
switch ($config['cache']['enabled']) {
case 'memcached':
if (!self::$cache)
self::init();
return self::$cache->flush();
case 'apc':
return apc_clear_cache('user');
case 'php':
self::$cache = array();
break;
case 'fs':
$files = glob('tmp/cache/*');
foreach ($files as $file) {
unlink($file);
}
break;
case 'redis':
if (!self::$cache)
self::init();
return self::$cache->flushDB();
}
self::getCache()->flush();
return false;
}
}

View File

@ -41,7 +41,7 @@ class CzaksCaptcha {
function mutate_sizes() {
foreach ($this->content as &$v) {
if (!isset ($v['font-size']))
$v['font-size'] = rand($this->height/3 - 4, $this->height/3 + 8);
$v['font-size'] = rand(intval($this->height/3) - 4, intval($this->height/3) + 8);
}
}
function mutate_positions() {

View File

@ -1,9 +0,0 @@
SET NAMES utf8;
CREATE TABLE `captchas` (
`cookie` VARCHAR(50),
`extra` VARCHAR(200),
`text` VARCHAR(255),
`created_at` INT(11),
PRIMARY KEY (cookie, extra)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;

View File

@ -1,10 +1,8 @@
I integrated this from: https://github.com/ctrlcctrlv/infinity/commit/62a6dac022cb338f7b719d0c35a64ab3efc64658
<strike>First import the captcha/dbschema.sql in your database</strike> it is no longer required.
In inc/captcha/config.php change the database_name database_user database_password to your own settings.
Add js/captcha.js in your instance-config.php or config.php
Add js/captcha.js in your secrets.php or config.php
Go to Line 305 in the /inc/config file and copy the settings in instance config, while changing the url to your website.
Go to the line beneath it if you only want to enable it when posting a new thread.

File diff suppressed because it is too large Load Diff

91
inc/context.php Normal file
View File

@ -0,0 +1,91 @@
<?php
namespace Vichan;
use Vichan\Data\Driver\{CacheDriver, HttpDriver, ErrorLogLogDriver, FileLogDriver, LogDriver, StderrLogDriver, SyslogLogDriver};
use Vichan\Service\HCaptchaQuery;
use Vichan\Service\NativeCaptchaQuery;
use Vichan\Service\ReCaptchaQuery;
use Vichan\Service\RemoteCaptchaQuery;
defined('TINYBOARD') or exit;
class Context {
private array $definitions;
public function __construct(array $definitions) {
$this->definitions = $definitions;
}
public function get(string $name){
if (!isset($this->definitions[$name])) {
throw new \RuntimeException("Could not find a dependency named $name");
}
$ret = $this->definitions[$name];
if (is_callable($ret) && !is_string($ret) && !is_array($ret)) {
$ret = $ret($this);
$this->definitions[$name] = $ret;
}
return $ret;
}
}
function build_context(array $config): Context {
return new Context([
'config' => $config,
LogDriver::class => function($c) {
$config = $c->get('config');
$name = $config['log_system']['name'];
$level = $config['debug'] ? LogDriver::DEBUG : LogDriver::NOTICE;
$backend = $config['log_system']['type'];
// Check 'syslog' for backwards compatibility.
if ((isset($config['syslog']) && $config['syslog']) || $backend === 'syslog') {
return new SyslogLogDriver($name, $level, $this->config['log_system']['syslog_stderr']);
} elseif ($backend === 'file') {
return new FileLogDriver($name, $level, $this->config['log_system']['file_path']);
} elseif ($backend === 'stderr') {
return new StderrLogDriver($name, $level);
} else {
return new ErrorLogLogDriver($name, $level);
}
},
HttpDriver::class => function($c) {
$config = $c->get('config');
return new HttpDriver($config['upload_by_url_timeout'], $config['max_filesize']);
},
RemoteCaptchaQuery::class => function($c) {
$config = $c->get('config');
$http = $c->get(HttpDriver::class);
switch ($config['captcha']['provider']) {
case 'recaptcha':
return new ReCaptchaQuery($http, $config['captcha']['recaptcha']['secret']);
case 'hcaptcha':
return new HCaptchaQuery(
$http,
$config['captcha']['hcaptcha']['secret'],
$config['captcha']['hcaptcha']['sitekey']
);
default:
throw new \RuntimeException('No remote captcha service available');
}
},
NativeCaptchaQuery::class => function($c) {
$config = $c->get('config');
if ($config['captcha']['provider'] !== 'native') {
throw new \RuntimeException('No native captcha service available');
}
return new NativeCaptchaQuery(
$c->get(HttpDriver::class),
$config['domain'],
$config['captcha']['native']['provider_check'],
$config['captcha']['native']['extra']
);
},
CacheDriver::class => function($c) {
// Use the global for backwards compatibility.
return \cache::getCache();
}
]);
}

View File

@ -85,24 +85,24 @@ function sb_api($b) { global $config, $build_pages;
}
function sb_ukko() {
rebuildTheme("ukko", "post-thread");
Vichan\Functions\Theme\rebuild_theme("ukko", "post-thread");
return true;
}
function sb_catalog($b) {
if (!openBoard($b)) return false;
rebuildTheme("catalog", "post-thread", $b);
Vichan\Functions\Theme\rebuild_theme("catalog", "post-thread", $b);
return true;
}
function sb_recent() {
rebuildTheme("recent", "post-thread");
Vichan\Functions\Theme\rebuild_theme("recent", "post-thread");
return true;
}
function sb_sitemap() {
rebuildTheme("sitemap", "all");
Vichan\Functions\Theme\rebuild_theme("sitemap", "all");
return true;
}

View File

@ -103,7 +103,7 @@ function mysql_version() {
$v = explode('.', $version);
if (count($v) != 3)
return false;
return (int) sprintf("%02d%02d%02d", $v[0], $v[1], $v[2]);
return (int) sprintf("%02d%02d%02d", $v[0], $v[1], is_int($v[2]) ? (int)$v[2] : 0);
}
function prepare($query) {

View File

@ -71,7 +71,7 @@ function createBoardlist($mod=false) {
);
}
function error($message, $priority = true, $debug_stuff = false) {
function error($message, $priority = true, $debug_stuff = []) {
global $board, $mod, $config, $db_error;
if ($config['syslog'] && $priority !== false) {
@ -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),
@ -348,8 +348,25 @@ class Post {
$this->{$key} = $value;
}
if (isset($this->files) && $this->files)
$this->files = @json_decode($this->files);
if (isset($this->files) && $this->files) {
$this->files = is_string($this->files) ? json_decode($this->files) : $this->files;
// Compatibility for posts before individual file hashing
foreach ($this->files as $i => &$file) {
if (empty($file)) {
unset($this->files[$i]);
continue;
}
if (is_array($file)) {
if (!isset($file['hash'])) {
$file['hash'] = $this->filehash;
}
} else if (is_object($file)) {
if (!isset($file->hash)) {
$file->hash = $this->filehash;
}
}
}
}
$this->subject = utf8tohtml($this->subject);
$this->name = utf8tohtml($this->name);
@ -384,7 +401,18 @@ 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));
$options = [
'config' => $config,
'board' => $board,
'post' => &$this,
'index' => $index,
'mod' => $this->mod
];
if ($this->mod) {
$options['pm'] = create_pm_header();
}
return Element($config['file_post_reply'], $options);
}
};
@ -399,7 +427,7 @@ class Thread {
}
if (isset($this->files))
$this->files = @json_decode($this->files);
$this->files = is_string($this->files) ? json_decode($this->files) : $this->files;
$this->subject = utf8tohtml($this->subject);
$this->name = utf8tohtml($this->name);
@ -448,10 +476,22 @@ class Thread {
event('show-thread', $this);
$file = ($index && $config['file_board']) ? 'post_thread_fileboard.html' : 'post_thread.html';
$built = Element($file, array('config' => $config, 'board' => $board, 'post' => &$this, 'index' => $index, 'hasnoko50' => $hasnoko50, 'isnoko50' => $isnoko50, 'mod' => $this->mod));
$options = [
'config' => $config,
'board' => $board,
'post' => &$this,
'index' => $index,
'hasnoko50' => $hasnoko50,
'isnoko50' => $isnoko50,
'mod' => $this->mod
];
if ($this->mod) {
$options['pm'] = create_pm_header();
}
$file = ($index && $config['file_board']) ? $config['file_post_thread_fileboard'] : $config['file_post_thread'];
$built = Element($file, $options);
return $built;
}
};

View File

@ -11,24 +11,27 @@ class Filter {
private $condition;
private $post;
public function __construct(array $arr) {
foreach ($arr as $key => $value)
foreach ($arr as $key => $value) {
$this->$key = $value;
}
}
public function match($condition, $match) {
$condition = strtolower($condition);
$post = &$this->post;
switch($condition) {
case 'custom':
if (!is_callable($match))
if (!is_callable($match)) {
error('Custom condition for filter is not callable!');
}
return $match($post);
case 'flood-match':
if (!is_array($match))
if (!is_array($match)) {
error('Filter condition "flood-match" must be an array.');
}
// Filter out "flood" table entries which do not match this filter.
@ -38,26 +41,32 @@ class Filter {
foreach ($match as $flood_match_arg) {
switch ($flood_match_arg) {
case 'ip':
if ($flood_post['ip'] != $_SERVER['REMOTE_ADDR'])
if ($flood_post['ip'] != $_SERVER['REMOTE_ADDR']) {
continue 3;
}
break;
case 'body':
if ($flood_post['posthash'] != make_comment_hex($post['body_nomarkup']))
if ($flood_post['posthash'] != make_comment_hex($post['body_nomarkup'])) {
continue 3;
}
break;
case 'file':
if (!isset($post['filehash']))
if (!isset($post['filehash'])) {
return false;
if ($flood_post['filehash'] != $post['filehash'])
}
if ($flood_post['filehash'] != $post['filehash']) {
continue 3;
}
break;
case 'board':
if ($flood_post['board'] != $post['board'])
if ($flood_post['board'] != $post['board']) {
continue 3;
}
break;
case 'isreply':
if ($flood_post['isreply'] == $post['op'])
if ($flood_post['isreply'] == $post['op']) {
continue 3;
}
break;
default:
error('Invalid filter flood condition: ' . $flood_match_arg);
@ -67,7 +76,6 @@ class Filter {
}
$this->flood_check = $flood_check_matched;
return !empty($this->flood_check);
case 'flood-time':
foreach ($this->flood_check as $flood_post) {
@ -97,8 +105,9 @@ class Filter {
case 'filehash':
return $match === $post['filehash'];
case 'filename':
if (!$post['files'])
if (!$post['files']) {
return false;
}
foreach ($post['files'] as $file) {
if (preg_match($match, $file['filename'])) {
@ -107,8 +116,9 @@ class Filter {
}
return false;
case 'extension':
if (!$post['files'])
if (!$post['files']) {
return false;
}
foreach ($post['files'] as $file) {
if (preg_match($match, $file['extension'])) {
@ -126,6 +136,14 @@ class Filter {
return $post['board'] == $match;
case 'password':
return $post['password'] == $match;
case 'unshorten':
$extracted_urls = get_urls($post['body_nomarkup']);
foreach ($extracted_urls as $url) {
if (preg_match($match, trace_url($url))) {
return true;
}
}
return false;
default:
error('Unknown filter condition: ' . $condition);
}
@ -143,12 +161,14 @@ class Filter {
$query->bindValue(':body', "Autoban message: ".$this->post['body']);
$query->execute() or error(db_error($query));
}
if (isset ($this->action)) switch($this->action) {
if (isset($this->action)) {
switch($this->action) {
case 'reject':
error(isset($this->message) ? $this->message : 'Posting throttled by filter.');
case 'ban':
if (!isset($this->reason))
if (!isset($this->reason)) {
error('The ban action requires a reason.');
}
$this->expires = isset($this->expires) ? $this->expires : false;
$this->reject = isset($this->reject) ? $this->reject : true;
@ -157,8 +177,9 @@ class Filter {
Bans::new_ban($_SERVER['REMOTE_ADDR'], $this->reason, $this->expires, $this->all_boards ? false : $board['uri'], -1);
if ($this->reject) {
if (isset($this->message))
if (isset($this->message)) {
error($message);
}
checkBan($board['uri']);
exit;
@ -169,6 +190,7 @@ class Filter {
error('Unknown filter action: ' . $this->action);
}
}
}
public function check(array $post) {
$this->post = $post;
@ -176,11 +198,14 @@ class Filter {
if ($condition[0] == '!') {
$NOT = true;
$condition = substr($condition, 1);
} else $NOT = false;
} else {
$NOT = false;
}
if ($this->match($condition, $value) == $NOT)
if ($this->match($condition, $value) == $NOT) {
return false;
}
}
return true;
}
}
@ -192,15 +217,16 @@ 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;
foreach ($config['filters'] as $filter) {
if (isset($filter['condition']['flood-time']))
if (isset($filter['condition']['flood-time'])) {
$max_time = max($max_time, $filter['condition']['flood-time']);
}
}
}
$time = time() - $max_time;
@ -210,8 +236,9 @@ function purge_flood_table() {
function do_filters(array $post) {
global $config;
if (!isset($config['filters']) || empty($config['filters']))
if (!isset($config['filters']) || empty($config['filters'])) {
return;
}
foreach ($config['filters'] as $filter) {
if (isset($filter['condition']['flood-match'])) {
@ -240,10 +267,10 @@ function do_filters(array $post) {
foreach ($config['filters'] as $filter_array) {
$filter = new Filter($filter_array);
$filter->flood_check = $flood_check;
if ($filter->check($post))
if ($filter->check($post)) {
$filter->action();
}
}
purge_flood_table();
}

View File

@ -21,9 +21,7 @@ loadConfig();
function init_locale($locale, $error='error') {
if (extension_loaded('gettext')) {
if (setlocale(LC_ALL, $locale) === false) {
//$error('The specified locale (' . $locale . ') does not exist on your platform!');
}
setlocale(LC_ALL, $locale);
bindtextdomain('tinyboard', './inc/locale');
bind_textdomain_codeset('tinyboard', 'UTF-8');
textdomain('tinyboard');
@ -56,7 +54,8 @@ function loadConfig() {
if (isset($config['cache_config']) &&
$config['cache_config'] &&
$config = Cache::get('config_' . $boardsuffix ) ) {
$config = Cache::get('config_' . $boardsuffix))
{
$events = Cache::get('events_' . $boardsuffix );
define_groups();
@ -69,8 +68,7 @@ function loadConfig() {
$current_locale = $config['locale'];
init_locale($config['locale'], $error);
}
}
else {
} else {
$config = array();
reset_events();
@ -109,7 +107,7 @@ function loadConfig() {
}
if (!file_exists('inc/instance-config.php'))
$error('Tinyboard is not configured! Create inc/instance-config.php.');
$error('vichan is not configured! Create inc/instance-config.php.');
// Initialize locale as early as possible
@ -122,7 +120,7 @@ function loadConfig() {
else {
$config['locale'] = 'en';
$configstr = file_get_contents('inc/instance-config.php');
$configstr = file_get_contents('inc/secrets.php');
if (isset($board['dir']) && file_exists($board['dir'] . '/config.php')) {
$configstr .= file_get_contents($board['dir'] . '/config.php');
@ -207,6 +205,8 @@ function loadConfig() {
$config['image_bumplocked'] = $config['dir']['static'] . 'sage.gif';
if (!isset($config['image_deleted']))
$config['image_deleted'] = $config['dir']['static'] . 'deleted.png';
if (!isset($config['image_cyclical']))
$config['image_cyclical'] = $config['dir']['static'] . 'cycle.png';
if (isset($board)) {
if (!isset($config['uri_thumb']))
@ -240,13 +240,14 @@ function loadConfig() {
$__version = file_exists('.installed') ? trim(file_get_contents('.installed')) : false;
$config['version'] = $__version;
if ($config['allow_roll'])
event_handler('post', 'diceRoller');
if ($config['allow_roll']) {
event_handler('post', 'email_dice_roll');
}
if (in_array('webm', $config['allowed_ext_files']) ||
in_array('mp4', $config['allowed_ext_files']))
if (in_array('webm', $config['allowed_ext_files']) || in_array('mp4', $config['allowed_ext_files'])) {
event_handler('post', 'postHandler');
}
}
// Effectful config processing below:
date_default_timezone_set($config['timezone']);
@ -278,8 +279,7 @@ function loadConfig() {
if ($config['cache']['enabled'])
require_once 'inc/cache.php';
if (in_array('webm', $config['allowed_ext_files']) ||
in_array('mp4', $config['allowed_ext_files']))
if (in_array('webm', $config['allowed_ext_files']) || in_array('mp4', $config['allowed_ext_files']))
require_once 'inc/lib/webm/posthandler.php';
event('load-config');
@ -391,114 +391,6 @@ function define_groups() {
ksort($config['mod']['groups']);
}
function create_antibot($board, $thread = null) {
require_once dirname(__FILE__) . '/anti-bot.php';
return _create_antibot($board, $thread);
}
function rebuildThemes($action, $boardname = false) {
global $config, $board, $current_locale;
// Save the global variables
$_config = $config;
$_board = $board;
// List themes
if ($themes = Cache::get("themes")) {
// OK, we already have themes loaded
}
else {
$query = query("SELECT `theme` FROM ``theme_settings`` WHERE `name` IS NULL AND `value` IS NULL") or error(db_error());
$themes = array();
while ($theme = $query->fetch(PDO::FETCH_ASSOC)) {
$themes[] = $theme;
}
Cache::set("themes", $themes);
}
foreach ($themes as $theme) {
// Restore them
$config = $_config;
$board = $_board;
// Reload the locale
if ($config['locale'] != $current_locale) {
$current_locale = $config['locale'];
init_locale($config['locale']);
}
if (PHP_SAPI === 'cli') {
echo "Rebuilding theme ".$theme['theme']."... ";
}
rebuildTheme($theme['theme'], $action, $boardname);
if (PHP_SAPI === 'cli') {
echo "done\n";
}
}
// Restore them again
$config = $_config;
$board = $_board;
// Reload the locale
if ($config['locale'] != $current_locale) {
$current_locale = $config['locale'];
init_locale($config['locale']);
}
}
function loadThemeConfig($_theme) {
global $config;
if (!file_exists($config['dir']['themes'] . '/' . $_theme . '/info.php'))
return false;
// Load theme information into $theme
include $config['dir']['themes'] . '/' . $_theme . '/info.php';
return $theme;
}
function rebuildTheme($theme, $action, $board = false) {
global $config, $_theme;
$_theme = $theme;
$theme = loadThemeConfig($_theme);
if (file_exists($config['dir']['themes'] . '/' . $_theme . '/theme.php')) {
require_once $config['dir']['themes'] . '/' . $_theme . '/theme.php';
$theme['build_function']($action, themeSettings($_theme), $board);
}
}
function themeSettings($theme) {
if ($settings = Cache::get("theme_settings_".$theme)) {
return $settings;
}
$query = prepare("SELECT `name`, `value` FROM ``theme_settings`` WHERE `theme` = :theme AND `name` IS NOT NULL");
$query->bindValue(':theme', $theme);
$query->execute() or error(db_error($query));
$settings = array();
while ($s = $query->fetch(PDO::FETCH_ASSOC)) {
$settings[$s['name']] = $s['value'];
}
Cache::set("theme_settings_".$theme, $settings);
return $settings;
}
function sprintf3($str, $vars, $delim = '%') {
$replaces = array();
foreach ($vars as $k => $v) {
@ -515,12 +407,11 @@ function mb_substr_replace($string, $replacement, $start, $length) {
function setupBoard($array) {
global $board, $config;
$board = array(
$board = [
'uri' => $array['uri'],
'title' => $array['title'],
'subtitle' => $array['subtitle'],
#'indexed' => $array['indexed'],
);
];
// older versions
$board['name'] = &$board['title'];
@ -634,14 +525,8 @@ function file_write($path, $data, $simple = false, $skip_purge = false) {
global $config, $debug;
if (preg_match('/^remote:\/\/(.+)\:(.+)$/', $path, $m)) {
if (isset($config['remote'][$m[1]])) {
require_once 'inc/remote.php';
$remote = new Remote($config['remote'][$m[1]]);
$remote->write($data, $m[2]);
return;
} else {
error('Invalid remote server: ' . $m[1]);
if (isset($config['remote'])) {
error('Remote server support has been removed');
}
}
@ -722,13 +607,19 @@ function file_unlink($path) {
$debug['unlink'][] = $path;
}
if (file_exists($path)) {
$ret = @unlink($path);
} else {
$ret = true;
}
if ($config['gzip_static']) {
$gzpath = "$path.gz";
if (file_exists($gzpath)) {
@unlink($gzpath);
}
}
if (isset($config['purge']) && $path[0] != '/' && isset($_SERVER['HTTP_HOST'])) {
// Purge cache
@ -801,42 +692,6 @@ function listBoards($just_uri = false) {
return $boards;
}
function until($timestamp) {
$difference = $timestamp - time();
switch(TRUE){
case ($difference < 60):
return $difference . ' ' . ngettext('second', 'seconds', $difference);
case ($difference < 3600): //60*60 = 3600
return ($num = round($difference/(60))) . ' ' . ngettext('minute', 'minutes', $num);
case ($difference < 86400): //60*60*24 = 86400
return ($num = round($difference/(3600))) . ' ' . ngettext('hour', 'hours', $num);
case ($difference < 604800): //60*60*24*7 = 604800
return ($num = round($difference/(86400))) . ' ' . ngettext('day', 'days', $num);
case ($difference < 31536000): //60*60*24*365 = 31536000
return ($num = round($difference/(604800))) . ' ' . ngettext('week', 'weeks', $num);
default:
return ($num = round($difference/(31536000))) . ' ' . ngettext('year', 'years', $num);
}
}
function ago($timestamp) {
$difference = time() - $timestamp;
switch(TRUE){
case ($difference < 60) :
return $difference . ' ' . ngettext('second', 'seconds', $difference);
case ($difference < 3600): //60*60 = 3600
return ($num = round($difference/(60))) . ' ' . ngettext('minute', 'minutes', $num);
case ($difference < 86400): //60*60*24 = 86400
return ($num = round($difference/(3600))) . ' ' . ngettext('hour', 'hours', $num);
case ($difference < 604800): //60*60*24*7 = 604800
return ($num = round($difference/(86400))) . ' ' . ngettext('day', 'days', $num);
case ($difference < 31536000): //60*60*24*365 = 31536000
return ($num = round($difference/(604800))) . ' ' . ngettext('week', 'weeks', $num);
default:
return ($num = round($difference/(31536000))) . ' ' . ngettext('year', 'years', $num);
}
}
function displayBan($ban) {
global $config, $board;
@ -877,11 +732,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,
@ -913,11 +768,13 @@ function checkBan($board = false) {
}
foreach ($ips as $ip) {
$bans = Bans::find($ip, $board, $config['show_modname']);
$bans = Bans::find($ip, $board, $config['show_modname'], null, $config['auto_maintenance']);
foreach ($bans as &$ban) {
if ($ban['expires'] && $ban['expires'] < time()) {
if ($config['auto_maintenance']) {
Bans::delete($ban['id']);
}
if ($config['require_ban_view'] && !$ban['seen']) {
if (!isset($_POST['json_response'])) {
displayBan($ban);
@ -937,17 +794,20 @@ function checkBan($board = false) {
}
}
if ($config['auto_maintenance']) {
// I'm not sure where else to put this. It doesn't really matter where; it just needs to be called every
// now and then to keep the ban list tidy.
if ($config['cache']['enabled'] && $last_time_purged = cache::get('purged_bans_last')) {
if (time() - $last_time_purged < $config['purge_bans'] )
return;
}
Bans::purge();
if ($config['cache']['enabled'])
if ($config['cache']['enabled']) {
$last_time_purged = cache::get('purged_bans_last');
if ($last_time_purged !== false && time() - $last_time_purged > $config['purge_bans']) {
Bans::purge($config['require_ban_view'], $config['purge_bans']);
cache::set('purged_bans_last', time());
}
} else {
// Purge every time.
Bans::purge($config['require_ban_view'], $config['purge_bans']);
}
}
}
function threadLocked($id) {
@ -1271,8 +1131,15 @@ 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']));
// 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();
@ -1282,6 +1149,7 @@ function deletePost($id, $error_if_doesnt_exist=true, $rebuild_after=true) {
$query->bindValue(':thread', $thread_id);
$query->execute() or error(db_error($query));
}
}
if (isset($rebuild) && $rebuild_after) {
buildThread($rebuild);
@ -1427,7 +1295,14 @@ function index($page, $mod=false, $brief = false) {
}
if ($config['file_board']) {
$body = Element('fileboard.html', array('body' => $body, 'mod' => $mod));
$options = [
'body' => $body,
'mod' => $mod
];
if ($mod) {
$options['pm'] = create_pm_header();
}
$body = Element($config['file_fileboard'], $options);
}
return array(
@ -1610,7 +1485,7 @@ function checkMute() {
if ($config['cache']['enabled']) {
// Cached mute?
if (($mute = cache::get("mute_${_SERVER['REMOTE_ADDR']}")) && ($mutetime = cache::get("mutetime_${_SERVER['REMOTE_ADDR']}"))) {
if (($mute = cache::get("mute_{$_SERVER['REMOTE_ADDR']}")) && ($mutetime = cache::get("mutetime_{$_SERVER['REMOTE_ADDR']}"))) {
error(sprintf($config['error']['youaremuted'], $mute['time'] + $mutetime - time()));
}
}
@ -1629,8 +1504,8 @@ function checkMute() {
if ($mute['time'] + $mutetime > time()) {
if ($config['cache']['enabled']) {
cache::set("mute_${_SERVER['REMOTE_ADDR']}", $mute, $mute['time'] + $mutetime - time());
cache::set("mutetime_${_SERVER['REMOTE_ADDR']}", $mutetime, $mute['time'] + $mutetime - time());
cache::set("mute_{$_SERVER['REMOTE_ADDR']}", $mute, $mute['time'] + $mutetime - time());
cache::set("mutetime_{$_SERVER['REMOTE_ADDR']}", $mutetime, $mute['time'] + $mutetime - time());
}
// Not expired yet
error(sprintf($config['error']['youaremuted'], $mute['time'] + $mutetime - time()));
@ -1641,34 +1516,10 @@ function checkMute() {
}
}
function _create_antibot($board, $thread) {
global $config, $purged_old_antispam;
$antibot = new AntiBot(array($board, $thread));
if (!isset($purged_old_antispam)) {
$purged_old_antispam = true;
query('DELETE FROM ``antispam`` WHERE `expires` < UNIX_TIMESTAMP()') or error(db_error());
}
if ($thread)
$query = prepare('UPDATE ``antispam`` SET `expires` = UNIX_TIMESTAMP() + :expires WHERE `board` = :board AND `thread` = :thread AND `expires` IS NULL');
else
$query = prepare('UPDATE ``antispam`` SET `expires` = UNIX_TIMESTAMP() + :expires WHERE `board` = :board AND `thread` IS NULL AND `expires` IS NULL');
$query->bindValue(':board', $board);
if ($thread)
$query->bindValue(':thread', $thread);
$query->bindValue(':expires', $config['spam']['hidden_inputs_expire']);
$query->execute() or error(db_error($query));
$query = prepare('INSERT INTO ``antispam`` VALUES (:board, :thread, :hash, UNIX_TIMESTAMP(), NULL, 0)');
$query->bindValue(':board', $board);
$query->bindValue(':thread', $thread);
$query->bindValue(':hash', $antibot->hash());
$query->execute() or error(db_error($query));
return $antibot;
function purge_old_antispam() {
$query = prepare('DELETE FROM ``antispam`` WHERE `expires` < UNIX_TIMESTAMP()');
$query->execute() or error(db_error());
return $query->rowCount();
}
function checkSpam(array $extra_salt = array()) {
@ -1733,15 +1584,18 @@ function incrementSpamHash($hash) {
}
function buildIndex($global_api = "yes") {
global $board, $config, $build_pages;
global $board, $config, $build_pages, $mod;
$catalog_api_action = generation_strategy('sb_api', array($board['uri']));
$pages = null;
$antibot = null;
if ($config['api']['enabled']) {
$api = new Api();
$api = new Api(
$config['show_filename'],
$config['hide_email'],
$config['country_flags']
);
$catalog = array();
}
@ -1760,6 +1614,21 @@ function buildIndex($global_api = "yes") {
if (!$content)
break;
// Tries to avoid rebuilding if the body is the same as the one in cache.
if ($config['cache']['enabled']) {
$contentHash = md5(json_encode($content['body']));
$contentHashKey = '_index_hashed_'. $board['uri'] . '_' . $page;
$cachedHash = cache::get($contentHashKey);
if ($cachedHash == $contentHash){
if ($config['api']['enabled']) {
// this is needed for the thread.json and catalog.json rebuilding below, which includes all pages.
$catalog[$page-1] = $content['threads'];
}
continue;
}
cache::set($contentHashKey, $contentHash, 3600);
}
// json api
if ($config['api']['enabled']) {
$threads = $content['threads'];
@ -1771,23 +1640,17 @@ function buildIndex($global_api = "yes") {
if ($wont_build_this_page) continue;
}
if ($config['try_smarter']) {
$antibot = create_antibot($board['uri'], 0 - $page);
$content['current_page'] = $page;
}
elseif (!$antibot) {
$antibot = create_antibot($board['uri']);
}
$antibot->reset();
if (!$pages) {
$pages = getPages();
}
$content['pages'] = $pages;
$content['pages'][$page-1]['selected'] = true;
$content['btn'] = getPageButtons($content['pages']);
$content['antibot'] = $antibot;
if ($mod) {
$content['pm'] = create_pm_header();
}
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);
@ -2010,7 +1873,7 @@ function extract_modifiers($body) {
}
function remove_modifiers($body) {
return preg_replace('@<tinyboard ([\w\s]+)>(.+?)</tinyboard>@usm', '', $body);
return $body ? preg_replace('@<tinyboard ([\w\s]+)>(.+?)</tinyboard>@usm', '', $body) : null;
}
function markup(&$body, $track_cites = false, $op = false) {
@ -2078,7 +1941,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']);
}
@ -2125,7 +1988,7 @@ function markup(&$body, $track_cites = false, $op = false) {
}
// Cross-board linking
if (preg_match_all('/(^|\s)&gt;&gt;&gt;\/(' . $config['board_regex'] . 'f?)\/(\d+)?([\s,.)?]|$)/um', $body, $cites, PREG_SET_ORDER | PREG_OFFSET_CAPTURE)) {
if (preg_match_all('/(^|[\s(])&gt;&gt;&gt;\/(' . $config['board_regex'] . 'f?)\/(\d+)?((?=[\s,.)?!])|$)/um', $body, $cites, PREG_SET_ORDER | PREG_OFFSET_CAPTURE)) {
if (count($cites[0]) > $config['max_cites']) {
error($config['error']['toomanycross']);
}
@ -2278,8 +2141,26 @@ function escape_markup_modifiers($string) {
return preg_replace('@<(tinyboard) ([\w\s]+)>@mi', '<$1 escape $2>', $string);
}
function defined_flags_accumulate($desired_flags) {
global $config;
$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_NOQUOTES', 'ENT_SUBSTITUTE', 'ENT_DISALLOWED']);
return $utf8 ? htmlspecialchars($utf8, $flags, 'UTF-8') : '';
}
function ordutf8($string, &$offset) {
@ -2305,19 +2186,11 @@ 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) {
@ -2356,9 +2229,8 @@ function buildThread($id, $return = false, $mod = false) {
error($config['error']['nonexistant']);
$hasnoko50 = $thread->postCount() >= $config['noko50_min'];
$antibot = $mod || $return ? false : create_antibot($board['uri'], $id);
$body = Element('thread.html', array(
$options = [
'board' => $board,
'thread' => $thread,
'body' => $thread->build(),
@ -2367,14 +2239,22 @@ function buildThread($id, $return = false, $mod = false) {
'mod' => $mod,
'hasnoko50' => $hasnoko50,
'isnoko50' => false,
'antibot' => $antibot,
'boardlist' => createBoardlist($mod),
'return' => ($mod ? '?' . $board['url'] . $config['file_index'] : $config['root'] . $board['dir'] . $config['file_index'])
));
];
if ($mod) {
$options['pm'] = create_pm_header();
}
$body = Element($config['file_thread'], $options);
// json api
if ($config['api']['enabled'] && !$mod) {
$api = new Api();
$api = new Api(
$config['show_filename'],
$config['hide_email'],
$config['country_flags']
);
$json = json_encode($api->translateThread($thread));
$jsonFilename = $board['dir'] . $config['dir']['res'] . $id . '.json';
file_write($jsonFilename, $json);
@ -2395,20 +2275,17 @@ function buildThread($id, $return = false, $mod = false) {
} elseif ($action == 'rebuild') {
$noko50fn = $board['dir'] . $config['dir']['res'] . link_for($thread, true);
if ($hasnoko50 || file_exists($noko50fn)) {
buildThread50($id, $return, $mod, $thread, $antibot);
buildThread50($id, $return, $mod, $thread);
}
file_write($board['dir'] . $config['dir']['res'] . link_for($thread), $body);
}
}
function buildThread50($id, $return = false, $mod = false, $thread = null, $antibot = false) {
global $board, $config, $build_pages;
function buildThread50($id, $return = false, $mod = false, $thread = null) {
global $board, $config;
$id = round($id);
if ($antibot)
$antibot->reset();
if (!$thread) {
$query = prepare(sprintf("SELECT * FROM ``posts_%s`` WHERE (`thread` IS NULL AND `id` = :id) OR `thread` = :id ORDER BY `thread`,`id` DESC LIMIT :limit", $board['uri']));
$query->bindValue(':id', $id, PDO::PARAM_INT);
@ -2461,7 +2338,7 @@ function buildThread50($id, $return = false, $mod = false, $thread = null, $anti
$hasnoko50 = $thread->postCount() >= $config['noko50_min'];
$body = Element('thread.html', array(
$options = [
'board' => $board,
'thread' => $thread,
'body' => $thread->build(false, true),
@ -2470,10 +2347,14 @@ function buildThread50($id, $return = false, $mod = false, $thread = null, $anti
'mod' => $mod,
'hasnoko50' => $hasnoko50,
'isnoko50' => true,
'antibot' => $mod ? false : ($antibot ? $antibot : create_antibot($board['uri'], $id)),
'boardlist' => createBoardlist($mod),
'return' => ($mod ? '?' . $board['url'] . $config['file_index'] : $config['root'] . $board['dir'] . $config['file_index'])
));
];
if ($mod) {
$options['pm'] = create_pm_header();
}
$body = Element($config['file_thread'], $options);
if ($return) {
return $body;
@ -2544,35 +2425,6 @@ function generate_tripcode($name) {
return array($name, $trip);
}
// Highest common factor
function hcf($a, $b){
$gcd = 1;
if ($a>$b) {
$a = $a+$b;
$b = $a-$b;
$a = $a-$b;
}
if ($b==(round($b/$a))*$a)
$gcd=$a;
else {
for ($i=round($a/2);$i;$i--) {
if ($a == round($a/$i)*$i && $b == round($b/$i)*$i) {
$gcd = $i;
$i = false;
}
}
}
return $gcd;
}
function fraction($numerator, $denominator, $sep) {
$gcf = hcf($numerator, $denominator);
$numerator = $numerator / $gcf;
$denominator = $denominator / $gcf;
return "{$numerator}{$sep}{$denominator}";
}
function getPostByHash($hash) {
global $board;
$query = prepare(sprintf("SELECT `id`,`thread` FROM ``posts_%s`` WHERE `filehash` = :hash", $board['uri']));
@ -2689,65 +2541,6 @@ function shell_exec_error($command, $suppress_stdout = false) {
return $return === 'TB_SUCCESS' ? false : $return;
}
/* 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
* added on. The result is displayed at the top of the post.
*/
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];
}
}
// Default values for X and Z
if($diceX == '') {
$diceX = '1';
}
if($diceZ == '') {
$diceZ = '+0';
}
// 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;
}
// 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;
}
}
}
function slugify($post) {
global $config;
@ -2838,24 +2631,6 @@ function prettify_textarea($s){
return str_replace("\t", '&#09;', str_replace("\n", '&#13;&#10;', htmlentities($s)));
}
/*class HTMLPurifier_URIFilter_NoExternalImages extends HTMLPurifier_URIFilter {
public $name = 'NoExternalImages';
public function filter(&$uri, $c, $context) {
global $config;
$ct = $context->get('CurrentToken');
if (!$ct || $ct->name !== 'img') return true;
if (!isset($uri->host) && !isset($uri->scheme)) return true;
if (!in_array($uri->scheme . '://' . $uri->host . '/', $config['allowed_offsite_urls'])) {
error('No off-site links in board announcement images.');
}
return true;
}
}*/
function purify_html($s) {
global $config;
@ -2871,7 +2646,6 @@ function purify_html($s) {
function markdown($s) {
$pd = new Parsedown();
$pd->setMarkupEscaped(true);
$pd->setimagesEnabled(false);
return $pd->text($s);
}
@ -2890,7 +2664,20 @@ function generation_strategy($fun, $array=array()) { global $config;
return 'rebuild';
case 'defer':
// Ok, it gets interesting here :)
get_queue('generate')->push(serialize(array('build', $fun, $array, $action)));
$queue = Queues::get_queue($config, 'generate');
if ($queue === false) {
if ($config['syslog']) {
_syslog(LOG_ERR, "Could not initialize generate queue, falling back to immediate rebuild strategy");
}
return 'rebuild';
}
$ret = $queue->push(serialize(array('build', $fun, $array, $action)));
if ($ret === false) {
if ($config['syslog']) {
_syslog(LOG_ERR, "Could not push item in the queue, falling back to immediate rebuild strategy");
}
return 'rebuild';
}
return 'ignore';
case 'build_on_load':
return 'delete';
@ -2989,7 +2776,7 @@ function cloak_ip($ip) {
if (strlen($ipbytes) >= 16)
$ipbytes = substr($ipbytes, 0, 16);
$cyphertext = openssl_encrypt($ipbytes, 'rc4-40', $ipcrypt_key, OPENSSL_RAW_DATA);
$cyphertext = openssl_encrypt($ipbytes, 'aes-256-ctr', $ipcrypt_key, OPENSSL_RAW_DATA);
$ret = $config['ipcrypt_prefix'].':' . base32_encode($cyphertext);
if (isset($tld) && !empty($tld)) {
@ -3012,7 +2799,7 @@ function uncloak_ip($ip) {
}
if (substr($ip, 0, strlen($config['ipcrypt_prefix']) + 1) === $config['ipcrypt_prefix'].':') {
$plaintext = openssl_decrypt(base32_decode($juice), 'rc4-40', $ipcrypt_key, OPENSSL_RAW_DATA);
$plaintext = openssl_decrypt(base32_decode($juice), 'aes-256-ctr', $ipcrypt_key, OPENSSL_RAW_DATA);
if ($plaintext === false || strlen($plaintext) == 0)
return '#ERROR';
@ -3048,3 +2835,47 @@ function uncloak_mask($mask) {
return $mask;
}
function check_thread_limit($post) {
global $config, $board;
if (!isset($config['max_threads_per_hour']) || !$config['max_threads_per_hour']) return false;
if ($post['op']) {
$query = prepare(sprintf('SELECT COUNT(*) AS `count` FROM ``posts_%s`` WHERE `thread` IS NULL AND FROM_UNIXTIME(`time`) > DATE_SUB(NOW(), INTERVAL 1 HOUR);', $board['uri']));
$query->execute() or error(db_error($query));
$r = $query->fetch(PDO::FETCH_ASSOC);
return $r['count'] >= $config['max_threads_per_hour'];
}
}
function hashPassword($password) {
global $config;
return hash('sha3-256', $password . $config['secure_password_salt']);
}
// Thanks to https://gist.github.com/marijn/3901938
function trace_url($url) {
$ch = curl_init($url);
curl_setopt_array($ch, array(
CURLOPT_FOLLOWLOCATION => TRUE, // the magic sauce
CURLOPT_RETURNTRANSFER => TRUE,
CURLOPT_SSL_VERIFYHOST => FALSE, // suppress certain SSL errors
CURLOPT_SSL_VERIFYPEER => FALSE,
CURLOPT_TIMEOUT => 30,
));
curl_exec($ch);
$url = curl_getinfo($ch, CURLINFO_EFFECTIVE_URL);
curl_close($ch);
return $url;
}
// Thanks to https://stackoverflow.com/questions/10002227/linkify-regex-function-php-daring-fireball-method/10002262#10002262
function get_urls($body) {
$regex = '(?xi)\b((?:https?://|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:\'".,<>?«»“”‘’]))';
$result = preg_match_all("#$regex#i", $body, $match);
return $match[0];
}

114
inc/functions/dice.php Normal file
View File

@ -0,0 +1,114 @@
<?php
namespace Vichan\Functions\Dice;
function _get_or_default_int(array $arr, int $index, int $default) {
return (isset($arr[$index]) && is_numeric($arr[$index])) ? (int)$arr[$index] : $default;
}
/* 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
* added on. The result is displayed at the top of the post.
*/
function email_dice_roll($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];
}
}
// Default values for X and Z
if($diceX == '') {
$diceX = '1';
}
if($diceZ == '') {
$diceZ = '+0';
}
// 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;
}
// 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;
}
}
}
/**
* Rolls a dice and generates the appropriate html from the markup.
* @param array $matches The array of the matches according to the default configuration.
* 1 -> The number of dices to roll.
* 3 -> The number faces of the dices.
* 4 -> The offset to apply to the dice.
* @param string $img_path Path to the image to use relative to the root. Null if none.
* @return string The html to replace the original markup with.
*/
function inline_dice_roll_markup(array $matches, ?string $img_path): string {
global $config;
$dice_count = _get_or_default_int($matches, 1, 1);
$dice_faces = _get_or_default_int($matches, 3, 6);
$dice_offset = _get_or_default_int($matches, 4, 0);
// Clamp between 1 and max_roll_count.
$dice_count = max(min($dice_count, $config['max_roll_count']), 1);
// Must be at least 2.
if ($dice_faces < 2) {
$dice_faces = 6;
}
$tot = 0;
for ($i = 0; $i < $dice_count; $i++) {
$tot += mt_rand(1, $dice_faces);
}
// Ensure that final result is at least an integer.
$tot = abs((int)($dice_offset + $tot));
if ($img_path !== null) {
$img_text = "<img src='{$config['root']}{$img_path}' alt='dice' title='dice' class=\"inline-dice\"/>";
} else {
$img_text = '';
}
if ($dice_offset === 0) {
$dice_offset_text = '';
} elseif ($dice_offset > 0) {
$dice_offset_text = "+{$dice_offset}";
} else {
$dice_offset_text = (string)$dice_offset;
}
return "<span>$img_text {$dice_count}d{$dice_faces}{$dice_offset_text} = <b>$tot</b></span>";
}

28
inc/functions/format.php Normal file
View File

@ -0,0 +1,28 @@
<?php
namespace Vichan\Functions\Format;
function format_timestamp(int $delta): string {
switch (true) {
case $delta < 60:
return $delta . ' ' . ngettext('second', 'seconds', $delta);
case $delta < 3600: //60*60 = 3600
return ($num = round($delta/ 60)) . ' ' . ngettext('minute', 'minutes', $num);
case $delta < 86400: //60*60*24 = 86400
return ($num = round($delta / 3600)) . ' ' . ngettext('hour', 'hours', $num);
case $delta < 604800: //60*60*24*7 = 604800
return ($num = round($delta / 86400)) . ' ' . ngettext('day', 'days', $num);
case $delta < 31536000: //60*60*24*365 = 31536000
return ($num = round($delta / 604800)) . ' ' . ngettext('week', 'weeks', $num);
default:
return ($num = round($delta / 31536000)) . ' ' . ngettext('year', 'years', $num);
}
}
function until(int $timestamp): string {
return format_timestamp($timestamp - time());
}
function ago(int $timestamp): string {
return format_timestamp(time() - $timestamp);
}

16
inc/functions/net.php Normal file
View File

@ -0,0 +1,16 @@
<?php
namespace Vichan\Functions\Net;
/**
* @param bool $trust_headers. If true, trust the `HTTP_X_FORWARDED_PROTO` header to check if the connection is HTTPS.
* @return bool Returns if the client-server connection is an encrypted one (HTTPS).
*/
function is_connection_secure(bool $trust_headers): bool {
if (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') {
return true;
} elseif ($trust_headers && isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') {
return true;
}
return false;
}

33
inc/functions/num.php Normal file
View File

@ -0,0 +1,33 @@
<?php
namespace Vichan\Functions\Num;
// Highest common factor
function hcf($a, $b){
$gcd = 1;
if ($a > $b) {
$a = $a+$b;
$b = $a-$b;
$a = $a-$b;
}
if ($b == (round($b / $a)) * $a) {
$gcd = $a;
} else {
for ($i = round($a / 2); $i; $i--) {
if ($a == round($a / $i) * $i && $b == round($b / $i) * $i) {
$gcd = $i;
$i = false;
}
}
}
return $gcd;
}
function fraction($numerator, $denominator, $sep) {
$gcf = hcf($numerator, $denominator);
$numerator = $numerator / $gcf;
$denominator = $denominator / $gcf;
return "{$numerator}{$sep}{$denominator}";
}

98
inc/functions/theme.php Normal file
View File

@ -0,0 +1,98 @@
<?php
namespace Vichan\Functions\Theme;
function rebuild_themes(string $action, $boardname = false): void {
global $config, $board, $current_locale;
// Save the global variables
$_config = $config;
$_board = $board;
// List themes
if ($themes = \Cache::get("themes")) {
// OK, we already have themes loaded
} else {
$query = query("SELECT `theme` FROM ``theme_settings`` WHERE `name` IS NULL AND `value` IS NULL") or error(db_error());
$themes = $query->fetchAll(\PDO::FETCH_NUM);
\Cache::set("themes", $themes);
}
foreach ($themes as $theme) {
// Restore them
$config = $_config;
$board = $_board;
// Reload the locale
if ($config['locale'] != $current_locale) {
$current_locale = $config['locale'];
init_locale($config['locale']);
}
if (PHP_SAPI === 'cli') {
echo "Rebuilding theme ".$theme[0]."... ";
}
rebuild_theme($theme[0], $action, $boardname);
if (PHP_SAPI === 'cli') {
echo "done\n";
}
}
// Restore them again
$config = $_config;
$board = $_board;
// Reload the locale
if ($config['locale'] != $current_locale) {
$current_locale = $config['locale'];
init_locale($config['locale']);
}
}
function load_theme_config($_theme) {
global $config;
if (!file_exists($config['dir']['themes'] . '/' . $_theme . '/info.php')) {
return false;
}
// Load theme information into $theme
include $config['dir']['themes'] . '/' . $_theme . '/info.php';
return $theme;
}
function rebuild_theme($theme, string $action, $board = false) {
global $config, $_theme;
$_theme = $theme;
$theme = load_theme_config($_theme);
if (file_exists($config['dir']['themes'] . '/' . $_theme . '/theme.php')) {
require_once $config['dir']['themes'] . '/' . $_theme . '/theme.php';
$theme['build_function']($action, theme_settings($_theme), $board);
}
}
function theme_settings($theme): array {
if ($settings = \Cache::get("theme_settings_" . $theme)) {
return $settings;
}
$query = prepare("SELECT `name`, `value` FROM ``theme_settings`` WHERE `theme` = :theme AND `name` IS NOT NULL");
$query->bindValue(':theme', $theme);
$query->execute() or error(db_error($query));
$settings = [];
while ($s = $query->fetch(\PDO::FETCH_ASSOC)) {
$settings[$s['name']] = $s['value'];
}
\Cache::set("theme_settings_".$theme, $settings);
return $settings;
}

View File

@ -291,6 +291,7 @@ class ImageConvert extends ImageBase {
} else {
rename($this->temp, $src);
chmod($src, 0664);
$this->temp = false;
}
}
public function width() {
@ -300,9 +301,11 @@ class ImageConvert extends ImageBase {
return $this->height;
}
public function destroy() {
if ($this->temp !== false) {
@unlink($this->temp);
$this->temp = false;
}
}
public function resize() {
global $config;
@ -311,7 +314,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'];
@ -324,11 +327,6 @@ class ImageConvert extends ImageBase {
error(_('Failed to resize image!'), null, $error);
}
} else {
if ($config['convert_manual_orient'] && ($this->format == 'jpg' || $this->format == 'jpeg'))
$convert_args = str_replace('-auto-orient', ImageConvert::jpeg_exif_orientation($this->src), $config['convert_args']);
elseif ($config['convert_manual_orient'])
$convert_args = str_replace('-auto-orient', '', $config['convert_args']);
else
$convert_args = &$config['convert_args'];
if (($error = shell_exec_error(($this->gm ? 'gm ' : '') . 'convert ' .
@ -348,12 +346,8 @@ class ImageConvert extends ImageBase {
}
}
} else {
if ($config['convert_manual_orient'] && ($this->format == 'jpg' || $this->format == 'jpeg'))
$convert_args = str_replace('-auto-orient', ImageConvert::jpeg_exif_orientation($this->src), $config['convert_args']);
elseif ($config['convert_manual_orient'])
$convert_args = str_replace('-auto-orient', '', $config['convert_args']);
else
$convert_args = &$config['convert_args'];
if (($error = shell_exec_error(($this->gm ? 'gm ' : '') . 'convert ' .
sprintf($convert_args,
$this->width,
@ -364,7 +358,8 @@ class ImageConvert extends ImageBase {
escapeshellarg($this->temp)))) || !file_exists($this->temp)) {
if (strpos($error, "known incorrect sRGB profile") === false &&
strpos($error, "iCCP: Not recognizing known sRGB profile that has been edited") === false) {
strpos($error, "iCCP: Not recognizing known sRGB profile that has been edited") === false &&
strpos($error, "cHRM chunk does not match sRGB") === false) {
$this->destroy();
error(_('Failed to resize image!')." "._('Details: ').nl2br(htmlspecialchars($error)), null, array('convert_error' => $error));
}
@ -379,69 +374,6 @@ class ImageConvert extends ImageBase {
}
}
}
// For when -auto-orient doesn't exist (older versions)
static public function jpeg_exif_orientation($src, $exif = false) {
if (!$exif) {
$exif = @exif_read_data($src);
if (!isset($exif['Orientation']))
return false;
}
switch($exif['Orientation']) {
case 1:
// Normal
return false;
case 2:
// 888888
// 88
// 8888
// 88
// 88
return '-flop';
case 3:
// 88
// 88
// 8888
// 88
// 888888
return '-flip -flop';
case 4:
// 88
// 88
// 8888
// 88
// 888888
return '-flip';
case 5:
// 8888888888
// 88 88
// 88
return '-rotate 90 -flop';
case 6:
// 88
// 88 88
// 8888888888
return '-rotate 90';
case 7:
// 88
// 88 88
// 8888888888
return '-rotate "-90" -flop';
case 8:
// 8888888888
// 88 88
// 88
return '-rotate "-90"';
}
}
}
class ImagePNG extends ImageBase {
@ -496,7 +428,11 @@ class ImageBMP extends ImageBase {
}
}
if (PHP_MAJOR_VERSION <= 7 && PHP_MINOR_VERSION < 2) {
include 'inc/image/bmp.php';
class ImageWEBP extends ImageBase {
public function from() {
$this->image = @imagecreatefromwebp($this->src);
}
public function to($src) {
imagewebp($this->image, $src);
}
}

View File

@ -1,186 +0,0 @@
<?php
/*
* These functions provide a polyfill for old versions of PHP.
* PHP >= 7.2.0 provides these functions by default.
*
* These functions were submitted by user DHKold to the PHP manual in 2005.
* http://php.net/manual/en/function.imagecreate.php#53879
*
* As per http://php.net/manual/en/about.notes.php ("About user notes", PHP
* Manual), all PHP User Contributed Notes are "...considered part of the PHP
* manual, and are therefore covered by the same license that covers this
* documentation."
*
* Therefore, as per http://php.net/manual/en/copyright.php ("Copyright", PHP
* Manual), the code below is licensed under the terms of the Creative Commons
* Attribution 3.0 license, AKA CC-BY 3.0.
* http://creativecommons.org/licenses/by/3.0/
*/
/*********************************************/
/* Function: imagecreatefrombmp */
/* Author: DHKold */
/* Contact: admin@dhkold.com */
/* Date: The 15th of June 2005 */
/* Version: 2.0B */
/*********************************************/
function imagecreatefrombmp($filename) {
if (! $f1 = fopen($filename,"rb")) return FALSE;
$FILE = unpack("vfile_type/Vfile_size/Vreserved/Vbitmap_offset", fread($f1,14));
if ($FILE['file_type'] != 19778) return FALSE;
$BMP = unpack('Vheader_size/Vwidth/Vheight/vplanes/vbits_per_pixel'.
'/Vcompression/Vsize_bitmap/Vhoriz_resolution'.
'/Vvert_resolution/Vcolors_used/Vcolors_important', fread($f1,40));
$BMP['colors'] = pow(2,$BMP['bits_per_pixel']);
if ($BMP['size_bitmap'] == 0) $BMP['size_bitmap'] = $FILE['file_size'] - $FILE['bitmap_offset'];
$BMP['bytes_per_pixel'] = $BMP['bits_per_pixel']/8;
$BMP['bytes_per_pixel2'] = ceil($BMP['bytes_per_pixel']);
$BMP['decal'] = ($BMP['width']*$BMP['bytes_per_pixel']/4);
$BMP['decal'] -= floor($BMP['width']*$BMP['bytes_per_pixel']/4);
$BMP['decal'] = 4-(4*$BMP['decal']);
if ($BMP['decal'] == 4) $BMP['decal'] = 0;
$PALETTE = array();
if ($BMP['colors'] < 16777216)
{
$PALETTE = unpack('V'.$BMP['colors'], fread($f1,$BMP['colors']*4));
}
$IMG = fread($f1,$BMP['size_bitmap']);
$VIDE = chr(0);
$res = imagecreatetruecolor($BMP['width'],$BMP['height']);
$P = 0;
$Y = $BMP['height']-1;
while ($Y >= 0)
{
$X=0;
while ($X < $BMP['width'])
{
if ($BMP['bits_per_pixel'] == 24)
$COLOR = unpack("V",substr($IMG,$P,3).$VIDE);
elseif ($BMP['bits_per_pixel'] == 16)
{
$COLOR = unpack("n",substr($IMG,$P,2));
$COLOR[1] = $PALETTE[$COLOR[1]+1];
}
elseif ($BMP['bits_per_pixel'] == 8)
{
$COLOR = unpack("n",$VIDE.substr($IMG,$P,1));
$COLOR[1] = $PALETTE[$COLOR[1]+1];
}
elseif ($BMP['bits_per_pixel'] == 4)
{
$COLOR = unpack("n",$VIDE.substr($IMG,floor($P),1));
if (($P*2)%2 == 0) $COLOR[1] = ($COLOR[1] >> 4) ; else $COLOR[1] = ($COLOR[1] & 0x0F);
$COLOR[1] = $PALETTE[$COLOR[1]+1];
}
elseif ($BMP['bits_per_pixel'] == 1)
{
$COLOR = unpack("n",$VIDE.substr($IMG,floor($P),1));
if (($P*8)%8 == 0) $COLOR[1] = $COLOR[1] >>7;
elseif (($P*8)%8 == 1) $COLOR[1] = ($COLOR[1] & 0x40)>>6;
elseif (($P*8)%8 == 2) $COLOR[1] = ($COLOR[1] & 0x20)>>5;
elseif (($P*8)%8 == 3) $COLOR[1] = ($COLOR[1] & 0x10)>>4;
elseif (($P*8)%8 == 4) $COLOR[1] = ($COLOR[1] & 0x8)>>3;
elseif (($P*8)%8 == 5) $COLOR[1] = ($COLOR[1] & 0x4)>>2;
elseif (($P*8)%8 == 6) $COLOR[1] = ($COLOR[1] & 0x2)>>1;
elseif (($P*8)%8 == 7) $COLOR[1] = ($COLOR[1] & 0x1);
$COLOR[1] = $PALETTE[$COLOR[1]+1];
}
else
return FALSE;
imagesetpixel($res,$X,$Y,$COLOR[1]);
$X++;
$P += $BMP['bytes_per_pixel'];
}
$Y--;
$P+=$BMP['decal'];
}
fclose($f1);
return $res;
}
function imagebmp(&$img, $filename='') {
$widthOrig = imagesx($img);
$widthFloor = ((floor($widthOrig/16))*16);
$widthCeil = ((ceil($widthOrig/16))*16);
$height = imagesy($img);
$size = ($widthCeil*$height*3)+54;
// Bitmap File Header
$result = 'BM'; // header (2b)
$result .= int_to_dword($size); // size of file (4b)
$result .= int_to_dword(0); // reserved (4b)
$result .= int_to_dword(54); // byte location in the file which is first byte of IMAGE (4b)
// Bitmap Info Header
$result .= int_to_dword(40); // Size of BITMAPINFOHEADER (4b)
$result .= int_to_dword($widthCeil); // width of bitmap (4b)
$result .= int_to_dword($height); // height of bitmap (4b)
$result .= int_to_word(1); // biPlanes = 1 (2b)
$result .= int_to_word(24); // biBitCount = {1 (mono) or 4 (16 clr ) or 8 (256 clr) or 24 (16 Mil)} (2b
$result .= int_to_dword(0); // RLE COMPRESSION (4b)
$result .= int_to_dword(0); // width x height (4b)
$result .= int_to_dword(0); // biXPelsPerMeter (4b)
$result .= int_to_dword(0); // biYPelsPerMeter (4b)
$result .= int_to_dword(0); // Number of palettes used (4b)
$result .= int_to_dword(0); // Number of important colour (4b)
// is faster than chr()
$arrChr = array();
for ($i=0; $i<256; $i++){
$arrChr[$i] = chr($i);
}
// creates image data
$bgfillcolor = array('red'=>0, 'green'=>0, 'blue'=>0);
// bottom to top - left to right - attention blue green red !!!
$y=$height-1;
for ($y2=0; $y2<$height; $y2++) {
for ($x=0; $x<$widthFloor; ) {
$rgb = imagecolorsforindex($img, imagecolorat($img, $x++, $y));
$result .= $arrChr[$rgb['blue']].$arrChr[$rgb['green']].$arrChr[$rgb['red']];
$rgb = imagecolorsforindex($img, imagecolorat($img, $x++, $y));
$result .= $arrChr[$rgb['blue']].$arrChr[$rgb['green']].$arrChr[$rgb['red']];
$rgb = imagecolorsforindex($img, imagecolorat($img, $x++, $y));
$result .= $arrChr[$rgb['blue']].$arrChr[$rgb['green']].$arrChr[$rgb['red']];
$rgb = imagecolorsforindex($img, imagecolorat($img, $x++, $y));
$result .= $arrChr[$rgb['blue']].$arrChr[$rgb['green']].$arrChr[$rgb['red']];
$rgb = imagecolorsforindex($img, imagecolorat($img, $x++, $y));
$result .= $arrChr[$rgb['blue']].$arrChr[$rgb['green']].$arrChr[$rgb['red']];
$rgb = imagecolorsforindex($img, imagecolorat($img, $x++, $y));
$result .= $arrChr[$rgb['blue']].$arrChr[$rgb['green']].$arrChr[$rgb['red']];
$rgb = imagecolorsforindex($img, imagecolorat($img, $x++, $y));
$result .= $arrChr[$rgb['blue']].$arrChr[$rgb['green']].$arrChr[$rgb['red']];
$rgb = imagecolorsforindex($img, imagecolorat($img, $x++, $y));
$result .= $arrChr[$rgb['blue']].$arrChr[$rgb['green']].$arrChr[$rgb['red']];
}
for ($x=$widthFloor; $x<$widthCeil; $x++) {
$rgb = ($x<$widthOrig) ? imagecolorsforindex($img, imagecolorat($img, $x, $y)) : $bgfillcolor;
$result .= $arrChr[$rgb['blue']].$arrChr[$rgb['green']].$arrChr[$rgb['red']];
}
$y--;
}
// see imagegif
if ($filename == '') {
echo $result;
} else {
$file = fopen($filename, 'wb');
fwrite($file, $result);
fclose($file);
}
}
// imagebmp helpers
function int_to_dword($n) {
return chr($n & 255).chr(($n >> 8) & 255).chr(($n >> 16) & 255).chr(($n >> 24) & 255);
}
function int_to_word($n) {
return chr($n & 255).chr(($n >> 8) & 255);
}
?>

View File

@ -19,5 +19,5 @@
//$config['root'] = '/';
@include('inc/secrets.php');
?>

View File

@ -1,45 +0,0 @@
<?php
/*
* This file is part of Twig.
*
* (c) 2010 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
{
/**
* 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());
}
/**
* 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';
}
}

View File

@ -1,137 +0,0 @@
<?php
class Twig_Extensions_Extension_Tinyboard extends Twig_Extension
{
/**
* 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('filesize', 'format_bytes'),
new Twig_SimpleFilter('truncate', 'twig_truncate_filter'),
new Twig_SimpleFilter('truncate_body', 'truncate'),
new Twig_SimpleFilter('truncate_filename', 'twig_filename_truncate_filter'),
new Twig_SimpleFilter('extension', 'twig_extension_filter'),
new Twig_SimpleFilter('sprintf', 'sprintf'),
new Twig_SimpleFilter('capcode', 'capcode'),
new Twig_SimpleFilter('remove_modifiers', 'remove_modifiers'),
new Twig_SimpleFilter('hasPermission', 'twig_hasPermission_filter'),
new Twig_SimpleFilter('date', 'twig_date_filter'),
new Twig_SimpleFilter('poster_id', 'poster_id'),
new Twig_SimpleFilter('remove_whitespace', 'twig_remove_whitespace_filter'),
new Twig_SimpleFilter('count', 'count'),
new Twig_SimpleFilter('ago', 'ago'),
new Twig_SimpleFilter('until', 'until'),
new Twig_SimpleFilter('push', 'twig_push_filter'),
new Twig_SimpleFilter('bidi_cleanup', 'bidi_cleanup'),
new Twig_SimpleFilter('addslashes', 'addslashes'),
new Twig_SimpleFilter('cloak_ip', 'cloak_ip'),
new Twig_SimpleFilter('cloak_mask', 'cloak_mask'),
);
}
/**
* Returns a list of functions to add to the existing list.
*
* @return array An array of filters
*/
public function getFunctions()
{
return array(
new Twig_SimpleFunction('time', 'time'),
new Twig_SimpleFunction('floor', 'floor'),
new Twig_SimpleFunction('timezone', 'twig_timezone_function'),
new Twig_SimpleFunction('hiddenInputs', 'hiddenInputs'),
new Twig_SimpleFunction('hiddenInputsHash', 'hiddenInputsHash'),
new Twig_SimpleFunction('ratio', 'twig_ratio_function'),
new Twig_SimpleFunction('secure_link_confirm', 'twig_secure_link_confirm'),
new Twig_SimpleFunction('secure_link', 'twig_secure_link'),
new Twig_SimpleFunction('link_for', 'link_for')
);
}
/**
* Returns the name of the extension.
*
* @return string The extension name
*/
public function getName()
{
return 'tinyboard';
}
}
function twig_timezone_function() {
return 'Z';
}
function twig_push_filter($array, $value) {
array_push($array, $value);
return $array;
}
function twig_remove_whitespace_filter($data) {
return preg_replace('/[\t\r\n]/', '', $data);
}
function twig_date_filter($date, $format) {
return gmstrftime($format, $date);
}
function twig_hasPermission_filter($mod, $permission, $board = null) {
return hasPermission($permission, $board, $mod);
}
function twig_extension_filter($value, $case_insensitive = true) {
$ext = mb_substr($value, mb_strrpos($value, '.') + 1);
if($case_insensitive)
$ext = mb_strtolower($ext);
return $ext;
}
function twig_sprintf_filter( $value, $var) {
return sprintf($value, $var);
}
function twig_truncate_filter($value, $length = 30, $preserve = false, $separator = '…') {
if (mb_strlen($value) > $length) {
if ($preserve) {
if (false !== ($breakpoint = mb_strpos($value, ' ', $length))) {
$length = $breakpoint;
}
}
return mb_substr($value, 0, $length) . $separator;
}
return $value;
}
function twig_filename_truncate_filter($value, $length = 30, $separator = '…') {
if (mb_strlen($value) > $length) {
$value = strrev($value);
$array = array_reverse(explode(".", $value, 2));
$array = array_map("strrev", $array);
$filename = &$array[0];
$extension = isset($array[1]) ? $array[1] : false;
$filename = mb_substr($filename, 0, $length - ($extension ? mb_strlen($extension) + 1 : 0)) . $separator;
return implode(".", $array);
}
return $value;
}
function twig_ratio_function($w, $h) {
return fraction($w, $h, ':');
}
function twig_secure_link_confirm($text, $title, $confirm_message, $href) {
global $config;
return '<a onclick="if (event.which==2) return true;if (confirm(\'' . htmlentities(addslashes($confirm_message)) . '\')) document.location=\'?/' . htmlspecialchars(addslashes($href . '/' . make_secure_link_token($href))) . '\';return false;" title="' . htmlentities($title) . '" href="?/' . $href . '">' . $text . '</a>';
}
function twig_secure_link($href) {
return $href . '/' . make_secure_link_token($href);
}

View File

@ -1,133 +0,0 @@
<?php
/*
* This file is part of Twig.
*
* (c) 2010 Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Represents a trans node.
*
* @package twig
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
*/
class Twig_Extensions_Node_Trans extends Twig_Node
{
public function __construct(Twig_NodeInterface $body, Twig_NodeInterface $plural = null, Twig_Node_Expression $count = null, $lineno, $tag = null)
{
parent::__construct(array('count' => $count, 'body' => $body, 'plural' => $plural), array(), $lineno, $tag);
}
/**
* Compiles the node to PHP.
*
* @param Twig_Compiler A Twig_Compiler instance
*/
public function compile(Twig_Compiler $compiler)
{
$compiler->addDebugInfo($this);
list($msg, $vars) = $this->compileString($this->getNode('body'));
if (null !== $this->getNode('plural')) {
list($msg1, $vars1) = $this->compileString($this->getNode('plural'));
$vars = array_merge($vars, $vars1);
}
$function = null === $this->getNode('plural') ? 'gettext' : 'ngettext';
if ($vars) {
$compiler
->write('echo strtr('.$function.'(')
->subcompile($msg)
;
if (null !== $this->getNode('plural')) {
$compiler
->raw(', ')
->subcompile($msg1)
->raw(', abs(')
->subcompile($this->getNode('count'))
->raw(')')
;
}
$compiler->raw('), array(');
foreach ($vars as $var) {
if ('count' === $var->getAttribute('name')) {
$compiler
->string('%count%')
->raw(' => abs(')
->subcompile($this->getNode('count'))
->raw('), ')
;
} else {
$compiler
->string('%'.$var->getAttribute('name').'%')
->raw(' => ')
->subcompile($var)
->raw(', ')
;
}
}
$compiler->raw("));\n");
} else {
$compiler
->write('echo '.$function.'(')
->subcompile($msg)
;
if (null !== $this->getNode('plural')) {
$compiler
->raw(', ')
->subcompile($msg1)
->raw(', abs(')
->subcompile($this->getNode('count'))
->raw(')')
;
}
$compiler->raw(");\n");
}
}
protected function compileString(Twig_NodeInterface $body)
{
if ($body instanceof Twig_Node_Expression_Name || $body instanceof Twig_Node_Expression_Constant || $body instanceof Twig_Node_Expression_TempName) {
return array($body, array());
}
$vars = array();
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) {
$n = $node->getNode('expr');
while ($n instanceof Twig_Node_Expression_Filter) {
$n = $n->getNode('node');
}
$msg .= sprintf('%%%s%%', $n->getAttribute('name'));
$vars[] = new Twig_Node_Expression_Name($n->getAttribute('name'), $n->getLine());
} else {
$msg .= $node->getAttribute('data');
}
}
} else {
$msg = $body->getAttribute('data');
}
return array(new Twig_Node(array(new Twig_Node_Expression_Constant(trim($msg), $body->getLine()))), $vars);
}
}

View File

@ -1,80 +0,0 @@
<?php
/*
* This file is part of Twig.
*
* (c) 2010 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
{
/**
* 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)
{
$lineno = $token->getLine();
$stream = $this->parser->getStream();
$count = null;
$plural = null;
if (!$stream->test(Twig_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()) {
$count = $this->parser->getExpressionParser()->parseExpression();
$stream->expect(Twig_Token::BLOCK_END_TYPE);
$plural = $this->parser->subparse(array($this, 'decideForEnd'), true);
}
}
$stream->expect(Twig_Token::BLOCK_END_TYPE);
$this->checkTransString($body, $lineno);
return new Twig_Extensions_Node_Trans($body, $plural, $count, $lineno, $this->getTag());
}
public function decideForFork(Twig_Token $token)
{
return $token->test(array('plural', 'endtrans'));
}
public function decideForEnd(Twig_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)
{
foreach ($body as $i => $node) {
if (
$node instanceof Twig_Node_Text
||
($node instanceof Twig_Node_Print && $node->getNode('expr') instanceof Twig_Node_Expression_Name)
) {
continue;
}
throw new Twig_Error_Syntax(sprintf('The text to be translated with "trans" can only contain references to simple variables'), $lineno);
}
}
}

View File

@ -14,13 +14,13 @@ Be aware that this is beta software. Please report any bugs you find.
Installation
------------
Add these lines to inc/instance-config.php:
Add these lines to inc/secrets.php:
$config['allowed_ext_files'][] = 'webm';
$config['additional_javascript'][] = 'js/webm-settings.js';
$config['additional_javascript'][] = 'js/expand-video.js';
If you have an [FFmpeg](https://www.ffmpeg.org/) binary on your server and you wish to generate real thumbnails (the webm thumbnails created with the original implementation reportedly cause users' browsers to crash), add the following to inc/instance-config.php as well:
If you have an [FFmpeg](https://www.ffmpeg.org/) binary on your server and you wish to generate real thumbnails (the webm thumbnails created with the original implementation reportedly cause users' browsers to crash), add the following to inc/secrets.php as well:
$config['webm']['use_ffmpeg'] = true;

View File

@ -2287,8 +2287,6 @@ msgstr "usuń"
msgid "New note"
msgstr "Nowa notka"
msgid "New telegram"
msgstr "Nowa depesza"
#. line 94
#. line 7

View File

@ -1,39 +1,76 @@
<?php
class Lock {
function __construct($key) { global $config;
if ($config['lock']['enabled'] == 'fs') {
class Locks {
private static function filesystem(string $key) {
$key = str_replace('/', '::', $key);
$key = str_replace("\0", '', $key);
$this->f = fopen("tmp/locks/$key", "w");
}
$fd = fopen("tmp/locks/$key", "w");
if ($fd === false) {
return false;
}
// Get a shared lock
function get($nonblock = false) { global $config;
if ($config['lock']['enabled'] == 'fs') {
return new class($fd) implements Lock {
// Resources have no type in PHP.
private $f;
public function __construct($fd) {
$this->f = $fd;
}
public function get(bool $nonblock = false) {
$wouldblock = false;
flock($this->f, LOCK_SH | ($nonblock ? LOCK_NB : 0), $wouldblock);
if ($nonblock && $wouldblock) return false;
if ($nonblock && $wouldblock) {
return false;
}
return $this;
}
// Get an exclusive lock
function get_ex($nonblock = false) { global $config;
if ($config['lock']['enabled'] == 'fs') {
public function get_ex(bool $nonblock = false) {
$wouldblock = false;
flock($this->f, LOCK_EX | ($nonblock ? LOCK_NB : 0), $wouldblock);
if ($nonblock && $wouldblock) return false;
if ($nonblock && $wouldblock) {
return false;
}
return $this;
}
// Free a lock
function free() { global $config;
if ($config['lock']['enabled'] == 'fs') {
public function free() {
flock($this->f, LOCK_UN);
}
return $this;
}
};
}
public static function none() {
return new class() implements Lock {
public function get(bool $nonblock = false) {
return $this;
}
public function get_ex(bool $nonblock = false) {
return $this;
}
public function free() {
return $this;
}
};
}
public static function get_lock(array $config, string $key) {
if ($config['lock']['enabled'] == 'fs') {
return self::filesystem($key);
} else {
return self::none();
}
}
}
interface Lock {
public function get(bool $nonblock = false);
public function get_ex(bool $nonblock = false);
public function free();
}

View File

@ -4,10 +4,13 @@
* Copyright (c) 2010-2013 Tinyboard Development Group
*/
use Vichan\Context;
use Vichan\Functions\Net;
defined('TINYBOARD') or exit;
// create a hash/salt pair for validate logins
function mkhash($username, $password, $salt = false) {
function mkhash(string $username, $password = null, $salt = false) {
global $config;
if (!$salt) {
@ -31,55 +34,52 @@ function mkhash($username, $password, $salt = false) {
), 0, 20
);
if (isset($generated_salt))
return array($hash, $salt);
else
if (isset($generated_salt)) {
return [ $hash, $salt ];
} else {
return $hash;
}
}
function crypt_password_old($password) {
$salt = generate_salt();
$password = hash('sha256', $salt . sha1($password));
return array($salt, $password);
}
function crypt_password($password) {
function crypt_password(string $password): array {
global $config;
// `salt` database field is reused as a version value. We don't want it to be 0.
$version = $config['password_crypt_version'] ? $config['password_crypt_version'] : 1;
$new_salt = generate_salt();
$password = crypt($password, $config['password_crypt'] . $new_salt . "$");
return array($version, $password);
return [ $version, $password ];
}
function test_password($password, $salt, $test) {
global $config;
function test_password(string $password, string $salt, string $test): array {
// Version = 0 denotes an old password hashing schema. In the same column, the
// password hash was kept previously
$version = (strlen($salt) <= 8) ? (int) $salt : 0;
$version = strlen($salt) <= 8 ? (int)$salt : 0;
if ($version == 0) {
$comp = hash('sha256', $salt . sha1($test));
}
else {
} else {
$comp = crypt($test, $password);
}
return array($version, hash_equals($password, $comp));
return [ $version, hash_equals($password, $comp) ];
}
function generate_salt() {
// mcrypt_create_iv() was deprecated in PHP 7.1.0, only use it if we're below that version number.
if (PHP_VERSION_ID < 70100) {
// 128 bits of entropy
return strtr(base64_encode(mcrypt_create_iv(16, MCRYPT_DEV_URANDOM)), '+', '.');
}
// Otherwise, use random_bytes()
function generate_salt(): string {
return strtr(base64_encode(random_bytes(16)), '+', '.');
}
function login($username, $password) {
function calc_cookie_name(bool $is_https, bool $is_path_jailed, string $base_name): string {
if ($is_https) {
if ($is_path_jailed) {
return "__Host-$base_name";
} else {
return "__Secure-$base_name";
}
} else {
return $base_name;
}
}
function login(string $username, string $password) {
global $mod, $config;
$query = prepare("SELECT `id`, `type`, `boards`, `password`, `version` FROM ``mods`` WHERE BINARY `username` = :username");
@ -100,40 +100,83 @@ function login($username, $password) {
$query->execute() or error(db_error($query));
}
return $mod = array(
return $mod = [
'id' => $user['id'],
'type' => $user['type'],
'username' => $username,
'hash' => mkhash($username, $user['password']),
'boards' => explode(',', $user['boards'])
);
];
}
}
return false;
}
function setCookies() {
function setCookies(): void {
global $mod, $config;
if (!$mod)
if (!$mod) {
error('setCookies() was called for a non-moderator!');
}
setcookie($config['cookies']['mod'],
$mod['username'] . // username
':' .
$mod['hash'][0] . // password
':' .
$mod['hash'][1], // salt
time() + $config['cookies']['expire'], $config['cookies']['jail'] ? $config['cookies']['path'] : '/', null, !empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off', $config['cookies']['httponly']);
$is_https = Net\is_connection_secure($config['cookies']['secure_login_only'] === 1);
$is_path_jailed = $config['cookies']['jail'];
$name = calc_cookie_name($is_https, $is_path_jailed, $config['cookies']['mod']);
// <username>:<password>:<salt>
$value = "{$mod['username']}:{$mod['hash'][0]}:{$mod['hash'][1]}";
$options = [
'expires' => time() + $config['cookies']['expire'],
'path' => $is_path_jailed ? $config['cookies']['path'] : '/',
'secure' => $is_https,
'httponly' => $config['cookies']['httponly'],
'samesite' => 'Strict'
];
setcookie($name, $value, $options);
}
function destroyCookies() {
function destroyCookies(): void {
global $config;
// Delete the cookies
setcookie($config['cookies']['mod'], 'deleted', time() - $config['cookies']['expire'], $config['cookies']['jail']?$config['cookies']['path'] : '/', null, !empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off', true);
$base_name = $config['cookies']['mod'];
$del_time = time() - 60 * 60 * 24 * 365; // 1 year.
$jailed_path = $config['cookies']['jail'] ? $config['cookies']['path'] : '/';
$http_only = $config['cookies']['httponly'];
$options_multi = [
$base_name => [
'expires' => $del_time,
'path' => $jailed_path ,
'secure' => false,
'httponly' => $http_only,
'samesite' => 'Strict'
],
"__Host-$base_name" => [
'expires' => $del_time,
'path' => $jailed_path,
'secure' => true,
'httponly' => $http_only,
'samesite' => 'Strict'
],
"__Secure-$base_name" => [
'expires' => $del_time,
'path' => '/',
'secure' => true,
'httponly' => $http_only,
'samesite' => 'Strict'
]
];
foreach ($options_multi as $name => $options) {
if (isset($_COOKIE[$name])) {
setcookie($name, 'deleted', $options);
unset($_COOKIE[$name]);
}
}
}
function modLog($action, $_board=null) {
function modLog(string $action, ?string $_board = null): void {
global $mod, $board, $config;
$query = prepare("INSERT INTO ``modlogs`` VALUES (:id, :ip, :board, :time, :text)");
$query->bindValue(':id', (isset($mod['id']) ? $mod['id'] : -1), PDO::PARAM_INT);
@ -148,16 +191,18 @@ function modLog($action, $_board=null) {
$query->bindValue(':board', null, PDO::PARAM_NULL);
$query->execute() or error(db_error($query));
if ($config['syslog'])
if ($config['syslog']) {
_syslog(LOG_INFO, '[mod/' . $mod['username'] . ']: ' . $action);
}
}
function create_pm_header() {
global $mod, $config;
if ($config['cache']['enabled'] && ($header = cache::get('pm_unread_' . $mod['id'])) != false) {
if ($header === true)
if ($header === true) {
return false;
}
return $header;
}
@ -166,35 +211,45 @@ function create_pm_header() {
$query->bindValue(':id', $mod['id'], PDO::PARAM_INT);
$query->execute() or error(db_error($query));
if ($pm = $query->fetch(PDO::FETCH_ASSOC))
$header = array('id' => $pm['id'], 'waiting' => $query->rowCount() - 1);
else
if ($pm = $query->fetch(PDO::FETCH_ASSOC)) {
$header = [ 'id' => $pm['id'], 'waiting' => $query->rowCount() - 1 ];
} else {
$header = true;
}
if ($config['cache']['enabled'])
if ($config['cache']['enabled']) {
cache::set('pm_unread_' . $mod['id'], $header);
}
if ($header === true)
if ($header === true) {
return false;
}
return $header;
}
function make_secure_link_token($uri) {
function make_secure_link_token(string $uri): string {
global $mod, $config;
return substr(sha1($config['cookies']['salt'] . '-' . $uri . '-' . $mod['id']), 0, 8);
}
function check_login($prompt = false) {
function check_login(Context $ctx, bool $prompt = false): void {
global $config, $mod;
$is_https = Net\is_connection_secure($config['cookies']['secure_login_only'] === 1);
$is_path_jailed = $config['cookies']['jail'];
$expected_cookie_name = calc_cookie_name($is_https, $is_path_jailed, $config['cookies']['mod']);
// Validate session
if (isset($_COOKIE[$config['cookies']['mod']])) {
if (isset($_COOKIE[$expected_cookie_name])) {
// Should be username:hash:salt
$cookie = explode(':', $_COOKIE[$config['cookies']['mod']]);
$cookie = explode(':', $_COOKIE[$expected_cookie_name]);
if (count($cookie) != 3) {
// Malformed cookies
destroyCookies();
if ($prompt) mod_login();
if ($prompt) {
mod_login($ctx);
}
exit;
}
@ -207,7 +262,9 @@ function check_login($prompt = false) {
if ($cookie[1] !== mkhash($cookie[0], $user['password'], $cookie[2])) {
// Malformed cookies
destroyCookies();
if ($prompt) mod_login();
if ($prompt) {
mod_login($ctx);
}
exit;
}

View File

@ -1,10 +0,0 @@
<?php
/*
* Copyright (c) 2010-2013 Tinyboard Development Group
*/
defined('TINYBOARD') or exit;
// This file is no longer used.

View File

@ -65,6 +65,10 @@ function config_vars() {
$temp_comment = false;
}
if (preg_match('!^\s*\$config\[(\'log_system\'|\'captcha\')\]!', $line)) {
continue;
}
if (preg_match('!^\s*// ([^$].*)$!', $line, $matches)) {
if ($var['default'] !== false) {
$line = '';

File diff suppressed because it is too large Load Diff

View File

@ -1,28 +0,0 @@
<?php
// PHP 5.4
if (!function_exists('hex2bin')) {
function hex2bin($data) {
return pack("H*" , $hex_string);
}
}
// PHP 5.6
if (!function_exists('hash_equals')) {
function hash_equals($ours, $theirs) {
$ours = (string)$ours;
$theirs = (string)$theirs;
$tlen = strlen($theirs);
$olen = strlen($ours);
$answer = 0;
for ($i = 0; $i < $tlen; $i++) {
$answer |= ord($ours[$olen > $i ? $i : 0]) ^ ord($theirs[$i]);
}
return $answer === 0 && $olen === $tlen;
}
}

View File

@ -1,49 +1,98 @@
<?php
class Queue {
function __construct($key) { global $config;
if ($config['queue']['enabled'] == 'fs') {
$this->lock = new Lock($key);
class Queues {
private static $queues = array();
/**
* This queue implementation isn't actually ordered, so it works more as a "bag".
*/
private static function filesystem(string $key, Lock $lock): Queue {
$key = str_replace('/', '::', $key);
$key = str_replace("\0", '', $key);
$this->key = "tmp/queue/$key/";
}
$key = "tmp/queue/$key/";
return new class($key, $lock) implements Queue {
private Lock $lock;
private string $key;
function __construct(string $key, Lock $lock) {
$this->lock = $lock;
$this->key = $key;
}
function push($str) { global $config;
if ($config['queue']['enabled'] == 'fs') {
public function push(string $str): bool {
$this->lock->get_ex();
file_put_contents($this->key.microtime(true), $str);
$ret = file_put_contents($this->key . microtime(true), $str);
$this->lock->free();
}
return $this;
return $ret !== false;
}
function pop($n = 1) { global $config;
if ($config['queue']['enabled'] == 'fs') {
public function pop(int $n = 1): array {
$this->lock->get_ex();
$dir = opendir($this->key);
$paths = array();
while ($n > 0) {
$path = readdir($dir);
if ($path === FALSE) break;
elseif ($path == '.' || $path == '..') continue;
else { $paths[] = $path; $n--; }
if ($path === false) {
break;
} elseif ($path == '.' || $path == '..') {
continue;
} else {
$paths[] = $path;
$n--;
}
}
$out = array();
foreach ($paths as $v) {
$out []= file_get_contents($this->key.$v);
unlink($this->key.$v);
$out[] = file_get_contents($this->key . $v);
unlink($this->key . $v);
}
$this->lock->free();
return $out;
}
};
}
/**
* No-op. Can be used for mocking.
*/
public static function none(): Queue {
return new class() implements Queue {
public function push(string $str): bool {
return true;
}
public function pop(int $n = 1): array {
return array();
}
};
}
public static function get_queue(array $config, string $name) {
if (!isset(self::$queues[$name])) {
if ($config['queue']['enabled'] == 'fs') {
$lock = Locks::get_lock($config, $name);
if ($lock === false) {
return false;
}
self::$queues[$name] = self::filesystem($name, $lock);
} else {
self::$queues[$name] = self::none();
}
}
return self::$queues[$name];
}
}
// Don't use the constructor. Use the get_queue function.
$queues = array();
interface Queue {
// Push a string in the queue.
public function push(string $str): bool;
function get_queue($name) { global $queues;
return $queues[$name] = isset ($queues[$name]) ? $queues[$name] : new Queue($name);
// Get a string from the queue.
public function pop(int $n = 1): array;
}

View File

@ -1,64 +0,0 @@
<?php
/*
* Copyright (c) 2010-2013 Tinyboard Development Group
*/
defined('TINYBOARD') or exit;
class Remote {
public function __construct($config) {
foreach ($config as $name => $value) {
$this->{$name} = $value;
}
$methods = array();
if (!isset($this->auth['method']))
error('Unspecified authentication method.');
// Connect
$this->connection = ssh2_connect($this->host, isset($this->port) ? $this->port : 22, $methods);
switch ($this->auth['method']) {
case 'pubkey':
if (!isset($this->auth['public']))
error('Public key filename not specified.');
if (!isset($this->auth['private']))
error('Private key filename not specified.');
if (!ssh2_auth_pubkey_file($this->connection, $this->auth['username'], $this->auth['public'], $this->auth['private'], isset($this->auth['passphrase']) ? $this->auth['passphrase']: null))
error('Public key authentication failed.');
break;
case 'plain':
if (!ssh2_auth_password($this->connection, $this->auth['username'], $this->auth['password']))
error('Plain-text authentication failed.');
break;
default:
error('Unknown authentication method: "' . $this->auth['method'] . '".');
}
}
public function write($data, $remote_path) {
global $config;
switch ($this->type) {
case 'sftp':
$sftp = ssh2_sftp($this->connection);
file_write('ssh2.sftp://' . $sftp . $remote_path, $data, true);
break;
case 'scp':
$file = tempnam($config['tmp'], 'tinyboard-scp');
// Write to temp file
file_write($file, $data);
ssh2_scp_send($this->connection, $file, $remote_path, 0755);
break;
default:
error('Unknown send method.');
}
}
};

1
inc/secrets.php Normal file
View File

@ -0,0 +1 @@
<?php

View File

@ -0,0 +1,143 @@
<?php // Verify captchas server side.
namespace Vichan\Service;
use Vichan\Data\Driver\HttpDriver;
defined('TINYBOARD') or exit;
class ReCaptchaQuery implements RemoteCaptchaQuery {
private HttpDriver $http;
private string $secret;
/**
* Creates a new ReCaptchaQuery using the google recaptcha service.
*
* @param HttpDriver $http The http client.
* @param string $secret Server side secret.
* @return ReCaptchaQuery A new ReCaptchaQuery query instance.
*/
public function __construct(HttpDriver $http, string $secret) {
$this->http = $http;
$this->secret = $secret;
}
public function responseField(): string {
return 'g-recaptcha-response';
}
public function verify(string $response, ?string $remote_ip): bool {
$data = [
'secret' => $this->secret,
'response' => $response
];
if ($remote_ip !== null) {
$data['remoteip'] = $remote_ip;
}
$ret = $this->http->requestGet('https://www.google.com/recaptcha/api/siteverify', $data);
$resp = json_decode($ret, true, 16, JSON_THROW_ON_ERROR);
return isset($resp['success']) && $resp['success'];
}
}
class HCaptchaQuery implements RemoteCaptchaQuery {
private HttpDriver $http;
private string $secret;
private string $sitekey;
/**
* Creates a new HCaptchaQuery using the hCaptcha service.
*
* @param HttpDriver $http The http client.
* @param string $secret Server side secret.
* @return HCaptchaQuery A new hCaptcha query instance.
*/
public function __construct(HttpDriver $http, string $secret, string $sitekey) {
$this->http = $http;
$this->secret = $secret;
$this->sitekey = $sitekey;
}
public function responseField(): string {
return 'h-captcha-response';
}
public function verify(string $response, ?string $remote_ip): bool {
$data = [
'secret' => $this->secret,
'response' => $response,
'sitekey' => $this->sitekey
];
if ($remote_ip !== null) {
$data['remoteip'] = $remote_ip;
}
$ret = $this->http->requestGet('https://hcaptcha.com/siteverify', $data);
$resp = json_decode($ret, true, 16, JSON_THROW_ON_ERROR);
return isset($resp['success']) && $resp['success'];
}
}
interface RemoteCaptchaQuery {
/**
* Name of the response field in the form data expected by the implementation.
*
* @return string The name of the field.
*/
public function responseField(): string;
/**
* Checks if the user at the remote ip passed the captcha.
*
* @param string $response User provided response.
* @param ?string $remote_ip User ip. Leave to null to only check the response value.
* @return bool Returns true if the user passed the captcha.
* @throws RuntimeException|JsonException Throws on IO errors or if it fails to decode the answer.
*/
public function verify(string $response, ?string $remote_ip): bool;
}
class NativeCaptchaQuery {
private HttpDriver $http;
private string $domain;
private string $provider_check;
private string $extra;
/**
* @param HttpDriver $http The http client.
* @param string $domain The server's domain.
* @param string $provider_check Path to the endpoint.
* @param string $extra Extra http parameters.
*/
function __construct(HttpDriver $http, string $domain, string $provider_check, string $extra) {
$this->http = $http;
$this->domain = $domain;
$this->provider_check = $provider_check;
$this->extra = $extra;
}
/**
* Checks if the user at the remote ip passed the native vichan captcha.
*
* @param string $user_text Remote user's text input.
* @param string $user_cookie Remote user cookie.
* @return bool Returns true if the user passed the check.
* @throws RuntimeException Throws on IO errors.
*/
public function verify(string $user_text, string $user_cookie): bool {
$data = [
'mode' => 'check',
'text' => $user_text,
'extra' => $this->extra,
'cookie' => $user_cookie
];
$ret = $this->http->requestGet($this->domain . '/' . $this->provider_check, $data);
return $ret === '1';
}
}

View File

@ -10,16 +10,22 @@ $twig = false;
function load_twig() {
global $twig, $config;
$loader = new Twig_Loader_Filesystem($config['dir']['template']);
$cache_dir = "{$config['dir']['template']}/cache/";
$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']
'cache' => is_writable('templates/') || (is_dir($cache_dir) && is_writable($cache_dir)) ?
new TinyboardTwigCache($cache_dir) : false,
'debug' => $config['debug'],
'auto_reload' => $config['twig_auto_reload']
));
$twig->addExtension(new Twig_Extensions_Extension_Tinyboard());
$twig->addExtension(new Twig_Extensions_Extension_I18n());
if ($config['debug'])
$twig->addExtension(new \Twig\Extension\DebugExtension());
$twig->addExtension(new Tinyboard());
$twig->addExtension(new PhpMyAdmin\Twig\Extensions\I18nExtension());
}
function Element($templateFile, array $options) {
@ -28,10 +34,6 @@ function Element($templateFile, array $options) {
if (!$twig)
load_twig();
if (function_exists('create_pm_header') && ((isset($options['mod']) && $options['mod']) || isset($options['__mod'])) && !preg_match('!^mod/!', $templateFile)) {
$options['pm'] = create_pm_header();
}
if (isset($options['body']) && $config['debug']) {
$_debug = $debug;
@ -54,7 +56,7 @@ function Element($templateFile, array $options) {
}
// Read the template file
if (@file_get_contents("{$config['dir']['template']}/${templateFile}")) {
if (@file_get_contents("{$config['dir']['template']}/{$templateFile}")) {
$body = $twig->render($templateFile, $options);
if ($config['minify_html'] && preg_match('/\.html$/', $templateFile)) {
@ -63,7 +65,163 @@ function Element($templateFile, array $options) {
return $body;
} else {
throw new Exception("Template file '${templateFile}' does not exist or is empty in '{$config['dir']['template']}'!");
throw new Exception("Template file '{$templateFile}' does not exist or is empty in '{$config['dir']['template']}'!");
}
}
class TinyboardTwigCache extends Twig\Cache\FilesystemCache {
private string $directory;
public function __construct(string $directory) {
parent::__construct($directory);
$this->directory = $directory;
}
/**
* This function was removed in Twig 2.x due to developer views on the Twig library.
* Who says we can't keep it for ourselves though?
*/
public function clear() {
$iter = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($this->directory),
RecursiveIteratorIterator::LEAVES_ONLY
);
foreach ($iter as $file) {
if ($file->isFile()) {
@unlink($file->getPathname());
}
}
}
}
class Tinyboard extends Twig\Extension\AbstractExtension
{
/**
* Returns a list of filters to add to the existing list.
*
* @return array An array of filters
*/
public function getFilters()
{
return array(
new Twig\TwigFilter('filesize', 'format_bytes'),
new Twig\TwigFilter('truncate', 'twig_truncate_filter'),
new Twig\TwigFilter('truncate_body', 'truncate'),
new Twig\TwigFilter('truncate_filename', 'twig_filename_truncate_filter'),
new Twig\TwigFilter('extension', 'twig_extension_filter'),
new Twig\TwigFilter('sprintf', 'sprintf'),
new Twig\TwigFilter('capcode', 'capcode'),
new Twig\TwigFilter('remove_modifiers', 'remove_modifiers'),
new Twig\TwigFilter('hasPermission', 'twig_hasPermission_filter'),
new Twig\TwigFilter('date', 'twig_date_filter'),
new Twig\TwigFilter('poster_id', 'poster_id'),
new Twig\TwigFilter('count', 'count'),
new Twig\TwigFilter('ago', 'Vichan\Functions\Format\ago'),
new Twig\TwigFilter('until', 'Vichan\Functions\Format\until'),
new Twig\TwigFilter('push', 'twig_push_filter'),
new Twig\TwigFilter('bidi_cleanup', 'bidi_cleanup'),
new Twig\TwigFilter('addslashes', 'addslashes'),
new Twig\TwigFilter('cloak_ip', 'cloak_ip'),
new Twig\TwigFilter('cloak_mask', 'cloak_mask'),
);
}
/**
* Returns a list of functions to add to the existing list.
*
* @return array An array of filters
*/
public function getFunctions()
{
return array(
new Twig\TwigFunction('time', 'time'),
new Twig\TwigFunction('floor', 'floor'),
new Twig\TwigFunction('hiddenInputs', 'hiddenInputs'),
new Twig\TwigFunction('hiddenInputsHash', 'hiddenInputsHash'),
new Twig\TwigFunction('ratio', 'twig_ratio_function'),
new Twig\TwigFunction('secure_link_confirm', 'twig_secure_link_confirm'),
new Twig\TwigFunction('secure_link', 'twig_secure_link'),
new Twig\TwigFunction('link_for', 'link_for')
);
}
/**
* Returns the name of the extension.
*
* @return string The extension name
*/
public function getName()
{
return 'tinyboard';
}
}
function twig_push_filter($array, $value) {
array_push($array, $value);
return $array;
}
function twig_date_filter($date, $format) {
if (is_numeric($date)) {
$date = new DateTime("@$date", new DateTimeZone('UTC'));
} else {
$date = new DateTime($date, new DateTimeZone('UTC'));
}
return $date->format($format);
}
function twig_hasPermission_filter($mod, $permission, $board = null) {
return hasPermission($permission, $board, $mod);
}
function twig_extension_filter($value, $case_insensitive = true) {
$ext = mb_substr($value, mb_strrpos($value, '.') + 1);
if($case_insensitive)
$ext = mb_strtolower($ext);
return $ext;
}
function twig_sprintf_filter( $value, $var) {
return sprintf($value, $var);
}
function twig_truncate_filter($value, $length = 30, $preserve = false, $separator = '…') {
if (mb_strlen($value) > $length) {
if ($preserve) {
if (false !== ($breakpoint = mb_strpos($value, ' ', $length))) {
$length = $breakpoint;
}
}
return mb_substr($value, 0, $length) . $separator;
}
return $value;
}
function twig_filename_truncate_filter($value, $length = 30, $separator = '…') {
if (mb_strlen($value) > $length) {
$value = strrev($value);
$array = array_reverse(explode(".", $value, 2));
$array = array_map("strrev", $array);
$filename = &$array[0];
$extension = isset($array[1]) ? $array[1] : false;
$filename = mb_substr($filename, 0, $length - ($extension ? mb_strlen($extension) + 1 : 0)) . $separator;
return implode(".", $array);
}
return $value;
}
function twig_ratio_function($w, $h) {
return fraction($w, $h, ':');
}
function twig_secure_link_confirm($text, $title, $confirm_message, $href) {
global $config;
return '<a onclick="if (event.which==2) return true;if (confirm(\'' . htmlentities(addslashes($confirm_message)) . '\')) document.location=\'?/' . htmlspecialchars(addslashes($href . '/' . make_secure_link_token($href))) . '\';return false;" title="' . htmlentities($title) . '" href="?/' . $href . '">' . $text . '</a>';
}
function twig_secure_link($href) {
return $href . '/' . make_secure_link_token($href);
}

View File

@ -1,7 +1,7 @@
<?php
// Installation/upgrade file
define('VERSION', '5.1.4');
define('VERSION', '5.2.1');
require 'inc/bootstrap.php';
loadConfig();
@ -142,7 +142,7 @@ if (file_exists($config['has_installed'])) {
query(sprintf("ALTER TABLE `posts_%s` CHANGE `subject` `subject` VARCHAR( 100 ) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL", $_board['uri'])) or error(db_error());
}
case 'v0.9.3-dev-6':
// change to MyISAM
// change to InnoDB
$tables = array(
'bans', 'boards', 'ip_notes', 'modlogs', 'mods', 'mutes', 'noticeboard', 'pms', 'reports', 'robot', 'theme_settings', 'news'
);
@ -151,7 +151,7 @@ if (file_exists($config['has_installed'])) {
}
foreach ($tables as &$table) {
query("ALTER TABLE `{$table}` ENGINE = MYISAM DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci") or error(db_error());
query("ALTER TABLE `{$table}` ENGINE = INNODB DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci") or error(db_error());
}
case 'v0.9.3-dev-7':
foreach ($boards as &$board) {
@ -212,7 +212,7 @@ if (file_exists($config['has_installed'])) {
foreach ($boards as &$board) {
query(sprintf("ALTER TABLE `posts_%s` ADD `body_nomarkup` TEXT NULL AFTER `body`", $board['uri'])) or error(db_error());
}
query("CREATE TABLE IF NOT EXISTS `cites` ( `board` varchar(8) NOT NULL, `post` int(11) NOT NULL, `target_board` varchar(8) NOT NULL, `target` int(11) NOT NULL, KEY `target` (`target_board`,`target`), KEY `post` (`board`,`post`)) ENGINE=MyISAM DEFAULT CHARSET=utf8;") or error(db_error());
query("CREATE TABLE IF NOT EXISTS `cites` ( `board` varchar(8) NOT NULL, `post` int(11) NOT NULL, `target_board` varchar(8) NOT NULL, `target` int(11) NOT NULL, KEY `target` (`target_board`,`target`), KEY `post` (`board`,`post`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;") or error(db_error());
case 'v0.9.5-dev-2':
query("ALTER TABLE `boards`
CHANGE `uri` `uri` VARCHAR( 15 ) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
@ -235,7 +235,7 @@ if (file_exists($config['has_installed'])) {
`passed` smallint(6) NOT NULL,
PRIMARY KEY (`hash`),
KEY `board` (`board`,`thread`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;") or error(db_error());
) ENGINE=InnoDB DEFAULT CHARSET=utf8;") or error(db_error());
case 'v0.9.6-dev-2':
query("ALTER TABLE `boards`
DROP `id`,
@ -462,7 +462,7 @@ if (file_exists($config['has_installed'])) {
KEY `posthash` (`posthash`),
KEY `filehash` (`filehash`),
KEY `time` (`time`)
) ENGINE=MyISAM DEFAULT CHARSET=ascii COLLATE=ascii_bin AUTO_INCREMENT=1 ;") or error(db_error());
) ENGINE=InnoDB DEFAULT CHARSET=ascii COLLATE=ascii_bin AUTO_INCREMENT=1 ;") or error(db_error());
case 'v0.9.6-dev-19':
query("UPDATE ``mods`` SET `type` = 10 WHERE `type` = 0") or error(db_error());
query("UPDATE ``mods`` SET `type` = 20 WHERE `type` = 1") or error(db_error());
@ -483,7 +483,7 @@ if (file_exists($config['has_installed'])) {
PRIMARY KEY (`id`),
KEY `expires` (`expires`),
KEY `ipstart` (`ipstart`,`ipend`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 AUTO_INCREMENT=1") or error(db_error());
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 AUTO_INCREMENT=1") or error(db_error());
$listquery = query("SELECT * FROM ``bans`` ORDER BY `id`") or error(db_error());
while ($ban = $listquery->fetch(PDO::FETCH_ASSOC)) {
$query = prepare("INSERT INTO ``bans_new_temp`` VALUES
@ -538,7 +538,7 @@ if (file_exists($config['has_installed'])) {
`denied` tinyint(1) NOT NULL,
PRIMARY KEY (`id`),
KEY `ban_id` (`ban_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 AUTO_INCREMENT=1 ;") or error(db_error());
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 AUTO_INCREMENT=1 ;") or error(db_error());
case 'v0.9.6-dev-22':
case 'v0.9.6-dev-22 + <a href="https://int.vichan.net/devel/">vichan-devel-4.4.91</a>':
case 'v0.9.6-dev-22 + <a href="https://int.vichan.net/devel/">vichan-devel-4.4.92</a>':
@ -634,10 +634,10 @@ if (file_exists($config['has_installed'])) {
`text` varchar(255),
`created_at` int(11),
PRIMARY KEY (`cookie`,`extra`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;') or error(db_error());
) ENGINE=InnoDB 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());
query("CREATE TABLE IF NOT EXISTS ``search_queries`` ( `ip` varchar(39) NOT NULL, `time` int(11) NOT NULL, `query` text NOT NULL) ENGINE=InnoDB DEFAULT CHARSET=utf8;") or error(db_error());
// Update version number
file_write($config['has_installed'], VERSION);
@ -689,6 +689,8 @@ if ($step == 0) {
echo Element('page.html', $page);
} elseif ($step == 1) {
// The HTTPS check doesn't work properly when in those arrays, so let's run it here and pass along the result during the actual check.
$httpsvalue = !empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off';
$page['title'] = 'Pre-installation test';
$can_exec = true;
@ -729,17 +731,10 @@ if ($step == 0) {
$tests = array(
array(
'category' => 'PHP',
'name' => 'PHP &ge; 5.4',
'name' => 'PHP &ge; 7.4',
'result' => PHP_VERSION_ID >= 50400,
'required' => true,
'message' => 'vichan requires PHP 5.4 or better.',
),
array(
'category' => 'PHP',
'name' => 'PHP &ge; 5.6',
'result' => PHP_VERSION_ID >= 50600,
'required' => false,
'message' => 'vichan works best on PHP 5.6 or better.',
'message' => 'vichan requires PHP 7.4 or better.',
),
array(
'category' => 'PHP',
@ -856,31 +851,37 @@ if ($step == 0) {
array(
'category' => 'File permissions',
'name' => getcwd() . '/templates/cache',
'result' => is_writable('templates') || (is_dir('templates/cache') && is_writable('templates/cache')),
'result' => is_dir('templates/cache/') && is_writable('templates/cache/'),
'required' => true,
'message' => 'You must give vichan permission to create (and write to) the <code>templates/cache</code> directory or performance will be drastically reduced.'
),
array(
'category' => 'File permissions',
'name' => getcwd() . '/tmp/cache',
'result' => is_dir('tmp/cache') && is_writable('tmp/cache'),
'result' => is_dir('tmp/cache/') && is_writable('tmp/cache/'),
'required' => true,
'message' => 'You must give vichan permission to write to the <code>tmp/cache</code> directory.'
),
array(
'category' => 'File permissions',
'name' => getcwd() . '/inc/instance-config.php',
'result' => is_writable('inc/instance-config.php'),
'name' => getcwd() . '/inc/secrets.php',
'result' => is_writable('inc/secrets.php'),
'required' => false,
'message' => 'vichan does not have permission to make changes to <code>inc/instance-config.php</code>. To complete the installation, you will be asked to manually copy and paste code into the file instead.'
'message' => 'vichan does not have permission to make changes to <code>inc/secrets.php</code>. To complete the installation, you will be asked to manually copy and paste code into the file instead.'
),
array(
'category' => 'Misc',
'name' => 'Caching available (APC, XCache, Memcached or Redis)',
'result' => extension_loaded('apc') || extension_loaded('xcache')
|| extension_loaded('memcached') || extension_loaded('redis'),
'name' => 'HTTPS being used',
'result' => $httpsvalue,
'required' => false,
'message' => 'You will not be able to enable the additional caching system, designed to minimize SQL queries and significantly improve performance. <a href="http://php.net/manual/en/book.apc.php">APC</a> is the recommended method of caching, but <a href="http://xcache.lighttpd.net/">XCache</a>, <a href="http://www.php.net/manual/en/intro.memcached.php">Memcached</a> and <a href="http://pecl.php.net/package/redis">Redis</a> are also supported.'
'message' => 'You are not currently using https for vichan, or at least for your backend server. If this intentional, add "$config[\'cookies\'][\'secure_login_only\'] = 0;" (or 1 if using a proxy) on a new line under "Additional configuration" on the next page.'
),
array(
'category' => 'Misc',
'name' => 'Caching available (APCu, Memcached or Redis)',
'result' => extension_loaded('apcu') || extension_loaded('memcached') || extension_loaded('redis'),
'required' => false,
'message' => 'You will not be able to enable the additional caching system, designed to minimize SQL queries and significantly improve performance. <a href="https://www.php.net/manual/en/book.apcu.php">APCu</a> is the recommended method of caching, but <a href="http://www.php.net/manual/en/intro.memcached.php">Memcached</a> and <a href="http://pecl.php.net/package/redis">Redis</a> are also supported.'
),
array(
'category' => 'Misc',
@ -920,6 +921,7 @@ if ($step == 0) {
$sg = new SaltGen();
$config['cookies']['salt'] = $sg->generate();
$config['secure_trip_salt'] = $sg->generate();
$config['secure_password_salt'] = $sg->generate();
echo Element('page.html', array(
'body' => Element('installer/config.html', array(
@ -952,17 +954,17 @@ if ($step == 0) {
$instance_config .= $more;
$instance_config .= "\n";
if (@file_put_contents('inc/instance-config.php', $instance_config)) {
if (@file_put_contents('inc/secrets.php', $instance_config)) {
// flushes opcache if php >= 5.5.0 or opcache is installed via PECL
if (function_exists('opcache_invalidate')) {
opcache_invalidate('inc/instance-config.php');
opcache_invalidate('inc/secrets.php');
}
header('Location: ?step=4', true, $config['redirect_http']);
} else {
$page['title'] = 'Manual installation required';
$page['body'] = '
<p>I couldn\'t write to <strong>inc/instance-config.php</strong> with the new configuration, probably due to a permissions error.</p>
<p>Please complete the installation manually by copying and pasting the following code into the contents of <strong>inc/instance-config.php</strong>:</p>
<p>I couldn\'t write to <strong>inc/secrets.php</strong> with the new configuration, probably due to a permissions error.</p>
<p>Please complete the installation manually by copying and pasting the following code into the contents of <strong>inc/secrets.php</strong>:</p>
<textarea style="width:700px;height:370px;margin:auto;display:block;background:white;color:black">' . htmlentities($instance_config) . '</textarea>
<p style="text-align:center">
<a href="?step=4">Once complete, click here to complete installation.</a>
@ -989,12 +991,16 @@ if ($step == 0) {
$queries[] = Element('posts.sql', array('board' => 'b'));
$sql_errors = '';
$sql_err_count = 0;
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>';
if (!query($query)) {
$sql_err_count++;
$error = db_error();
$sql_errors .= "<li>$sql_err_count<ul><li>$query</li><li>$error</li></ul></li>";
}
}
$page['title'] = 'Installation complete';
@ -1033,4 +1039,3 @@ if ($step == 0) {
echo Element('page.html', $page);
}

View File

@ -31,7 +31,7 @@ CREATE TABLE IF NOT EXISTS `antispam` (
PRIMARY KEY (`hash`),
KEY `board` (`board`,`thread`),
KEY `expires` (`expires`)
) ENGINE=MyISAM DEFAULT CHARSET=ascii COLLATE=ascii_bin;
) ENGINE=InnoDB DEFAULT CHARSET=ascii COLLATE=ascii_bin;
-- --------------------------------------------------------
@ -53,7 +53,7 @@ CREATE TABLE IF NOT EXISTS `bans` (
PRIMARY KEY (`id`),
KEY `expires` (`expires`),
KEY `ipstart` (`ipstart`,`ipend`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 AUTO_INCREMENT=1 ;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 AUTO_INCREMENT=1 ;
-- --------------------------------------------------------
@ -67,7 +67,7 @@ CREATE TABLE IF NOT EXISTS `boards` (
`subtitle` tinytext,
-- `indexed` boolean default true,
PRIMARY KEY (`uri`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
--
-- Dumping data for table `boards`
@ -89,7 +89,7 @@ CREATE TABLE IF NOT EXISTS `cites` (
`target` int(11) NOT NULL,
KEY `target` (`target_board`,`target`),
KEY `post` (`board`,`post`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- --------------------------------------------------------
@ -105,7 +105,7 @@ CREATE TABLE IF NOT EXISTS `ip_notes` (
`body` text NOT NULL,
UNIQUE KEY `id` (`id`),
KEY `ip_lookup` (`ip`, `time`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 AUTO_INCREMENT=1 ;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 AUTO_INCREMENT=1 ;
-- --------------------------------------------------------
@ -121,7 +121,7 @@ CREATE TABLE IF NOT EXISTS `modlogs` (
`text` text NOT NULL,
KEY `time` (`time`),
KEY `mod`(`mod`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- --------------------------------------------------------
@ -138,7 +138,7 @@ CREATE TABLE IF NOT EXISTS `mods` (
`boards` text CHARACTER SET utf8 NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `id` (`id`,`username`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 AUTO_INCREMENT=1 ;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 AUTO_INCREMENT=1 ;
--
-- Dumping data for table `mods`
@ -157,7 +157,7 @@ CREATE TABLE IF NOT EXISTS `mutes` (
`ip` varchar(39) NOT NULL,
`time` int(11) NOT NULL,
KEY `ip` (`ip`)
) ENGINE=MyISAM DEFAULT CHARSET=ascii;
) ENGINE=InnoDB DEFAULT CHARSET=ascii;
-- --------------------------------------------------------
@ -173,7 +173,7 @@ CREATE TABLE IF NOT EXISTS `news` (
`body` text NOT NULL,
UNIQUE KEY `id` (`id`),
KEY `time` (`time`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 AUTO_INCREMENT=1 ;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 AUTO_INCREMENT=1 ;
-- --------------------------------------------------------
@ -189,7 +189,7 @@ CREATE TABLE IF NOT EXISTS `noticeboard` (
`body` text NOT NULL,
UNIQUE KEY `id` (`id`),
KEY `time` (`time`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 AUTO_INCREMENT=1 ;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 AUTO_INCREMENT=1 ;
-- --------------------------------------------------------
@ -206,7 +206,7 @@ CREATE TABLE IF NOT EXISTS `pms` (
`unread` tinyint(1) NOT NULL,
PRIMARY KEY (`id`),
KEY `to` (`to`, `unread`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 AUTO_INCREMENT=1 ;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 AUTO_INCREMENT=1 ;
-- --------------------------------------------------------
@ -222,7 +222,7 @@ CREATE TABLE IF NOT EXISTS `reports` (
`post` int(11) NOT NULL,
`reason` text NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 AUTO_INCREMENT=1 ;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 AUTO_INCREMENT=1 ;
-- --------------------------------------------------------
@ -233,7 +233,7 @@ CREATE TABLE IF NOT EXISTS `reports` (
CREATE TABLE IF NOT EXISTS `robot` (
`hash` varchar(40) COLLATE ascii_bin NOT NULL COMMENT 'SHA1',
PRIMARY KEY (`hash`)
) ENGINE=MyISAM DEFAULT CHARSET=ascii COLLATE=ascii_bin;
) ENGINE=InnoDB DEFAULT CHARSET=ascii COLLATE=ascii_bin;
-- --------------------------------------------------------
@ -245,7 +245,7 @@ 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=utf8mb4;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- --------------------------------------------------------
@ -258,7 +258,7 @@ CREATE TABLE IF NOT EXISTS `theme_settings` (
`name` varchar(40) DEFAULT NULL,
`value` text,
KEY `theme` (`theme`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- --------------------------------------------------------
@ -279,7 +279,7 @@ CREATE TABLE IF NOT EXISTS `flood` (
KEY `posthash` (`posthash`),
KEY `filehash` (`filehash`),
KEY `time` (`time`)
) ENGINE=MyISAM DEFAULT CHARSET=ascii COLLATE=ascii_bin AUTO_INCREMENT=1 ;
) ENGINE=InnoDB DEFAULT CHARSET=ascii COLLATE=ascii_bin AUTO_INCREMENT=1 ;
-- --------------------------------------------------------
@ -294,8 +294,9 @@ CREATE TABLE IF NOT EXISTS `ban_appeals` (
`message` text NOT NULL,
`denied` tinyint(1) NOT NULL,
PRIMARY KEY (`id`),
KEY `ban_id` (`ban_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 AUTO_INCREMENT=1 ;
KEY `ban_id` (`ban_id`),
CONSTRAINT `fk_ban_id` FOREIGN KEY (`ban_id`) REFERENCES `bans`(`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 AUTO_INCREMENT=1 ;
-- --------------------------------------------------------
@ -312,7 +313,7 @@ CREATE TABLE IF NOT EXISTS `pages` (
`content` text,
PRIMARY KEY (`id`),
UNIQUE KEY `u_pages` (`name`,`board`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- --------------------------------------------------------
@ -330,7 +331,7 @@ CREATE TABLE IF NOT EXISTS `nntp_references` (
PRIMARY KEY (`message_id_digest`),
UNIQUE KEY `message_id` (`message_id`),
UNIQUE KEY `u_board_id` (`board`, `id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- --------------------------------------------------------
@ -344,23 +345,8 @@ CREATE TABLE IF NOT EXISTS `captchas` (
`text` VARCHAR(255),
`created_at` INT(11),
PRIMARY KEY (`cookie`,`extra`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- --------------------------------------------------------
--
-- Table structure for table `telegrams`
--
CREATE TABLE IF NOT EXISTS `telegrams` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`mod_id` int(11) unsigned NOT NULL,
`ip` varchar(39) CHARACTER SET ascii NOT NULL,
`message` text NOT NULL,
`seen` tinyint(1) NOT NULL DEFAULT FALSE,
`created_at` INT(11),
PRIMARY KEY(`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;

View File

@ -15,6 +15,9 @@
* //$config['additional_javascript'][] = 'js/titlebar-notifications.js';
* $config['additional_javascript'][] = 'js/auto-reload.js';
*
* You must have boardlinks or else this script will not load.
* Search for "$config['boards'] = array(" within your inc/config.php and add something similar to your instance-config.php.
*
*/
@ -144,19 +147,26 @@ $(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;
}
$(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
@ -234,4 +244,3 @@ $(document).ready(function(){
auto_update(poll_interval_delay);
}
});

View File

@ -25,7 +25,7 @@ function load_captcha(provider, extra) {
$(function() {
$(".captcha>td").html("<input class='captcha_text' type='text' name='captcha_text' size='32' maxlength='6' autocomplete='off'>"+
"<input class='captcha_cookie' name='captcha_cookie' type='hidden'>"+
"<div class='captcha_html'></div>");
"<div class='captcha_html'><img src='/static/clickme.gif'></div>");
$("#quick-reply .captcha .captcha_text").prop("placeholder", _("Verification"));

View File

@ -13,36 +13,31 @@
*/
function catalog() {
var board = $("input[name='board']");
var board = $("input[name='board']");
var boardValue = board.first().val();
var catalog_url = configRoot + board.first().val() + "/catalog.html";
var catalog_url = '';
if (window.location.href.includes('mod.php?/')) {
catalog_url = configRoot + 'mod.php?/' + boardValue + '/catalog.html';
} else {
catalog_url = configRoot + boardValue + '/catalog.html';
}
var pages = document.getElementsByClassName('pages')[0];
var bottom = document.getElementsByClassName('boardlist bottom')[0]
var subtitle = document.getElementsByClassName('subtitle')[0];
var pages = document.getElementsByClassName('pages')[0];
var bottom = document.getElementsByClassName('boardlist bottom')[0];
var subtitle = document.getElementsByClassName('subtitle')[0];
var link = document.createElement('a');
link.href = catalog_url;
var link = document.createElement('a');
link.href = catalog_url;
if (pages) {
link.textContent = _('Catalog');
link.style.color = '#F10000';
link.style.padding = '4px';
link.style.paddingLeft = '9px';
link.style.borderLeft = '1px solid'
link.style.borderLeftColor = '#A8A8A8';
link.style.textDecoration = "underline";
pages.appendChild(link)
}
else {
if (!pages) {
link.textContent = '['+_('Catalog')+']';
link.style.paddingLeft = '10px';
link.style.textDecoration = "underline";
document.body.insertBefore(link, bottom);
}
}
if (subtitle) {
if (subtitle) {
var link2 = document.createElement('a');
link2.textContent = _('Catalog');
link2.href = catalog_url;
@ -50,7 +45,7 @@ if (subtitle) {
var br = document.createElement('br');
subtitle.appendChild(br);
subtitle.appendChild(link2);
}
}
}
if (active_page == 'thread' || active_page == 'index') {

View File

@ -8,21 +8,21 @@
* $config['additional_javascript'][] = 'js/comment-toolbar.js';
*/
if (active_page == 'catalog') {
onready(function () {
onReady(function() {
'use strict';
// 'true' = enable shortcuts
var useKeybinds = true;
let useKeybinds = true;
// trigger the search 400ms after last keystroke
var delay = 400;
var timeoutHandle;
let delay = 400;
let timeoutHandle;
//search and hide none matching threads
// 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();
let subject = $(this).children('.intro').text().toLowerCase();
let comment = $(this).clone().children().remove(':lt(2)').end().text().trim().toLowerCase();
search_term = search_term.toLowerCase();
if (subject.indexOf(search_term) == -1 && comment.indexOf(search_term) == -1) {
@ -34,7 +34,7 @@ if (active_page == 'catalog') {
}
function searchToggle() {
var button = $('#catalog_search_button');
let button = $('#catalog_search_button');
if (!button.data('expanded')) {
button.data('expanded', '1');

View File

@ -15,9 +15,9 @@
*
*/
onready(function(){
var do_original_filename = function() {
var filename, truncated;
onReady(function() {
let doOriginalFilename = function() {
let filename, truncated;
if ($(this).attr('title')) {
filename = $(this).attr('title');
truncated = true;
@ -34,9 +34,9 @@ onready(function(){
);
};
$('.postfilename').each(do_original_filename);
$('.postfilename').each(doOriginalFilename);
$(document).on('new_post', function(e, post) {
$(post).find('.postfilename').each(do_original_filename);
$(post).find('.postfilename').each(doOriginalFilename);
});
});

View File

@ -16,23 +16,26 @@
*
*/
if (active_page == 'ukko' || active_page == 'thread' || active_page == 'index')
onready(function(){
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>');
$('div#expand-all-images a')
.text(_('Expand all images'))
.click(function() {
$('a img.post-image').each(function() {
// Don't expand YouTube embeds
if ($(this).parent().parent().hasClass('video-container'))
if ($(this).parent().parent().hasClass('video-container')) {
return;
}
// or WEBM
if (/^\/player\.php\?/.test($(this).parent().attr('href')))
if (/^\/player\.php\?/.test($(this).parent().attr('href'))) {
return;
}
if (!$(this).parent().data('expanded'))
if (!$(this).parent().data('expanded')) {
$(this).parent().click();
}
});
if (!$('#shrink-all-images').length) {
@ -41,12 +44,14 @@ onready(function(){
$('div#shrink-all-images a')
.text(_('Shrink all images'))
.click(function(){
.click(function() {
$('a img.full-image').each(function() {
if ($(this).parent().data('expanded'))
if ($(this).parent().data('expanded')) {
$(this).parent().click();
}
});
$(this).parent().remove();
});
});
});
});
}

53
js/expand-filename.js Normal file
View File

@ -0,0 +1,53 @@
/*
* expand-filename.js
* https://github.com/vichan-devel/vichan/blob/master/js/expand-filename.js
*
* Released under the MIT license
* Copyright (c) 2024 Perdedora <weav@anche.no>
*
* Usage:
* $config['additional_javascript'][] = 'js/expand-filename.js';
*
*/
function doFilename(element) {
const filenames = element.querySelectorAll('[data-truncate="true"]');
filenames.forEach(filename => {
filename.addEventListener('mouseover', event => addHover(event.target));
filename.addEventListener('mouseout', event => removeHover(event.target));
});
}
function addHover(element) {
element.dataset.truncatedFilename = element.textContent;
element.textContent = element.download;
}
function removeHover(element) {
element.textContent = element.dataset.truncatedFilename;
delete element.dataset.truncatedFilename;
}
document.addEventListener('DOMContentLoaded', () => {
doFilename(document);
// Create a MutationObserver to watch for new elements
const observer = new MutationObserver(mutationsList => {
mutationsList.forEach(mutation => {
if (mutation.type === 'childList') {
mutation.addedNodes.forEach(addedNode => {
if (addedNode.nodeType === Node.ELEMENT_NODE) {
// Apply `doFilename` to newly added elements
doFilename(addedNode);
}
});
}
});
});
// Start observing the document body for changes
observer.observe(document.body, {
childList: true,
subtree: true
});
});

View File

@ -1,39 +0,0 @@
/*
* expand-too-long.js
* https://github.com/vichan-devel/vichan/blob/master/js/expand-too-long.js
*
* Released under the MIT license
* Copyright (c) 2013-2014 Marcin Łabanowski <marcin@6irc.net>
*
* Usage:
* $config['additional_javascript'][] = 'js/jquery.min.js';
* $config['additional_javascript'][] = 'js/expand-too-long.js';
*
*/
$(function() {
var do_expand = function() {
$(this).find('a').click(function(e) {
e.preventDefault();
var url = $(this).attr('href');
var body = $(this).parents('.body');
$.ajax({
url: url,
context: document.body,
success: function(data) {
var content = $(data).find('#'+url.split('#')[1]).parent().parent().find(".body").first().html();
body.html(content);
}
});
});
};
$('.toolong').each(do_expand);
$(document).on("new_post", function(e, post) {
$(post).find('.toolong').each(do_expand)
});
});

View File

@ -2,26 +2,32 @@
/* Note: This code expects the global variable configRoot to be set. */
if (typeof _ == 'undefined') {
var _ = function(a) { return a; };
var _ = function(a) {
return a;
};
}
function setupVideo(thumb, url) {
if (thumb.videoAlreadySetUp) return;
if (thumb.videoAlreadySetUp) {
return;
}
thumb.videoAlreadySetUp = true;
var video = null;
var videoContainer, videoHide;
var expanded = false;
var hovering = false;
var loop = true;
var loopControls = [document.createElement("span"), document.createElement("span")];
var fileInfo = thumb.parentNode.querySelector(".fileinfo");
var mouseDown = false;
let video = null;
let videoContainer, videoHide;
let expanded = false;
let hovering = false;
let loop = true;
let loopControls = [document.createElement("span"), document.createElement("span")];
let fileInfo = thumb.parentNode.querySelector(".fileinfo");
let mouseDown = false;
function unexpand() {
if (expanded) {
expanded = false;
if (video.pause) video.pause();
if (video.pause) {
video.pause();
}
videoContainer.style.display = "none";
thumb.style.display = "inline";
video.style.maxWidth = "inherit";
@ -32,7 +38,9 @@ function setupVideo(thumb, url) {
function unhover() {
if (hovering) {
hovering = false;
if (video.pause) video.pause();
if (video.pause) {
video.pause();
}
videoContainer.style.display = "none";
video.style.maxWidth = "inherit";
video.style.maxHeight = "inherit";
@ -81,6 +89,12 @@ function setupVideo(thumb, url) {
}
}
function setVolume() {
const volume = setting("videovolume");
video.volume = volume;
video.muted = (volume === 0);
}
// Clicking on thumbnail expands video
thumb.addEventListener("click", function(e) {
if (setting("videoexpand") && !e.shiftKey && !e.ctrlKey && !e.altKey && !e.metaKey) {
@ -97,15 +111,21 @@ function setupVideo(thumb, url) {
video.parentNode.parentNode.removeAttribute('style');
thumb.style.display = "none";
video.muted = (setting("videovolume") == 0);
video.volume = setting("videovolume");
setVolume();
video.controls = true;
if (video.readyState == 0) {
video.addEventListener("loadedmetadata", expand2, false);
} else {
setTimeout(expand2, 0);
}
let promise = video.play();
if (promise !== undefined) {
promise.then(_ => {
}).catch(_ => {
video.muted = true;
video.play();
});
}
e.preventDefault();
}
}, false);
@ -129,16 +149,18 @@ function setupVideo(thumb, url) {
expanded = false;
hovering = true;
var docRight = document.documentElement.getBoundingClientRect().right;
var thumbRight = thumb.querySelector("img, video").getBoundingClientRect().right;
var maxWidth = docRight - thumbRight - 20;
if (maxWidth < 250) maxWidth = 250;
let docRight = document.documentElement.getBoundingClientRect().right;
let thumbRight = thumb.querySelector("img, video").getBoundingClientRect().right;
let maxWidth = docRight - thumbRight - 20;
if (maxWidth < 250) {
maxWidth = 250;
}
video.style.position = "fixed";
video.style.right = "0px";
video.style.top = "0px";
var docRight = document.documentElement.getBoundingClientRect().right;
var thumbRight = thumb.querySelector("img, video").getBoundingClientRect().right;
docRight = document.documentElement.getBoundingClientRect().right;
thumbRight = thumb.querySelector("img, video").getBoundingClientRect().right;
video.style.maxWidth = maxWidth + "px";
video.style.maxHeight = "100%";
video.style.pointerEvents = "none";
@ -148,10 +170,16 @@ function setupVideo(thumb, url) {
videoContainer.style.display = "inline";
videoContainer.style.position = "fixed";
video.muted = (setting("videovolume") == 0);
video.volume = setting("videovolume");
setVolume();
video.controls = false;
let promise = video.play();
if (promise !== undefined) {
promise.then(_ => {
}).catch(_ => {
video.muted = true;
video.play();
});
}
}
}, false);
@ -161,10 +189,18 @@ function setupVideo(thumb, url) {
thumb.addEventListener("wheel", function(e) {
if (setting("videohover")) {
var volume = setting("videovolume");
if (e.deltaY > 0) volume -= 0.1;
if (e.deltaY < 0) volume += 0.1;
if (volume < 0) volume = 0;
if (volume > 1) volume = 1;
if (e.deltaY > 0) {
volume -= 0.1;
}
if (e.deltaY < 0) {
volume += 0.1;
}
if (volume < 0) {
volume = 0;
}
if (volume > 1) {
volume = 1;
}
if (video != null) {
video.muted = (volume == 0);
video.volume = volume;
@ -202,36 +238,41 @@ function setupVideo(thumb, url) {
}
function setupVideosIn(element) {
var thumbs = element.querySelectorAll("a.file");
for (var i = 0; i < thumbs.length; i++) {
let thumbs = element.querySelectorAll("a.file");
for (let i = 0; i < thumbs.length; i++) {
if (/\.webm$|\.mp4$/.test(thumbs[i].pathname)) {
setupVideo(thumbs[i], thumbs[i].href);
} else {
var m = thumbs[i].search.match(/\bv=([^&]*)/);
let m = thumbs[i].search.match(/\bv=([^&]*)/);
if (m != null) {
var url = decodeURIComponent(m[1]);
if (/\.webm$|\.mp4$/.test(url)) setupVideo(thumbs[i], url);
let url = decodeURIComponent(m[1]);
if (/\.webm$|\.mp4$/.test(url)) {
setupVideo(thumbs[i], url);
}
}
}
}
}
onready(function(){
onReady(function(){
// Insert menu from settings.js
if (typeof settingsMenu != "undefined" && typeof Options == "undefined")
if (typeof settingsMenu != "undefined" && typeof Options == "undefined") {
document.body.insertBefore(settingsMenu, document.getElementsByTagName("hr")[0]);
}
// Setup Javascript events for videos in document now
setupVideosIn(document);
// Setup Javascript events for videos added by updater
if (window.MutationObserver) {
var observer = new MutationObserver(function(mutations) {
for (var i = 0; i < mutations.length; i++) {
var additions = mutations[i].addedNodes;
if (additions == null) continue;
for (var j = 0; j < additions.length; j++) {
var node = additions[j];
let observer = new MutationObserver(function(mutations) {
for (let i = 0; i < mutations.length; i++) {
let additions = mutations[i].addedNodes;
if (additions == null) {
continue;
}
for (let j = 0; j < additions.length; j++) {
let node = additions[j];
if (node.nodeType == 1) {
setupVideosIn(node);
}
@ -241,4 +282,3 @@ onready(function(){
observer.observe(document.body, {childList: true, subtree: true});
}
});

View File

@ -8,6 +8,11 @@
*/
function init_file_selector(max_images) {
// Temporarily block iOS
if (/iPad|iPhone|iPod/.test(navigator.userAgent)) {
return;
}
$(document).ready(function () {
// add options panel item
if (window.Options && Options.get_tab('general')) {

23
js/hide-form.js Normal file
View File

@ -0,0 +1,23 @@
/*
* Adds 4chan-like [Start a New Thread] and [Post a Reply] buttons to pages.
*
* Usage:
* $config['additional_javascript'][] = 'js/jquery.min.js';
* $config['additional_javascript'][] = 'js/hide-form.js';
*
*/
$(document).ready(() => {
if (active_page !== 'index' && active_page !== 'thread')
return;
let form_el = $('form[name="post"]');
let form_msg = active_page === 'index' ? 'Start a New Thread' : 'Post a Reply';
form_el.hide();
form_el.after(`<div id="show-post-form" style="font-size:175%;text-align:center;font-weight:bold">[<a href="#" style="text-decoration:none">${_(form_msg)}</a>]</div>`);
$('div#show-post-form').click(() => {
$('div#show-post-form').hide();
form_el.show();
});
});

View File

@ -13,21 +13,21 @@
*
*/
onready(function(){
var inline_expanding_filename = function() {
$(this).find(".fileinfo > a").click(function(){
var imagelink = $(this).parent().parent().find('a[target="_blank"]:first');
if(imagelink.length > 0) {
onReady(function() {
let inlineExpandingFilename = function() {
$(this).find(".fileinfo > a").click(function() {
let imagelink = $(this).parent().parent().find('a[target="_blank"]:first');
if (imagelink.length > 0) {
imagelink.click();
return false;
}
});
};
$('div[id^="thread_"]').each(inline_expanding_filename);
$('div[id^="thread_"]').each(inlineExpandingFilename);
// allow to work with auto-reload.js, etc.
$(document).on('new_post', function(e, post) {
inline_expanding_filename.call(post);
inlineExpandingFilename.call(post);
});
});

View File

@ -17,29 +17,45 @@ $(document).ready(function(){
'use strict';
var iso8601 = function(s) {
s = s.replace(/\.\d\d\d+/,""); // remove milliseconds
s = s.replace(/-/,"/").replace(/-/,"/");
s = s.replace(/T/," ").replace(/Z/," UTC");
s = s.replace(/([\+\-]\d\d)\:?(\d\d)/," $1$2"); // -04:00 -> -0400
var parts = s.split('T');
if (parts.length === 2) {
var timeParts = parts[1].split(':');
if (timeParts.length === 3) {
var seconds = timeParts[2];
if (seconds.length > 2) {
seconds = seconds.substr(0, 2) + '.' + seconds.substr(2);
}
// Ensure seconds part is valid
if (parseFloat(seconds) > 59) {
seconds = '59';
}
timeParts[2] = seconds;
}
parts[1] = timeParts.join(':');
}
s = parts.join('T');
if (!s.endsWith('Z')) {
s += 'Z';
}
return new Date(s);
};
var zeropad = function(num, count) {
return [Math.pow(10, count - num.toString().length), num].join('').substr(1);
};
var dateformat = (typeof strftime === 'undefined') ? function(t) {
return zeropad(t.getMonth() + 1, 2) + "/" + zeropad(t.getDate(), 2) + "/" + t.getFullYear().toString().substring(2) +
" (" + [_("Sun"), _("Mon"), _("Tue"), _("Wed"), _("Thu"), _("Fri"), _("Sat"), _("Sun")][t.getDay()] + ") " +
" (" + ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"][t.getDay()] + ") " +
// time
zeropad(t.getHours(), 2) + ":" + zeropad(t.getMinutes(), 2) + ":" + zeropad(t.getSeconds(), 2);
} : function(t) {
// post_date is defined in templates/main.js
return strftime(window.post_date, t, datelocale);
};
function timeDifference(current, previous) {
var msPerMinute = 60 * 1000;
var msPerHour = msPerMinute * 60;
var msPerDay = msPerHour * 24;
@ -51,15 +67,15 @@ $(document).ready(function(){
if (elapsed < msPerMinute) {
return 'Just now';
} else if (elapsed < msPerHour) {
return Math.round(elapsed/msPerMinute) + (Math.round(elapsed/msPerMinute)<=1 ? ' minute ago':' minutes ago');
} else if (elapsed < msPerDay ) {
return Math.round(elapsed/msPerHour ) + (Math.round(elapsed/msPerHour)<=1 ? ' hour ago':' hours ago');
return Math.round(elapsed / msPerMinute) + (Math.round(elapsed / msPerMinute) <= 1 ? ' minute ago' : ' minutes ago');
} else if (elapsed < msPerDay) {
return Math.round(elapsed / msPerHour) + (Math.round(elapsed / msPerHour) <= 1 ? ' hour ago' : ' hours ago');
} else if (elapsed < msPerMonth) {
return Math.round(elapsed/msPerDay) + (Math.round(elapsed/msPerDay)<=1 ? ' day ago':' days ago');
return Math.round(elapsed / msPerDay) + (Math.round(elapsed / msPerDay) <= 1 ? ' day ago' : ' days ago');
} else if (elapsed < msPerYear) {
return Math.round(elapsed/msPerMonth) + (Math.round(elapsed/msPerMonth)<=1 ? ' month ago':' months ago');
return Math.round(elapsed / msPerMonth) + (Math.round(elapsed / msPerMonth) <= 1 ? ' month ago' : ' months ago');
} else {
return Math.round(elapsed/msPerYear ) + (Math.round(elapsed/msPerYear)<=1 ? ' year ago':' years ago');
return Math.round(elapsed / msPerYear) + (Math.round(elapsed / msPerYear) <= 1 ? ' year ago' : ' years ago');
}
}
@ -67,20 +83,19 @@ $(document).ready(function(){
var times = elem.getElementsByTagName('time');
var currentTime = Date.now();
for(var i = 0; i < times.length; i++) {
for (var i = 0; i < times.length; i++) {
var t = times[i].getAttribute('datetime');
var postTime = new Date(t);
var postTime = iso8601(t);
times[i].setAttribute('data-local', 'true');
if (localStorage.show_relative_time === 'false') {
times[i].innerHTML = dateformat(iso8601(t));
times[i].innerHTML = dateformat(postTime);
times[i].setAttribute('title', timeDifference(currentTime, postTime.getTime()));
} else {
times[i].innerHTML = timeDifference(currentTime, postTime.getTime());
times[i].setAttribute('title', dateformat(iso8601(t)));
times[i].setAttribute('title', dateformat(postTime));
}
}
};
@ -101,7 +116,7 @@ $(document).ready(function(){
});
if (localStorage.show_relative_time !== 'false') {
$('#show-relative-time>input').attr('checked','checked');
$('#show-relative-time>input').attr('checked', 'checked');
interval_id = setInterval(do_localtime, 30000, document);
}
@ -113,3 +128,4 @@ $(document).ready(function(){
do_localtime(document);
});

View File

@ -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) {
@ -60,8 +60,8 @@ var banlist_init = function(token, my_boards, inMod) {
// duration?
expires: {name: _("Expires"), width: "235px", fmt: function(f) {
if (!f.expires || f.expires == 0) return "<em>"+_("never")+"</em>";
return strftime(window.post_date, new Date((f.expires|0)*1000), datelocale) +
((f.expires < time()) ? "" : " <small>"+_("in ")+until(f.expires|0)+"</small>");
var formattedDate = strftime("%m/%d/%Y (%a) %H:%M:%S", new Date((f.expires|0)*1000), datelocale);
return formattedDate + ((f.expires < time()) ? "" : " <small>"+_("in ")+until(f.expires|0)+"</small>");
} },
username: {name: _("Staff"), width: "100px", fmt: function(f) {
var pre='',suf='',un=f.username;
@ -73,6 +73,11 @@ 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);
@ -124,6 +129,7 @@ var banlist_init = function(token, my_boards, inMod) {
$(".banform").on("submit", function() { return false; });
$("#unban").on("click", function() {
if (confirm('Are you sure you want to unban the selected IPs?')) {
$(".banform .hiddens").remove();
$("<input type='hidden' name='unban' value='unban' class='hiddens'>").appendTo(".banform");
@ -132,6 +138,7 @@ var banlist_init = function(token, my_boards, inMod) {
});
$(".banform").off("submit").submit();
}
});
if (device_type == 'desktop') {

12
js/mod/mod_snippets.js Normal file
View File

@ -0,0 +1,12 @@
/*
* mod_snippets.js
*
* Javascript snippets to be loaded when in mod mode
*
*/
function populateFormJQuery(frm, data) {
$.each(data, function(key, value){
$('[name='+key+']', frm).val(value);
});
}

View File

@ -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;
}
}
}
@ -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">' +

View File

@ -13,59 +13,62 @@
*
*/
onready(function(){
var dont_fetch_again = [];
init_hover = function() {
var $link = $(this);
onReady(function() {
let dontFetchAgain = [];
initHover = function() {
let link = $(this);
let id;
let matches;
var id;
var matches;
if ($link.is('[data-thread]')) {
id = $link.attr('data-thread');
}
else if(matches = $link.text().match(/^>>(?:>\/([^\/]+)\/)?(\d+)$/)) {
if (link.is('[data-thread]')) {
id = link.attr('data-thread');
} else if (matches = link.text().match(/^>>(?:>\/([^\/]+)\/)?(\d+)$/)) {
id = matches[2];
}
else {
} else {
return;
}
var board = $(this);
let board = $(this);
while (board.data('board') === undefined) {
board = board.parent();
}
var threadid;
if ($link.is('[data-thread]')) threadid = 0;
else threadid = board.attr('id').replace("thread_", "");
let threadid;
if (link.is('[data-thread]')) {
threadid = 0;
} else {
threadid = board.attr('id').replace("thread_", "");
}
board = board.data('board');
var parentboard = board;
let parentboard = board;
if ($link.is('[data-thread]')) parentboard = $('form[name="post"] input[name="board"]').val();
else if (matches[1] !== undefined) board = matches[1];
if (link.is('[data-thread]')) {
parentboard = $('form[name="post"] input[name="board"]').val();
} else if (matches[1] !== undefined) {
board = matches[1];
}
var $post = false;
var hovering = false;
var hovered_at;
$link.hover(function(e) {
let post = false;
let hovering = false;
let hoveredAt;
link.hover(function(e) {
hovering = true;
hovered_at = {'x': e.pageX, 'y': e.pageY};
hoveredAt = {'x': e.pageX, 'y': e.pageY};
var start_hover = function($link) {
if ($post.is(':visible') &&
$post.offset().top >= $(window).scrollTop() &&
$post.offset().top + $post.height() <= $(window).scrollTop() + $(window).height()) {
let startHover = function(link) {
if (post.is(':visible') &&
post.offset().top >= $(window).scrollTop() &&
post.offset().top + post.height() <= $(window).scrollTop() + $(window).height()) {
// post is in view
$post.addClass('highlighted');
post.addClass('highlighted');
} else {
var $newPost = $post.clone();
$newPost.find('>.reply, >br').remove();
$newPost.find('span.mentioned').remove();
$newPost.find('a.post_anchor').remove();
let newPost = post.clone();
newPost.find('>.reply, >br').remove();
newPost.find('span.mentioned').remove();
newPost.find('a.post_anchor').remove();
$newPost
newPost
.attr('id', 'post-hover-' + id)
.attr('data-board', board)
.addClass('post-hover')
@ -76,95 +79,99 @@ onready(function(){
.css('font-style', 'normal')
.css('z-index', '100')
.addClass('reply').addClass('post')
.insertAfter($link.parent())
.insertAfter(link.parent())
$link.trigger('mousemove');
link.trigger('mousemove');
}
};
$post = $('[data-board="' + board + '"] div.post#reply_' + id + ', [data-board="' + board + '"]div#thread_' + id);
if($post.length > 0) {
start_hover($(this));
post = $('[data-board="' + board + '"] div.post#reply_' + id + ', [data-board="' + board + '"]div#thread_' + id);
if (post.length > 0) {
startHover($(this));
} else {
var url = $link.attr('href').replace(/#.*$/, '');
let url = link.attr('href').replace(/#.*$/, '');
if($.inArray(url, dont_fetch_again) != -1) {
if ($.inArray(url, dontFetchAgain) != -1) {
return;
}
dont_fetch_again.push(url);
dontFetchAgain.push(url);
$.ajax({
url: url,
context: document.body,
success: function(data) {
var mythreadid = $(data).find('div[id^="thread_"]').attr('id').replace("thread_", "");
let mythreadid = $(data).find('div[id^="thread_"]').attr('id').replace("thread_", "");
if (mythreadid == threadid && parentboard == board) {
$(data).find('div.post.reply').each(function() {
if($('[data-board="' + board + '"] #' + $(this).attr('id')).length == 0) {
if ($('[data-board="' + board + '"] #' + $(this).attr('id')).length == 0) {
$('[data-board="' + board + '"]#thread_' + threadid + " .post.reply:first").before($(this).hide().addClass('hidden'));
}
});
}
else if ($('[data-board="' + board + '"]#thread_'+mythreadid).length > 0) {
} else if ($('[data-board="' + board + '"]#thread_' + mythreadid).length > 0) {
$(data).find('div.post.reply').each(function() {
if($('[data-board="' + board + '"] #' + $(this).attr('id')).length == 0) {
if ($('[data-board="' + board + '"] #' + $(this).attr('id')).length == 0) {
$('[data-board="' + board + '"]#thread_' + mythreadid + " .post.reply:first").before($(this).hide().addClass('hidden'));
}
});
}
else {
} else {
$(data).find('div[id^="thread_"]').hide().attr('data-cached', 'yes').prependTo('form[name="postcontrols"]');
}
$post = $('[data-board="' + board + '"] div.post#reply_' + id + ', [data-board="' + board + '"]div#thread_' + id);
post = $('[data-board="' + board + '"] div.post#reply_' + id + ', [data-board="' + board + '"]div#thread_' + id);
if(hovering && $post.length > 0) {
start_hover($link);
if (hovering && post.length > 0) {
startHover(link);
}
}
});
}
}, function() {
hovering = false;
if(!$post)
if (!post) {
return;
$post.removeClass('highlighted');
if($post.hasClass('hidden') || $post.data('cached') == 'yes')
$post.css('display', 'none');
$('.post-hover').remove();
}).mousemove(function(e) {
if(!$post)
return;
var $hover = $('#post-hover-' + id + '[data-board="' + board + '"]');
if($hover.length == 0)
return;
var scrollTop = $(window).scrollTop();
if ($link.is("[data-thread]")) scrollTop = 0;
var epy = e.pageY;
if ($link.is("[data-thread]")) epy -= $(window).scrollTop();
var top = (epy ? epy : hovered_at['y']) - 10;
if(epy < scrollTop + 15) {
top = scrollTop;
} else if(epy > scrollTop + $(window).height() - $hover.height() - 15) {
top = scrollTop + $(window).height() - $hover.height() - 15;
}
post.removeClass('highlighted');
if (post.hasClass('hidden') || post.data('cached') == 'yes') {
post.css('display', 'none');
}
$('.post-hover').remove();
}).mousemove(function(e) {
if (!post) {
return;
}
$hover.css('left', (e.pageX ? e.pageX : hovered_at['x'])).css('top', top);
let hover = $('#post-hover-' + id + '[data-board="' + board + '"]');
if (hover.length == 0) {
return;
}
let scrollTop = $(window).scrollTop();
if (link.is("[data-thread]")) {
scrollTop = 0;
}
let epy = e.pageY;
if (link.is("[data-thread]")) {
epy -= $(window).scrollTop();
}
let top = (epy ? epy : hoveredAt['y']) - 10;
if (epy < scrollTop + 15) {
top = scrollTop;
} else if (epy > scrollTop + $(window).height() - hover.height() - 15) {
top = scrollTop + $(window).height() - hover.height() - 15;
}
hover.css('left', (e.pageX ? e.pageX : hoveredAt['x'])).css('top', top);
});
};
$('div.body a:not([rel="nofollow"])').each(init_hover);
$('div.body a:not([rel="nofollow"])').each(initHover);
// allow to work with auto-reload.js, etc.
$(document).on('new_post', function(e, post) {
$(post).find('div.body a:not([rel="nofollow"])').each(init_hover);
$(post).find('div.body a:not([rel="nofollow"])').each(initHover);
});
});

View File

@ -10,12 +10,12 @@
* Usage:
* $config['additional_javascript'][] = 'js/jquery.min.js';
* $config['additional_javascript'][] = 'js/quote-selection.js';
*
*/
$(document).ready(function(){
if (!window.getSelection)
$(document).ready(function() {
if (!window.getSelection) {
return;
}
$.fn.selectRange = function(start, end) {
return this.each(function() {
@ -23,7 +23,7 @@ $(document).ready(function(){
this.focus();
this.setSelectionRange(start, end);
} else if (this.createTextRange) {
var range = this.createTextRange();
let range = this.createTextRange();
range.collapse(true);
range.moveEnd('character', end);
range.moveStart('character', start);
@ -32,81 +32,74 @@ $(document).ready(function(){
});
};
var altKey = false;
var ctrlKey = false;
var metaKey = false;
let altKey = false;
let ctrlKey = false;
let metaKey = false;
$(document).keyup(function(e) {
if (e.keyCode == 18)
if (e.keyCode == 18) {
altKey = false;
else if (e.keyCode == 17)
} else if (e.keyCode == 17) {
ctrlKey = false;
else if (e.keyCode == 91)
} else if (e.keyCode == 91) {
metaKey = false;
}
});
$(document).keydown(function(e) {
if (e.altKey)
if (e.altKey) {
altKey = true;
else if (e.ctrlKey)
} else if (e.ctrlKey) {
ctrlKey = true;
else if (e.metaKey)
} else if (e.metaKey) {
metaKey = true;
}
if (altKey || ctrlKey || metaKey) {
// console.log('CTRL/ALT/Something used. Ignoring');
return;
}
if (e.keyCode < 48 || e.keyCode > 90)
return;
var selection = window.getSelection();
var $post = $(selection.anchorNode).parents('.post');
if ($post.length == 0) {
// console.log('Start of selection was not post div', $(selection.anchorNode).parent());
if (e.keyCode < 48 || e.keyCode > 90) {
return;
}
var postID = $post.find('.post_no:eq(1)').text();
let selection = window.getSelection();
let post = $(selection.anchorNode).parents('.post');
if (post.length == 0) {
return;
}
let postID = post.find('.post_no:eq(1)').text();
if (postID != $(selection.focusNode).parents('.post').find('.post_no:eq(1)').text()) {
// console.log('Selection left post div', $(selection.focusNode).parent());
return;
}
;
var selectedText = selection.toString();
// console.log('Selected text: ' + selectedText.replace(/\n/g, '\\n').replace(/\r/g, '\\r'));
let selectedText = selection.toString();
if ($('body').hasClass('debug'))
if ($('body').hasClass('debug')) {
alert(selectedText);
}
if (selectedText.length == 0)
if (selectedText.length == 0) {
return;
}
var body = $('textarea#body')[0];
let body = $('textarea#body')[0];
var last_quote = body.value.match(/[\S.]*(^|[\S\s]*)>>(\d+)/);
if (last_quote)
let last_quote = body.value.match(/[\S.]*(^|[\S\s]*)>>(\d+)/);
if (last_quote) {
last_quote = last_quote[2];
}
/* to solve some bugs on weird browsers, we need to replace \r\n with \n and then undo that after */
var quote = (last_quote != postID ? '>>' + postID + '\r\n' : '') + $.trim(selectedText).replace(/\r\n/g, '\n').replace(/^/mg, '>').replace(/\n/g, '\r\n') + '\r\n';
let quote = (last_quote != postID ? '>>' + postID + '\r\n' : '') + $.trim(selectedText).replace(/\r\n/g, '\n').replace(/^/mg, '>').replace(/\n/g, '\r\n') + '\r\n';
// console.log('Deselecting text');
selection.removeAllRanges();
if (document.selection) {
// IE
body.focus();
var sel = document.selection.createRange();
sel.text = quote;
body.focus();
} else if (body.selectionStart || body.selectionStart == '0') {
// Mozilla
var start = body.selectionStart;
var end = body.selectionEnd;
if (body.selectionStart || body.selectionStart == '0') {
let start = body.selectionStart;
let end = body.selectionEnd;
if (!body.value.substring(0, start).match(/(^|\n)$/)) {
quote = '\r\n\r\n' + quote;
@ -121,4 +114,3 @@ $(document).ready(function(){
}
});
});

View File

@ -13,38 +13,42 @@
*
*/
onready(function(){
var showBackLinks = function() {
var reply_id = $(this).attr('id').replace(/(^reply_)|(^op_)/, '');
onReady(function() {
let showBackLinks = function() {
let reply_id = $(this).attr('id').replace(/(^reply_)|(^op_)/, '');
$(this).find('div.body a:not([rel="nofollow"])').each(function() {
var id, post, $mentioned;
let id, post, $mentioned;
if(id = $(this).text().match(/^>>(\d+)$/))
if (id = $(this).text().match(/^>>(\d+)$/)) {
id = id[1];
else
return;
$post = $('#reply_' + id);
if($post.length == 0){
$post = $('#op_' + id);
if($post.length == 0)
} else {
return;
}
$mentioned = $post.find('p.intro span.mentioned');
if($mentioned.length == 0)
$mentioned = $('<span class="mentioned unimportant"></span>').appendTo($post.find('p.intro'));
if ($mentioned.find('a.mentioned-' + reply_id).length != 0)
$post = $('#reply_' + id);
if ($post.length == 0){
$post = $('#op_' + id);
if ($post.length == 0) {
return;
}
}
var $link = $('<a class="mentioned-' + reply_id + '" onclick="highlightReply(\'' + reply_id + '\');" href="#' + reply_id + '">&gt;&gt;' +
$mentioned = $post.find('p.intro span.mentioned');
if($mentioned.length == 0) {
$mentioned = $('<span class="mentioned unimportant"></span>').appendTo($post.find('p.intro'));
}
if ($mentioned.find('a.mentioned-' + reply_id).length != 0) {
return;
}
let link = $('<a class="mentioned-' + reply_id + '" onclick="highlightReply(\'' + reply_id + '\');" href="#' + reply_id + '">&gt;&gt;' +
reply_id + '</a>');
$link.appendTo($mentioned)
link.appendTo($mentioned)
if (window.init_hover) {
$link.each(init_hover);
link.each(init_hover);
}
});
};
@ -53,9 +57,10 @@ onready(function(){
$('div.post.op').each(showBackLinks);
$(document).on('new_post', function(e, post) {
showBackLinks.call(post);
if ($(post).hasClass("op")) {
$(post).find('div.post.reply').each(showBackLinks);
} else {
$(post).parent().find('div.post.reply').each(showBackLinks);
}
});
});

View File

@ -12,11 +12,11 @@
*
*/
onready(function(){
if(device_type == 'mobile') {
var fix_spoilers = function(where) {
var spoilers = where.getElementsByClassName('spoiler');
for(var i = 0; i < spoilers.length; i++) {
onReady(function() {
if (device_type == 'mobile') {
let fix_spoilers = function(where) {
let spoilers = where.getElementsByClassName('spoiler');
for (let i = 0; i < spoilers.length; i++) {
spoilers[i].onmousedown = function() {
this.style.color = 'white';
};
@ -31,4 +31,3 @@ onready(function(){
}
});

View File

@ -14,17 +14,18 @@
*
*/
onready(function(){
var stylesDiv = $('div.styles');
var stylesSelect = $('<select></select>');
onReady(function() {
let stylesDiv = $('div.styles');
let stylesSelect = $('<select></select>');
var i = 1;
let i = 1;
stylesDiv.children().each(function() {
var opt = $('<option></option>')
let opt = $('<option></option>')
.html(this.innerHTML.replace(/(^\[|\]$)/g, ''))
.val(i);
if ($(this).hasClass('selected'))
if ($(this).hasClass('selected')) {
opt.attr('selected', true);
}
stylesSelect.append(opt);
$(this).attr('id', 'style-select-' + i);
i++;
@ -42,4 +43,3 @@ onready(function(){
.append(stylesSelect)
);
});

1
js/twemoji Submodule

@ -0,0 +1 @@
Subproject commit 25dc1757783115136a83af34c42c603ac8b470aa

View File

@ -22,13 +22,12 @@
*
*/
onready(function(){
var do_embed_yt = function(tag) {
onReady(function() {
let do_embed_yt = function(tag) {
$('div.video-container a', tag).click(function() {
var videoID = $(this.parentNode).data('video');
let videoID = $(this.parentNode).data('video');
$(this.parentNode).html('<iframe style="float:left;margin: 10px 20px" type="text/html" '+
$(this.parentNode).html('<iframe style="float:left;margin: 10px 20px" type="text/html" ' +
'width="360" height="270" src="//www.youtube.com/embed/' + videoID +
'?autoplay=1&html5=1" allowfullscreen frameborder="0"/>');
@ -42,4 +41,3 @@ onready(function(){
do_embed_yt(post);
});
});

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