Compare commits

...

1151 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
h00j
4fd93f490f add sischan style, adjust boardlist/options background 2021-08-08 18:46:44 +02: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
h00j
fd9a3c07dd Merge branch 'master' of https://github.com/vichan-devel/vichan 2021-04-01 22:30:49 +02:00
h00j
281dccb690 fix undefined index in cloak_mask/uncloak_mask 2021-04-01 22:30:22 +02:00
27chan
61d6132bb3 Fix post.php
I believe that you ended up closing some functions by mistake and then gave the following error when making a post:

PHP message: PHP Parse error:  syntax error, unexpected '}', expecting end of file in /var/www/vichan/html/post.php on line 462
2021-03-17 20:40:26 -04:00
Łiźnier Hełam Łabej
5a027781cc
Merge pull request #423 from vichan-devel/telegrams
add telegrams
2021-03-12 22:07:42 +01:00
h00j
064deec26f fix date 2021-03-12 21:55:27 +01:00
h00j
8002455061 important.html relative time 2021-03-12 21:54:31 +01:00
h00j
f43b5ffbc4 woops 2021-03-12 21:53:16 +01:00
h00j
59c0f788d2 typo 2021-03-12 21:52:37 +01:00
h00j
4ef7feabf2 add telegrams 2021-03-12 21:51:42 +01:00
h00j
8decd4f73f backlinks on ukko are fine 2021-03-04 15:09:52 +01:00
Łiźnier Hełam Łabej
ee57b6b5ca
Merge pull request #405 from PVNFU-28/patch-3
Update config.php
2021-02-26 21:41:46 +01:00
Łiźnier Hełam Łabej
7a44731f27
Merge pull request #413 from PVNFU-28/patch-9
replace rbl.efnet.org with rbl.efnetrbl.org
2021-02-26 21:40:01 +01:00
Łiźnier Hełam Łabej
35068d221b
Merge pull request #412 from isaacimran/patch-1
Update recent_posts.html
2021-02-26 21:36:59 +01:00
Łiźnier Hełam Łabej
2772891486
Merge pull request #411 from PVNFU-28/patch-8
Update config.php
2021-02-26 21:36:00 +01:00
PVNFU-28
d166db6a50
replace rbl.efnet.org with rbl.efnetrbl.org
rbl.efnet.org redirects to rbl.efnetrbl.org and says
"Notice We have created a mirror of the rbl.efnet.org zone at rbl.efnetrbl.org.
It has the exact same data and responses. Please use the mirror in your configs.
The mirror was created after repeated failures at Network Solutions."

Also deleted some random tabs on empty lines and some extra spaces which were put by error I guess
2021-02-21 01:03:10 -03:00
Mert Kasım İmran
3e5e9bdae5
Update recent_posts.html
Fixes #406
2021-02-20 20:11:35 +03:00
PVNFU-28
30eb4e8335
Update config.php
Fixes https://github.com/vichan-devel/vichan/issues/404
2021-02-16 23:33:52 -03:00
Łiźnier Hełam Łabej
3242e28b19
Merge pull request #407 from vichan-devel/composer
update authors in composer.json
2021-02-14 10:22:46 +01:00
h00j
a8e455b014 update authors 2021-02-14 10:20:10 +01:00
h00j
e9e5128604 fix cli tools 2021-02-14 09:22:44 +01:00
h00j
00349d0315 mute constant redefinition notice 2021-02-14 09:22:01 +01:00
panfu28
c25e3dea78
Update config.php
Fixed embedding for dailymotion, metacage and vocaroo.
Google Videos stopped being a thing in 2012 it seems.
2021-02-14 02:49:15 -03:00
h00j
7d855c5316 fix require's 2021-02-13 21:30:27 +01:00
h00j
e405a6c676 fix install script 2021-02-13 21:23:07 +01:00
h00j
908b843854 move antibot functions 2021-02-13 21:14:06 +01:00
Łiźnier Hełam Łabej
d5e8747a19
Merge pull request #401 from vichan-devel/composer
use composer
2021-02-13 19:52:00 +01:00
h00j
0daf03c360 update README 2021-02-13 19:37:18 +01:00
h00j
5b7fb87ec1 add required extensions to composer.json 2021-02-13 19:37:00 +01:00
h00j
599fee5e21 use composer 2021-02-13 19:24:03 +01:00
Łiźnier Hełam Łabej
47df9c6485
Merge pull request #399 from vichan-devel/ip-cloaking
ip cloaking
2021-02-13 17:28:30 +01:00
Łiźnier Hełam Łabej
40d1cea043
Merge pull request #391 from panfu28/patch-3
Update index_fs.css
2021-02-13 17:28:05 +01:00
Łiźnier Hełam Łabej
1843f4a265
Merge pull request #390 from panfu28/patch-2
Update index.css
2021-02-13 17:27:41 +01:00
Łiźnier Hełam Łabej
20ce4a314e
Merge pull request #389 from panfu28/patch-1
Update config.php
2021-02-13 17:27:25 +01:00
h00j
bce71c1f98 ip cloaking 2021-02-13 14:11:41 +01:00
h00j
7a42158177 add maintainer timeline to README 2021-02-09 10:53:34 +01:00
h00j
e7d29cdba9 update irc channel 2021-02-07 00:51:17 +01:00
h00j
4f87eb5d52 update README 2021-02-07 00:48:27 +01:00
panfu28
4070e82ae4
Update index_fs.css
More consistency fixes
Is this type of PR allowed? I just see small errors or inconsistencies all over the place and can't help but want to make the code look and read better.
2020-12-11 23:12:08 -03:00
panfu28
f91cbc16f8
Update index.css
Fixed some errors and replaced lines started with 4 spaces for a tab, some lines started with a tab already so why not make it consistent.
2020-12-11 23:02:44 -03:00
panfu28
d8e3925183
Update config.php
just fixed a comment that might confuse new users, plus it was outdated
2020-12-11 17:10:43 -03:00
Mert Kasım İmran
d6d1082991 Fix for title/subtitle related issue
Subtitle was the page title and title was unused.
2020-12-02 15:56:35 -05:00
Fredrick Brennan
d7d478181d
Update README.md
not offering paid support right now
2020-11-13 16:05:04 -08:00
Sardach
19151def82 insignificant fix
PHP7 shows a warning when executing tools/rebuild2.php: "Declaration of case-insensitive constants is deprecated"
$group_name and $group_value really not need be case-insensitive, so i simply removed that "true".
2020-10-31 04:24:21 -07:00
papereth
6ccaf19045
Fixed thread.html template (#380)
* Update functions.php

` $_SERVER['REMOTE_ADDR']` was hardcoded in ban lookup instead of `$ip` variable

* Fix information leak in thread.html

Sensitive information can be leaked due to inadequate/absent escaping.

Line 14 is truncating before removing tags, this can cause some tags to be cut and therefore not be removed by the `remove_` functions.
Line 22 is just leaking it all, not removing anything.

* Fixed thread template

`remove_markup` is not available on vichan, arguably it makes things better but it's out of scope for this CHANGE, removing modifiers is enough to stop the info leak
consider adding it again after pulling:
fallenPineapple@a5b3336

also moving truncation before escaping for extra safety
2020-08-15 07:19:47 -07:00
papereth
2275735fdf
Fix information leak in thread.html (#377)
Sensitive information can be leaked due to inadequate/absent escaping, if proxy_save enabled

Line 14 is truncating before removing tags, this can cause some tags to be cut and therefore not be removed by the `remove_` functions.
Line 22 is just leaking it all, not removing anything.
2020-08-10 09:50:02 -07:00
Eman Gamer
cf6a733931
Fix Capitalization on setUpControl for Chromium (#378)
Chromium browsers expect capital U in setUpControl and old version of webm-settings.js has setupControl which causes a error and makes expand-video.js not work and options.js not save when this is in use. This bug is not present on FireFox which automatically fixes this for some reason. Stupid bug, stupid fix. No lines actually added or removed.
2020-08-09 19:25:33 -07:00
papereth
a268004c7a
Fix wrong variable used in ban lookup (#376)
` $_SERVER['REMOTE_ADDR']` was hardcoded in ban lookup instead of `$ip` variable
2020-08-09 18:31:50 -07:00
Fredrick Brennan
01538ed33a Close #366
I don't know why this works, but this extra whitespace is required on
PHP 7.4.

If you want to know why I hate PHP updates, this is why.
2020-01-21 09:46:29 +08:00
Daniel Saunders
2600298be8 Theme fixes 2020-01-20 10:37:54 +08:00
Daniel Saunders
c7e5cd6814 $board can be NULL here, prevent indexing it
Simplest 7.4 fix ever? Possibly.
2020-01-20 10:37:10 +08:00
Fredrick Brennan
5e809047ad By default, no longer treat deprecations as errors
Close #363.

See also https://www.youtube.com/watch?v=9crnlHLVdno
2020-01-20 10:04:39 +08:00
Fredrick Brennan
a2ba03849f Fix PHP 7.4 deprecations 2020-01-20 10:04:39 +08:00
Fredrick Brennan
af06cf3737
Disable check_updates by default
It no longer works and never will again, so...
2020-01-15 11:36:51 +08:00
rarjpg
63b0b92690 Move HTML Tidy 'bare' option to the config 2019-11-29 19:21:20 +08:00
rarjpg
0a3bca7dd8 Various fixes 2019-11-29 13:54:08 +08:00
Kureva
1613f6baea Option to reset thread bump after last post deleted. 2019-10-14 18:56:13 +08:00
Fredrick Brennan
9fdbc6c1f0
clarify previous commit 2019-09-22 18:58:10 +08:00
Fredrick Brennan
1173f86869
You can now pay for support 2019-09-22 18:54:50 +08:00
Daniel Saunders
e15e966cdb Fix a bug related to deleting boards (see https://github.com/vichan-devel/vichan/issues/331) 2019-09-22 16:57:12 +08:00
Fredrick Brennan
3a41c24e6e Fix PHP 7.3 regression in ?/users
This gets rid of the "Case-insensitive constants are deprecated" error
by passing doing |upper before passing to constant().
2019-05-02 12:19:50 +08:00
Fredrick Brennan
23378e2623
Remove refs to abandoned domains; IRC channels 2019-04-19 12:23:47 +08:00
Fredrick Brennan
47e4af1eb1 Moved to vichan-devel/vichan wiki
I decided it made more sense to put these in our wiki than to distribute
them with vichan, so right after I added them I removed them.

They can now be read at https://github.com/vichan-devel/vichan/wiki -
and checked out from https://github.com/vichan-devel/vichan.wiki.git
2019-04-18 20:20:38 +08:00
Amsemy Teladi
14384ba1ee Fix docs 2019-04-18 19:52:50 +08:00
Amsemy Teladi
a99c45de22 Save Tinyboard docs (2012-10-16) from web.archive.org 2019-04-18 19:52:50 +08:00
Brayden
997326af59 fix typo in inc/config.php 2019-03-22 10:02:38 +08:00
Circlepuller
dd5dbe65c7 Close #293 2019-03-09 11:54:18 +08:00
Fredrick Brennan
bcfb39bde8
remove superfluous ``and" 2018-11-21 13:37:33 +08:00
Fredrick Brennan
e0e0edc885
not anymore lol 2018-11-21 13:37:00 +08:00
Fredrick Brennan
84bb3833a6
add fact unix is required to readme 2018-11-21 13:35:57 +08:00
Fredrick Brennan
7514f31b36 count➜length in confeditor for PHP7.2 2018-10-12 14:47:38 +08:00
Fredrick Brennan
a2f938c814
Merge pull request #310 from H1K1CH4N/master
Adds a new theme called "Index"
2018-10-12 14:39:35 +08:00
H1K1CH4N
b1842dfe10 removed NPFchan copyright 2018-09-29 04:19:36 +02:00
H1K1CH4N
6f15b56b65 made the image header a little bit better in index theme 2018-09-27 04:58:05 +02:00
H1K1CH4N
44dcbca6c9 improved index theme css 2018-09-27 03:02:21 +02:00
H1K1CH4N
b8f1c219f0 Adds a new theme called "Index"
adds an option to use textarea in theme settings.
Merged most Basic, Recent and Frameset theme functions in one.
you can add a video picture icon and quote in the homepage.
@ctrlcctrlv feel free to add suggestions and fix bladly formed code or let me know and I will try to fix.
i installed it on my demo site: https://hikichan.com/
2018-09-27 00:41:22 +02:00
Fredrick Brennan
a510ebcab9
Remove "Donations" section, add note about php7.2 2018-09-18 14:15:41 +08:00
Fredrick Brennan
aeb4a31194 Close #304 2018-09-18 14:08:06 +08:00
Fredrick Brennan
f6cc993bce
Update README.md
+and other serious bugs
2018-08-08 18:51:56 +08:00
Fredrick Brennan
ac971f36d5 Fix capcodes in PHP7.2. Close #299 2018-07-27 20:08:03 +08:00
Fredrick Brennan
41cfd500de Fix dashboard Countable on non-countable error
Only affected PHP7.2, was due to a sloppy template. PHP7.2 is much more
strict than previous versions so these kinds of bugs are coming to the
fore.
2018-07-27 19:40:44 +08:00
Fredrick Brennan
be2eafd4fc Fix reports under PHP7.2
You'll need to delete all reports made before applying this patch for it
to work right. However, all reports made after applying this patch will
appear correctly in `mod.php?/reports`.

This closes #300.
2018-07-27 19:40:32 +08:00
Fredrick Brennan
524d48110b Fix bans of form "5d", "1y", etc. for PHP7.2
This closes #301.
2018-07-27 19:06:31 +08:00
Fredrick Brennan
814b9246d4 Fix broken link in install.php 2018-07-27 18:48:27 +08:00
Fredrick Brennan
0aa4e3badc Update Twig. This closes #295 2018-05-10 18:25:37 +08:00
Fredrick Brennan
b078ffb1e4 Close #282 2018-05-10 17:33:07 +08:00
Fredrick Brennan
ce72d9c8ee
Merge pull request #287 from antedeguemon/master
Update license and copyright dates
2018-03-02 14:55:56 +00:00
antedeguemon
b94bf5ec19 Update license and copyright dates 2018-03-01 22:57:53 -03:00
Fredrick Brennan
693fa1bdfa Fix #284 for new installations _only_
Users with existing installations are still required to follow the
advice in security bulletin #284.

This commit isn't perfect -- PHP installations below 7.0 and w/o OpenSSL
cannot be fully secured in my estimation. . .
2018-01-29 18:19:16 +08:00
Fredrick Brennan
6ae0f45c31
warn against new vichan installations 2018-01-23 18:57:25 +08:00
Fredrick Brennan
d55721d033
add my email to README for security problems 2018-01-15 22:18:17 +08:00
H0K4
b3e16cfa0d fixed banned redirect i think (#270)
* fixed banned redirect i think

my tinyboard script has been altered a lot but i think this is the fix for the wrong ban redirect.

* maybe it's just this
2017-11-06 20:19:35 +08:00
Fredrick Brennan
464d3b4a04
Merge pull request #269 from H0K4/patch-1
Prevents multiple submit/copy when moving a thread/reply.
2017-11-06 11:21:51 +08:00
H0K4
e8edadeda9
Update move.html 2017-11-05 18:07:52 +01:00
H0K4
896d9e2f75
Update move_reply.html 2017-11-05 18:07:22 +01:00
H0K4
714cb95194
Update move_reply.html 2017-11-05 18:04:34 +01:00
H0K4
767e8f5d6a
Prevents double submit when moving a thread.
I probably moved 1000 of threads and if you double click on the submit button it duplicates the moved thread.
2017-11-05 18:03:20 +01:00
Fredrick Brennan
c2f7073dd4 Fix warnings in PHP7 in gettext library
Not sure how to commit this upstream, the library seems to be
unmaintained.
2017-11-05 21:17:20 +08:00
Fredrick Brennan
ac8f15f0f2 Close #267 2017-11-05 16:25:02 +08:00
Fredrick Brennan
8e811cec44 Close #265 2017-11-03 22:12:35 +08:00
Fredrick Brennan
b7a3bde4c8
Merge pull request #264 from deysu/patch-1
Remove links to (dead) tinyboard.org
2017-11-02 15:39:51 +08:00
Christopher Henly
598843547e
Remove links to (dead) tinyboard.org
Instead we link to the archived documentation, just like in README.md. I also removed the link to tinyboard.org from the boardlinks example, and replaced it with a Github one.
2017-10-30 15:20:43 -04:00
Fredrick Brennan
6cc68ffdc8
Merge pull request #262 from antedeguemon/patch-1
New vimeo embed code
2017-10-28 10:57:36 +08:00
vi
8c0b413c94
New vimeo embed 2017-10-27 22:41:08 -02:00
Fredrick Brennan
09b373cf60 Merge pull request #245 from ghost/patch-2
Bug fixing inc.
2017-10-26 13:35:26 +08:00
Fredrick Brennan
74283b131d Merge pull request #259 from RalphORama/master
Use random_bytes if PHP version >= 7.1.0
2017-10-26 13:27:30 +08:00
RalphORama
c8765dede4 Update PHP version check
Removed trailing zero
2017-10-24 17:36:14 -04:00
RalphORama
2097562596 PHP version check for mcrypt_create_iv
Use `mcrypt_create_iv()` if PHP version is less than 7.1.0, otherwise use `random_bytes()` (introduced in PHP 7.1 to replace `mcrypt_create_iv()`)
2017-10-24 16:27:00 -04:00
RalphORama
67b1565ef8 Replace mcrypt_create_iv with random_bytes
`mcrypt_create_iv()` was deprecated in PHP 7.1.0.
2017-10-24 16:16:25 -04:00
j147
7477215eee Merge pull request #247 from antedeguemon/master
Bugfixes: edit post page minor fix and install.php bug
2017-10-01 21:31:13 +02:00
j147
f25c641278 Merge pull request #248 from roIyat/patch-1
Update thread-watcher.js
2017-10-01 21:29:39 +02:00
j147
617dbb1711 Merge pull request #257 from ev1l0rd/patch-1
Minor error fix for upgrades from 5.1.3 or earlier
2017-10-01 21:28:53 +02:00
Valentijn
be651829e7 Minor error fix for upgrades from 5.1.3 or earlier
Closes #256 and #199.

Full credit for locating the fix goes to @LZZZZ.
2017-10-01 20:14:52 +02:00
antedeguemon
b766b958d1 Fix: cache flushing to avoid installation error 2017-08-02 00:38:23 -03:00
roIyat
7aeac11460 Update thread-watcher.js
A watch thread button is now placed before reply button so citation links don't go between them.
2017-07-30 02:57:39 +02:00
antedeguemon
4d1dc45a7c Bugfix: allow mods to edit poster name at edit post page 2017-07-29 20:23:18 -03:00
Thalis
7529c83a00 added fix from @Circlepuller to mod_move_reply also
thx @Cirlepuller
2017-07-28 22:51:59 +02:00
Thalis
0846d0c784 might fix moving replies with deleted image
brace yourself for other conflicts lol
2017-07-28 22:41:20 +02:00
Thalis
0b84fc26d6 fixed deleted.png image not found on the front end 2017-07-28 21:40:06 +02:00
Thalis
7883998a78 should fix moving a spoilered thread (OP) image 2017-07-28 21:01:12 +02:00
czaks
d5ee60009f downgrade jquery to 2.2.4 2017-07-26 23:44:49 -04:00
czaks
b7875be471 search form in index: fix order 2017-07-26 23:37:05 -04:00
czaks
1371b64986 undo the reason limit 2017-07-26 21:37:08 -04:00
Marcin Łabanowski
19cb514ebb Merge pull request #242 from 27chan/master
Error in report
2017-07-27 03:25:30 +02:00
27chan
899a98c503 Fix error in report threads and replys 2017-07-26 22:22:34 -03:00
27chan
98cb2725eb Merge pull request #1 from vichan-devel/master
Update to 5.1.4
2017-07-26 22:13:29 -03:00
Marcin Łabanowski
ced2b3814e Merge pull request #241 from Circlepuller/master
Updated to reCAPTCHA 2, added a new style, and fixed a JS bug.
2017-07-25 11:31:44 +02:00
Daniel Saunders
2a152b7ff1 Minor changes to form and boardlist in sharp.css 2017-07-25 02:29:52 -04:00
Daniel Saunders
504282b55f Merge remote-tracking branch 'vichan/master' 2017-07-24 22:40:39 -04:00
czaks
4025705eac replace faulty dnsbl with efnetrbl 2017-07-24 15:04:01 -04:00
Daniel Saunders
64cf4b11ee Dirty quick fix to allow moderation to move threads with deleted files 2017-07-24 15:01:17 -04:00
czaks
7b538f0eae Merge branch 'master' of github.com:vichan-devel/Tinyboard 2017-07-24 15:00:44 -04:00
Daniel Saunders
d8e12a15b7 Dirty quick fix to allow moderation to move threads with deleted files 2017-07-24 15:00:33 -04:00
Daniel Saunders
18aed77ecc urlencode reCAPTCHA response for safety 2017-07-24 13:24:44 -04:00
Daniel Saunders
1ad9f49694 Color fixes (sharp.css) 2017-07-24 13:03:12 -04:00
Daniel Saunders
04ce72f24b fuck go back 2017-07-24 12:57:10 -04:00
Daniel Saunders
9c3124177f Fixes to sharp.css including some cross-platform font support 2017-07-24 12:51:33 -04:00
Daniel Saunders
a31a3a281f Bug related to antispam and reCAPTCHA 2 valid fields 2017-07-24 12:22:53 -04:00
Daniel Saunders
09fadec620 inline-expanding.js needs jQuery to function, so why are we not including it in the default configuration too? 2017-07-24 04:38:38 -04:00
Daniel Saunders
b5fac28a8b Fully removed the outdated recaptchalib.php (freed up some room hehe) 2017-07-24 04:03:49 -04:00
Daniel Saunders
7a43a3ea34 Updated reCAPTCHA v2 to use api.js method 2017-07-24 03:40:56 -04:00
Daniel Saunders
78fb0a25a4 Added sharp.css 2017-07-24 03:23:29 -04:00
Marcin Łabanowski
39715e3595 Merge pull request #224 from ghost/patch-2
Implementing Czaks captcha
2017-07-23 17:57:59 +02:00
Marcin Łabanowski
59bcf88872 Merge pull request #146 from szalwia/master
Fix thumbnail scaling in recent theme
2017-07-23 17:55:40 +02:00
Marcin Łabanowski
3e23c028de Merge pull request #215 from antedeguemon/master
Prevents reports with too many characters
2017-07-23 17:55:22 +02:00
Marcin Łabanowski
daa922a239 Merge pull request #239 from bui/fix-search
fix search not remembering board
2017-07-23 17:54:26 +02:00
Marcin Łabanowski
ef1898833d Merge pull request #226 from ghost/patch-1
Fixed DNSBL TOR
2017-07-23 17:54:08 +02:00
Marcin Łabanowski
dc7e4d5b89 Merge pull request #240 from H0K4/patch-1
Board search implementation
2017-07-23 17:53:24 +02:00
Thalis
fe495fed64 Update config.php 2017-07-23 17:50:54 +02:00
Thalis
4efaf50c90 Board search content implementation
you can search boards with this mod.
2017-07-23 17:49:01 +02:00
Bui
1c4b67a597 fix search not remembering board 2017-07-07 23:35:38 +01:00
Marcin Łabanowski
d3cb413d6b Merge pull request #232 from miitroid/patch-1
New CSS: greendark.css
2017-06-28 14:03:57 +02:00
Z Blanche
0976a27cc4 New theme: greendark.css
Used on the /hentaiporn/ 8chan board.
2017-06-10 10:31:39 +00:00
Marcin Łabanowski
74ccc89e64 Merge pull request #230 from antedeguemon/master
Fixed XSS in post edit page and modsearch
2017-05-21 22:35:57 +02:00
antedeguemon
4f85b7c570 Fixed XSS in post edit page and modsearch 2017-05-21 17:08:43 -03:00
czaks
e672d490cd Merge branch 'master' of github.com:vichan-devel/Tinyboard 2017-05-17 14:56:21 -04:00
czaks
40fe35fedc early 404 staged 2017-05-17 14:54:35 -04:00
czaks
736e982945 [SECURITY] Lessen security impact
post.php: misc fixes
2017-05-17 14:05:01 -04:00
Hollick
29409f1456 Update config.php
Well I'm just putting this info out there for those that need tor protection. no reason to pull this if not stable.
2017-04-28 19:07:17 +02:00
Hollick
c4358b078e Update config.php 2017-04-28 16:19:32 +02:00
Hollick
bb86d55b1f Fixed DNSBL TOR
Don't know if Sectoor.de comes online again. Found this new dnsbl tor blacklist checker. in case you really need a tor blacklist.
2017-04-28 16:09:15 +02:00
Horija
e0f913a38f forgot Grave accents 2017-04-25 05:59:42 +02:00
Horija
dc7a507526 Update post.php 2017-04-24 12:21:33 +02:00
Horija
24491cf3c4 Update install.php 2017-04-24 11:51:26 +02:00
Horija
8b0c86d8bd Update install.sql 2017-04-24 11:49:38 +02:00
Horija
ce755b8b51 Update quick-reply.js 2017-04-24 11:46:00 +02:00
Horija
5dbfc0ab24 Update post_form.html 2017-04-24 11:44:39 +02:00
Horija
e94f25d4cd Add files via upload 2017-04-24 11:42:58 +02:00
Horija
e1bc4f1da9 Update config.php 2017-04-24 11:41:49 +02:00
Horija
3438718667 Add files via upload 2017-04-24 11:40:49 +02:00
Horija
6a53b99feb Create readme.md 2017-04-24 11:40:22 +02:00
Horija
9ddb5833b3 Implementing a new captcha
I'm (trying) to integrate @Czaks custom captcha
2017-04-24 11:38:56 +02:00
Marcin Łabanowski
29827d1ca4 Merge pull request #223 from Horija/patch-1
Fixed go to bottom link
2017-04-21 08:06:30 +02:00
Horija
175b54b7f0 Fixed go to bottom link 2017-04-21 03:09:48 +02:00
czaks
37eaea6312 Merge branch 'master' of github.com:vichan-devel/Tinyboard 2017-04-07 13:07:30 -04:00
Marcin Łabanowski
a492e778af Merge pull request #220 from kekukin/patch-1
Fixed uninstall error for themes.
2017-03-30 01:04:59 +02:00
KekuKin
57732bdff5 Fixed uninstall error for themes.
Was receiving uninstall errors: undefined index: theme
2017-03-30 00:58:11 +02:00
czaks
d85cb47647 report event 2017-03-11 08:34:58 -05:00
vholmes
387ebe9c0c Prevents reports with too many characters 2017-02-15 23:07:50 -02:00
Marcin Łabanowski
f139b34c4a Merge pull request #213 from tlm-2501/patch-1
Show backlinks on OP's as well, and disable the script on Ukko boards, it's broken there anyway.
2017-01-23 20:13:57 +01:00
tlm-2501
e672cf9ba1 Show backlinks on OP's as well, and disable the script on Ukko boards (it's broken there anyway) 2017-01-23 21:15:13 +03:00
Marcin Łabanowski
a989435253 Merge pull request #212 from SHooZ/uk_UA-localization
Add uk_UA localization files
2017-01-22 23:58:32 +01:00
SHooZ
2ca02733ac Add uk_UA localization files 2017-01-22 22:16:56 +00:00
Marcin Łabanowski
90f27d8c2e Merge pull request #211 from tlm-2501/patch-1
Change "Configuration Basics" link to point to the archived tinyboard website
2017-01-18 04:27:38 +01:00
tlm-2501
50d2cfd45e Change "Configuration Basics" link to point to the archived tinyboard website. 2017-01-18 06:21:56 +03:00
Marcin Łabanowski
05cd180102 Merge pull request #209 from int15h/master
New EXIF viewer
2016-12-24 13:45:10 +01:00
int15h
b3071152dc new exif provider 2016-12-24 10:39:30 -02:00
Marcin Łabanowski
8eb8a6e729 Merge pull request #205 from Montrosos/patch-1
Included header.html for better boardlist
2016-12-12 14:38:59 +01:00
Montrosos
1f4de533f0 Included header.html for better boardlist
Simply included the header.html so that the compact boardlist works with it and it's responsive now.
2016-12-12 13:52:42 +01:00
Marcin Łabanowski
4a21c97cb0 Merge pull request #202 from pngcrypt/master
table `pages` fix
2016-11-20 16:44:52 +01:00
pngcrypt
2a669d7113 table pages fix 2016-11-20 03:13:17 +03:00
czaks
65f39b8a07 local-time.js: fixup 2016-10-09 02:11:55 +02:00
czaks
741c29b452 Merge branch 'master' of github.com:vichan-devel/Tinyboard 2016-10-09 02:02:23 +02:00
czaks
c21eeff605 local-time.js: relative time by default 2016-10-09 02:02:13 +02:00
czaks
6049979daf nntpchan install.sql fix 2016-10-09 02:01:23 +02:00
Marcin Łabanowski
1ae5df54dc Merge pull request #200 from seisatsu/patch-2
Fix typo in max_images comment
2016-10-02 00:09:40 +02:00
Michael D. Reiley
372c26491a Fix typo in max_images comment
multi_image.js should be multi-image.js, with a dash, not an underscore.
2016-10-01 15:06:09 -07:00
Marcin Łabanowski
a4b619ae5e Merge pull request #198 from seisatsu/patch-1
Rebuild index when mod deletes a thread.
2016-09-23 09:24:05 +02:00
Michael D. Reiley
8951cb74c8 Rebuild index when mod deletes a thread.
The index does not properly rebuild when a mod deletes a thread, resulting in a ghost thread remaining in the index until the next rebuild. This fix was originally contributed to Uboachan's codebase by Mannosuke.
2016-09-22 23:03:11 -07:00
czaks
3f38a6db97 Merge branch 'master' of github.com:vichan-devel/Tinyboard 2016-08-19 23:15:47 +02:00
czaks
a5e7b3da6f nntpchan: work around php nonsense 2016-08-19 23:15:42 +02:00
Marcin Łabanowski
fc21854770 Merge pull request #196 from majestrate/master
patch for nntpchan stream
2016-08-19 22:08:33 +02:00
Jeff Becker
1c3e6e590a
patch for nntpchan stream 2016-08-19 16:05:50 -04:00
czaks
0b19051891 fix a notice; increase waiting time for dns 2016-08-15 04:13:26 +02:00
czaks
a779b96370 second iteration of nntpchan implementation 2016-08-15 00:56:06 +02:00
czaks
1c24c69999 Merge branch 'master' of github.com:vichan-devel/Tinyboard 2016-08-14 16:24:25 +02:00
czaks
5e335a8564 preliminary inbound nntpchan support 2016-08-14 16:24:17 +02:00
Marcin Łabanowski
a209216656 Merge pull request #195 from ptchan-foss/master
Fixed report syslog message
2016-08-12 19:21:35 +02:00
ptchan-foss
8548a4ff70 Fixed report syslog message 2016-08-12 18:18:54 +01:00
Marcin Łabanowski
7bec8a0a85 Merge pull request #191 from nekomiko482/patch-1
BSD md5 output format compatibility fix
2016-07-08 05:45:18 +02:00
nekomiko482
a55760299c Fixes incompatibility with BSD's md5 output format.
fixes #190
2016-07-07 12:53:40 +03:00
czaks
11cecf8452 Revert "[BUG] Image reject repost board option now also affects YT embeds"
This reverts commit b476b660073adbe827836a7e7860b0fb3a061ff0.
2016-06-21 05:03:44 +02:00
czaks
8f4aa27329 fix file-selector.js dependencies 2016-06-19 02:40:24 +02:00
czaks
fed9065cf1 skip non-image files in recent themes; fixes vichan-devel/vichan#185 2016-06-19 02:23:24 +02:00
czaks
356f46237c fix install.sql after a bad merge 2016-06-19 02:15:49 +02:00
czaks
e230f1472c don`t ocr non-images 2016-06-10 12:41:53 +02:00
fatchan
c9ef21bff9 Better solution to prevent ID wrapping. Also no longer text-selectable. Much cleaner. 2016-06-09 11:23:32 +02:00
Duane Moody
f23d11be60 Prevent poster IDs from wordwrapping
Poster IDs still linebreak between "ID:" and the ID, this corrects that without having to replace the space inbetween with an &nbsp; nonbreaking space.
2016-06-09 11:23:08 +02:00
fatchan
4f3cc7f316 Whoops 2016-06-09 11:22:57 +02:00
fatchan
f27c26907d Remove hardcoded 8chan links in catalog RSS 2016-06-09 11:22:47 +02:00
fatchan
aa0d92a2b4 Force post-hover.js to show OP's on hover. 2016-06-09 11:20:39 +02:00
fatchan
04f42b3802 CSS hover instead of javascript mouseover. Need to use important because the dark/light text determined by IDToRGB is added inline and takes priority over stylesheets. 2016-06-09 11:20:23 +02:00
fatchan
cdd963e79e fix flag spacing 2016-06-09 11:18:34 +02:00
czaks
d2bb4a776f fail gracefully on no thumbnail 2016-06-09 11:15:45 +02:00
czaks
8a46c7a0d5 tesseract OCR support for spamfilters 2016-06-09 11:09:10 +02:00
czaks
36d762514c Merge branch 'master' of github.com:vichan-devel/Tinyboard 2016-06-09 04:51:17 +02:00
czaks
94c91db097 fix news deletion; thanks MrFreeman 2016-06-09 04:51:05 +02:00
Marcin Łabanowski
6a7be4a058 Merge pull request #182 from fatchan/master
Move the 'Go back and rebuild again' to the top of the rebuilt page s…
2016-05-31 16:08:50 +02:00
fatchan
d285a79667 Move the 'Go back and rebuild again' to the top of the rebuilt page so you dont have to scroll 2016-05-31 23:28:55 +10:00
czaks
52fe9bc873 fix sane_strategy for advanced build. should fix the ajax.js problem. 2016-05-15 15:53:30 +02:00
czaks
4fe2da2fcd post-filter & ukko: final solution i think 2016-05-09 13:02:21 +02:00
czaks
018dd48a66 post-filter + ukko fixes 2016-05-09 11:58:46 +02:00
czaks
4479fc7681 thread-watcher and favorites in catalog and ukko 2016-05-09 11:18:35 +02:00
czaks
1cff10fd95 ukko & fix-re-de-su: fix reporting and deleting from ukko actually 2016-05-09 11:08:24 +02:00
czaks
ccc9cff23d ukko: post filters and reporting 2016-05-09 10:59:50 +02:00
czaks
bb9aaad899 i forgot about a queue and a lock implementation 2016-05-08 15:37:49 +02:00
czaks
f24e0f9814 optimize out openboard when we don`t need it. a big performance improvement too 🏎
also, don't call dnsbl for local ip addresses
2016-05-08 14:02:17 +02:00
czaks
12e6aba5d4 (2/2) advanced build. implement a daemon that will build static pages.
implement a queue and a lock. fix notice in bans. and it even works!

the daemon is basic right now, it could work in a mode that it will defer building certain
pages until a certain time.
2016-05-08 13:23:41 +02:00
czaks
e265375475 fixup 2016-05-08 10:59:36 +02:00
czaks
b6f0317bde advanced build (1/2): a small refactor of index generating procedure; generation strategies 2016-05-08 10:54:30 +02:00
czaks
644f227ab3 fix "Undefined variable: pid"; thanks fpdl and MrFreeman 2016-05-08 03:09:20 +02:00
czaks
a5e22f6d63 split route and controller parts from smart build 2016-05-08 02:50:44 +02:00
czaks
deef54fe13 introduce smart_build_helper 2016-05-07 02:16:54 +02:00
czaks
ce9f9eec25 settings dialog refinements: now you can resize it 2016-05-06 17:11:12 +02:00
czaks
3f405b3484 what if IP address contained bad characters? (highly local) 2016-05-06 16:53:28 +02:00
czaks
3571670b98 fix catalog link someone? 2016-05-06 16:51:34 +02:00
czaks
a5bd39dc4a mod dashboard html: link to page editor 2016-05-06 16:49:35 +02:00
Fredrick Brennan
505adffcdc Cyclical threads ♺ 2016-05-06 16:39:20 +02:00
czaks
ab02a42725 maybe we can try to load Parsedown, after all we can silence the error 2016-05-06 16:27:43 +02:00
8chan
d788131202 Allow a board called news to exist 2016-05-06 16:26:17 +02:00
czaks
91c02c3ec4 board pages: add a migration 2016-05-06 16:21:43 +02:00
czaks
d726eaf195 we don't have a htmlpurifier yet ;_; 2016-05-06 16:07:21 +02:00
Fredrick Brennan
95b1e103cb Edit static pages commit 2016-05-06 16:03:55 +02:00
8chan
7911c374e8 Public action logs commit (log.php)
Note: In a previous commit, I began making inc/mod/auth.php more modular with the check_login() function. Including it does NOT check mod login by default anymore like it does on vichan. You have to call check_login(). I've finally included it in inc/functions.php. If you have any custom pages that use inc/mod/auth.php, just including functions.php is enough now.

===================================
Also: backports 351375185e5 (early 404)
2016-05-06 15:44:26 +02:00
8chan
6dd1420f91 Add event to quote backlinks 2016-05-06 15:15:17 +02:00
8chan
ce3ce4f1b6 Fix *0 secure tripcodes caused by accidentally feeding + signs to crypt() 2016-05-06 15:14:55 +02:00
8chan
7831da83fc New event: rebuildpost, allows you to bind events to ?/edit 2016-05-06 15:13:27 +02:00
Fredrick Brennan
b476b66007 [BUG] Image reject repost board option now also affects YT embeds 2016-05-06 15:12:08 +02:00
czaks
2fa37278db boardlist goes before #top 2016-05-06 15:09:25 +02:00
czaks
6e33de568d hide-threads.js: add div.file to fields to hide; thanks fpdl 2016-05-06 15:03:53 +02:00
czaks
8496b021a9 comment out global reports 2016-05-06 14:57:07 +02:00
Fredrick Brennan
65ea7b78c5 Catalog: click to scroll thread 2016-05-06 14:53:17 +02:00
sourcerect
3515fdabe7 Fix tab freeze when inlining
Fixes ctrlcctrlv/infinity#451
2016-05-06 14:49:30 +02:00
czaks
aa98ca337e i think this lump of code deserves a version bump; v5.1.0 here 2016-05-06 14:36:14 +02:00
czaks
126ee42b9d better rules for stripping combined chars, based on 45c0d327619 by @ctrlcctrlv 2016-05-06 14:34:42 +02:00
Forkless
d069a4c9fd Added option for hiding IDs. 2016-05-06 14:32:53 +02:00
czaks
33ef3f9b01 synchronize catalog_link 2016-05-06 14:14:22 +02:00
Bui
33ef1d2123 add active page classes to body; czaks: go to bottom @ thread: fixes 2016-05-06 14:05:16 +02:00
8n-tech
6644ff666a Also improved some CSS and HTML aspects of the thread layout.
Signed-off-by: 8n-tech <8n-tech@users.noreply.github.com>
2016-05-06 13:51:15 +02:00
8chan
7a7574bdca SECURITY / XSS : ?/edit allowed arbitrary HTML to be added by any user thru addition of <tinyboard raw html>1</tinyboard>
This allowed ANY user with ?/edit privilege to also have raw_html regardless of whether they had $config['mod']['rawhtml']

Now, any changes to <tinyboard> markup modifiers via ?/edit are not allowed. They are removed at read time, and before write they are removed again and the ones in the database (which should be clean...) are inserted instead.

Please immediately apply this patch to your instance if you are running any version of 8chan/infinity.
2016-05-06 12:43:25 +02:00
8chan
6da7f4d25a No more country flags in <title> 2016-05-06 12:40:37 +02:00
8chan
ae4eb4d3d9 RSS 2016-05-06 12:40:07 +02:00
8chan
632d0a76d0 Display placeholder if no file in catalog/theme.php; czaks: fix the code a bit 2016-05-06 12:37:00 +02:00
anonfagola
cb97029d0d Update catalog.html
Changed title from being - "Catalog /board/" to "/board/ - Catalog"
2016-05-06 12:27:21 +02:00
czaks
3f29170f1b debrand 8chan; btw. the previous commit was [SECURITY] i think? 2016-05-06 12:23:18 +02:00
8chan
ce62673a2c OpenGraph information in thread pages https://en.wikipedia.org/wiki/Facebook_Platform#Open_Graph_protocol 2016-05-06 12:18:31 +02:00
8chan
aa0d3395b1 Show first 256 chars of body in <title> 2016-05-06 12:17:51 +02:00
8chan
b6f3d44080 Go to bottom link 2016-05-06 12:16:01 +02:00
czaks
293543878a backport parts of 2d6d449bd2d72, in particular html classes 2016-05-06 12:14:28 +02:00
Bui
6c334a3b44 lol spaces 2016-05-06 11:45:52 +02:00
Bui
d46bf4e2f2 add id to thread links 2016-05-06 11:45:37 +02:00
czaks
02c3c28a16 main.js: a bit more sane code 2016-05-05 15:37:50 +02:00
czaks
6991ca270e fix bad merge 2016-05-05 13:53:52 +02:00
8chan
129eb154b3 Merge 2016-05-05 13:45:36 +02:00
Zixaphir
633c223282 Fix trailing comma 2016-05-05 13:43:32 +02:00
Zixaphir
cad8019068 Prevent images from hovering off-page
This entirely affects the "imageHoverFollowCursor" option.
2016-05-05 13:43:17 +02:00
Pashe
5f043d0a29 Have image-hover.js use data-fullimage instead of the API 2016-05-05 13:43:04 +02:00
8chan
913420e040 Image hover backported from 8chan X 2016-05-05 13:42:54 +02:00
czaks
130b32d08b remove image hover, so we can import the whole 8chan history of that file 2016-05-05 13:42:38 +02:00
Fredrick Brennan
2712235f15 Make js/options/favs.js actually usable
I pretty much had to rework this completely to get it into a usable state

Reference ctrlcctrlv/infinity#424
2016-05-05 13:36:26 +02:00
Harry Hackett
6cb3039b71 Create fav.js 2016-05-05 13:35:02 +02:00
Fredrick Brennan
71fde35938 Oops forgot a file 2016-05-05 13:33:14 +02:00
Mark Taiwan
c2e3ff162f Added missing curly brackets in post-filter.js 2016-05-05 13:22:59 +02:00
8chan
7cf3fccda5 Fix menu brokenness if user post deletion disabled 2016-05-05 13:22:36 +02:00
8n-tech
e64b01b690 Javascript ammendments, dio_ on Windows.
Signed-off-by: 8n-tech <8n-tech@users.noreply.github.com>
2016-05-05 13:22:11 +02:00
8chan
6b04b3c671 Fix post deletion 2016-05-05 13:21:09 +02:00
8chan
5f10badee9 Make no-animated-gif.js trigger on new_post 2016-05-05 13:12:10 +02:00
8chan
8412299fa5 Fix hide-threads.js interaction in no-animated-gif.js 2016-05-05 13:11:54 +02:00
Ringstaart
4e635229b4 Replace capital X by clearly superior ASCII ×
There's no reason to use a malformed letter when an ASCII character of a proper cross is available. This is an important issue, and I care about it very much.
2016-05-05 13:08:30 +02:00
8chan
f02226449a Fix own post (You) 2016-05-05 13:06:25 +02:00
Fredrick Brennan
cac428b30c Add some missing i18n tags 2016-05-05 13:03:31 +02:00
8chan
5267098cb8 Make bottom watchlist-toggle work 2016-05-05 13:01:24 +02:00
8chan
fd2e9df30c This script was breaking boards.html 2016-05-05 13:01:00 +02:00
Pashe
01446aad12 thread-watcher.js: fix background and border 2016-05-05 13:00:47 +02:00
Pashe
6f301505e3 thread-watcher.js: change display format 2016-05-05 13:00:33 +02:00
7185
0b1c67574a Fix selector in inline.js
Should make >>>/crossboard/links (and expanding links) work again
2016-05-05 12:59:25 +02:00
Fredrick Brennan
8943bb0bb3 Rewrite report system due to flooding 2016-05-05 12:57:52 +02:00
8chan
95a9b7b72b Completely rewrite fix-report-delete-submit.js, add report/delete to menu 2016-05-05 12:52:17 +02:00
Forkless
4e39262223 Moved the Options tab stuff to be setup after the document is ready. 2016-05-05 12:45:25 +02:00
Forkless
81daf934fb Fix for the js being shitty inside the compiled main.js. 2016-05-05 12:45:07 +02:00
Forkless
f6b4b2ac18 Removed redundant setting.
Bugfix.
2016-05-05 12:44:48 +02:00
Forkless
1663efcf9d Fix for update to comment toolbar (should work now) 2016-05-05 12:43:57 +02:00
Fredrick Brennan
1b0f5fd24c Revert "Comment toolbar update and Thread stats addition" 2016-05-05 12:42:41 +02:00
ForklessAnon
69a6631742 Added option to disable/ignore keybinds. 2016-05-05 12:36:28 +02:00
ForklessAnon
86ddb4ecbb Update formatting toolbar to include user definable settings and customized options. 2016-05-05 12:35:58 +02:00
Markerov
9265ebea43 initial commit 2016-05-05 12:35:45 +02:00
marktaiwan
59ee8a990f post-filter.js: prevent extra space characters
prevent extra spaces in comment caused by joining strings with leading
or trailing space.
2016-05-05 12:33:44 +02:00
8chan
4e27112147 Fix filter for users with emoji in thier names 2016-05-05 12:33:23 +02:00
marktaiwan
a9b29c7232 Bugfix: convert it to string 2016-05-05 12:33:02 +02:00
marktaiwan
513c8f7b68 post-filter.js catalog support
Removes hidden threads from catalog page
Shift click on catalog to hide thread
Improved word matching for simple comment and subject filter.
2016-05-05 12:32:27 +02:00
marktaiwan
dc725641c3 minor options template adjustment 2016-05-05 12:32:00 +02:00
marktaiwan
8f6ea6dd94 post-filter.js restructure
- Changed how filters are stored. Have included code to migrate from
previous version
- Added support for 'subject' and 'comment' in the options panel
- Added regex support in the options panel
2016-05-05 12:31:11 +02:00
marktaiwan
f84d0b9027 Changed 'hide post' button behavior 2016-05-05 12:30:54 +02:00
8chan
b469855126 use onready in post-menu.js 2016-05-05 12:30:18 +02:00
marktaiwan
af91ddf637 Split post menu into own function 2016-05-05 12:29:11 +02:00
8chan
e3dbca5616 Add one click [-]/[+] to post-filter.js 2016-05-05 12:28:52 +02:00
marktaiwan
84bc3b0f7b I forgot how to jQuery 2016-05-05 12:16:55 +02:00
marktaiwan
3e579ee1d4 Bugfix: reset multifile container width
#371
2016-05-05 12:16:17 +02:00
marktaiwan
deefe7225d Bugfix: scroll to thumbnail on multipost close
Indroduced in 7364e85
2016-05-05 12:15:51 +02:00
8chan
5176377045 Fix many features for Internet Explorer 9 and 10
Please don't use "dataset" in scripts anymore, it doesn't work in IE9 or 10. Instead use $.data
2016-05-05 12:15:26 +02:00
marktaiwan
88f6088a42 fix Relative Time and Image Throttler 2016-05-05 12:14:22 +02:00
Markerov
7160cd650e merge relative-time into local-time 2016-05-05 12:12:53 +02:00
Markerov
c3146e1794 image throttler
initial commit
2016-05-05 12:11:12 +02:00
topkek
63491b0b9a Scroll to thumbnail when shrinking an expanded image 2016-05-05 12:10:09 +02:00
Markerov
fd2b41c1f0 Fix bug with inline expansion with Unanimate Gif
Issue #273
2016-05-05 12:09:55 +02:00
Markerov
bc2257be08 scroll to thumb
scroll up to image if its top is out of view when shrunk.
Requires jQuery
2016-05-05 12:09:41 +02:00
Markerov
4767a63178 renamed 'this.childNodes[0]' to thumb 2016-05-05 12:09:10 +02:00
8chan
99ee2e36ba Don't expand webm or YouTube videos on js/expand-all-images.js 2016-05-05 12:06:27 +02:00
Markerov
a723ff8e66 Display inline image when it starts loading,
Rewrote inline-expanding.js to display full image as soon as it starts
loading.
Modified expand-all-images.js to work with the new change
Moved max-width to style.css
2016-05-05 12:05:45 +02:00
marktaiwan
26130c43ea fix menu UI for Firefox 2016-05-05 12:03:24 +02:00
marktaiwan
c8eaebce9a reset thread timer on new filter 2016-05-05 12:03:05 +02:00
marktaiwan
709a248d10 Clear empty thread/board from filter list 2016-05-05 12:02:50 +02:00
marktaiwan
2808b1b8b2 post-filter
Adds a dropdown menu to each post that allows for filtering by:
- post
- post and all replies to it
- poster ID
- poster ID and all replies to it
- name
- tripcode

In the options panel, adds a new tab for adding/removing name and
tripcode filters.
2016-05-05 12:02:34 +02:00
8chan
dd27026618 Show stickies at top of catalog 2016-05-05 12:01:16 +02:00
8chan
e8b530a783 Shift-click to hide threads in catalog 2016-05-05 12:00:21 +02:00
8chan
01207dfcbb Catalog script fix for hiding threads with short IDs 2016-05-05 11:59:59 +02:00
8chan
b056124e49 Catalog: don't show hidden threads 2016-05-05 11:59:16 +02:00
8chan
4f96263e3b Upgrade jQuery mixitup, preserve settings, no animations 2016-05-05 11:59:00 +02:00
8chan
e2016340e1 Fix expand-too-long error with new <p> markup 2016-05-05 11:56:41 +02:00
czaks
cd01191072 those parts are extraneous 2016-05-05 11:45:29 +02:00
8chan
3eb755ee7e Move login check in inc/mod/auth.php to a function
This allows pages like create.php to not include inc/mod/pages.php while still being able to use the mod auth functions (like generating salts and passwords)
2016-05-05 11:40:52 +02:00
8chan Admin
93f748e6a8 Security: capitalization of mods username is significant 2016-05-05 11:39:12 +02:00
czaks
d310abc95c Merge branch 'master' of github.com:vichan-devel/vichan 2016-05-05 10:54:09 +02:00
czaks
186ad5ca86 bsd fixup 2016-05-05 10:53:44 +02:00
czaks
abe4bdd6ae fixup 2016-05-05 10:52:58 +02:00
czaks
19b70663d7 remove magic_quotes check; it`s 2016 after all 2016-05-05 10:29:13 +02:00
czaks
4c827cf105 fix some nonsense 2016-05-05 10:22:34 +02:00
czaks
c4b98e94ce [SECURITY] harden for imagetragick (we aren`t hit by the bug, but we were passing uncommon filetypes, like JPEG2000, directly to imagemagick) 2016-05-05 10:17:14 +02:00
czaks
77176faece enable javascript in mod panel 2016-05-05 09:56:54 +02:00
czaks
38bf3276e4 update copyright years; remove a link to tinyboard (website is dead) 2016-05-05 09:39:23 +02:00
czaks
89fe3db556 ... 2016-05-05 09:25:47 +02:00
czaks
985c113190 ... 2016-05-05 09:18:36 +02:00
czaks
8dac72e924 update installer 2016-05-05 09:16:09 +02:00
czaks
a42256b296 locale cache: fix a bug when perms are done wrong 2016-05-05 08:43:34 +02:00
czaks
36b78e5f98 fix for editor highlighting 2016-05-05 08:40:13 +02:00
czaks
295f597699 Merge branch 'master' of github.com:vichan-devel/vichan 2016-05-05 08:22:47 +02:00
czaks
dcf5d699bd simplify the md5 execution logic 2016-05-05 08:22:19 +02:00
czaks
9768161327 simplify the code a bit 2016-05-05 07:51:55 +02:00
czaks
c898f97493 Merge branch 'master' of https://github.com/vichan-devel/vichan 2016-05-05 06:45:08 +02:00
czaks
7c3126866c ease the migration process for the previous security patch (by introducing another migration); restore php 5.4 compatibility (introducing a polyfill system) 2016-05-05 06:43:22 +02:00
Marcin Łabanowski
2caad90755 Merge pull request #177 from 27chan/master
Fix default theme and add Font Awesome Icons in recent.html
2016-04-30 04:09:24 +02:00
27chan
199931dc1a Fix default theme and add Font Awesome Icons 2016-04-29 18:00:33 -03:00
27chan
f7a47f1b9e New theme northboard_cb
For circleboard by kotiakrobaatti
2016-04-29 15:40:01 -03:00
Marcin Łabanowski
e0a4b479f9 Merge pull request #176 from 27chan/master
Update Font Awesome
2016-04-28 07:09:38 +02:00
27chan
48726950d9 Update Font Awesome
Update Font Awesome to 4.6.1
2016-04-27 14:25:51 -03:00
czaks
caaf741691 [SECURITY] keep up with modern password hashing standards 2016-04-22 05:35:43 +02:00
czaks
c53f13bf90 Merge branch 'master' of https://github.com/vichan-devel/vichan 2016-02-09 12:37:03 +01:00
szalwia
11c7c57873 Fix thumbnail scaling in recent theme
Use decoded JSON values to set thumbnail sizes in the recent theme
2015-06-04 17:18:27 +01:00
czaks
fe66c51a19 ... (minor fix for locales) 2015-03-10 13:13:18 +01:00
515 changed files with 20784 additions and 34127 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.

8
.gitignore vendored
View File

@ -11,11 +11,8 @@
!/templates/*.html !/templates/*.html
!/inc/config.php !/inc/config.php
# minify
/inc/lib/minify
# instance-config # instance-config
/inc/instance-config.php /inc/secrets.php
# .installed # .installed
/.installed /.installed
@ -47,3 +44,6 @@ Thumbs.db
#vichan custom #vichan custom
favicon.ico favicon.ico
/static/spoiler.png /static/spoiler.png
/local-instances
/vendor/

9
.gitmodules vendored
View File

@ -1,3 +1,12 @@
[submodule "js/wPaint"] [submodule "js/wPaint"]
path = js/wPaint path = js/wPaint
url = https://github.com/vichan-devel/wPaint.git url = https://github.com/vichan-devel/wPaint.git
branch = master
[submodule "inc/lib/parsedown"]
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,5 +1,5 @@
# License of vichan # License of vichan
Copyright (c) 2012-2015 vichan-devel Copyright (c) 2012-2018 vichan-devel
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

@ -1,36 +1,50 @@
vichan - A lightweight and full featured PHP imageboard. vichan - A lightweight and full featured PHP imageboard.
======================================================== ========================================================
**Please do not contact Fredrick Brennan in regards to vichan issues.**
As of 29 August 2022 it supports PHP8.1.
About About
------------ ------------
vichan is a free light-weight, fast, highly configurable and user-friendly vichan is a free light-weight, fast, highly configurable and user-friendly
imageboard software package. It is written in PHP and has few dependencies. imageboard software package. It is written in PHP and has few dependencies.
Some documentation may be found on our [wiki](https://github.com/vichan-devel/vichan/wiki). (feel free to contribute)
History
------------
vichan is a fork of (now defunc'd) [Tinyboard](http://github.com/savetheinternet/Tinyboard), vichan is a fork of (now defunc'd) [Tinyboard](http://github.com/savetheinternet/Tinyboard),
a great imageboard package, actively building on it and adding a lot of features and other a great imageboard package, actively building on it and adding a lot of features and other
improvements. improvements.
Support and announcements: https://engine.vichan.net/ ![](static/doc/timeline.svg)
### Maintainer timeline
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 Requirements
------------ ------------
1. PHP >= 5.4 (we still try to keep compatibility with php 5.3 as much as possible) 1. PHP >= 7.4
PHP 7.0 is explicitly supported.
2. MySQL/MariaDB server 2. MySQL/MariaDB server
3. [mbstring](http://www.php.net/manual/en/mbstring.installation.php) 3. [mbstring](http://www.php.net/manual/en/mbstring.installation.php)
4. [PHP GD](http://www.php.net/manual/en/intro.image.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) 5. [PHP PDO](http://www.php.net/manual/en/intro.pdo.php)
6. A Unix-like OS, preferrably FreeBSD or GNU/Linux
We try to make sure vichan is compatible with all major web servers and 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.
operating systems. vichan does not include an Apache ```.htaccess``` file nor does
it need one.
### Recommended ### Recommended
1. MySQL/MariaDB server >= 5.5.3 1. MySQL/MariaDB server >= 5.5.3
2. ImageMagick (command-line ImageMagick or GraphicsMagick preferred). 2. ImageMagick (command-line ImageMagick or GraphicsMagick preferred).
3. [APC (Alternative PHP Cache)](http://php.net/manual/en/book.apc.php), 3. [APCu (Alternative PHP Cache)](http://php.net/manual/en/book.apcu.php),
[XCache](http://xcache.lighttpd.net/) or [Memcached](http://www.php.net/manual/en/intro.memcached.php) or
[Memcached](http://www.php.net/manual/en/intro.memcached.php) [Redis](https://redis.io/docs/about/)
Contributing Contributing
------------ ------------
@ -39,23 +53,21 @@ You can contribute to vichan by:
* Providing feedback and suggestions * Providing feedback and suggestions
* Writing/editing documentation * Writing/editing documentation
If you need help developing a patch, please join our IRC channel.
Installation Installation
------------- -------------
1. Download and extract vichan to your web directory or get the latest 1. Get the latest development version with:
development version with:
git clone git://github.com/vichan-devel/vichan.git git clone git://github.com/vichan-devel/vichan.git
2. Navigate to ```install.php``` in your web browser and follow the 2. run ```composer install``` inside the directory
3. Navigate to ```install.php``` in your web browser and follow the
prompts. prompts.
3. vichan should now be installed. Log in to ```mod.php``` with the 4. vichan should now be installed. Log in to ```mod.php``` with the
default username and password combination: **admin / password**. default username and password combination: **admin / password**.
Please remember to change the administrator account password. Please remember to change the administrator account password.
See also: [Configuration Basics](http://tinyboard.org/docs/?p=Config). See also: [Configuration Basics](https://github.com/vichan-devel/vichan/wiki/config).
Upgrade Upgrade
------- -------
@ -68,35 +80,20 @@ finally run ```install.php```.
To migrate from a Kusaba X board, use http://github.com/vichan-devel/Tinyboard-Migration 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 Support
-------- --------
vichan is still beta software -- there are bound to be bugs. If you find a vichan is still beta software -- there are bound to be bugs. If you find a
bug, please report it. bug, please report it.
If you need assistance with installing, configuring, or using vichan, you may
find support from a variety of sources:
* If you're unsure about how to enable or configure certain features, make
sure you have read the comments in ```inc/config.php```.
* Check out an [official vichan board](http://int.vichan.net/devel/).
* You can join vichan's IRC channel for support
[irc.6irc.net #vichan-devel](irc://irc.6irc.net/vichan-devel)
### Tinyboard support
vichan is based on a Tinyboard, so both engines have very much in common. These
links may be helpful for you as well:
* Tinyboard documentation can be found [here](https://web.archive.org/web/20121016074303/http://tinyboard.org/docs/?p=Main_Page).
Donations
---------
Do you like our work? You can motivate us financially to do better ;)
* Bitcoin: 1GjZEdLaTQ8JWVFGZW921Yv4x59f9oiZME
You can also ask us to develop some feature specially for you <3. Join our IRC
channel and ask for a quote (there are a few of us, who work with the codebase
and are skilled enough to develop such features pretty quickly).
CLI tools CLI tools
----------------- -----------------
There are a few command line interface tools, based on Tinyboard-Tools. These need There are a few command line interface tools, based on Tinyboard-Tools. These need
@ -107,12 +104,6 @@ You actually don't need these tools for your imageboard functioning, they are ai
at the power users. You won't be able to run these from shared hosting accounts at the power users. You won't be able to run these from shared hosting accounts
(i.e. all free web servers). (i.e. all free web servers).
Localisation
------------
Wanting to have vichan in your language? You can contribute your translations at this URL:
https://www.transifex.com/projects/p/tinyboard-vichan-devel/
Oekaki Oekaki
------ ------
vichan makes use of [wPaint](https://github.com/websanova/wPaint) for oekaki. After you pull the repository, however, you will need to download wPaint separately using git's `submodule` feature. Use the following commands: vichan makes use of [wPaint](https://github.com/websanova/wPaint) for oekaki. After you pull the repository, however, you will need to download wPaint separately using git's `submodule` feature. Use the following commands:
@ -128,6 +119,11 @@ WebM support
------------ ------------
Read `inc/lib/webm/README.md` for information about enabling webm. 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 API
---------- ----------
vichan provides by default a 4chan-compatible JSON API. For documentation on this, see: vichan provides by default a 4chan-compatible JSON API. For documentation on this, see:
@ -136,4 +132,3 @@ https://github.com/vichan-devel/vichan-API/ .
License License
-------- --------
See [LICENSE.md](http://github.com/vichan-devel/vichan/blob/master/LICENSE.md). 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,8 +1,14 @@
<?php <?php
require_once 'inc/functions.php'; require_once 'inc/bootstrap.php';
require_once 'inc/bans.php'; checkBan();
checkBan();
print "<!doctype html><html><head><meta charset='utf-8'><title>"._("Banned?")."</title></head><body>"; //If the user is not banned, show the "not banned" page.
print "<h1>"._("You are not banned.")."</h1>"; die(
print "</body></html>"; 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

65
composer.json Normal file
View File

@ -0,0 +1,65 @@
{
"name": "vichan-devel/vichan",
"description": "vichan imageboard",
"type": "project",
"config": {
"platform": {
"php": "7.4"
}
},
"require": {
"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": "^5.5",
"mrclay/minify": "^2.1.6",
"geoip/geoip": "^1.17",
"dapphp/securimage": "^4.0",
"erusev/parsedown": "^1.7.4"
},
"autoload": {
"classmap": ["inc/"],
"files": [
"inc/bootstrap.php",
"inc/display.php",
"inc/template.php",
"inc/database.php",
"inc/events.php",
"inc/api.php",
"inc/mod/auth.php",
"inc/lock.php",
"inc/queue.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",
"authors": [
{
"name": "savetheinternet",
"homepage": "https://github.com/savetheinternet"
},
{
"name": "czaks",
"homepage": "https://github.com/czaks"
},
{
"name": "ctrlcctrlv",
"homepage": "https://github.com/ctrlcctrlv"
},
{
"name": "h00j",
"homepage": "https://github.com/h00j"
}
]
}

757
composer.lock generated Normal file
View File

@ -0,0 +1,757 @@
{
"_readme": [
"This file locks the dependencies of your project to a known state",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"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",
"source": {
"type": "git",
"url": "https://github.com/maxmind/geoip-api-php.git",
"reference": "2053a85c2ed3e7adcbaf0117e26832f57366e370"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/maxmind/geoip-api-php/zipball/2053a85c2ed3e7adcbaf0117e26832f57366e370",
"reference": "2053a85c2ed3e7adcbaf0117e26832f57366e370",
"shasum": ""
},
"conflict": {
"ext-geoip": "*"
},
"require-dev": {
"phpunit/phpunit": "3.7.*",
"satooshi/php-coveralls": "1.0.*"
},
"type": "library",
"autoload": {
"files": [
"src/geoip.inc",
"src/geoipcity.inc",
"src/timezone.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"LGPL 2.1+"
],
"authors": [
{
"name": "MaxMind, Inc.",
"email": "support@maxmind.com",
"homepage": "http://www.maxmind.com/"
}
],
"description": "MaxMind GeoIP PHP API",
"homepage": "http://dev.maxmind.com/geoip/legacy/downloadable",
"keywords": [
"geoip",
"geolocation",
"maxmind"
],
"support": {
"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": "v5.7.0",
"source": {
"type": "git",
"url": "https://github.com/php-gettext/Gettext.git",
"reference": "8657e580747bb3baacccdcebe69cac094661e404"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-gettext/Gettext/zipball/8657e580747bb3baacccdcebe69cac094661e404",
"reference": "8657e580747bb3baacccdcebe69cac094661e404",
"shasum": ""
},
"require": {
"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-4": {
"Gettext\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Oscar Otero",
"email": "oom@oscarotero.com",
"homepage": "http://oscarotero.com",
"role": "Developer"
}
],
"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/php-gettext/Gettext/issues",
"source": "https://github.com/php-gettext/Gettext/tree/v5.7.0"
},
"funding": [
{
"url": "https://paypal.me/oscarotero",
"type": "custom"
},
{
"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/php-gettext/Languages.git",
"reference": "4d61d67fe83a2ad85959fe6133d6d9ba7dddd1ab"
},
"dist": {
"type": "zip",
"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": {
"ext-bcmath": "*",
"php": ">=5.3.0"
},
"type": "library",
"autoload": {
"psr-4": {
"Lifo\\IP\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Jason Morriss",
"email": "lifo2013@gmail.com"
}
],
"description": "IP address helper PHP library for working with IPv4 and IPv6 addresses",
"keywords": [
"IP",
"ip address",
"ipv4",
"ipv6"
],
"support": {
"issues": "https://github.com/lifo101/ip/issues",
"source": "https://github.com/lifo101/ip/tree/v1.1.1"
},
"time": "2022-07-12T15:45:54+00:00"
},
{
"name": "mrclay/minify",
"version": "2.3.3",
"source": {
"type": "git",
"url": "https://github.com/mrclay/minify.git",
"reference": "1928e89208d28e91427b2f13b67acdbd8cd01ac9"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/mrclay/minify/zipball/1928e89208d28e91427b2f13b67acdbd8cd01ac9",
"reference": "1928e89208d28e91427b2f13b67acdbd8cd01ac9",
"shasum": ""
},
"require": {
"ext-pcre": "*",
"php": ">=5.2.1"
},
"require-dev": {
"tubalmartin/cssmin": "~2.4.8"
},
"suggest": {
"tubalmartin/cssmin": "Support minify with CSSMin (YUI PHP port)"
},
"type": "library",
"autoload": {
"classmap": [
"min/lib/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Stephen Clay",
"email": "steve@mrclay.org",
"role": "Developer"
}
],
"description": "Minify is a PHP5 app that helps you follow several rules for client-side performance. It combines multiple CSS or Javascript files, removes unnecessary whitespace and comments, and serves them with gzip encoding and optimal client-side cache headers",
"homepage": "http://code.google.com/p/minify/",
"support": {
"email": "minify@googlegroups.com",
"issues": "http://code.google.com/p/minify/issues/list",
"source": "https://github.com/mrclay/minify/tree/2.x",
"wiki": "http://code.google.com/p/minify/w/list"
},
"time": "2017-11-03T21:04:01+00:00"
},
{
"name": "phpmyadmin/twig-i18n-extension",
"version": "v4.0.1",
"source": {
"type": "git",
"url": "https://github.com/phpmyadmin/twig-i18n-extension.git",
"reference": "c0d0dd171cd1c7733bf152fd44b61055843df052"
},
"dist": {
"type": "zip",
"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.27-dev"
},
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
"autoload": {
"files": [
"bootstrap.php"
],
"psr-4": {
"Symfony\\Polyfill\\Ctype\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Gert de Pagter",
"email": "BackEndTea@gmail.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill for ctype functions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"ctype",
"polyfill",
"portable"
],
"support": {
"source": "https://github.com/symfony/polyfill-ctype/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-mbstring",
"version": "v1.27.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git",
"reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/8ad114f6b39e2c98a8b0e3bd907732c207c2b534",
"reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
"provide": {
"ext-mbstring": "*"
},
"suggest": {
"ext-mbstring": "For best performance"
},
"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\\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": {
"psr-0": {
"Twig_": "lib/"
},
"psr-4": {
"Twig\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com",
"homepage": "http://fabien.potencier.org",
"role": "Lead Developer"
},
{
"name": "Twig Team",
"role": "Contributors"
},
{
"name": "Armin Ronacher",
"email": "armin.ronacher@active-4.com",
"role": "Project Founder"
}
],
"description": "Twig, the flexible, fast, and secure template language for PHP",
"homepage": "https://twig.symfony.com",
"keywords": [
"templating"
],
"support": {
"issues": "https://github.com/twigphp/Twig/issues",
"source": "https://github.com/twigphp/Twig/tree/v2.15.4"
},
"funding": [
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/twig/twig",
"type": "tidelift"
}
],
"time": "2022-12-27T12:26:20+00:00"
}
],
"packages-dev": [],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": [],
"prefer-stable": false,
"prefer-lowest": false,
"platform": {
"ext-mbstring": ">=5.4",
"ext-gd": ">=5.4",
"ext-pdo": ">=5.4"
},
"platform-dev": [],
"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,282 +1,5 @@
<?php <?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);
}
}
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 checkSpam(array $extra_salt = array()) {
global $config, $pdo;
if (!isset($_POST['hash']))
return true;
$hash = $_POST['hash'];
if (!empty($extra_salt)) {
// create a salted hash of the "extra salt"
$extra_salt = implode(':', $extra_salt);
} else {
$extra_salt = '';
}
// Reconsturct the $inputs array
$inputs = array();
foreach ($_POST as $name => $value) {
if (in_array($name, $config['spam']['valid_inputs']))
continue;
$inputs[$name] = $value;
}
// Sort the inputs in alphabetical order (A-Z)
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
$_hash = sha1($_hash . $extra_salt);
if ($hash != $_hash)
return true;
$query = prepare('SELECT `passed` FROM ``antispam`` WHERE `hash` = :hash');
$query->bindValue(':hash', $hash);
$query->execute() or error(db_error($query));
if ((($passed = $query->fetchColumn(0)) === false) || ($passed > $config['spam']['hidden_inputs_max_pass'])) {
// there was no database entry for this hash. most likely expired.
return true;
}
return $hash;
}
function incrementSpamHash($hash) {
$query = prepare('UPDATE ``antispam`` SET `passed` = `passed` + 1 WHERE `hash` = :hash');
$query->bindValue(':hash', $hash);
$query->execute() or error(db_error($query));
}

View File

@ -9,52 +9,12 @@ defined('TINYBOARD') or exit;
* Class for generating json API compatible with 4chan API * Class for generating json API compatible with 4chan API
*/ */
class Api { class Api {
function __construct(){ private bool $show_filename;
global $config; private bool $hide_email;
/** private bool $country_flags;
* Translation from local fields to fields in 4chan-style API private array $postFields;
*/
$this->config = $config;
$this->postFields = array( private const INTS = [
'id' => 'no',
'thread' => 'resto',
'subject' => 'sub',
'body' => 'com',
'email' => 'email',
'name' => 'name',
'trip' => 'trip',
'capcode' => 'capcode',
'time' => 'time',
'omitted' => 'omitted_posts',
'omitted_images' => 'omitted_images',
'replies' => 'replies',
'images' => 'images',
'sticky' => 'sticky',
'locked' => 'locked',
'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, 'no' => 1,
'resto' => 1, 'resto' => 1,
'time' => 1, 'time' => 1,
@ -70,42 +30,105 @@ class Api {
'sticky' => 1, 'sticky' => 1,
'locked' => 1, 'locked' => 1,
'last_modified' => 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',
'body' => 'com',
'email' => 'email',
'name' => 'name',
'trip' => 'trip',
'capcode' => 'capcode',
'time' => 'time',
'omitted' => 'omitted_posts',
'omitted_images' => 'omitted_images',
'replies' => 'replies',
'images' => 'images',
'sticky' => 'sticky',
'locked' => 'locked',
'cycle' => 'cyclical',
'bump' => 'last_modified',
'embed' => 'embed',
];
if (isset($config['api']['extra_fields']) && gettype($config['api']['extra_fields']) == 'array'){
$this->postFields = array_merge($this->postFields, $config['api']['extra_fields']);
}
}
private function translateFields($fields, $object, &$apiPost) { private function translateFields($fields, $object, &$apiPost) {
foreach ($fields as $local => $translated) { foreach ($fields as $local => $translated) {
if (!isset($object->$local)) if (!isset($object->$local)) {
continue; continue;
}
$toInt = isset(self::$ints[$translated]); $toInt = isset(self::INTS[$translated]);
$val = $object->$local; $val = $object->$local;
if ($this->hide_email && $local === 'email') {
$val = '';
}
if ($val !== null && $val !== '') { if ($val !== null && $val !== '') {
$apiPost[$translated] = $toInt ? (int) $val : $val; $apiPost[$translated] = $toInt ? (int) $val : $val;
} }
} }
} }
private function translateFile($file, $post, &$apiPost) { private function translateFile($file, $post, &$apiPost) {
$this->translateFields($this->fileFields, $file, $apiPost); $this->translateFields(self::FILE_FIELDS, $file, $apiPost);
$apiPost['filename'] = @substr($file->name, 0, strrpos($file->name, '.'));
$dotPos = strrpos($file->file, '.'); $dotPos = strrpos($file->file, '.');
$apiPost['ext'] = substr($file->file, $dotPos); $apiPost['ext'] = substr($file->file, $dotPos);
$apiPost['tim'] = substr($file->file, 0, $dotPos); $apiPost['tim'] = substr($file->file, 0, $dotPos);
$apiPost['md5'] = base64_encode(hex2bin($post->filehash));
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));
} 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; 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); $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 // 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); $modifiers = extract_modifiers($post->body_nomarkup);
if (isset($modifiers['flag']) && isset($modifiers['flag alt']) && preg_match('/^[a-z]{2}$/', $modifiers['flag'])) { if (isset($modifiers['flag']) && isset($modifiers['flag alt']) && preg_match('/^[a-z]{2}$/', $modifiers['flag'])) {
$country = strtoupper($modifiers['flag']); $country = strtoupper($modifiers['flag']);
@ -125,12 +148,15 @@ class Api {
if (isset($post->files) && $post->files && !$threadsPage) { if (isset($post->files) && $post->files && !$threadsPage) {
$file = $post->files[0]; $file = $post->files[0];
$this->translateFile($file, $post, $apiPost); $this->translateFile($file, $post, $apiPost);
if (sizeof($post->files) > 1) { if (sizeof($post->files) > 1) {
$extra_files = array(); $extra_files = [];
foreach ($post->files as $i => $f) { foreach ($post->files as $i => $f) {
if ($i == 0) continue; if ($i == 0) {
continue;
$extra_file = array(); }
$extra_file = [];
$this->translateFile($f, $post, $extra_file); $this->translateFile($f, $post, $extra_file);
$extra_files[] = $extra_file; $extra_files[] = $extra_file;
@ -142,8 +168,8 @@ class Api {
return $apiPost; return $apiPost;
} }
function translateThread(Thread $thread, $threadsPage = false) { public function translateThread(Thread $thread, bool $threadsPage = false) {
$apiPosts = array(); $apiPosts = [];
$op = $this->translatePost($thread, $threadsPage); $op = $this->translatePost($thread, $threadsPage);
if (!$threadsPage) $op['resto'] = 0; if (!$threadsPage) $op['resto'] = 0;
$apiPosts['posts'][] = $op; $apiPosts['posts'][] = $op;
@ -155,16 +181,16 @@ class Api {
return $apiPosts; return $apiPosts;
} }
function translatePage(array $threads) { public function translatePage(array $threads) {
$apiPage = array(); $apiPage = [];
foreach ($threads as $thread) { foreach ($threads as $thread) {
$apiPage['threads'][] = $this->translateThread($thread); $apiPage['threads'][] = $this->translateThread($thread);
} }
return $apiPage; return $apiPage;
} }
function translateCatalogPage(array $threads, $threadsPage = false) { public function translateCatalogPage(array $threads, bool $threadsPage = false) {
$apiPage = array(); $apiPage = [];
foreach ($threads as $thread) { foreach ($threads as $thread) {
$ts = $this->translateThread($thread, $threadsPage); $ts = $this->translateThread($thread, $threadsPage);
$apiPage['threads'][] = current($ts['posts']); $apiPage['threads'][] = current($ts['posts']);
@ -172,8 +198,8 @@ class Api {
return $apiPage; return $apiPage;
} }
function translateCatalog($catalog, $threadsPage = false) { public function translateCatalog($catalog, bool $threadsPage = false) {
$apiCatalog = array(); $apiCatalog = [];
foreach ($catalog as $page => $threads) { foreach ($catalog as $page => $threads) {
$apiPage = $this->translateCatalogPage($threads, $threadsPage); $apiPage = $this->translateCatalogPage($threads, $threadsPage);
$apiPage['page'] = $page; $apiPage['page'] = $page;

View File

@ -1,86 +1,237 @@
<?php <?php
require 'inc/lib/IP/Lifo/IP/IP.php'; use Vichan\Functions\Format;
require 'inc/lib/IP/Lifo/IP/BC.php';
require 'inc/lib/IP/Lifo/IP/CIDR.php';
use Lifo\IP\CIDR; use Lifo\IP\CIDR;
class Bans { 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) { static public function range_to_string($mask) {
list($ipstart, $ipend) = $mask; list($ipstart, $ipend) = $mask;
if (!isset($ipend) || $ipend === false) { if (!isset($ipend) || $ipend === false) {
// Not a range. Single IP address. // Not a range. Single IP address.
$ipstr = inet_ntop($ipstart); $ipstr = inet_ntop($ipstart);
return $ipstr; return $ipstr;
} }
if (strlen($ipstart) != strlen($ipend)) if (strlen($ipstart) != strlen($ipend))
return '???'; // What the fuck are you doing, son? return '???'; // What the fuck are you doing, son?
$range = CIDR::range_to_cidr(inet_ntop($ipstart), inet_ntop($ipend)); $range = CIDR::range_to_cidr(inet_ntop($ipstart), inet_ntop($ipend));
if ($range !== false) if ($range !== false)
return $range; return $range;
return '???'; return '???';
} }
private static function calc_cidr($mask) { private static function calc_cidr($mask) {
$cidr = new CIDR($mask); $cidr = new CIDR($mask);
$range = $cidr->getRange(); $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) { public static function parse_time($str) {
if (empty($str)) if (empty($str))
return false; return false;
if (($time = @strtotime($str)) !== false) if (($time = @strtotime($str)) !== false)
return $time; return $time;
if (!preg_match('/^((\d+)\s?ye?a?r?s?)?\s?+((\d+)\s?mon?t?h?s?)?\s?+((\d+)\s?we?e?k?s?)?\s?+((\d+)\s?da?y?s?)?((\d+)\s?ho?u?r?s?)?\s?+((\d+)\s?mi?n?u?t?e?s?)?\s?+((\d+)\s?se?c?o?n?d?s?)?$/', $str, $matches)) if (!preg_match('/^((\d+)\s?ye?a?r?s?)?\s?+((\d+)\s?mon?t?h?s?)?\s?+((\d+)\s?we?e?k?s?)?\s?+((\d+)\s?da?y?s?)?((\d+)\s?ho?u?r?s?)?\s?+((\d+)\s?mi?n?u?t?e?s?)?\s?+((\d+)\s?se?c?o?n?d?s?)?$/', $str, $matches))
return false; return false;
$expire = 0; $expire = 0;
if (isset($matches[2])) { if (isset($matches[2])) {
// Years // Years
$expire += $matches[2]*60*60*24*365; $expire += (int)$matches[2]*60*60*24*365;
} }
if (isset($matches[4])) { if (isset($matches[4])) {
// Months // Months
$expire += $matches[4]*60*60*24*30; $expire += (int)$matches[4]*60*60*24*30;
} }
if (isset($matches[6])) { if (isset($matches[6])) {
// Weeks // Weeks
$expire += $matches[6]*60*60*24*7; $expire += (int)$matches[6]*60*60*24*7;
} }
if (isset($matches[8])) { if (isset($matches[8])) {
// Days // Days
$expire += $matches[8]*60*60*24; $expire += (int)$matches[8]*60*60*24;
} }
if (isset($matches[10])) { if (isset($matches[10])) {
// Hours // Hours
$expire += $matches[10]*60*60; $expire += (int)$matches[10]*60*60;
} }
if (isset($matches[12])) { if (isset($matches[12])) {
// Minutes // Minutes
$expire += $matches[12]*60; $expire += (int)$matches[12]*60;
} }
if (isset($matches[14])) { if (isset($matches[14])) {
// Seconds // Seconds
$expire += $matches[14]; $expire += (int)$matches[14];
} }
return time() + $expire; return time() + $expire;
} }
static public function parse_range($mask) { static public function parse_range($mask) {
$ipstart = false; $ipstart = false;
$ipend = false; $ipend = false;
if (preg_match('@^(\d{1,3}\.){1,3}([\d*]{1,3})?$@', $mask) && substr_count($mask, '*') == 1) { if (preg_match('@^(\d{1,3}\.){1,3}([\d*]{1,3})?$@', $mask) && substr_count($mask, '*') == 1) {
// IPv4 wildcard mask // IPv4 wildcard mask
$parts = explode('.', $mask); $parts = explode('.', $mask);
@ -101,52 +252,38 @@ class Bans {
list($ipv4, $bits) = explode('/', $mask); list($ipv4, $bits) = explode('/', $mask);
if ($bits > 32) if ($bits > 32)
return false; return false;
list($ipstart, $ipend) = self::calc_cidr($mask); list($ipstart, $ipend) = self::calc_cidr($mask);
} elseif (preg_match('@^[:a-z\d]+/\d+$@i', $mask)) { } elseif (preg_match('@^[:a-z\d]+/\d+$@i', $mask)) {
list($ipv6, $bits) = explode('/', $mask); list($ipv6, $bits) = explode('/', $mask);
if ($bits > 128) if ($bits > 128)
return false; return false;
list($ipstart, $ipend) = self::calc_cidr($mask); list($ipstart, $ipend) = self::calc_cidr($mask);
} else { } else {
if (($ipstart = @inet_pton($mask)) === false) if (($ipstart = @inet_pton($mask)) === false)
return false; 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) {
global $config; if ($auto_gc) {
return self::findSingleAutoGc($ip, $ban_id, $require_ban_view);
$query = prepare('SELECT ``bans``.*' . ($get_mod_info ? ', `username`' : '') . ' FROM ``bans`` } else {
' . ($get_mod_info ? 'LEFT JOIN ``mods`` ON ``mods``.`id` = `creator`' : '') . ' return self::findSingleNoGc($ban_id);
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'); static public function find(?string $ip, $board = false, bool $get_mod_info = false, ?int $ban_id = null, bool $auto_gc = true) {
global $config;
if ($board !== false)
$query->bindValue(':board', $board, PDO::PARAM_STR); if ($auto_gc) {
return self::findAutoGc($ip, $board, $get_mod_info, $config['require_ban_view'], $ban_id);
$query->bindValue(':ip', inet_pton($ip)); } else {
$query->execute() or error(db_error($query)); return self::findNoGc($ip, $board, $get_mod_info, $ban_id);
$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']);
} else {
if ($ban['post'])
$ban['post'] = json_decode($ban['post'], true);
$ban['mask'] = self::range_to_string(array($ban['ipstart'], $ban['ipend']));
$ban_list[] = $ban;
}
} }
return $ban_list;
} }
static public function stream_json($out = false, $filter_ips = false, $filter_staff = false, $board_access = false) { static public function stream_json($out = false, $filter_ips = false, $filter_staff = false, $board_access = false) {
@ -161,12 +298,12 @@ class Bans {
$end = end($bans); $end = end($bans);
foreach ($bans as &$ban) { foreach ($bans as &$ban) {
$ban['mask'] = self::range_to_string(array($ban['ipstart'], $ban['ipend'])); $ban['mask'] = self::range_to_string([$ban['ipstart'], $ban['ipend']]);
if ($ban['post']) { if ($ban['post']) {
$post = json_decode($ban['post']); $post = json_decode($ban['post']);
$ban['message'] = $post->body; $ban['message'] = isset($post->body) ? $post->body : 0;
} }
unset($ban['ipstart'], $ban['ipend'], $ban['post'], $ban['creator']); unset($ban['ipstart'], $ban['ipend'], $ban['post'], $ban['creator']);
@ -174,11 +311,11 @@ class Bans {
$ban['access'] = true; $ban['access'] = true;
} }
if (filter_var($ban['mask'], FILTER_VALIDATE_IP) !== false) { if (filter_var($uncloaked_mask, FILTER_VALIDATE_IP) !== false) {
$ban['single_addr'] = true; $ban['single_addr'] = true;
} }
if ($filter_staff || ($board_access !== false && !in_array($ban['board'], $board_access))) { if ($filter_staff || ($board_access !== false && !in_array($ban['board'], $board_access))) {
$ban['username'] = '?'; $ban['username'] = '?';
} }
if ($filter_ips || ($board_access !== false && !in_array($ban['board'], $board_access))) { if ($filter_ips || ($board_access !== false && !in_array($ban['board'], $board_access))) {
@list($ban['mask'], $subnet) = explode("/", $ban['mask']); @list($ban['mask'], $subnet) = explode("/", $ban['mask']);
@ -200,20 +337,32 @@ class Bans {
} }
} }
$out ? fputs($out, "]") : print("]"); $out ? fputs($out, "]") : print("]");
} }
static public function seen($ban_id) { static public function seen($ban_id) {
$query = query("UPDATE ``bans`` SET `seen` = 1 WHERE `id` = " . (int)$ban_id) or error(db_error()); $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() { static public function purge($require_seen, $moratorium) {
$query = query("DELETE FROM ``bans`` WHERE `expires` IS NOT NULL AND `expires` < " . time() . " AND `seen` = 1") or error(db_error()); if ($require_seen) {
rebuildThemes('bans'); $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) { static public function delete($ban_id, $modlog = false, $boards = false, $dont_rebuild = false) {
global $config; global $config;
@ -228,48 +377,51 @@ class Bans {
if ($boards !== false && !in_array($ban['board'], $boards)) if ($boards !== false && !in_array($ban['board'], $boards))
error($config['error']['noaccess']); error($config['error']['noaccess']);
$mask = self::range_to_string(array($ban['ipstart'], $ban['ipend'])); $mask = self::range_to_string([$ban['ipstart'], $ban['ipend']]);
modLog("Removed ban #{$ban_id} for " . modLog("Removed ban #{$ban_id} for " .
(filter_var($mask, FILTER_VALIDATE_IP) !== false ? "<a href=\"?/IP/$mask\">$mask</a>" : $mask)); (filter_var($mask, FILTER_VALIDATE_IP) !== false ? "<a href=\"?/IP/$cloaked_mask\">$cloaked_mask</a>" : $cloaked_mask));
} }
query("DELETE FROM ``bans`` WHERE `id` = " . (int)$ban_id) or error(db_error()); 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; return true;
} }
static public function new_ban($mask, $reason, $length = false, $ban_board = false, $mod_id = false, $post = false) { static public function new_ban($cloaked_mask, $reason, $length = false, $ban_board = false, $mod_id = false, $post = false) {
$mask = uncloak_mask($cloaked_mask);
global $mod, $pdo, $board; global $mod, $pdo, $board;
if ($mod_id === false) { if ($mod_id === false) {
$mod_id = isset($mod['id']) ? $mod['id'] : -1; $mod_id = isset($mod['id']) ? $mod['id'] : -1;
} }
$range = self::parse_range($mask); $range = self::parse_range($mask);
$mask = self::range_to_string($range); $mask = self::range_to_string($range);
$cloaked_mask = cloak_mask($mask);
$query = prepare("INSERT INTO ``bans`` VALUES (NULL, :ipstart, :ipend, :time, :expires, :board, :mod, :reason, 0, :post)"); $query = prepare("INSERT INTO ``bans`` VALUES (NULL, :ipstart, :ipend, :time, :expires, :board, :mod, :reason, 0, :post)");
$query->bindValue(':ipstart', $range[0]); $query->bindValue(':ipstart', $range[0]);
if ($range[1] !== false && $range[1] != $range[0]) if ($range[1] !== false && $range[1] != $range[0])
$query->bindValue(':ipend', $range[1]); $query->bindValue(':ipend', $range[1]);
else else
$query->bindValue(':ipend', null, PDO::PARAM_NULL); $query->bindValue(':ipend', null, PDO::PARAM_NULL);
$query->bindValue(':mod', $mod_id); $query->bindValue(':mod', $mod_id);
$query->bindValue(':time', time()); $query->bindValue(':time', time());
if ($reason !== '') { if ($reason !== '') {
$reason = escape_markup_modifiers($reason); $reason = escape_markup_modifiers($reason);
markup($reason); markup($reason);
$query->bindValue(':reason', $reason); $query->bindValue(':reason', $reason);
} else } else
$query->bindValue(':reason', null, PDO::PARAM_NULL); $query->bindValue(':reason', null, PDO::PARAM_NULL);
if ($length) { if ($length) {
if (is_int($length) || ctype_digit($length)) { if (is_int($length) || ctype_digit($length)) {
$length = time() + $length; $length = time() + $length;
@ -280,32 +432,40 @@ class Bans {
} else { } else {
$query->bindValue(':expires', null, PDO::PARAM_NULL); $query->bindValue(':expires', null, PDO::PARAM_NULL);
} }
if ($ban_board) if ($ban_board)
$query->bindValue(':board', $ban_board); $query->bindValue(':board', $ban_board);
else else
$query->bindValue(':board', null, PDO::PARAM_NULL); $query->bindValue(':board', null, PDO::PARAM_NULL);
if ($post) { if ($post) {
if (!isset($board['uri']))
openBoard($post['board']);
$post['board'] = $board['uri']; $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)); $query->bindValue(':post', json_encode($post));
} else } else
$query->bindValue(':post', null, PDO::PARAM_NULL); $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/$mask\">$mask</a>" : $mask) .
' (<small>#' . $pdo->lastInsertId() . '</small>)' .
' with ' . ($reason ? 'reason: ' . utf8tohtml($reason) . '' : 'no reason'));
}
rebuildThemes('bans'); $query->execute() or error(db_error($query));
$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(); return $pdo->lastInsertId();
} }

3
inc/bootstrap.php Normal file
View File

@ -0,0 +1,3 @@
<?php
@define('TINYBOARD', 'xD');
require_once('vendor/autoload.php');

View File

@ -4,170 +4,89 @@
* Copyright (c) 2010-2013 Tinyboard Development Group * Copyright (c) 2010-2013 Tinyboard Development Group
*/ */
use Vichan\Data\Driver\{CacheDriver, ApcuCacheDriver, ArrayCacheDriver, FsCacheDriver, MemcachedCacheDriver, NoneCacheDriver, RedisCacheDriver};
defined('TINYBOARD') or exit; defined('TINYBOARD') or exit;
class Cache { class Cache {
private static $cache; private static function buildCache(): CacheDriver {
public static function init() {
global $config; global $config;
switch ($config['cache']['enabled']) { switch ($config['cache']['enabled']) {
case 'memcached': case 'memcached':
self::$cache = new Memcached(); return new MemcachedCacheDriver(
self::$cache->addServers($config['cache']['memcached']); $config['cache']['prefix'],
break; $config['cache']['memcached']
);
case 'redis': case 'redis':
self::$cache = new Redis(); return new RedisCacheDriver(
self::$cache->connect($config['cache']['redis'][0], $config['cache']['redis'][1]); $config['cache']['prefix'],
if ($config['cache']['redis'][2]) { $config['cache']['redis'][0],
self::$cache->auth($config['cache']['redis'][2]); $config['cache']['redis'][1],
} $config['cache']['redis'][2],
self::$cache->select($config['cache']['redis'][3]) or die('cache select failure'); $config['cache']['redis'][3]
break; );
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': case 'php':
self::$cache = array(); default:
break; return new ArrayCacheDriver();
} }
} }
public static function getCache(): CacheDriver {
static $cache;
return $cache ??= self::buildCache();
}
public static function get($key) { public static function get($key) {
global $config, $debug; global $config, $debug;
$key = $config['cache']['prefix'] . $key; $ret = self::getCache()->get($key);
if ($ret === null) {
$data = false; $ret = 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;
} }
if ($config['debug']) if ($config['debug']) {
$debug['cached'][] = $key . ($data === false ? ' (miss)' : ' (hit)'); $debug['cached'][] = $config['cache']['prefix'] . $key . ($ret === false ? ' (miss)' : ' (hit)');
}
return $data;
return $ret;
} }
public static function set($key, $value, $expires = false) { public static function set($key, $value, $expires = false) {
global $config, $debug; global $config, $debug;
$key = $config['cache']['prefix'] . $key; if (!$expires) {
if (!$expires)
$expires = $config['cache']['timeout']; $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']) self::getCache()->set($key, $value, $expires);
$debug['cached'][] = $key . ' (set)';
if ($config['debug']) {
$debug['cached'][] = $config['cache']['prefix'] . $key . ' (set)';
}
} }
public static function delete($key) { public static function delete($key) {
global $config, $debug; global $config, $debug;
$key = $config['cache']['prefix'] . $key; self::getCache()->delete($key);
switch ($config['cache']['enabled']) { if ($config['debug']) {
case 'memcached': $debug['cached'][] = $config['cache']['prefix'] . $key . ' (deleted)';
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'][] = $key . ' (deleted)';
} }
public static function flush() { public static function flush() {
global $config; self::getCache()->flush();
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();
}
return false; return false;
} }
} }

357
inc/captcha/captcha.php Normal file
View File

@ -0,0 +1,357 @@
<?php
class CzaksCaptcha {
var $content = array();
var $width, $height, $color, $charset, $style;
function __construct($text, $left, $top, $charset=false) {
if (!$charset) {
$charset = 'abcdefghijklmnopqrstuvwxyz';
}
$len = mb_strlen($text, 'utf-8');
$this->width = $left;
$this->height = $top;
$this->charset = preg_split('//u', $charset);
$this->style = "";
for ($i = 0; $i < $len; $i++) {
$this->content[] = array(mb_substr($text, $i, 1, 'utf-8'), "top" => $top / 2 - $top / 4,
"left" => $left/10 + 9*$left*$i/10/$len,
"position" => "absolute");
}
$this->color = "hsla(".rand(1,360).", 76%, 78%, 1)";
$this->add_junk();
$this->mutate_sizes();
$this->mutate_positions();
$this->mutate_transform();
$this->mutate_anchors();
$this->randomize();
$this->mutate_containers();
$this->mutate_margins();
$this->mutate_styles();
$this->randomize();
}
function mutate_sizes() {
foreach ($this->content as &$v) {
if (!isset ($v['font-size']))
$v['font-size'] = rand(intval($this->height/3) - 4, intval($this->height/3) + 8);
}
}
function mutate_positions() {
foreach ($this->content as &$v) {
$v['top'] += rand(-10,10);
$v['left'] += rand(-10,10);
}
}
function mutate_transform() {
$fromto = array('6'=>'9', '9'=>'6', '8'=>'8', '0'=>'0',
'z'=>'z', 's'=>'s', 'n'=>'u', 'u'=>'n',
'a'=>'ɐ', 'e'=>'ə', 'p'=>'d', 'd'=>'p',
'A'=>'∀', 'E'=>'∃', 'H'=>'H', 'o'=>'o',
'O'=>'O');
foreach ($this->content as &$v) {
$basefrom = -20;
$baseto = 20;
if (isset($fromto[$v[0]]) && rand(0,1)) {
$v[0] = $fromto[$v[0]];
$basefrom = 160;
$baseto = 200;
}
$v['transform'] = 'rotate('.rand($basefrom,$baseto).'deg)';
$v['-ms-transform'] = 'rotate('.rand($basefrom,$baseto).'deg)';
$v['-webkit-transform'] = 'rotate('.rand($basefrom,$baseto).'deg)';
}
}
function randomize(&$a = false) {
if ($a === false) {
$a = &$this->content;
}
shuffle($a);
foreach ($a as &$v) {
$this->shuffle_assoc($v);
if (is_array ($v[0])) {
$this->randomize($v[0]);
}
}
}
function add_junk() {
$count = rand(200, 300);
while ($count--) {
$elem = array();
$elem['top'] = rand(0, $this->height);
$elem['left'] = rand(0, $this->width);
$elem['position'] = 'absolute';
$elem[0] = $this->charset[rand(0, count($this->charset)-1)];
switch($t = rand (0,9)) {
case 0:
$elem['display'] = 'none'; break;
case 1:
$elem['top'] = rand(-60, -90); break;
case 2:
$elem['left'] = rand(-40, -70); break;
case 3:
$elem['top'] = $this->height + rand(10, 60); break;
case 4:
$elem['left'] = $this->width + rand(10, 60); break;
case 5:
$elem['color'] = $this->color; break;
case 6:
$elem['visibility'] = 'hidden'; break;
case 7:
$elem['height'] = rand(0,2);
$elem['overflow'] = 'hidden'; break;
case 8:
$elem['width'] = rand(0,1);
$elem['overflow'] = 'hidden'; break;
case 9:
$elem['font-size'] = rand(2, 6); break;
}
$this->content[] = $elem;
}
}
function mutate_anchors() {
foreach ($this->content as &$elem) {
if (rand(0,1)) {
$elem['right'] = $this->width - $elem['left'] - (int)(0.5*$elem['font-size']);
unset($elem['left']);
}
if (rand(0,1)) {
$elem['bottom'] = $this->height - $elem['top'] - (int)(1.5*$elem['font-size']);
unset($elem['top']);
}
}
}
function mutate_containers() {
for ($i = 0; $i <= 80; $i++) {
$new = [];
$new['width'] = rand(0, $this->width*2);
$new['height'] = rand(0, $this->height*2);
$new['top'] = rand(-$this->height * 2, $this->height * 2);
$new['bottom'] = $this->height - ($new['top'] + $new['height']);
$new['left'] = rand(-$this->width * 2, $this->width * 2);
$new['right'] = $this->width - ($new['left'] + $new['width']);
$new['position'] = 'absolute';
$new[0] = [];
$cnt = rand(0,10);
for ($j = 0; $j < $cnt; $j++) {
$elem = array_pop($this->content);
if (!$elem) break;
if (isset($elem['top'])) $elem['top'] -= $new['top'];
if (isset($elem['bottom'])) $elem['bottom'] -= $new['bottom'];
if (isset($elem['left'])) $elem['left'] -= $new['left'];
if (isset($elem['right'])) $elem['right'] -= $new['right'];
$new[0][] = $elem;
}
if (rand (0,1)) unset($new['top']);
else unset($new['bottom']);
if (rand (0,1)) unset($new['left']);
else unset($new['right']);
$this->content[] = $new;
shuffle($this->content);
}
}
function mutate_margins(&$a = false) {
if ($a === false) {
$a = &$this->content;
}
foreach ($a as &$v) {
$ary = ['top', 'left', 'bottom', 'right'];
shuffle($ary);
$cnt = rand(0,4);
$ary = array_slice($ary, 0, $cnt);
foreach ($ary as $prop) {
$margin = rand(-1000, 1000);
$v['margin-'.$prop] = $margin;
if (isset($v[$prop])) {
$v[$prop] -= $margin;
}
}
if (is_array($v[0])) {
$this->mutate_margins($v[0]);
}
}
}
function mutate_styles(&$a = false) {
if ($a === false) {
$a = &$this->content;
}
foreach ($a as &$v) {
$content = $v[0];
unset($v[0]);
$styles = array_splice($v, 0, rand(0, 6));
$v[0] = $content;
$id_or_class = rand(0,1);
$param = $id_or_class ? "id" : "class";
$prefix = $id_or_class ? "#" : ".";
$genname = "zz-".base_convert(rand(1,999999999), 10, 36);
if ($styles || rand(0,1)) {
$this->style .= $prefix.$genname."{";
$this->style .= $this->rand_whitespace();
foreach ($styles as $k => $val) {
if (is_int($val)) {
$val = "".$val."px";
}
$this->style .= "$k:";
$this->style .= $this->rand_whitespace();
$this->style .= "$val;";
$this->style .= $this->rand_whitespace();
}
$this->style .= "}";
$this->style .= $this->rand_whitespace();
}
$v[$param] = $genname;
if (is_array($v[0])) {
$this->mutate_styles($v[0]);
}
}
}
function to_html(&$a = false) {
$inside = true;
if ($a === false) {
if ($this->style) {
echo "<style type='text/css'>";
echo $this->style;
echo "</style>";
}
echo "<div style='position: relative; width: ".$this->width."px; height: ".$this->height."px; overflow: hidden; background-color: ".$this->color."'>";
$a = &$this->content;
$inside = false;
}
foreach ($a as &$v) {
$letter = $v[0];
unset ($v[0]);
echo "<div";
echo $this->rand_whitespace(1);
if (isset ($v['id'])) {
echo "id='$v[id]'";
echo $this->rand_whitespace(1);
unset ($v['id']);
}
if (isset ($v['class'])) {
echo "class='$v[class]'";
echo $this->rand_whitespace(1);
unset ($v['class']);
}
echo "style='";
foreach ($v as $k => $val) {
if (is_int($val)) {
$val = "".$val."px";
}
echo "$k:";
echo $this->rand_whitespace();
echo "$val;";
echo $this->rand_whitespace();
}
echo "'>";
echo $this->rand_whitespace();
if (is_array ($letter)) {
$this->to_html($letter);
}
else {
echo $letter;
}
echo "</div>";
}
if (!$inside) {
echo "</div>";
}
}
function rand_whitespace($r = 0) {
switch (rand($r,4)) {
case 0:
return "";
case 1:
return "\n";
case 2:
return "\t";
case 3:
return " ";
case 4:
return " ";
}
}
function shuffle_assoc(&$array) {
$keys = array_keys($array);
shuffle($keys);
foreach($keys as $key) {
$new[$key] = $array[$key];
}
$array = $new;
return true;
}
}
//$charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789卐";
//(new CzaksCaptcha("hotwheels", 300, 80, $charset))->to_html();
?>

16
inc/captcha/config.php Normal file
View File

@ -0,0 +1,16 @@
<?php
// We are using a custom path here to connect to the database.
// Why? Performance reasons.
$pdo = new PDO("mysql:dbname=database_name;host=localhost", "database_user", "database_password", array(PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8'));
// Captcha expiration:
$expires_in = 120; // 120 seconds
// Captcha dimensions:
$width = 250;
$height = 80;
// Captcha length:
$length = 6;

View File

@ -0,0 +1,85 @@
<?php
$mode = @$_GET['mode'];
require_once("captcha.php");
function rand_string($length, $charset) {
$ret = "";
while ($length--) {
$ret .= mb_substr($charset, rand(0, mb_strlen($charset, 'utf-8')-1), 1, 'utf-8');
}
return $ret;
}
function cleanup ($pdo, $expires_in) {
$pdo->prepare("DELETE FROM `captchas` WHERE `created_at` < ?")->execute([time() - $expires_in]);
}
switch ($mode) {
// Request: GET entrypoint.php?mode=get&extra=1234567890
// Response: JSON: cookie => "generatedcookie", captchahtml => "captchahtml", expires_in => 120
case "get":
if (!isset ($_GET['extra'])) {
die();
}
header("Content-type: application/json");
$extra = $_GET['extra'];
require_once("config.php");
$text = rand_string($length, $extra);
$captcha = new CzaksCaptcha($text, $width, $height, $extra);
$cookie = rand_string(20, "abcdefghijklmnopqrstuvwxyz");
ob_start();
$captcha->to_html();
$html = ob_get_contents();
ob_end_clean();
$query = $pdo->prepare("INSERT INTO `captchas` (`cookie`, `extra`, `text`, `created_at`) VALUES (?, ?, ?, ?)");
$query->execute( [$cookie, $extra, $text, time()]);
echo json_encode(["cookie" => $cookie, "captchahtml" => $html, "expires_in" => $expires_in]);
break;
// Request: GET entrypoint.php?mode=check&cookie=generatedcookie&extra=1234567890&text=captcha
// Response: 0 OR 1
case "check":
if (!isset ($_GET['mode'])
|| !isset ($_GET['cookie'])
|| !isset ($_GET['extra'])
|| !isset ($_GET['text'])) {
die();
}
require_once("config.php");
cleanup($pdo, $expires_in);
$query = $pdo->prepare("SELECT * FROM `captchas` WHERE `cookie` = ? AND `extra` = ?");
$query->execute([$_GET['cookie'], $_GET['extra']]);
$ary = $query->fetchAll();
if (!$ary) {
echo "0";
}
else {
$query = $pdo->prepare("DELETE FROM `captchas` WHERE `cookie` = ? AND `extra` = ?");
$query->execute([$_GET['cookie'], $_GET['extra']]);
if ($ary[0]['text'] !== $_GET['text']) {
echo "0";
}
else {
echo "1";
}
}
break;
}

8
inc/captcha/readme.md Normal file
View File

@ -0,0 +1,8 @@
I integrated this from: https://github.com/ctrlcctrlv/infinity/commit/62a6dac022cb338f7b719d0c35a64ab3efc64658
In inc/captcha/config.php change the database_name database_user database_password to your own settings.
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();
}
]);
}

108
inc/controller.php Normal file
View File

@ -0,0 +1,108 @@
<?php
// This file contains the controller part of vichan
// don't bother with that unless you use smart build or advanced build
// you can use those parts for your own implementations though :^)
defined('TINYBOARD') or exit;
function sb_board($b, $page = 1) { global $config, $build_pages; $page = (int)$page;
if ($page < 1) return false;
if (!openBoard($b)) return false;
if ($page > $config['max_pages']) return false;
$config['try_smarter'] = true;
$build_pages = array($page);
buildIndex("skip");
return true;
}
function sb_api_board($b, $page = 0) { $page = (int)$page;
return sb_board($b, $page + 1);
}
function sb_thread($b, $thread, $slugcheck = false) { global $config; $thread = (int)$thread;
if ($thread < 1) return false;
if (!preg_match('/^'.$config['board_regex'].'$/u', $b)) return false;
if (Cache::get("thread_exists_".$b."_".$thread) == "no") return false;
$query = prepare(sprintf("SELECT MAX(`id`) AS `max` FROM ``posts_%s``", $b));
if (!$query->execute()) return false;
$s = $query->fetch(PDO::FETCH_ASSOC);
$max = $s['max'];
if ($thread > $max) return false;
$query = prepare(sprintf("SELECT `id` FROM ``posts_%s`` WHERE `id` = :id AND `thread` IS NULL", $b));
$query->bindValue(':id', $thread);
if (!$query->execute() || !$query->fetch(PDO::FETCH_ASSOC) ) {
Cache::set("thread_exists_".$b."_".$thread, "no", 3600);
return false;
}
if ($slugcheck && $config['slugify']) {
global $request;
$link = link_for(array("id" => $thread), $slugcheck === 50, array("uri" => $b));
$link = "/".$b."/".$config['dir']['res'].$link;
if ($link != $request) {
header("Location: $link", true, 301);
die();
}
}
if ($slugcheck == 50) { // Should we really generate +50 page? Maybe there are not enough posts anyway
global $request;
$r = str_replace("+50", "", $request);
$r = substr($r, 1); // Cut the slash
if (file_exists($r)) return false;
}
if (!openBoard($b)) return false;
buildThread($thread);
return true;
}
function sb_thread_slugcheck($b, $thread) {
return sb_thread($b, $thread, true);
}
function sb_thread_slugcheck50($b, $thread) {
return sb_thread($b, $thread, 50);
}
function sb_api($b) { global $config, $build_pages;
if (!openBoard($b)) return false;
$config['try_smarter'] = true;
$build_pages = array(-1);
buildIndex();
return true;
}
function sb_ukko() {
Vichan\Functions\Theme\rebuild_theme("ukko", "post-thread");
return true;
}
function sb_catalog($b) {
if (!openBoard($b)) return false;
Vichan\Functions\Theme\rebuild_theme("catalog", "post-thread", $b);
return true;
}
function sb_recent() {
Vichan\Functions\Theme\rebuild_theme("recent", "post-thread");
return true;
}
function sb_sitemap() {
Vichan\Functions\Theme\rebuild_theme("sitemap", "all");
return true;
}

View File

@ -103,7 +103,7 @@ function mysql_version() {
$v = explode('.', $version); $v = explode('.', $version);
if (count($v) != 3) if (count($v) != 3)
return false; 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) { function prepare($query) {

View File

@ -9,7 +9,7 @@ if (realpath($_SERVER['SCRIPT_FILENAME']) == str_replace('\\', '/', __FILE__)) {
exit; exit;
} }
/* /*
joaoptm78@gmail.com joaoptm78@gmail.com
http://www.php.net/manual/en/function.filesize.php#100097 http://www.php.net/manual/en/function.filesize.php#100097
*/ */
@ -21,7 +21,7 @@ function format_bytes($size) {
function doBoardListPart($list, $root, &$boards) { function doBoardListPart($list, $root, &$boards) {
global $config; global $config;
$body = ''; $body = '';
foreach ($list as $key => $board) { foreach ($list as $key => $board) {
if (is_array($board)) if (is_array($board))
@ -34,21 +34,21 @@ function doBoardListPart($list, $root, &$boards) {
if (isset ($boards[$board])) { if (isset ($boards[$board])) {
$title = ' title="'.$boards[$board].'"'; $title = ' title="'.$boards[$board].'"';
} }
$body .= ' <a href="' . $root . $board . '/' . $config['file_index'] . '"'.$title.'>' . $board . '</a> /'; $body .= ' <a href="' . $root . $board . '/' . $config['file_index'] . '"'.$title.'>' . $board . '</a> /';
} }
} }
} }
$body = preg_replace('/\/$/', '', $body); $body = preg_replace('/\/$/', '', $body);
return $body; return $body;
} }
function createBoardlist($mod=false) { function createBoardlist($mod=false) {
global $config; global $config;
if (!isset($config['boards'])) return array('top'=>'','bottom'=>''); if (!isset($config['boards'])) return array('top'=>'','bottom'=>'');
$xboards = listBoards(); $xboards = listBoards();
$boards = array(); $boards = array();
foreach ($xboards as $val) { foreach ($xboards as $val) {
@ -59,26 +59,26 @@ function createBoardlist($mod=false) {
if ($config['boardlist_wrap_bracket'] && !preg_match('/\] $/', $body)) if ($config['boardlist_wrap_bracket'] && !preg_match('/\] $/', $body))
$body = '[' . $body . ']'; $body = '[' . $body . ']';
$body = trim($body); $body = trim($body);
// Message compact-boardlist.js faster, so that page looks less ugly during loading // Message compact-boardlist.js faster, so that page looks less ugly during loading
$top = "<script type='text/javascript'>if (typeof do_boardlist != 'undefined') do_boardlist();</script>"; $top = "<script type='text/javascript'>if (typeof do_boardlist != 'undefined') do_boardlist();</script>";
return array( return array(
'top' => '<div class="boardlist">' . $body . '</div>' . $top, 'top' => '<div class="boardlist">' . $body . '</div>' . $top,
'bottom' => '<div class="boardlist bottom">' . $body . '</div>' 'bottom' => '<div class="boardlist bottom">' . $body . '</div>'
); );
} }
function error($message, $priority = true, $debug_stuff = false) { function error($message, $priority = true, $debug_stuff = []) {
global $board, $mod, $config, $db_error; global $board, $mod, $config, $db_error;
if ($config['syslog'] && $priority !== false) { if ($config['syslog'] && $priority !== false) {
// Use LOG_NOTICE instead of LOG_ERR or LOG_WARNING because most error message are not significant. // Use LOG_NOTICE instead of LOG_ERR or LOG_WARNING because most error message are not significant.
_syslog($priority !== true ? $priority : LOG_NOTICE, $message); _syslog($priority !== true ? $priority : LOG_NOTICE, $message);
} }
if (defined('STDIN')) { if (defined('STDIN')) {
// Running from CLI // Running from CLI
echo('Error: ' . $message . "\n"); echo('Error: ' . $message . "\n");
@ -94,23 +94,18 @@ function error($message, $priority = true, $debug_stuff = false) {
$debug_stuff['backtrace'] = debug_backtrace(); $debug_stuff['backtrace'] = debug_backtrace();
} }
// Return the bad request header, necessary for AJAX posts
// czaks: is it really so? the ajax errors only work when this is commented out
// better yet use it when ajax is disabled
if (!isset ($_POST['json_response'])) {
header($_SERVER['SERVER_PROTOCOL'] . ' 400 Bad Request');
}
// Is there a reason to disable this?
if (isset($_POST['json_response'])) { if (isset($_POST['json_response'])) {
header('Content-Type: text/json; charset=utf-8'); header('Content-Type: text/json; charset=utf-8');
die(json_encode(array( die(json_encode(array(
'error' => $message 'error' => $message
))); )));
} }
else {
header($_SERVER['SERVER_PROTOCOL'] . ' 400 Bad Request');
}
$pw = $config['db']['password']; $pw = $config['db']['password'];
$debug_callback = function(&$item) use (&$debug_callback, $pw) { $debug_callback = function($item) use (&$debug_callback, $pw) {
if (is_array($item)) { if (is_array($item)) {
$item = array_filter($item, $debug_callback); $item = array_filter($item, $debug_callback);
} }
@ -118,31 +113,31 @@ function error($message, $priority = true, $debug_stuff = false) {
}; };
if ($debug_stuff) if ($debug_stuff)
$debug_stuff = array_filter($debug_stuff, $debug_callback); $debug_stuff = array_filter($debug_stuff, $debug_callback);
die(Element('page.html', array( die(Element($config['file_page_template'], array(
'config' => $config, 'config' => $config,
'title' => _('Error'), 'title' => _('Error'),
'subtitle' => _('An error has occured.'), 'subtitle' => _('An error has occured.'),
'body' => Element('error.html', array( 'body' => Element($config['file_error'], array(
'config' => $config, 'config' => $config,
'message' => $message, 'message' => $message,
'mod' => $mod, 'mod' => $mod,
'board' => isset($board) ? $board : false, '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
)) ))
))); )));
} }
function loginForm($error=false, $username=false, $redirect=false) { function loginForm($error=false, $username=false, $redirect=false) {
global $config; global $config;
die(Element('page.html', array( die(Element($config['file_page_template'], array(
'index' => $config['root'], 'index' => $config['root'],
'title' => _('Login'), 'title' => _('Login'),
'config' => $config, 'config' => $config,
'body' => Element('login.html', array( 'body' => Element($config['file_login'], array(
'config'=>$config, 'config'=>$config,
'error'=>$error, 'error'=>$error,
'username'=>utf8tohtml($username), 'username'=>utf8tohtml($username),
@ -154,34 +149,34 @@ function loginForm($error=false, $username=false, $redirect=false) {
function pm_snippet($body, $len=null) { function pm_snippet($body, $len=null) {
global $config; global $config;
if (!isset($len)) if (!isset($len))
$len = &$config['mod']['snippet_length']; $len = &$config['mod']['snippet_length'];
// Replace line breaks with some whitespace // Replace line breaks with some whitespace
$body = preg_replace('@<br/?>@i', ' ', $body); $body = preg_replace('@<br/?>@i', ' ', $body);
// Strip tags // Strip tags
$body = strip_tags($body); $body = strip_tags($body);
// Unescape HTML characters, to avoid splitting them in half // Unescape HTML characters, to avoid splitting them in half
$body = html_entity_decode($body, ENT_COMPAT, 'UTF-8'); $body = html_entity_decode($body, ENT_COMPAT, 'UTF-8');
// calculate strlen() so we can add "..." after if needed // calculate strlen() so we can add "..." after if needed
$strlen = mb_strlen($body); $strlen = mb_strlen($body);
$body = mb_substr($body, 0, $len); $body = mb_substr($body, 0, $len);
// Re-escape the characters. // Re-escape the characters.
return '<em>' . utf8tohtml($body) . ($strlen > $len ? '&hellip;' : '') . '</em>'; return '<em>' . utf8tohtml($body) . ($strlen > $len ? '&hellip;' : '') . '</em>';
} }
function capcode($cap) { function capcode($cap) {
global $config; global $config;
if (!$cap) if (!$cap)
return false; return false;
$capcode = array(); $capcode = array();
if (isset($config['custom_capcode'][$cap])) { if (isset($config['custom_capcode'][$cap])) {
if (is_array($config['custom_capcode'][$cap])) { if (is_array($config['custom_capcode'][$cap])) {
@ -196,59 +191,59 @@ function capcode($cap) {
} else { } else {
$capcode['cap'] = sprintf($config['capcode'], $cap); $capcode['cap'] = sprintf($config['capcode'], $cap);
} }
return $capcode; return $capcode;
} }
function truncate($body, $url, $max_lines = false, $max_chars = false) { function truncate($body, $url, $max_lines = false, $max_chars = false) {
global $config; global $config;
if ($max_lines === false) if ($max_lines === false)
$max_lines = $config['body_truncate']; $max_lines = $config['body_truncate'];
if ($max_chars === false) if ($max_chars === false)
$max_chars = $config['body_truncate_char']; $max_chars = $config['body_truncate_char'];
// We don't want to risk truncating in the middle of an HTML comment. // We don't want to risk truncating in the middle of an HTML comment.
// It's easiest just to remove them all first. // It's easiest just to remove them all first.
$body = preg_replace('/<!--.*?-->/s', '', $body); $body = preg_replace('/<!--.*?-->/s', '', $body);
$original_body = $body; $original_body = $body;
$lines = substr_count($body, '<br/>'); $lines = substr_count($body, '<br/>');
// Limit line count // Limit line count
if ($lines > $max_lines) { if ($lines > $max_lines) {
if (preg_match('/(((.*?)<br\/>){' . $max_lines . '})/', $body, $m)) if (preg_match('/(((.*?)<br\/>){' . $max_lines . '})/', $body, $m))
$body = $m[0]; $body = $m[0];
} }
$body = mb_substr($body, 0, $max_chars); $body = mb_substr($body, 0, $max_chars);
if ($body != $original_body) { if ($body != $original_body) {
// Remove any corrupt tags at the end // Remove any corrupt tags at the end
$body = preg_replace('/<([\w]+)?([^>]*)?$/', '', $body); $body = preg_replace('/<([\w]+)?([^>]*)?$/', '', $body);
// Open tags // Open tags
if (preg_match_all('/<([\w]+)[^>]*>/', $body, $open_tags)) { if (preg_match_all('/<([\w]+)[^>]*>/', $body, $open_tags)) {
$tags = array(); $tags = array();
for ($x=0;$x<count($open_tags[0]);$x++) { for ($x=0;$x<count($open_tags[0]);$x++) {
if (!preg_match('/\/(\s+)?>$/', $open_tags[0][$x])) if (!preg_match('/\/(\s+)?>$/', $open_tags[0][$x]))
$tags[] = $open_tags[1][$x]; $tags[] = $open_tags[1][$x];
} }
// List successfully closed tags // List successfully closed tags
if (preg_match_all('/(<\/([\w]+))>/', $body, $closed_tags)) { if (preg_match_all('/(<\/([\w]+))>/', $body, $closed_tags)) {
for ($x=0;$x<count($closed_tags[0]);$x++) { for ($x=0;$x<count($closed_tags[0]);$x++) {
unset($tags[array_search($closed_tags[2][$x], $tags)]); unset($tags[array_search($closed_tags[2][$x], $tags)]);
} }
} }
// remove broken HTML entity at the end (if existent) // remove broken HTML entity at the end (if existent)
$body = preg_replace('/&[^;]+$/', '', $body); $body = preg_replace('/&[^;]+$/', '', $body);
$tags_no_close_needed = array("colgroup", "dd", "dt", "li", "optgroup", "option", "p", "tbody", "td", "tfoot", "th", "thead", "tr", "br", "img"); $tags_no_close_needed = array("colgroup", "dd", "dt", "li", "optgroup", "option", "p", "tbody", "td", "tfoot", "th", "thead", "tr", "br", "img");
// Close any open tags // Close any open tags
foreach ($tags as &$tag) { foreach ($tags as &$tag) {
if (!in_array($tag, $tags_no_close_needed)) if (!in_array($tag, $tags_no_close_needed))
@ -258,10 +253,10 @@ function truncate($body, $url, $max_lines = false, $max_chars = false) {
// remove broken HTML entity at the end (if existent) // remove broken HTML entity at the end (if existent)
$body = preg_replace('/&[^;]*$/', '', $body); $body = preg_replace('/&[^;]*$/', '', $body);
} }
$body .= '<span class="toolong">'.sprintf(_('Post too long. Click <a href="%s">here</a> to view the full text.'), $url).'</span>'; $body .= '<span class="toolong">'.sprintf(_('Post too long. Click <a href="%s">here</a> to view the full text.'), $url).'</span>';
} }
return $body; return $body;
} }
@ -271,21 +266,21 @@ function bidi_cleanup($data) {
$explicits = '\xE2\x80\xAA|\xE2\x80\xAB|\xE2\x80\xAD|\xE2\x80\xAE'; $explicits = '\xE2\x80\xAA|\xE2\x80\xAB|\xE2\x80\xAD|\xE2\x80\xAE';
$pdf = '\xE2\x80\xAC'; $pdf = '\xE2\x80\xAC';
preg_match_all("!$explicits!", $data, $m1, PREG_OFFSET_CAPTURE | PREG_SET_ORDER); preg_match_all("!$explicits!", $data, $m1, PREG_OFFSET_CAPTURE | PREG_SET_ORDER);
preg_match_all("!$pdf!", $data, $m2, PREG_OFFSET_CAPTURE | PREG_SET_ORDER); preg_match_all("!$pdf!", $data, $m2, PREG_OFFSET_CAPTURE | PREG_SET_ORDER);
if (count($m1) || count($m2)){ if (count($m1) || count($m2)){
$p = array(); $p = array();
foreach ($m1 as $m){ $p[$m[0][1]] = 'push'; } foreach ($m1 as $m){ $p[$m[0][1]] = 'push'; }
foreach ($m2 as $m){ $p[$m[0][1]] = 'pop'; } foreach ($m2 as $m){ $p[$m[0][1]] = 'pop'; }
ksort($p); ksort($p);
$offset = 0; $offset = 0;
$stack = 0; $stack = 0;
foreach ($p as $pos => $type){ foreach ($p as $pos => $type){
if ($type == 'push'){ if ($type == 'push'){
$stack++; $stack++;
}else{ }else{
@ -299,15 +294,15 @@ function bidi_cleanup($data) {
} }
} }
} }
# now add some pops if your stack is bigger than 0 # now add some pops if your stack is bigger than 0
for ($i=0; $i<$stack; $i++){ for ($i=0; $i<$stack; $i++){
$data .= "\xE2\x80\xAC"; $data .= "\xE2\x80\xAC";
} }
return $data; return $data;
} }
return $data; return $data;
} }
@ -322,24 +317,24 @@ function secure_link($href) {
function embed_html($link) { function embed_html($link) {
global $config; global $config;
foreach ($config['embedding'] as $embed) { foreach ($config['embedding'] as $embed) {
if ($html = preg_replace($embed[0], $embed[1], $link)) { if ($html = preg_replace($embed[0], $embed[1], $link)) {
if ($html == $link) if ($html == $link)
continue; // Nope continue; // Nope
$html = str_replace('%%tb_width%%', $config['embed_width'], $html); $html = str_replace('%%tb_width%%', $config['embed_width'], $html);
$html = str_replace('%%tb_height%%', $config['embed_height'], $html); $html = str_replace('%%tb_height%%', $config['embed_height'], $html);
return $html; return $html;
} }
} }
if ($link[0] == '<') { if ($link[0] == '<') {
// Prior to v0.9.6-dev-8, HTML code for embedding was stored in the database instead of the link. // Prior to v0.9.6-dev-8, HTML code for embedding was stored in the database instead of the link.
return $link; return $link;
} }
return 'Embedding error.'; return 'Embedding error.';
} }
@ -348,29 +343,46 @@ class Post {
global $config; global $config;
if (!isset($root)) if (!isset($root))
$root = &$config['root']; $root = &$config['root'];
foreach ($post as $key => $value) { foreach ($post as $key => $value) {
$this->{$key} = $value; $this->{$key} = $value;
} }
if (isset($this->files) && $this->files) if (isset($this->files) && $this->files) {
$this->files = @json_decode($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->subject = utf8tohtml($this->subject);
$this->name = utf8tohtml($this->name); $this->name = utf8tohtml($this->name);
$this->mod = $mod; $this->mod = $mod;
$this->root = $root; $this->root = $root;
if ($this->embed) if ($this->embed)
$this->embed = embed_html($this->embed); $this->embed = embed_html($this->embed);
$this->modifiers = extract_modifiers($this->body_nomarkup); $this->modifiers = extract_modifiers($this->body_nomarkup);
if ($config['always_regenerate_markup']) { if ($config['always_regenerate_markup']) {
$this->body = $this->body_nomarkup; $this->body = $this->body_nomarkup;
markup($this->body); markup($this->body);
} }
if ($this->mod) if ($this->mod)
// Fix internal links // Fix internal links
// Very complicated regex // Very complicated regex
@ -382,14 +394,25 @@ class Post {
} }
public function link($pre = '', $page = false) { public function link($pre = '', $page = false) {
global $config, $board; global $config, $board;
return $this->root . $board['dir'] . $config['dir']['res'] . link_for((array)$this, $page == '50') . '#' . $pre . $this->id; return $this->root . $board['dir'] . $config['dir']['res'] . link_for((array)$this, $page == '50') . '#' . $pre . $this->id;
} }
public function build($index=false) { public function build($index=false) {
global $board, $config; 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);
} }
}; };
@ -398,14 +421,14 @@ class Thread {
global $config; global $config;
if (!isset($root)) if (!isset($root))
$root = &$config['root']; $root = &$config['root'];
foreach ($post as $key => $value) { foreach ($post as $key => $value) {
$this->{$key} = $value; $this->{$key} = $value;
} }
if (isset($this->files)) 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->subject = utf8tohtml($this->subject);
$this->name = utf8tohtml($this->name); $this->name = utf8tohtml($this->name);
$this->mod = $mod; $this->mod = $mod;
@ -415,17 +438,17 @@ class Thread {
$this->posts = array(); $this->posts = array();
$this->omitted = 0; $this->omitted = 0;
$this->omitted_images = 0; $this->omitted_images = 0;
if ($this->embed) if ($this->embed)
$this->embed = embed_html($this->embed); $this->embed = embed_html($this->embed);
$this->modifiers = extract_modifiers($this->body_nomarkup); $this->modifiers = extract_modifiers($this->body_nomarkup);
if ($config['always_regenerate_markup']) { if ($config['always_regenerate_markup']) {
$this->body = $this->body_nomarkup; $this->body = $this->body_nomarkup;
markup($this->body); markup($this->body);
} }
if ($this->mod) if ($this->mod)
// Fix internal links // Fix internal links
// Very complicated regex // Very complicated regex
@ -437,7 +460,7 @@ class Thread {
} }
public function link($pre = '', $page = false) { public function link($pre = '', $page = false) {
global $config, $board; global $config, $board;
return $this->root . $board['dir'] . $config['dir']['res'] . link_for((array)$this, $page == '50') . '#' . $pre . $this->id; return $this->root . $board['dir'] . $config['dir']['res'] . link_for((array)$this, $page == '50') . '#' . $pre . $this->id;
} }
public function add(Post $post) { public function add(Post $post) {
@ -448,15 +471,27 @@ class Thread {
} }
public function build($index=false, $isnoko50=false) { public function build($index=false, $isnoko50=false) {
global $board, $config, $debug; global $board, $config, $debug;
$hasnoko50 = $this->postCount() >= $config['noko50_min']; $hasnoko50 = $this->postCount() >= $config['noko50_min'];
event('show-thread', $this); event('show-thread', $this);
$file = ($index && $config['file_board']) ? 'post_thread_fileboard.html' : 'post_thread.html'; $options = [
$built = Element($file, array('config' => $config, 'board' => $board, 'post' => &$this, 'index' => $index, 'hasnoko50' => $hasnoko50, 'isnoko50' => $isnoko50, 'mod' => $this->mod)); '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; return $built;
} }
}; };

View File

@ -10,54 +10,63 @@ class Filter {
public $flood_check; public $flood_check;
private $condition; private $condition;
private $post; private $post;
public function __construct(array $arr) { public function __construct(array $arr) {
foreach ($arr as $key => $value) foreach ($arr as $key => $value) {
$this->$key = $value; $this->$key = $value;
}
} }
public function match($condition, $match) { public function match($condition, $match) {
$condition = strtolower($condition); $condition = strtolower($condition);
$post = &$this->post; $post = &$this->post;
switch($condition) { switch($condition) {
case 'custom': case 'custom':
if (!is_callable($match)) if (!is_callable($match)) {
error('Custom condition for filter is not callable!'); error('Custom condition for filter is not callable!');
}
return $match($post); return $match($post);
case 'flood-match': case 'flood-match':
if (!is_array($match)) if (!is_array($match)) {
error('Filter condition "flood-match" must be an array.'); error('Filter condition "flood-match" must be an array.');
}
// Filter out "flood" table entries which do not match this filter. // Filter out "flood" table entries which do not match this filter.
$flood_check_matched = array(); $flood_check_matched = array();
foreach ($this->flood_check as $flood_post) { foreach ($this->flood_check as $flood_post) {
foreach ($match as $flood_match_arg) { foreach ($match as $flood_match_arg) {
switch ($flood_match_arg) { switch ($flood_match_arg) {
case 'ip': case 'ip':
if ($flood_post['ip'] != $_SERVER['REMOTE_ADDR']) if ($flood_post['ip'] != $_SERVER['REMOTE_ADDR']) {
continue 3; continue 3;
}
break; break;
case 'body': case 'body':
if ($flood_post['posthash'] != make_comment_hex($post['body_nomarkup'])) if ($flood_post['posthash'] != make_comment_hex($post['body_nomarkup'])) {
continue 3; continue 3;
}
break; break;
case 'file': case 'file':
if (!isset($post['filehash'])) if (!isset($post['filehash'])) {
return false; return false;
if ($flood_post['filehash'] != $post['filehash']) }
if ($flood_post['filehash'] != $post['filehash']) {
continue 3; continue 3;
}
break; break;
case 'board': case 'board':
if ($flood_post['board'] != $post['board']) if ($flood_post['board'] != $post['board']) {
continue 3; continue 3;
}
break; break;
case 'isreply': case 'isreply':
if ($flood_post['isreply'] == $post['op']) if ($flood_post['isreply'] == $post['op']) {
continue 3; continue 3;
}
break; break;
default: default:
error('Invalid filter flood condition: ' . $flood_match_arg); error('Invalid filter flood condition: ' . $flood_match_arg);
@ -65,9 +74,8 @@ class Filter {
} }
$flood_check_matched[] = $flood_post; $flood_check_matched[] = $flood_post;
} }
$this->flood_check = $flood_check_matched; $this->flood_check = $flood_check_matched;
return !empty($this->flood_check); return !empty($this->flood_check);
case 'flood-time': case 'flood-time':
foreach ($this->flood_check as $flood_post) { foreach ($this->flood_check as $flood_post) {
@ -97,8 +105,9 @@ class Filter {
case 'filehash': case 'filehash':
return $match === $post['filehash']; return $match === $post['filehash'];
case 'filename': case 'filename':
if (!$post['files']) if (!$post['files']) {
return false; return false;
}
foreach ($post['files'] as $file) { foreach ($post['files'] as $file) {
if (preg_match($match, $file['filename'])) { if (preg_match($match, $file['filename'])) {
@ -107,8 +116,9 @@ class Filter {
} }
return false; return false;
case 'extension': case 'extension':
if (!$post['files']) if (!$post['files']) {
return false; return false;
}
foreach ($post['files'] as $file) { foreach ($post['files'] as $file) {
if (preg_match($match, $file['extension'])) { if (preg_match($match, $file['extension'])) {
@ -126,60 +136,75 @@ class Filter {
return $post['board'] == $match; return $post['board'] == $match;
case 'password': case 'password':
return $post['password'] == $match; 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: default:
error('Unknown filter condition: ' . $condition); error('Unknown filter condition: ' . $condition);
} }
} }
public function action() { public function action() {
global $board; global $board;
$this->add_note = isset($this->add_note) ? $this->add_note : false; $this->add_note = isset($this->add_note) ? $this->add_note : false;
if ($this->add_note) { if ($this->add_note) {
$query = prepare('INSERT INTO ``ip_notes`` VALUES (NULL, :ip, :mod, :time, :body)'); $query = prepare('INSERT INTO ``ip_notes`` VALUES (NULL, :ip, :mod, :time, :body)');
$query->bindValue(':ip', $_SERVER['REMOTE_ADDR']); $query->bindValue(':ip', $_SERVER['REMOTE_ADDR']);
$query->bindValue(':mod', -1); $query->bindValue(':mod', -1);
$query->bindValue(':time', time()); $query->bindValue(':time', time());
$query->bindValue(':body', "Autoban message: ".$this->post['body']); $query->bindValue(':body', "Autoban message: ".$this->post['body']);
$query->execute() or error(db_error($query)); $query->execute() or error(db_error($query));
} }
if (isset ($this->action)) switch($this->action) { if (isset($this->action)) {
case 'reject': switch($this->action) {
error(isset($this->message) ? $this->message : 'Posting throttled by filter.'); case 'reject':
case 'ban': error(isset($this->message) ? $this->message : 'Posting throttled by filter.');
if (!isset($this->reason)) case 'ban':
error('The ban action requires a 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;
$this->all_boards = isset($this->all_boards) ? $this->all_boards : false;
Bans::new_ban($_SERVER['REMOTE_ADDR'], $this->reason, $this->expires, $this->all_boards ? false : $board['uri'], -1);
if ($this->reject) { $this->expires = isset($this->expires) ? $this->expires : false;
if (isset($this->message)) $this->reject = isset($this->reject) ? $this->reject : true;
error($message); $this->all_boards = isset($this->all_boards) ? $this->all_boards : false;
checkBan($board['uri']); Bans::new_ban($_SERVER['REMOTE_ADDR'], $this->reason, $this->expires, $this->all_boards ? false : $board['uri'], -1);
exit;
} if ($this->reject) {
if (isset($this->message)) {
break; error($message);
default: }
error('Unknown filter action: ' . $this->action);
checkBan($board['uri']);
exit;
}
break;
default:
error('Unknown filter action: ' . $this->action);
}
} }
} }
public function check(array $post) { public function check(array $post) {
$this->post = $post; $this->post = $post;
foreach ($this->condition as $condition => $value) { foreach ($this->condition as $condition => $value) {
if ($condition[0] == '!') { if ($condition[0] == '!') {
$NOT = true; $NOT = true;
$condition = substr($condition, 1); $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 false;
}
} }
return true; return true;
} }
@ -187,39 +212,41 @@ class Filter {
function purge_flood_table() { function purge_flood_table() {
global $config; global $config;
// Determine how long we need to keep a cache of posts for flood prevention. Unfortunately, it is not // Determine how long we need to keep a cache of posts for flood prevention. Unfortunately, it is not
// aware of flood filters in other board configurations. You can solve this problem by settings the // aware of flood filters in other board configurations. You can solve this problem by settings the
// config variable $config['flood_cache'] (seconds). // config variable $config['flood_cache'] (seconds).
if (isset($config['flood_cache'])) { if ($config['flood_cache'] != -1) {
$max_time = &$config['flood_cache']; $max_time = &$config['flood_cache'];
} else { } else {
$max_time = 0; $max_time = 0;
foreach ($config['filters'] as $filter) { 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']); $max_time = max($max_time, $filter['condition']['flood-time']);
}
} }
} }
$time = time() - $max_time; $time = time() - $max_time;
query("DELETE FROM ``flood`` WHERE `time` < $time") or error(db_error()); query("DELETE FROM ``flood`` WHERE `time` < $time") or error(db_error());
} }
function do_filters(array $post) { function do_filters(array $post) {
global $config; global $config;
if (!isset($config['filters']) || empty($config['filters'])) if (!isset($config['filters']) || empty($config['filters'])) {
return; return;
}
foreach ($config['filters'] as $filter) { foreach ($config['filters'] as $filter) {
if (isset($filter['condition']['flood-match'])) { if (isset($filter['condition']['flood-match'])) {
$has_flood = true; $has_flood = true;
break; break;
} }
} }
if (isset($has_flood)) { if (isset($has_flood)) {
if ($post['has_file']) { if ($post['has_file']) {
$query = prepare("SELECT * FROM ``flood`` WHERE `ip` = :ip OR `posthash` = :posthash OR `filehash` = :filehash"); $query = prepare("SELECT * FROM ``flood`` WHERE `ip` = :ip OR `posthash` = :posthash OR `filehash` = :filehash");
@ -236,14 +263,14 @@ function do_filters(array $post) {
} else { } else {
$flood_check = false; $flood_check = false;
} }
foreach ($config['filters'] as $filter_array) { foreach ($config['filters'] as $filter_array) {
$filter = new Filter($filter_array); $filter = new Filter($filter_array);
$filter->flood_check = $flood_check; $filter->flood_check = $flood_check;
if ($filter->check($post)) if ($filter->check($post)) {
$filter->action(); $filter->action();
}
} }
purge_flood_table(); purge_flood_table();
} }

File diff suppressed because it is too large Load Diff

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

@ -10,7 +10,7 @@ class Image {
public $src, $format, $image, $size; public $src, $format, $image, $size;
public function __construct($src, $format = false, $size = false) { public function __construct($src, $format = false, $size = false) {
global $config; global $config;
$this->src = $src; $this->src = $src;
$this->format = $format; $this->format = $format;
@ -24,21 +24,21 @@ class Image {
error(_('Unsupported file format: ') . $this->format); error(_('Unsupported file format: ') . $this->format);
} }
} }
$this->image = new $classname($this, $size); $this->image = new $classname($this, $size);
if (!$this->image->valid()) { if (!$this->image->valid()) {
$this->delete(); $this->delete();
error($config['error']['invalidimg']); error($config['error']['invalidimg']);
} }
$this->size = (object)array('width' => $this->image->_width(), 'height' => $this->image->_height()); $this->size = (object)array('width' => $this->image->_width(), 'height' => $this->image->_height());
if ($this->size->width < 1 || $this->size->height < 1) { if ($this->size->width < 1 || $this->size->height < 1) {
$this->delete(); $this->delete();
error($config['error']['invalidimg']); error($config['error']['invalidimg']);
} }
} }
public function resize($extension, $max_width, $max_height) { public function resize($extension, $max_width, $max_height) {
global $config; global $config;
@ -62,16 +62,16 @@ class Image {
error(_('Unsupported file format: ') . $extension); error(_('Unsupported file format: ') . $extension);
} }
} }
$thumb = new $classname(false); $thumb = new $classname(false);
$thumb->src = $this->src; $thumb->src = $this->src;
$thumb->format = $this->format; $thumb->format = $this->format;
$thumb->original_width = $this->size->width; $thumb->original_width = $this->size->width;
$thumb->original_height = $this->size->height; $thumb->original_height = $this->size->height;
$x_ratio = $max_width / $this->size->width; $x_ratio = $max_width / $this->size->width;
$y_ratio = $max_height / $this->size->height; $y_ratio = $max_height / $this->size->height;
if (($this->size->width <= $max_width) && ($this->size->height <= $max_height)) { if (($this->size->width <= $max_width) && ($this->size->height <= $max_height)) {
$width = $this->size->width; $width = $this->size->width;
$height = $this->size->height; $height = $this->size->height;
@ -82,16 +82,16 @@ class Image {
$width = ceil($y_ratio * $this->size->width); $width = ceil($y_ratio * $this->size->width);
$height = $max_height; $height = $max_height;
} }
$thumb->_resize($this->image->image, $width, $height); $thumb->_resize($this->image->image, $width, $height);
return $thumb; return $thumb;
} }
public function to($dst) { public function to($dst) {
$this->image->to($dst); $this->image->to($dst);
} }
public function delete() { public function delete() {
file_unlink($this->src); file_unlink($this->src);
} }
@ -114,26 +114,26 @@ class ImageGD {
} }
class ImageBase extends ImageGD { class ImageBase extends ImageGD {
public $image, $src, $original, $original_width, $original_height, $width, $height; public $image, $src, $original, $original_width, $original_height, $width, $height;
public function valid() { public function valid() {
return (bool)$this->image; return (bool)$this->image;
} }
public function __construct($img, $size = false) { public function __construct($img, $size = false) {
if (method_exists($this, 'init')) if (method_exists($this, 'init'))
$this->init(); $this->init();
if ($size && $size[0] > 0 && $size[1] > 0) { if ($size && $size[0] > 0 && $size[1] > 0) {
$this->width = $size[0]; $this->width = $size[0];
$this->height = $size[1]; $this->height = $size[1];
} }
if ($img !== false) { if ($img !== false) {
$this->src = $img->src; $this->src = $img->src;
$this->from(); $this->from();
} }
} }
public function _width() { public function _width() {
if (method_exists($this, 'width')) if (method_exists($this, 'width'))
return $this->width(); return $this->width();
@ -156,7 +156,7 @@ class ImageBase extends ImageGD {
$this->original = &$original; $this->original = &$original;
$this->width = $width; $this->width = $width;
$this->height = $height; $this->height = $height;
if (method_exists($this, 'resize')) if (method_exists($this, 'resize'))
$this->resize(); $this->resize();
else else
@ -199,31 +199,31 @@ class ImageImagick extends ImageBase {
} }
public function resize() { public function resize() {
global $config; global $config;
if ($this->format == 'gif' && ($config['thumb_ext'] == 'gif' || $config['thumb_ext'] == '')) { if ($this->format == 'gif' && ($config['thumb_ext'] == 'gif' || $config['thumb_ext'] == '')) {
$this->image = new Imagick(); $this->image = new Imagick();
$this->image->setFormat('gif'); $this->image->setFormat('gif');
$keep_frames = array(); $keep_frames = array();
for ($i = 0; $i < $this->original->getNumberImages(); $i += floor($this->original->getNumberImages() / $config['thumb_keep_animation_frames'])) for ($i = 0; $i < $this->original->getNumberImages(); $i += floor($this->original->getNumberImages() / $config['thumb_keep_animation_frames']))
$keep_frames[] = $i; $keep_frames[] = $i;
$i = 0; $i = 0;
$delay = 0; $delay = 0;
foreach ($this->original as $frame) { foreach ($this->original as $frame) {
$delay += $frame->getImageDelay(); $delay += $frame->getImageDelay();
if (in_array($i, $keep_frames)) { if (in_array($i, $keep_frames)) {
// $frame->scaleImage($this->width, $this->height, false); // $frame->scaleImage($this->width, $this->height, false);
$frame->sampleImage($this->width, $this->height); $frame->sampleImage($this->width, $this->height);
$frame->setImagePage($this->width, $this->height, 0, 0); $frame->setImagePage($this->width, $this->height, 0, 0);
$frame->setImageDelay($delay); $frame->setImageDelay($delay);
$delay = 0; $delay = 0;
$this->image->addImage($frame->getImage()); $this->image->addImage($frame->getImage());
} }
$i++; $i++;
} }
} else { } else {
$this->image = clone $this->original; $this->image = clone $this->original;
$this->image->scaleImage($this->width, $this->height, false); $this->image->scaleImage($this->width, $this->height, false);
@ -234,15 +234,15 @@ class ImageImagick extends ImageBase {
class ImageConvert extends ImageBase { class ImageConvert extends ImageBase {
public $width, $height, $temp, $gm = false, $gifsicle = false; public $width, $height, $temp, $gm = false, $gifsicle = false;
public function init() { public function init() {
global $config; global $config;
if ($config['thumb_method'] == 'gm' || $config['thumb_method'] == 'gm+gifsicle') if ($config['thumb_method'] == 'gm' || $config['thumb_method'] == 'gm+gifsicle')
$this->gm = true; $this->gm = true;
if ($config['thumb_method'] == 'convert+gifsicle' || $config['thumb_method'] == 'gm+gifsicle') if ($config['thumb_method'] == 'convert+gifsicle' || $config['thumb_method'] == 'gm+gifsicle')
$this->gifsicle = true; $this->gifsicle = true;
$this->temp = false; $this->temp = false;
} }
public function get_size($src, $try_gd_first = true) { public function get_size($src, $try_gd_first = true) {
@ -264,7 +264,7 @@ class ImageConvert extends ImageBase {
if ($size) { if ($size) {
$this->width = $size[0]; $this->width = $size[0];
$this->height = $size[1]; $this->height = $size[1];
$this->image = true; $this->image = true;
} else { } else {
// mark as invalid // mark as invalid
@ -273,7 +273,7 @@ class ImageConvert extends ImageBase {
} }
public function to($src) { public function to($src) {
global $config; global $config;
if (!$this->temp) { if (!$this->temp) {
if ($config['strip_exif']) { if ($config['strip_exif']) {
if($error = shell_exec_error(($this->gm ? 'gm ' : '') . 'convert ' . if($error = shell_exec_error(($this->gm ? 'gm ' : '') . 'convert ' .
@ -291,6 +291,7 @@ class ImageConvert extends ImageBase {
} else { } else {
rename($this->temp, $src); rename($this->temp, $src);
chmod($src, 0664); chmod($src, 0664);
$this->temp = false;
} }
} }
public function width() { public function width() {
@ -300,21 +301,23 @@ class ImageConvert extends ImageBase {
return $this->height; return $this->height;
} }
public function destroy() { public function destroy() {
@unlink($this->temp); if ($this->temp !== false) {
$this->temp = false; @unlink($this->temp);
$this->temp = false;
}
} }
public function resize() { public function resize() {
global $config; global $config;
if ($this->temp) { if ($this->temp) {
// remove old // remove old
$this->destroy(); $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']; $config['thumb_keep_animation_frames'] = (int)$config['thumb_keep_animation_frames'];
if ($this->format == 'gif' && ($config['thumb_ext'] == 'gif' || $config['thumb_ext'] == '') && $config['thumb_keep_animation_frames'] > 1) { if ($this->format == 'gif' && ($config['thumb_ext'] == 'gif' || $config['thumb_ext'] == '') && $config['thumb_keep_animation_frames'] > 1) {
if ($this->gifsicle) { if ($this->gifsicle) {
if (($error = shell_exec("gifsicle -w --unoptimize -O2 --resize {$this->width}x{$this->height} < " . if (($error = shell_exec("gifsicle -w --unoptimize -O2 --resize {$this->width}x{$this->height} < " .
@ -324,12 +327,7 @@ class ImageConvert extends ImageBase {
error(_('Failed to resize image!'), null, $error); error(_('Failed to resize image!'), null, $error);
} }
} else { } else {
if ($config['convert_manual_orient'] && ($this->format == 'jpg' || $this->format == 'jpeg')) $convert_args = &$config['convert_args'];
$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 ' . if (($error = shell_exec_error(($this->gm ? 'gm ' : '') . 'convert ' .
sprintf($convert_args, sprintf($convert_args,
@ -348,12 +346,8 @@ class ImageConvert extends ImageBase {
} }
} }
} else { } else {
if ($config['convert_manual_orient'] && ($this->format == 'jpg' || $this->format == 'jpeg')) $convert_args = &$config['convert_args'];
$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 ' . if (($error = shell_exec_error(($this->gm ? 'gm ' : '') . 'convert ' .
sprintf($convert_args, sprintf($convert_args,
$this->width, $this->width,
@ -364,7 +358,8 @@ class ImageConvert extends ImageBase {
escapeshellarg($this->temp)))) || !file_exists($this->temp)) { escapeshellarg($this->temp)))) || !file_exists($this->temp)) {
if (strpos($error, "known incorrect sRGB profile") === false && 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(); $this->destroy();
error(_('Failed to resize image!')." "._('Details: ').nl2br(htmlspecialchars($error)), null, array('convert_error' => $error)); 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 { class ImagePNG extends ImageBase {
@ -496,170 +428,11 @@ class ImageBMP extends ImageBase {
} }
} }
class ImageWEBP extends ImageBase {
/*********************************************/ public function from() {
/* Fonction: imagecreatefrombmp */ $this->image = @imagecreatefromwebp($this->src);
/* 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--; public function to($src) {
$P+=$BMP['decal']; imagewebp($this->image, $src);
}
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'] = '/'; //$config['root'] = '/';
@include('inc/secrets.php');
?> ?>

View File

@ -1,20 +0,0 @@
Copyright (c) 2013 Jason Morriss
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@ -1,293 +0,0 @@
<?php
/**
* This file is part of the Lifo\IP PHP Library.
*
* (c) Jason Morriss <lifo2013@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Lifo\IP;
/**
* BCMath helper class.
*
* Provides a handful of BCMath routines that are not included in the native
* PHP library.
*
* Note: The Bitwise functions operate on fixed byte boundaries. For example,
* comparing the following numbers uses X number of bits:
* 0xFFFF and 0xFF will result in comparison of 16 bits.
* 0xFFFFFFFF and 0xF will result in comparison of 32 bits.
* etc...
*
*/
abstract class BC
{
// Some common (maybe useless) constants
const MAX_INT_32 = '2147483647'; // 7FFFFFFF
const MAX_UINT_32 = '4294967295'; // FFFFFFFF
const MAX_INT_64 = '9223372036854775807'; // 7FFFFFFFFFFFFFFF
const MAX_UINT_64 = '18446744073709551615'; // FFFFFFFFFFFFFFFF
const MAX_INT_96 = '39614081257132168796771975167'; // 7FFFFFFFFFFFFFFFFFFFFFFF
const MAX_UINT_96 = '79228162514264337593543950335'; // FFFFFFFFFFFFFFFFFFFFFFFF
const MAX_INT_128 = '170141183460469231731687303715884105727'; // 7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
const MAX_UINT_128 = '340282366920938463463374607431768211455'; // FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
/**
* BC Math function to convert a HEX string into a DECIMAL
*/
public static function bchexdec($hex)
{
if (strlen($hex) == 1) {
return hexdec($hex);
}
$remain = substr($hex, 0, -1);
$last = substr($hex, -1);
return bcadd(bcmul(16, self::bchexdec($remain), 0), hexdec($last), 0);
}
/**
* BC Math function to convert a DECIMAL string into a BINARY string
*/
public static function bcdecbin($dec, $pad = null)
{
$bin = '';
while ($dec) {
$m = bcmod($dec, 2);
$dec = bcdiv($dec, 2, 0);
$bin = abs($m) . $bin;
}
return $pad ? sprintf("%0{$pad}s", $bin) : $bin;
}
/**
* BC Math function to convert a BINARY string into a DECIMAL string
*/
public static function bcbindec($bin)
{
$dec = '0';
for ($i=0, $j=strlen($bin); $i<$j; $i++) {
$dec = bcmul($dec, '2', 0);
$dec = bcadd($dec, $bin[$i], 0);
}
return $dec;
}
/**
* BC Math function to convert a BINARY string into a HEX string
*/
public static function bcbinhex($bin, $pad = 0)
{
return self::bcdechex(self::bcbindec($bin));
}
/**
* BC Math function to convert a DECIMAL into a HEX string
*/
public static function bcdechex($dec)
{
$last = bcmod($dec, 16);
$remain = bcdiv(bcsub($dec, $last, 0), 16, 0);
return $remain == 0 ? dechex($last) : self::bcdechex($remain) . dechex($last);
}
/**
* Bitwise AND two arbitrarily large numbers together.
*/
public static function bcand($left, $right)
{
$len = self::_bitwise($left, $right);
$value = '';
for ($i=0; $i<$len; $i++) {
$value .= (($left{$i} + 0) & ($right{$i} + 0)) ? '1' : '0';
}
return self::bcbindec($value != '' ? $value : '0');
}
/**
* Bitwise OR two arbitrarily large numbers together.
*/
public static function bcor($left, $right)
{
$len = self::_bitwise($left, $right);
$value = '';
for ($i=0; $i<$len; $i++) {
$value .= (($left{$i} + 0) | ($right{$i} + 0)) ? '1' : '0';
}
return self::bcbindec($value != '' ? $value : '0');
}
/**
* Bitwise XOR two arbitrarily large numbers together.
*/
public static function bcxor($left, $right)
{
$len = self::_bitwise($left, $right);
$value = '';
for ($i=0; $i<$len; $i++) {
$value .= (($left{$i} + 0) ^ ($right{$i} + 0)) ? '1' : '0';
}
return self::bcbindec($value != '' ? $value : '0');
}
/**
* Bitwise NOT two arbitrarily large numbers together.
*/
public static function bcnot($left, $bits = null)
{
$right = 0;
$len = self::_bitwise($left, $right, $bits);
$value = '';
for ($i=0; $i<$len; $i++) {
$value .= $left{$i} == '1' ? '0' : '1';
}
return self::bcbindec($value);
}
/**
* Shift number to the left
*
* @param integer $bits Total bits to shift
*/
public static function bcleft($num, $bits) {
return bcmul($num, bcpow('2', $bits));
}
/**
* Shift number to the right
*
* @param integer $bits Total bits to shift
*/
public static function bcright($num, $bits) {
return bcdiv($num, bcpow('2', $bits));
}
/**
* Determine how many bits are needed to store the number rounded to the
* nearest bit boundary.
*/
public static function bits_needed($num, $boundary = 4)
{
$bits = 0;
while ($num > 0) {
$num = bcdiv($num, '2', 0);
$bits++;
}
// round to nearest boundrary
return $boundary ? ceil($bits / $boundary) * $boundary : $bits;
}
/**
* BC Math function to return an arbitrarily large random number.
*/
public static function bcrand($min, $max = null)
{
if ($max === null) {
$max = $min;
$min = 0;
}
// swap values if $min > $max
if (bccomp($min, $max) == 1) {
list($min,$max) = array($max,$min);
}
return bcadd(
bcmul(
bcdiv(
mt_rand(0, mt_getrandmax()),
mt_getrandmax(),
strlen($max)
),
bcsub(
bcadd($max, '1'),
$min
)
),
$min
);
}
/**
* Computes the natural logarithm using a series.
* @author Thomas Oldbury.
* @license Public domain.
*/
public static function bclog($num, $iter = 10, $scale = 100)
{
$log = "0.0";
for($i = 0; $i < $iter; $i++) {
$pow = 1 + (2 * $i);
$mul = bcdiv("1.0", $pow, $scale);
$fraction = bcmul($mul, bcpow(bcsub($num, "1.0", $scale) / bcadd($num, "1.0", $scale), $pow, $scale), $scale);
$log = bcadd($fraction, $log, $scale);
}
return bcmul("2.0", $log, $scale);
}
/**
* Computes the base2 log using baseN log.
*/
public static function bclog2($num, $iter = 10, $scale = 100)
{
return bcdiv(self::bclog($num, $iter, $scale), self::bclog("2", $iter, $scale), $scale);
}
public static function bcfloor($num)
{
if (substr($num, 0, 1) == '-') {
return bcsub($num, 1, 0);
}
return bcadd($num, 0, 0);
}
public static function bcceil($num)
{
if (substr($num, 0, 1) == '-') {
return bcsub($num, 0, 0);
}
return bcadd($num, 1, 0);
}
/**
* Compare two numbers and return -1, 0, 1 depending if the LEFT number is
* < = > the RIGHT.
*
* @param string|integer $left Left side operand
* @param string|integer $right Right side operand
* @return integer Return -1,0,1 for <=> comparison
*/
public static function cmp($left, $right)
{
// @todo could an optimization be done to determine if a normal 32bit
// comparison could be done instead of using bccomp? But would
// the number verification cause too much overhead to be useful?
return bccomp($left, $right, 0);
}
/**
* Internal function to prepare for bitwise operations
*/
private static function _bitwise(&$left, &$right, $bits = null)
{
if ($bits === null) {
$bits = max(self::bits_needed($left), self::bits_needed($right));
}
$left = self::bcdecbin($left);
$right = self::bcdecbin($right);
$len = max(strlen($left), strlen($right), (int)$bits);
$left = sprintf("%0{$len}s", $left);
$right = sprintf("%0{$len}s", $right);
return $len;
}
}

View File

@ -1,706 +0,0 @@
<?php
/**
* This file is part of the Lifo\IP PHP Library.
*
* (c) Jason Morriss <lifo2013@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Lifo\IP;
/**
* CIDR Block helper class.
*
* Most routines can be used statically or by instantiating an object and
* calling its methods.
*
* Provides routines to do various calculations on IP addresses and ranges.
* Convert to/from CIDR to ranges, etc.
*/
class CIDR
{
const INTERSECT_NO = 0;
const INTERSECT_YES = 1;
const INTERSECT_LOW = 2;
const INTERSECT_HIGH = 3;
protected $start;
protected $end;
protected $prefix;
protected $version;
protected $istart;
protected $iend;
private $cache;
/**
* Create a new CIDR object.
*
* The IP range can be arbitrary and does not have to fall on a valid CIDR
* range. Some methods will return different values depending if you ignore
* the prefix or not. By default all prefix sensitive methods will assume
* the prefix is used.
*
* @param string $cidr An IP address (1.2.3.4), CIDR block (1.2.3.4/24),
* or range "1.2.3.4-1.2.3.10"
* @param string $end Ending IP in range if no cidr/prefix is given
*/
public function __construct($cidr, $end = null)
{
if ($end !== null) {
$this->setRange($cidr, $end);
} else {
$this->setCidr($cidr);
}
}
/**
* Returns the string representation of the CIDR block.
*/
public function __toString()
{
// do not include the prefix if its a single IP
try {
if ($this->isTrueCidr() && (
($this->version == 4 and $this->prefix != 32) ||
($this->version == 6 and $this->prefix != 128)
)
) {
return $this->start . '/' . $this->prefix;
}
} catch (\Exception $e) {
// isTrueCidr() calls getRange which can throw an exception
}
if (strcmp($this->start, $this->end) == 0) {
return $this->start;
}
return $this->start . ' - ' . $this->end;
}
public function __clone()
{
// do not clone the cache. No real reason why. I just want to keep the
// memory foot print as low as possible, even though this is trivial.
$this->cache = array();
}
/**
* Set an arbitrary IP range.
* The closest matching prefix will be calculated but the actual range
* stored in the object can be arbitrary.
* @param string $start Starting IP or combination "start-end" string.
* @param string $end Ending IP or null.
*/
public function setRange($ip, $end = null)
{
if (strpos($ip, '-') !== false) {
list($ip, $end) = array_map('trim', explode('-', $ip, 2));
}
if (false === filter_var($ip, FILTER_VALIDATE_IP) ||
false === filter_var($end, FILTER_VALIDATE_IP)) {
throw new \InvalidArgumentException("Invalid IP range \"$ip-$end\"");
}
// determine version (4 or 6)
$this->version = (false === filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) ? 6 : 4;
$this->istart = IP::inet_ptod($ip);
$this->iend = IP::inet_ptod($end);
// fix order
if (bccomp($this->istart, $this->iend) == 1) {
list($this->istart, $this->iend) = array($this->iend, $this->istart);
list($ip, $end) = array($end, $ip);
}
$this->start = $ip;
$this->end = $end;
// calculate real prefix
$len = $this->version == 4 ? 32 : 128;
$this->prefix = $len - strlen(BC::bcdecbin(BC::bcxor($this->istart, $this->iend)));
}
/**
* Returns true if the current IP is a true cidr block
*/
public function isTrueCidr()
{
return $this->start == $this->getNetwork() && $this->end == $this->getBroadcast();
}
/**
* Set the CIDR block.
*
* The prefix length is optional and will default to 32 ot 128 depending on
* the version detected.
*
* @param string $cidr CIDR block string, eg: "192.168.0.0/24" or "2001::1/64"
* @throws \InvalidArgumentException If the CIDR block is invalid
*/
public function setCidr($cidr)
{
if (strpos($cidr, '-') !== false) {
return $this->setRange($cidr);
}
list($ip, $bits) = array_pad(array_map('trim', explode('/', $cidr, 2)), 2, null);
if (false === filter_var($ip, FILTER_VALIDATE_IP)) {
throw new \InvalidArgumentException("Invalid IP address \"$cidr\"");
}
// determine version (4 or 6)
$this->version = (false === filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) ? 6 : 4;
$this->start = $ip;
$this->istart = IP::inet_ptod($ip);
if ($bits !== null and $bits !== '') {
$this->prefix = $bits;
} else {
$this->prefix = $this->version == 4 ? 32 : 128;
}
if (($this->prefix < 0)
|| ($this->prefix > 32 and $this->version == 4)
|| ($this->prefix > 128 and $this->version == 6)) {
throw new \InvalidArgumentException("Invalid IP address \"$cidr\"");
}
$this->end = $this->getBroadcast();
$this->iend = IP::inet_ptod($this->end);
$this->cache = array();
}
/**
* Get the IP version. 4 or 6.
*
* @return integer
*/
public function getVersion()
{
return $this->version;
}
/**
* Get the prefix.
*
* Always returns the "proper" prefix, even if the IP range is arbitrary.
*
* @return integer
*/
public function getPrefix()
{
return $this->prefix;
}
/**
* Return the starting presentational IP or Decimal value.
*
* Ignores prefix
*/
public function getStart($decimal = false)
{
return $decimal ? $this->istart : $this->start;
}
/**
* Return the ending presentational IP or Decimal value.
*
* Ignores prefix
*/
public function getEnd($decimal = false)
{
return $decimal ? $this->iend : $this->end;
}
/**
* Return the next presentational IP or Decimal value (following the
* broadcast address of the current CIDR block).
*/
public function getNext($decimal = false)
{
$next = bcadd($this->getEnd(true), '1');
return $decimal ? $next : new self(IP::inet_dtop($next));
}
/**
* Returns true if the IP is an IPv4
*
* @return boolean
*/
public function isIPv4()
{
return $this->version == 4;
}
/**
* Returns true if the IP is an IPv6
*
* @return boolean
*/
public function isIPv6()
{
return $this->version == 6;
}
/**
* Get the cidr notation for the subnet block.
*
* This is useful for when you want a string representation of the IP/prefix
* and the starting IP is not on a valid network boundrary (eg: Displaying
* an IP from an interface).
*
* @return string IP in CIDR notation "ipaddr/prefix"
*/
public function getCidr()
{
return $this->start . '/' . $this->prefix;
}
/**
* Get the [low,high] range of the CIDR block
*
* Prefix sensitive.
*
* @param boolean $ignorePrefix If true the arbitrary start-end range is
* returned. default=false.
*/
public function getRange($ignorePrefix = false)
{
$range = $ignorePrefix
? array($this->start, $this->end)
: self::cidr_to_range($this->start, $this->prefix);
// watch out for IP '0' being converted to IPv6 '::'
if ($range[0] == '::' and strpos($range[1], ':') == false) {
$range[0] = '0.0.0.0';
}
return $range;
}
/**
* Return the IP in its fully expanded form.
*
* For example: 2001::1 == 2007:0000:0000:0000:0000:0000:0000:0001
*
* @see IP::inet_expand
*/
public function getExpanded()
{
return IP::inet_expand($this->start);
}
/**
* Get network IP of the CIDR block
*
* Prefix sensitive.
*
* @param boolean $ignorePrefix If true the arbitrary start-end range is
* returned. default=false.
*/
public function getNetwork($ignorePrefix = false)
{
// micro-optimization to prevent calling getRange repeatedly
$k = $ignorePrefix ? 1 : 0;
if (!isset($this->cache['range'][$k])) {
$this->cache['range'][$k] = $this->getRange($ignorePrefix);
}
return $this->cache['range'][$k][0];
}
/**
* Get broadcast IP of the CIDR block
*
* Prefix sensitive.
*
* @param boolean $ignorePrefix If true the arbitrary start-end range is
* returned. default=false.
*/
public function getBroadcast($ignorePrefix = false)
{
// micro-optimization to prevent calling getRange repeatedly
$k = $ignorePrefix ? 1 : 0;
if (!isset($this->cache['range'][$k])) {
$this->cache['range'][$k] = $this->getRange($ignorePrefix);
}
return $this->cache['range'][$k][1];
}
/**
* Get the network mask based on the prefix.
*
*/
public function getMask()
{
return self::prefix_to_mask($this->prefix, $this->version);
}
/**
* Get total hosts within CIDR range
*
* Prefix sensitive.
*
* @param boolean $ignorePrefix If true the arbitrary start-end range is
* returned. default=false.
*/
public function getTotal($ignorePrefix = false)
{
// micro-optimization to prevent calling getRange repeatedly
$k = $ignorePrefix ? 1 : 0;
if (!isset($this->cache['range'][$k])) {
$this->cache['range'][$k] = $this->getRange($ignorePrefix);
}
return bcadd(bcsub(IP::inet_ptod($this->cache['range'][$k][1]),
IP::inet_ptod($this->cache['range'][$k][0])), '1');
}
public function intersects($cidr)
{
return self::cidr_intersect((string)$this, $cidr);
}
/**
* Determines the intersection between an IP (with optional prefix) and a
* CIDR block.
*
* The IP will be checked against the CIDR block given and will either be
* inside or outside the CIDR completely, or partially.
*
* NOTE: The caller should explicitly check against the INTERSECT_*
* constants because this method will return a value > 1 even for partial
* matches.
*
* @param mixed $ip The IP/cidr to match
* @param mixed $cidr The CIDR block to match within
* @return integer Returns an INTERSECT_* constant
* @throws \InvalidArgumentException if either $ip or $cidr is invalid
*/
public static function cidr_intersect($ip, $cidr)
{
// use fixed length HEX strings so we can easily do STRING comparisons
// instead of using slower bccomp() math.
list($lo,$hi) = array_map(function($v){ return sprintf("%032s", IP::inet_ptoh($v)); }, CIDR::cidr_to_range($ip));
list($min,$max) = array_map(function($v){ return sprintf("%032s", IP::inet_ptoh($v)); }, CIDR::cidr_to_range($cidr));
/** visualization of logic used below
lo-hi = $ip to check
min-max = $cidr block being checked against
--- --- --- lo --- --- hi --- --- --- --- --- IP/prefix to check
--- min --- --- max --- --- --- --- --- --- --- Partial "LOW" match
--- --- --- --- --- min --- --- max --- --- --- Partial "HIGH" match
--- --- --- --- min max --- --- --- --- --- --- No match "NO"
--- --- --- --- --- --- --- --- min --- max --- No match "NO"
min --- max --- --- --- --- --- --- --- --- --- No match "NO"
--- --- min --- --- --- --- max --- --- --- --- Full match "YES"
*/
// IP is exact match or completely inside the CIDR block
if ($lo >= $min and $hi <= $max) {
return self::INTERSECT_YES;
}
// IP is completely outside the CIDR block
if ($max < $lo or $min > $hi) {
return self::INTERSECT_NO;
}
// @todo is it useful to return LOW/HIGH partial matches?
// IP matches the lower end
if ($max <= $hi and $min <= $lo) {
return self::INTERSECT_LOW;
}
// IP matches the higher end
if ($min >= $lo and $max >= $hi) {
return self::INTERSECT_HIGH;
}
return self::INTERSECT_NO;
}
/**
* Converts an IPv4 or IPv6 CIDR block into its range.
*
* @todo May not be the fastest way to do this.
*
* @static
* @param string $cidr CIDR block or IP address string.
* @param integer|null $bits If /bits is not specified on string they can be
* passed via this parameter instead.
* @return array A 2 element array with the low, high range
*/
public static function cidr_to_range($cidr, $bits = null)
{
if (strpos($cidr, '/') !== false) {
list($ip, $_bits) = array_pad(explode('/', $cidr, 2), 2, null);
} else {
$ip = $cidr;
$_bits = $bits;
}
if (false === filter_var($ip, FILTER_VALIDATE_IP)) {
throw new \InvalidArgumentException("IP address \"$cidr\" is invalid");
}
// force bit length to 32 or 128 depending on type of IP
$bitlen = (false === filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) ? 128 : 32;
if ($bits === null) {
// if no prefix is given use the length of the binary string which
// will give us 32 or 128 and result in a single IP being returned.
$bits = $_bits !== null ? $_bits : $bitlen;
}
if ($bits > $bitlen) {
throw new \InvalidArgumentException("IP address \"$cidr\" is invalid");
}
$ipdec = IP::inet_ptod($ip);
$ipbin = BC::bcdecbin($ipdec, $bitlen);
// calculate network
$netmask = BC::bcbindec(str_pad(str_repeat('1',$bits), $bitlen, '0'));
$ip1 = BC::bcand($ipdec, $netmask);
// calculate "broadcast" (not technically a broadcast in IPv6)
$ip2 = BC::bcor($ip1, BC::bcnot($netmask));
return array(IP::inet_dtop($ip1), IP::inet_dtop($ip2));
}
/**
* Return the CIDR string from the range given
*/
public static function range_to_cidr($start, $end)
{
$cidr = new CIDR($start, $end);
return (string)$cidr;
}
/**
* Return the maximum prefix length that would fit the IP address given.
*
* This is useful to determine how my bit would be needed to store the IP
* address when you don't already have a prefix for the IP.
*
* @example 216.240.32.0 would return 27
*
* @param string $ip IP address without prefix
* @param integer $bits Maximum bits to check; defaults to 32 for IPv4 and 128 for IPv6
*/
public static function max_prefix($ip, $bits = null)
{
static $mask = array();
$ver = (false === filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) ? 6 : 4;
$max = $ver == 6 ? 128 : 32;
if ($bits === null) {
$bits = $max;
}
$int = IP::inet_ptod($ip);
while ($bits > 0) {
// micro-optimization; calculate mask once ...
if (!isset($mask[$ver][$bits-1])) {
// 2^$max - 2^($max - $bits);
if ($ver == 4) {
$mask[$ver][$bits-1] = pow(2, $max) - pow(2, $max - ($bits-1));
} else {
$mask[$ver][$bits-1] = bcsub(bcpow(2, $max), bcpow(2, $max - ($bits-1)));
}
}
$m = $mask[$ver][$bits-1];
//printf("%s/%d: %s & %s == %s\n", $ip, $bits-1, BC::bcdecbin($m, 32), BC::bcdecbin($int, 32), BC::bcdecbin(BC::bcand($int, $m)));
//echo "$ip/", $bits-1, ": ", IP::inet_dtop($m), " ($m) & $int == ", BC::bcand($int, $m), "\n";
if (bccomp(BC::bcand($int, $m), $int) != 0) {
return $bits;
}
$bits--;
}
return $bits;
}
/**
* Return a contiguous list of true CIDR blocks that span the range given.
*
* Note: It's not a good idea to call this with IPv6 addresses. While it may
* work for certain ranges this can be very slow. Also an IPv6 list won't be
* as accurate as an IPv4 list.
*
* @example
* range_to_cidrlist(192.168.0.0, 192.168.0.15) ==
* 192.168.0.0/28
* range_to_cidrlist(192.168.0.0, 192.168.0.20) ==
* 192.168.0.0/28
* 192.168.0.16/30
* 192.168.0.20/32
*/
public static function range_to_cidrlist($start, $end)
{
$ver = (false === filter_var($start, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) ? 6 : 4;
$start = IP::inet_ptod($start);
$end = IP::inet_ptod($end);
$len = $ver == 4 ? 32 : 128;
$log2 = $ver == 4 ? log(2) : BC::bclog(2);
$list = array();
while (BC::cmp($end, $start) >= 0) { // $end >= $start
$prefix = self::max_prefix(IP::inet_dtop($start), $len);
if ($ver == 4) {
$diff = $len - floor( log($end - $start + 1) / $log2 );
} else {
// this is not as accurate due to the bclog function
$diff = bcsub($len, BC::bcfloor(bcdiv(BC::bclog(bcadd(bcsub($end, $start), '1')), $log2)));
}
if ($prefix < $diff) {
$prefix = $diff;
}
$list[] = IP::inet_dtop($start) . "/" . $prefix;
if ($ver == 4) {
$start += pow(2, $len - $prefix);
} else {
$start = bcadd($start, bcpow(2, $len - $prefix));
}
}
return $list;
}
/**
* Return an list of optimized CIDR blocks by collapsing adjacent CIDR
* blocks into larger blocks.
*
* @param array $cidrs List of CIDR block strings or objects
* @param integer $maxPrefix Maximum prefix to allow
* @return array Optimized list of CIDR objects
*/
public static function optimize_cidrlist($cidrs, $maxPrefix = 32)
{
// all indexes must be a CIDR object
$cidrs = array_map(function($o){ return $o instanceof CIDR ? $o : new CIDR($o); }, $cidrs);
// sort CIDR blocks in proper order so we can easily loop over them
$cidrs = self::cidr_sort($cidrs);
$list = array();
while ($cidrs) {
$c = array_shift($cidrs);
$start = $c->getStart();
$max = bcadd($c->getStart(true), $c->getTotal());
// loop through each cidr block until its ending range is more than
// the current maximum.
while (!empty($cidrs) and $cidrs[0]->getStart(true) <= $max) {
$b = array_shift($cidrs);
$newmax = bcadd($b->getStart(true), $b->getTotal());
if ($newmax > $max) {
$max = $newmax;
}
}
// add the new cidr range to the optimized list
$list = array_merge($list, self::range_to_cidrlist($start, IP::inet_dtop(bcsub($max, '1'))));
}
return $list;
}
/**
* Sort the list of CIDR blocks, optionally with a custom callback function.
*
* @param array $cidrs A list of CIDR blocks (strings or objects)
* @param Closure $callback Optional callback to perform the sorting.
* See PHP usort documentation for more details.
*/
public static function cidr_sort($cidrs, $callback = null)
{
// all indexes must be a CIDR object
$cidrs = array_map(function($o){ return $o instanceof CIDR ? $o : new CIDR($o); }, $cidrs);
if ($callback === null) {
$callback = function($a, $b) {
if (0 != ($o = BC::cmp($a->getStart(true), $b->getStart(true)))) {
return $o; // < or >
}
if ($a->getPrefix() == $b->getPrefix()) {
return 0;
}
return $a->getPrefix() < $b->getPrefix() ? -1 : 1;
};
} elseif (!($callback instanceof \Closure) or !is_callable($callback)) {
throw new \InvalidArgumentException("Invalid callback in CIDR::cidr_sort, expected Closure, got " . gettype($callback));
}
usort($cidrs, $callback);
return $cidrs;
}
/**
* Return the Prefix bits from the IPv4 mask given.
*
* This is only valid for IPv4 addresses since IPv6 addressing does not
* have a concept of network masks.
*
* Example: 255.255.255.0 == 24
*
* @param string $mask IPv4 network mask.
*/
public static function mask_to_prefix($mask)
{
if (false === filter_var($mask, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
throw new \InvalidArgumentException("Invalid IP netmask \"$mask\"");
}
return strrpos(IP::inet_ptob($mask, 32), '1') + 1;
}
/**
* Return the network mask for the prefix given.
*
* Normally this is only useful for IPv4 addresses but you can generate a
* mask for IPv6 addresses as well, only because its mathematically
* possible.
*
* @param integer $prefix CIDR prefix bits (0-128)
* @param integer $version IP version. If null the version will be detected
* based on the prefix length given.
*/
public static function prefix_to_mask($prefix, $version = null)
{
if ($version === null) {
$version = $prefix > 32 ? 6 : 4;
}
if ($prefix < 0 or $prefix > 128) {
throw new \InvalidArgumentException("Invalid prefix length \"$prefix\"");
}
if ($version != 4 and $version != 6) {
throw new \InvalidArgumentException("Invalid version \"$version\". Must be 4 or 6");
}
if ($version == 4) {
return long2ip($prefix == 0 ? 0 : (0xFFFFFFFF >> (32 - $prefix)) << (32 - $prefix));
} else {
return IP::inet_dtop($prefix == 0 ? 0 : BC::bcleft(BC::bcright(BC::MAX_UINT_128, 128-$prefix), 128-$prefix));
}
}
/**
* Return true if the $ip given is a true CIDR block.
*
* A true CIDR block is one where the $ip given is the actual Network
* address and broadcast matches the prefix appropriately.
*/
public static function cidr_is_true($ip)
{
$ip = new CIDR($ip);
return $ip->isTrueCidr();
}
}

View File

@ -1,207 +0,0 @@
<?php
/**
* This file is part of the Lifo\IP PHP Library.
*
* (c) Jason Morriss <lifo2013@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Lifo\IP;
/**
* IP Address helper class.
*
* Provides routines to translate IPv4 and IPv6 addresses between human readable
* strings, decimal, hexidecimal and binary.
*
* Requires BCmath extension and IPv6 PHP support
*/
abstract class IP
{
/**
* Convert a human readable (presentational) IP address string into a decimal string.
*/
public static function inet_ptod($ip)
{
// shortcut for IPv4 addresses
if (strpos($ip, ':') === false && strpos($ip, '.') !== false) {
return sprintf('%u', ip2long($ip));
}
// remove any cidr block notation
if (($o = strpos($ip, '/')) !== false) {
$ip = substr($ip, 0, $o);
}
// unpack into 4 32bit integers
$parts = unpack('N*', inet_pton($ip));
foreach ($parts as &$part) {
if ($part < 0) {
// convert signed int into unsigned
$part = sprintf('%u', $part);
//$part = bcadd($part, '4294967296');
}
}
// add each 32bit integer to the proper bit location in our big decimal
$decimal = $parts[4]; // << 0
$decimal = bcadd($decimal, bcmul($parts[3], '4294967296')); // << 32
$decimal = bcadd($decimal, bcmul($parts[2], '18446744073709551616')); // << 64
$decimal = bcadd($decimal, bcmul($parts[1], '79228162514264337593543950336')); // << 96
return $decimal;
}
/**
* Convert a decimal string into a human readable IP address.
*/
public static function inet_dtop($decimal, $expand = false)
{
$parts = array();
$parts[1] = bcdiv($decimal, '79228162514264337593543950336', 0); // >> 96
$decimal = bcsub($decimal, bcmul($parts[1], '79228162514264337593543950336'));
$parts[2] = bcdiv($decimal, '18446744073709551616', 0); // >> 64
$decimal = bcsub($decimal, bcmul($parts[2], '18446744073709551616'));
$parts[3] = bcdiv($decimal, '4294967296', 0); // >> 32
$decimal = bcsub($decimal, bcmul($parts[3], '4294967296'));
$parts[4] = $decimal; // >> 0
foreach ($parts as &$part) {
if (bccomp($part, '2147483647') == 1) {
$part = bcsub($part, '4294967296');
}
$part = (int) $part;
}
// if the first 96bits is all zeros then we can safely assume we
// actually have an IPv4 address. Even though it's technically possible
// you're not really ever going to see an IPv6 address in the range:
// ::0 - ::ffff
// It's feasible to see an IPv6 address of "::", in which case the
// caller is going to have to account for that on their own.
if (($parts[1] | $parts[2] | $parts[3]) == 0) {
$ip = long2ip($parts[4]);
} else {
$packed = pack('N4', $parts[1], $parts[2], $parts[3], $parts[4]);
$ip = inet_ntop($packed);
}
// Turn IPv6 to IPv4 if it's IPv4
if (preg_match('/^::\d+\./', $ip)) {
return substr($ip, 2);
}
return $expand ? self::inet_expand($ip) : $ip;
}
/**
* Convert a human readable (presentational) IP address into a HEX string.
*/
public static function inet_ptoh($ip)
{
return bin2hex(inet_pton($ip));
//return BC::bcdechex(self::inet_ptod($ip));
}
/**
* Convert a human readable (presentational) IP address into a BINARY string.
*/
public static function inet_ptob($ip, $bits = 128)
{
return BC::bcdecbin(self::inet_ptod($ip), $bits);
}
/**
* Convert a binary string into an IP address (presentational) string.
*/
public static function inet_btop($bin)
{
return self::inet_dtop(BC::bcbindec($bin));
}
/**
* Convert a HEX string into a human readable (presentational) IP address
*/
public static function inet_htop($hex)
{
return self::inet_dtop(BC::bchexdec($hex));
}
/**
* Expand an IP address. IPv4 addresses are returned as-is.
*
* Example:
* 2001::1 expands to 2001:0000:0000:0000:0000:0000:0000:0001
* ::127.0.0.1 expands to 0000:0000:0000:0000:0000:0000:7f00:0001
* 127.0.0.1 expands to 127.0.0.1
*/
public static function inet_expand($ip)
{
// strip possible cidr notation off
if (($pos = strpos($ip, '/')) !== false) {
$ip = substr($ip, 0, $pos);
}
$bytes = unpack('n*', inet_pton($ip));
if (count($bytes) > 2) {
return implode(':', array_map(function ($b) {
return sprintf("%04x", $b);
}, $bytes));
}
return $ip;
}
/**
* Convert an IPv4 address into an IPv6 address.
*
* One use-case for this is IP 6to4 tunnels used in networking.
*
* @example
* to_ipv4("10.10.10.10") == a0a:a0a
*
* @param string $ip IPv4 address.
* @param boolean $mapped If true a Full IPv6 address is returned within the
* official ipv4to6 mapped space "0:0:0:0:0:ffff:x:x"
*/
public static function to_ipv6($ip, $mapped = false)
{
if (!self::isIPv4($ip)) {
throw new \InvalidArgumentException("Invalid IPv4 address \"$ip\"");
}
$num = IP::inet_ptod($ip);
$o1 = dechex($num >> 16);
$o2 = dechex($num & 0x0000FFFF);
return $mapped ? "0:0:0:0:0:ffff:$o1:$o2" : "$o1:$o2";
}
/**
* Returns true if the IP address is a valid IPv4 address
*/
public static function isIPv4($ip)
{
return $ip === filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4);
}
/**
* Returns true if the IP address is a valid IPv6 address
*/
public static function isIPv6($ip)
{
return $ip === filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6);
}
/**
* Compare two IP's (v4 or v6) and return -1, 0, 1 if the first is < = >
* the second.
*
* @param string $ip1 IP address
* @param string $ip2 IP address to compare against
* @return integer Return -1,0,1 depending if $ip1 is <=> $ip2
*/
public static function cmp($ip1, $ip2)
{
return bccomp(self::inet_ptod($ip1), self::inet_ptod($ip2), 0);
}
}

View File

@ -1,48 +0,0 @@
<?php
/*
* This file is part of Twig.
*
* (c) 2009 Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Autoloads Twig classes.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class Twig_Autoloader
{
/**
* Registers Twig_Autoloader as an SPL autoloader.
*
* @param Boolean $prepend Whether to prepend the autoloader or not.
*/
public static function register($prepend = false)
{
if (version_compare(phpversion(), '5.3.0', '>=')) {
spl_autoload_register(array(new self, 'autoload'), true, $prepend);
} else {
spl_autoload_register(array(new self, 'autoload'));
}
}
/**
* Handles autoloading of classes.
*
* @param string $class A class name.
*/
public static function autoload($class)
{
if (0 !== strpos($class, 'Twig')) {
return;
}
if (is_file($file = dirname(__FILE__).'/../'.str_replace(array('_', "\0"), array('/', ''), $class).'.php')) {
require $file;
}
}
}

View File

@ -1,268 +0,0 @@
<?php
/*
* This file is part of Twig.
*
* (c) 2009 Fabien Potencier
* (c) 2009 Armin Ronacher
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Compiles a node to PHP code.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class Twig_Compiler implements Twig_CompilerInterface
{
protected $lastLine;
protected $source;
protected $indentation;
protected $env;
protected $debugInfo;
protected $sourceOffset;
protected $sourceLine;
protected $filename;
/**
* Constructor.
*
* @param Twig_Environment $env The twig environment instance
*/
public function __construct(Twig_Environment $env)
{
$this->env = $env;
$this->debugInfo = array();
}
public function getFilename()
{
return $this->filename;
}
/**
* Returns the environment instance related to this compiler.
*
* @return Twig_Environment The environment instance
*/
public function getEnvironment()
{
return $this->env;
}
/**
* Gets the current PHP code after compilation.
*
* @return string The PHP code
*/
public function getSource()
{
return $this->source;
}
/**
* Compiles a node.
*
* @param Twig_NodeInterface $node The node to compile
* @param integer $indentation The current indentation
*
* @return Twig_Compiler The current compiler instance
*/
public function compile(Twig_NodeInterface $node, $indentation = 0)
{
$this->lastLine = null;
$this->source = '';
$this->sourceOffset = 0;
// source code starts at 1 (as we then increment it when we encounter new lines)
$this->sourceLine = 1;
$this->indentation = $indentation;
if ($node instanceof Twig_Node_Module) {
$this->filename = $node->getAttribute('filename');
}
$node->compile($this);
return $this;
}
public function subcompile(Twig_NodeInterface $node, $raw = true)
{
if (false === $raw) {
$this->addIndentation();
}
$node->compile($this);
return $this;
}
/**
* Adds a raw string to the compiled code.
*
* @param string $string The string
*
* @return Twig_Compiler The current compiler instance
*/
public function raw($string)
{
$this->source .= $string;
return $this;
}
/**
* Writes a string to the compiled code by adding indentation.
*
* @return Twig_Compiler The current compiler instance
*/
public function write()
{
$strings = func_get_args();
foreach ($strings as $string) {
$this->addIndentation();
$this->source .= $string;
}
return $this;
}
/**
* Appends an indentation to the current PHP code after compilation.
*
* @return Twig_Compiler The current compiler instance
*/
public function addIndentation()
{
$this->source .= str_repeat(' ', $this->indentation * 4);
return $this;
}
/**
* Adds a quoted string to the compiled code.
*
* @param string $value The string
*
* @return Twig_Compiler The current compiler instance
*/
public function string($value)
{
$this->source .= sprintf('"%s"', addcslashes($value, "\0\t\"\$\\"));
return $this;
}
/**
* Returns a PHP representation of a given value.
*
* @param mixed $value The value to convert
*
* @return Twig_Compiler The current compiler instance
*/
public function repr($value)
{
if (is_int($value) || is_float($value)) {
if (false !== $locale = setlocale(LC_NUMERIC, 0)) {
setlocale(LC_NUMERIC, 'C');
}
$this->raw($value);
if (false !== $locale) {
setlocale(LC_NUMERIC, $locale);
}
} elseif (null === $value) {
$this->raw('null');
} elseif (is_bool($value)) {
$this->raw($value ? 'true' : 'false');
} elseif (is_array($value)) {
$this->raw('array(');
$first = true;
foreach ($value as $key => $value) {
if (!$first) {
$this->raw(', ');
}
$first = false;
$this->repr($key);
$this->raw(' => ');
$this->repr($value);
}
$this->raw(')');
} else {
$this->string($value);
}
return $this;
}
/**
* Adds debugging information.
*
* @param Twig_NodeInterface $node The related twig node
*
* @return Twig_Compiler The current compiler instance
*/
public function addDebugInfo(Twig_NodeInterface $node)
{
if ($node->getLine() != $this->lastLine) {
$this->write("// line {$node->getLine()}\n");
// when mbstring.func_overload is set to 2
// mb_substr_count() replaces substr_count()
// but they have different signatures!
if (((int) ini_get('mbstring.func_overload')) & 2) {
// this is much slower than the "right" version
$this->sourceLine += mb_substr_count(mb_substr($this->source, $this->sourceOffset), "\n");
} else {
$this->sourceLine += substr_count($this->source, "\n", $this->sourceOffset);
}
$this->sourceOffset = strlen($this->source);
$this->debugInfo[$this->sourceLine] = $node->getLine();
$this->lastLine = $node->getLine();
}
return $this;
}
public function getDebugInfo()
{
return $this->debugInfo;
}
/**
* Indents the generated code.
*
* @param integer $step The number of indentation to add
*
* @return Twig_Compiler The current compiler instance
*/
public function indent($step = 1)
{
$this->indentation += $step;
return $this;
}
/**
* Outdents the generated code.
*
* @param integer $step The number of indentation to remove
*
* @return Twig_Compiler The current compiler instance
*/
public function outdent($step = 1)
{
// can't outdent by more steps than the current indentation level
if ($this->indentation < $step) {
throw new LogicException('Unable to call outdent() as the indentation would become negative');
}
$this->indentation -= $step;
return $this;
}
}

View File

@ -1,35 +0,0 @@
<?php
/*
* This file is part of Twig.
*
* (c) 2009 Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Interface implemented by compiler classes.
*
* @author Fabien Potencier <fabien@symfony.com>
* @deprecated since 1.12 (to be removed in 2.0)
*/
interface Twig_CompilerInterface
{
/**
* Compiles a node.
*
* @param Twig_NodeInterface $node The node to compile
*
* @return Twig_CompilerInterface The current compiler instance
*/
public function compile(Twig_NodeInterface $node);
/**
* Gets the current PHP code after compilation.
*
* @return string The PHP code
*/
public function getSource();
}

File diff suppressed because it is too large Load Diff

View File

@ -1,243 +0,0 @@
<?php
/*
* This file is part of Twig.
*
* (c) 2009 Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Twig base exception.
*
* This exception class and its children must only be used when
* an error occurs during the loading of a template, when a syntax error
* is detected in a template, or when rendering a template. Other
* errors must use regular PHP exception classes (like when the template
* cache directory is not writable for instance).
*
* To help debugging template issues, this class tracks the original template
* name and line where the error occurred.
*
* Whenever possible, you must set these information (original template name
* and line number) yourself by passing them to the constructor. If some or all
* these information are not available from where you throw the exception, then
* this class will guess them automatically (when the line number is set to -1
* and/or the filename is set to null). As this is a costly operation, this
* can be disabled by passing false for both the filename and the line number
* when creating a new instance of this class.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class Twig_Error extends Exception
{
protected $lineno;
protected $filename;
protected $rawMessage;
protected $previous;
/**
* Constructor.
*
* Set both the line number and the filename to false to
* disable automatic guessing of the original template name
* and line number.
*
* Set the line number to -1 to enable its automatic guessing.
* Set the filename to null to enable its automatic guessing.
*
* By default, automatic guessing is enabled.
*
* @param string $message The error message
* @param integer $lineno The template line where the error occurred
* @param string $filename The template file name where the error occurred
* @param Exception $previous The previous exception
*/
public function __construct($message, $lineno = -1, $filename = null, Exception $previous = null)
{
if (version_compare(PHP_VERSION, '5.3.0', '<')) {
$this->previous = $previous;
parent::__construct('');
} else {
parent::__construct('', 0, $previous);
}
$this->lineno = $lineno;
$this->filename = $filename;
if (-1 === $this->lineno || null === $this->filename) {
$this->guessTemplateInfo();
}
$this->rawMessage = $message;
$this->updateRepr();
}
/**
* Gets the raw message.
*
* @return string The raw message
*/
public function getRawMessage()
{
return $this->rawMessage;
}
/**
* Gets the filename where the error occurred.
*
* @return string The filename
*/
public function getTemplateFile()
{
return $this->filename;
}
/**
* Sets the filename where the error occurred.
*
* @param string $filename The filename
*/
public function setTemplateFile($filename)
{
$this->filename = $filename;
$this->updateRepr();
}
/**
* Gets the template line where the error occurred.
*
* @return integer The template line
*/
public function getTemplateLine()
{
return $this->lineno;
}
/**
* Sets the template line where the error occurred.
*
* @param integer $lineno The template line
*/
public function setTemplateLine($lineno)
{
$this->lineno = $lineno;
$this->updateRepr();
}
public function guess()
{
$this->guessTemplateInfo();
$this->updateRepr();
}
/**
* For PHP < 5.3.0, provides access to the getPrevious() method.
*
* @param string $method The method name
* @param array $arguments The parameters to be passed to the method
*
* @return Exception The previous exception or null
*
* @throws BadMethodCallException
*/
public function __call($method, $arguments)
{
if ('getprevious' == strtolower($method)) {
return $this->previous;
}
throw new BadMethodCallException(sprintf('Method "Twig_Error::%s()" does not exist.', $method));
}
protected function updateRepr()
{
$this->message = $this->rawMessage;
$dot = false;
if ('.' === substr($this->message, -1)) {
$this->message = substr($this->message, 0, -1);
$dot = true;
}
if ($this->filename) {
if (is_string($this->filename) || (is_object($this->filename) && method_exists($this->filename, '__toString'))) {
$filename = sprintf('"%s"', $this->filename);
} else {
$filename = json_encode($this->filename);
}
$this->message .= sprintf(' in %s', $filename);
}
if ($this->lineno && $this->lineno >= 0) {
$this->message .= sprintf(' at line %d', $this->lineno);
}
if ($dot) {
$this->message .= '.';
}
}
protected function guessTemplateInfo()
{
$template = null;
$templateClass = null;
if (version_compare(phpversion(), '5.3.6', '>=')) {
$backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS | DEBUG_BACKTRACE_PROVIDE_OBJECT);
} else {
$backtrace = debug_backtrace();
}
foreach ($backtrace as $trace) {
if (isset($trace['object']) && $trace['object'] instanceof Twig_Template && 'Twig_Template' !== get_class($trace['object'])) {
$currentClass = get_class($trace['object']);
$isEmbedContainer = 0 === strpos($templateClass, $currentClass);
if (null === $this->filename || ($this->filename == $trace['object']->getTemplateName() && !$isEmbedContainer)) {
$template = $trace['object'];
$templateClass = get_class($trace['object']);
}
}
}
// update template filename
if (null !== $template && null === $this->filename) {
$this->filename = $template->getTemplateName();
}
if (null === $template || $this->lineno > -1) {
return;
}
$r = new ReflectionObject($template);
$file = $r->getFileName();
$exceptions = array($e = $this);
while (($e instanceof self || method_exists($e, 'getPrevious')) && $e = $e->getPrevious()) {
$exceptions[] = $e;
}
while ($e = array_pop($exceptions)) {
$traces = $e->getTrace();
while ($trace = array_shift($traces)) {
if (!isset($trace['file']) || !isset($trace['line']) || $file != $trace['file']) {
continue;
}
foreach ($template->getDebugInfo() as $codeLine => $templateLine) {
if ($codeLine <= $trace['line']) {
// update template line
$this->lineno = $templateLine;
return;
}
}
}
}
}
}

View File

@ -1,31 +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.
*/
/**
* Exception thrown when an error occurs during template loading.
*
* Automatic template information guessing is always turned off as
* if a template cannot be loaded, there is nothing to guess.
* However, when a template is loaded from another one, then, we need
* to find the current context and this is automatically done by
* Twig_Template::displayWithErrorHandling().
*
* This strategy makes Twig_Environment::resolveTemplate() much faster.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class Twig_Error_Loader extends Twig_Error
{
public function __construct($message, $lineno = -1, $filename = null, Exception $previous = null)
{
parent::__construct($message, false, false, $previous);
}
}

View File

@ -1,20 +0,0 @@
<?php
/*
* This file is part of Twig.
*
* (c) 2009 Fabien Potencier
* (c) 2009 Armin Ronacher
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Exception thrown when an error occurs at runtime.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class Twig_Error_Runtime extends Twig_Error
{
}

View File

@ -1,20 +0,0 @@
<?php
/*
* This file is part of Twig.
*
* (c) 2009 Fabien Potencier
* (c) 2009 Armin Ronacher
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Exception thrown when a syntax error occurs during lexing or parsing of a template.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class Twig_Error_Syntax extends Twig_Error
{
}

View File

@ -1,28 +0,0 @@
<?php
/*
* This file is part of Twig.
*
* (c) 2009 Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Adds an exists() method for loaders.
*
* @author Florin Patan <florinpatan@gmail.com>
* @deprecated since 1.12 (to be removed in 2.0)
*/
interface Twig_ExistsLoaderInterface
{
/**
* Check if we have the source code of a template, given its name.
*
* @param string $name The name of the template to check if we can load
*
* @return boolean If the template source code is handled by this loader or not
*/
public function exists($name);
}

View File

@ -1,611 +0,0 @@
<?php
/*
* This file is part of Twig.
*
* (c) 2009 Fabien Potencier
* (c) 2009 Armin Ronacher
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Parses expressions.
*
* This parser implements a "Precedence climbing" algorithm.
*
* @see http://www.engr.mun.ca/~theo/Misc/exp_parsing.htm
* @see http://en.wikipedia.org/wiki/Operator-precedence_parser
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class Twig_ExpressionParser
{
const OPERATOR_LEFT = 1;
const OPERATOR_RIGHT = 2;
protected $parser;
protected $unaryOperators;
protected $binaryOperators;
public function __construct(Twig_Parser $parser, array $unaryOperators, array $binaryOperators)
{
$this->parser = $parser;
$this->unaryOperators = $unaryOperators;
$this->binaryOperators = $binaryOperators;
}
public function parseExpression($precedence = 0)
{
$expr = $this->getPrimary();
$token = $this->parser->getCurrentToken();
while ($this->isBinary($token) && $this->binaryOperators[$token->getValue()]['precedence'] >= $precedence) {
$op = $this->binaryOperators[$token->getValue()];
$this->parser->getStream()->next();
if (isset($op['callable'])) {
$expr = call_user_func($op['callable'], $this->parser, $expr);
} else {
$expr1 = $this->parseExpression(self::OPERATOR_LEFT === $op['associativity'] ? $op['precedence'] + 1 : $op['precedence']);
$class = $op['class'];
$expr = new $class($expr, $expr1, $token->getLine());
}
$token = $this->parser->getCurrentToken();
}
if (0 === $precedence) {
return $this->parseConditionalExpression($expr);
}
return $expr;
}
protected function getPrimary()
{
$token = $this->parser->getCurrentToken();
if ($this->isUnary($token)) {
$operator = $this->unaryOperators[$token->getValue()];
$this->parser->getStream()->next();
$expr = $this->parseExpression($operator['precedence']);
$class = $operator['class'];
return $this->parsePostfixExpression(new $class($expr, $token->getLine()));
} elseif ($token->test(Twig_Token::PUNCTUATION_TYPE, '(')) {
$this->parser->getStream()->next();
$expr = $this->parseExpression();
$this->parser->getStream()->expect(Twig_Token::PUNCTUATION_TYPE, ')', 'An opened parenthesis is not properly closed');
return $this->parsePostfixExpression($expr);
}
return $this->parsePrimaryExpression();
}
protected function parseConditionalExpression($expr)
{
while ($this->parser->getStream()->test(Twig_Token::PUNCTUATION_TYPE, '?')) {
$this->parser->getStream()->next();
if (!$this->parser->getStream()->test(Twig_Token::PUNCTUATION_TYPE, ':')) {
$expr2 = $this->parseExpression();
if ($this->parser->getStream()->test(Twig_Token::PUNCTUATION_TYPE, ':')) {
$this->parser->getStream()->next();
$expr3 = $this->parseExpression();
} else {
$expr3 = new Twig_Node_Expression_Constant('', $this->parser->getCurrentToken()->getLine());
}
} else {
$this->parser->getStream()->next();
$expr2 = $expr;
$expr3 = $this->parseExpression();
}
$expr = new Twig_Node_Expression_Conditional($expr, $expr2, $expr3, $this->parser->getCurrentToken()->getLine());
}
return $expr;
}
protected function isUnary(Twig_Token $token)
{
return $token->test(Twig_Token::OPERATOR_TYPE) && isset($this->unaryOperators[$token->getValue()]);
}
protected function isBinary(Twig_Token $token)
{
return $token->test(Twig_Token::OPERATOR_TYPE) && isset($this->binaryOperators[$token->getValue()]);
}
public function parsePrimaryExpression()
{
$token = $this->parser->getCurrentToken();
switch ($token->getType()) {
case Twig_Token::NAME_TYPE:
$this->parser->getStream()->next();
switch ($token->getValue()) {
case 'true':
case 'TRUE':
$node = new Twig_Node_Expression_Constant(true, $token->getLine());
break;
case 'false':
case 'FALSE':
$node = new Twig_Node_Expression_Constant(false, $token->getLine());
break;
case 'none':
case 'NONE':
case 'null':
case 'NULL':
$node = new Twig_Node_Expression_Constant(null, $token->getLine());
break;
default:
if ('(' === $this->parser->getCurrentToken()->getValue()) {
$node = $this->getFunctionNode($token->getValue(), $token->getLine());
} else {
$node = new Twig_Node_Expression_Name($token->getValue(), $token->getLine());
}
}
break;
case Twig_Token::NUMBER_TYPE:
$this->parser->getStream()->next();
$node = new Twig_Node_Expression_Constant($token->getValue(), $token->getLine());
break;
case Twig_Token::STRING_TYPE:
case Twig_Token::INTERPOLATION_START_TYPE:
$node = $this->parseStringExpression();
break;
default:
if ($token->test(Twig_Token::PUNCTUATION_TYPE, '[')) {
$node = $this->parseArrayExpression();
} elseif ($token->test(Twig_Token::PUNCTUATION_TYPE, '{')) {
$node = $this->parseHashExpression();
} else {
throw new Twig_Error_Syntax(sprintf('Unexpected token "%s" of value "%s"', Twig_Token::typeToEnglish($token->getType(), $token->getLine()), $token->getValue()), $token->getLine(), $this->parser->getFilename());
}
}
return $this->parsePostfixExpression($node);
}
public function parseStringExpression()
{
$stream = $this->parser->getStream();
$nodes = array();
// a string cannot be followed by another string in a single expression
$nextCanBeString = true;
while (true) {
if ($stream->test(Twig_Token::STRING_TYPE) && $nextCanBeString) {
$token = $stream->next();
$nodes[] = new Twig_Node_Expression_Constant($token->getValue(), $token->getLine());
$nextCanBeString = false;
} elseif ($stream->test(Twig_Token::INTERPOLATION_START_TYPE)) {
$stream->next();
$nodes[] = $this->parseExpression();
$stream->expect(Twig_Token::INTERPOLATION_END_TYPE);
$nextCanBeString = true;
} else {
break;
}
}
$expr = array_shift($nodes);
foreach ($nodes as $node) {
$expr = new Twig_Node_Expression_Binary_Concat($expr, $node, $node->getLine());
}
return $expr;
}
public function parseArrayExpression()
{
$stream = $this->parser->getStream();
$stream->expect(Twig_Token::PUNCTUATION_TYPE, '[', 'An array element was expected');
$node = new Twig_Node_Expression_Array(array(), $stream->getCurrent()->getLine());
$first = true;
while (!$stream->test(Twig_Token::PUNCTUATION_TYPE, ']')) {
if (!$first) {
$stream->expect(Twig_Token::PUNCTUATION_TYPE, ',', 'An array element must be followed by a comma');
// trailing ,?
if ($stream->test(Twig_Token::PUNCTUATION_TYPE, ']')) {
break;
}
}
$first = false;
$node->addElement($this->parseExpression());
}
$stream->expect(Twig_Token::PUNCTUATION_TYPE, ']', 'An opened array is not properly closed');
return $node;
}
public function parseHashExpression()
{
$stream = $this->parser->getStream();
$stream->expect(Twig_Token::PUNCTUATION_TYPE, '{', 'A hash element was expected');
$node = new Twig_Node_Expression_Array(array(), $stream->getCurrent()->getLine());
$first = true;
while (!$stream->test(Twig_Token::PUNCTUATION_TYPE, '}')) {
if (!$first) {
$stream->expect(Twig_Token::PUNCTUATION_TYPE, ',', 'A hash value must be followed by a comma');
// trailing ,?
if ($stream->test(Twig_Token::PUNCTUATION_TYPE, '}')) {
break;
}
}
$first = false;
// a hash key can be:
//
// * a number -- 12
// * a string -- 'a'
// * a name, which is equivalent to a string -- a
// * an expression, which must be enclosed in parentheses -- (1 + 2)
if ($stream->test(Twig_Token::STRING_TYPE) || $stream->test(Twig_Token::NAME_TYPE) || $stream->test(Twig_Token::NUMBER_TYPE)) {
$token = $stream->next();
$key = new Twig_Node_Expression_Constant($token->getValue(), $token->getLine());
} elseif ($stream->test(Twig_Token::PUNCTUATION_TYPE, '(')) {
$key = $this->parseExpression();
} else {
$current = $stream->getCurrent();
throw new Twig_Error_Syntax(sprintf('A hash key must be a quoted string, a number, a name, or an expression enclosed in parentheses (unexpected token "%s" of value "%s"', Twig_Token::typeToEnglish($current->getType(), $current->getLine()), $current->getValue()), $current->getLine(), $this->parser->getFilename());
}
$stream->expect(Twig_Token::PUNCTUATION_TYPE, ':', 'A hash key must be followed by a colon (:)');
$value = $this->parseExpression();
$node->addElement($value, $key);
}
$stream->expect(Twig_Token::PUNCTUATION_TYPE, '}', 'An opened hash is not properly closed');
return $node;
}
public function parsePostfixExpression($node)
{
while (true) {
$token = $this->parser->getCurrentToken();
if ($token->getType() == Twig_Token::PUNCTUATION_TYPE) {
if ('.' == $token->getValue() || '[' == $token->getValue()) {
$node = $this->parseSubscriptExpression($node);
} elseif ('|' == $token->getValue()) {
$node = $this->parseFilterExpression($node);
} else {
break;
}
} else {
break;
}
}
return $node;
}
public function getFunctionNode($name, $line)
{
switch ($name) {
case 'parent':
$args = $this->parseArguments();
if (!count($this->parser->getBlockStack())) {
throw new Twig_Error_Syntax('Calling "parent" outside a block is forbidden', $line, $this->parser->getFilename());
}
if (!$this->parser->getParent() && !$this->parser->hasTraits()) {
throw new Twig_Error_Syntax('Calling "parent" on a template that does not extend nor "use" another template is forbidden', $line, $this->parser->getFilename());
}
return new Twig_Node_Expression_Parent($this->parser->peekBlockStack(), $line);
case 'block':
return new Twig_Node_Expression_BlockReference($this->parseArguments()->getNode(0), false, $line);
case 'attribute':
$args = $this->parseArguments();
if (count($args) < 2) {
throw new Twig_Error_Syntax('The "attribute" function takes at least two arguments (the variable and the attributes)', $line, $this->parser->getFilename());
}
return new Twig_Node_Expression_GetAttr($args->getNode(0), $args->getNode(1), count($args) > 2 ? $args->getNode(2) : new Twig_Node_Expression_Array(array(), $line), Twig_Template::ANY_CALL, $line);
default:
$args = $this->parseArguments(true);
if (null !== $alias = $this->parser->getImportedSymbol('macro', $name)) {
return new Twig_Node_Expression_MacroCall($alias['node'], $alias['name'], $this->createArrayFromArguments($args), $line);
}
try {
$class = $this->getFunctionNodeClass($name, $line);
} catch (Twig_Error_Syntax $e) {
if (!$this->parser->hasMacro($name)) {
throw $e;
}
return new Twig_Node_Expression_MacroCall(new Twig_Node_Expression_Name('_self', $line), $name, $this->createArrayFromArguments($args), $line);
}
return new $class($name, $args, $line);
}
}
public function parseSubscriptExpression($node)
{
$stream = $this->parser->getStream();
$token = $stream->next();
$lineno = $token->getLine();
$arguments = new Twig_Node_Expression_Array(array(), $lineno);
$type = Twig_Template::ANY_CALL;
if ($token->getValue() == '.') {
$token = $stream->next();
if (
$token->getType() == Twig_Token::NAME_TYPE
||
$token->getType() == Twig_Token::NUMBER_TYPE
||
($token->getType() == Twig_Token::OPERATOR_TYPE && preg_match(Twig_Lexer::REGEX_NAME, $token->getValue()))
) {
$arg = new Twig_Node_Expression_Constant($token->getValue(), $lineno);
} else {
throw new Twig_Error_Syntax('Expected name or number', $lineno, $this->parser->getFilename());
}
if ($node instanceof Twig_Node_Expression_Name && null !== $this->parser->getImportedSymbol('template', $node->getAttribute('name'))) {
if (!$arg instanceof Twig_Node_Expression_Constant) {
throw new Twig_Error_Syntax(sprintf('Dynamic macro names are not supported (called on "%s")', $node->getAttribute('name')), $token->getLine(), $this->parser->getFilename());
}
$arguments = $this->createArrayFromArguments($this->parseArguments(true));
return new Twig_Node_Expression_MacroCall($node, $arg->getAttribute('value'), $arguments, $lineno);
}
if ($stream->test(Twig_Token::PUNCTUATION_TYPE, '(')) {
$type = Twig_Template::METHOD_CALL;
$arguments = $this->createArrayFromArguments($this->parseArguments());
}
} else {
$type = Twig_Template::ARRAY_CALL;
// slice?
$slice = false;
if ($stream->test(Twig_Token::PUNCTUATION_TYPE, ':')) {
$slice = true;
$arg = new Twig_Node_Expression_Constant(0, $token->getLine());
} else {
$arg = $this->parseExpression();
}
if ($stream->test(Twig_Token::PUNCTUATION_TYPE, ':')) {
$slice = true;
$stream->next();
}
if ($slice) {
if ($stream->test(Twig_Token::PUNCTUATION_TYPE, ']')) {
$length = new Twig_Node_Expression_Constant(null, $token->getLine());
} else {
$length = $this->parseExpression();
}
$class = $this->getFilterNodeClass('slice', $token->getLine());
$arguments = new Twig_Node(array($arg, $length));
$filter = new $class($node, new Twig_Node_Expression_Constant('slice', $token->getLine()), $arguments, $token->getLine());
$stream->expect(Twig_Token::PUNCTUATION_TYPE, ']');
return $filter;
}
$stream->expect(Twig_Token::PUNCTUATION_TYPE, ']');
}
return new Twig_Node_Expression_GetAttr($node, $arg, $arguments, $type, $lineno);
}
public function parseFilterExpression($node)
{
$this->parser->getStream()->next();
return $this->parseFilterExpressionRaw($node);
}
public function parseFilterExpressionRaw($node, $tag = null)
{
while (true) {
$token = $this->parser->getStream()->expect(Twig_Token::NAME_TYPE);
$name = new Twig_Node_Expression_Constant($token->getValue(), $token->getLine());
if (!$this->parser->getStream()->test(Twig_Token::PUNCTUATION_TYPE, '(')) {
$arguments = new Twig_Node();
} else {
$arguments = $this->parseArguments(true);
}
$class = $this->getFilterNodeClass($name->getAttribute('value'), $token->getLine());
$node = new $class($node, $name, $arguments, $token->getLine(), $tag);
if (!$this->parser->getStream()->test(Twig_Token::PUNCTUATION_TYPE, '|')) {
break;
}
$this->parser->getStream()->next();
}
return $node;
}
/**
* Parses arguments.
*
* @param Boolean $namedArguments Whether to allow named arguments or not
* @param Boolean $definition Whether we are parsing arguments for a function definition
*
* @return Twig_Node
*/
public function parseArguments($namedArguments = false, $definition = false)
{
$args = array();
$stream = $this->parser->getStream();
$stream->expect(Twig_Token::PUNCTUATION_TYPE, '(', 'A list of arguments must begin with an opening parenthesis');
while (!$stream->test(Twig_Token::PUNCTUATION_TYPE, ')')) {
if (!empty($args)) {
$stream->expect(Twig_Token::PUNCTUATION_TYPE, ',', 'Arguments must be separated by a comma');
}
if ($definition) {
$token = $stream->expect(Twig_Token::NAME_TYPE, null, 'An argument must be a name');
$value = new Twig_Node_Expression_Name($token->getValue(), $this->parser->getCurrentToken()->getLine());
} else {
$value = $this->parseExpression();
}
$name = null;
if ($namedArguments && $stream->test(Twig_Token::OPERATOR_TYPE, '=')) {
$token = $stream->next();
if (!$value instanceof Twig_Node_Expression_Name) {
throw new Twig_Error_Syntax(sprintf('A parameter name must be a string, "%s" given', get_class($value)), $token->getLine(), $this->parser->getFilename());
}
$name = $value->getAttribute('name');
if ($definition) {
$value = $this->parsePrimaryExpression();
if (!$this->checkConstantExpression($value)) {
throw new Twig_Error_Syntax('A default value for an argument must be a constant (a boolean, a string, a number, or an array).', $token->getLine(), $this->parser->getFilename());
}
} else {
$value = $this->parseExpression();
}
}
if ($definition && null === $name) {
$name = $value->getAttribute('name');
$value = new Twig_Node_Expression_Constant(null, $this->parser->getCurrentToken()->getLine());
}
if (null === $name) {
$args[] = $value;
} else {
if ($definition && isset($args[$name])) {
throw new Twig_Error_Syntax(sprintf('Arguments cannot contain the same argument name more than once ("%s" is defined twice).', $name), $token->getLine(), $this->parser->getFilename());
}
$args[$name] = $value;
}
}
$stream->expect(Twig_Token::PUNCTUATION_TYPE, ')', 'A list of arguments must be closed by a parenthesis');
return new Twig_Node($args);
}
public function parseAssignmentExpression()
{
$targets = array();
while (true) {
$token = $this->parser->getStream()->expect(Twig_Token::NAME_TYPE, null, 'Only variables can be assigned to');
if (in_array($token->getValue(), array('true', 'false', 'none'))) {
throw new Twig_Error_Syntax(sprintf('You cannot assign a value to "%s"', $token->getValue()), $token->getLine(), $this->parser->getFilename());
}
$targets[] = new Twig_Node_Expression_AssignName($token->getValue(), $token->getLine());
if (!$this->parser->getStream()->test(Twig_Token::PUNCTUATION_TYPE, ',')) {
break;
}
$this->parser->getStream()->next();
}
return new Twig_Node($targets);
}
public function parseMultitargetExpression()
{
$targets = array();
while (true) {
$targets[] = $this->parseExpression();
if (!$this->parser->getStream()->test(Twig_Token::PUNCTUATION_TYPE, ',')) {
break;
}
$this->parser->getStream()->next();
}
return new Twig_Node($targets);
}
protected function getFunctionNodeClass($name, $line)
{
$env = $this->parser->getEnvironment();
if (false === $function = $env->getFunction($name)) {
$message = sprintf('The function "%s" does not exist', $name);
if ($alternatives = $env->computeAlternatives($name, array_keys($env->getFunctions()))) {
$message = sprintf('%s. Did you mean "%s"', $message, implode('", "', $alternatives));
}
throw new Twig_Error_Syntax($message, $line, $this->parser->getFilename());
}
if ($function instanceof Twig_SimpleFunction) {
return $function->getNodeClass();
}
return $function instanceof Twig_Function_Node ? $function->getClass() : 'Twig_Node_Expression_Function';
}
protected function getFilterNodeClass($name, $line)
{
$env = $this->parser->getEnvironment();
if (false === $filter = $env->getFilter($name)) {
$message = sprintf('The filter "%s" does not exist', $name);
if ($alternatives = $env->computeAlternatives($name, array_keys($env->getFilters()))) {
$message = sprintf('%s. Did you mean "%s"', $message, implode('", "', $alternatives));
}
throw new Twig_Error_Syntax($message, $line, $this->parser->getFilename());
}
if ($filter instanceof Twig_SimpleFilter) {
return $filter->getNodeClass();
}
return $filter instanceof Twig_Filter_Node ? $filter->getClass() : 'Twig_Node_Expression_Filter';
}
// checks that the node only contains "constant" elements
protected function checkConstantExpression(Twig_NodeInterface $node)
{
if (!($node instanceof Twig_Node_Expression_Constant || $node instanceof Twig_Node_Expression_Array)) {
return false;
}
foreach ($node as $n) {
if (!$this->checkConstantExpression($n)) {
return false;
}
}
return true;
}
private function createArrayFromArguments(Twig_Node $arguments, $line = null)
{
$line = null === $line ? $arguments->getLine() : $line;
$array = new Twig_Node_Expression_Array(array(), $line);
foreach ($arguments as $key => $value) {
$array->addElement($value, new Twig_Node_Expression_Constant($key, $value->getLine()));
}
return $array;
}
}

View File

@ -1,93 +0,0 @@
<?php
/*
* This file is part of Twig.
*
* (c) 2009 Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
abstract class Twig_Extension implements Twig_ExtensionInterface
{
/**
* Initializes the runtime environment.
*
* This is where you can load some file that contains filter functions for instance.
*
* @param Twig_Environment $environment The current Twig_Environment instance
*/
public function initRuntime(Twig_Environment $environment)
{
}
/**
* 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();
}
/**
* Returns the node visitor instances to add to the existing list.
*
* @return array An array of Twig_NodeVisitorInterface instances
*/
public function getNodeVisitors()
{
return array();
}
/**
* Returns a list of filters to add to the existing list.
*
* @return array An array of filters
*/
public function getFilters()
{
return array();
}
/**
* Returns a list of tests to add to the existing list.
*
* @return array An array of tests
*/
public function getTests()
{
return array();
}
/**
* Returns a list of functions to add to the existing list.
*
* @return array An array of functions
*/
public function getFunctions()
{
return array();
}
/**
* Returns a list of operators to add to the existing list.
*
* @return array An array of operators
*/
public function getOperators()
{
return array();
}
/**
* Returns a list of global variables to add to the existing list.
*
* @return array An array of global variables
*/
public function getGlobals()
{
return array();
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,71 +0,0 @@
<?php
/*
* This file is part of Twig.
*
* (c) 2011 Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
class Twig_Extension_Debug extends Twig_Extension
{
/**
* Returns a list of global functions to add to the existing list.
*
* @return array An array of global functions
*/
public function getFunctions()
{
// dump is safe if var_dump is overridden by xdebug
$isDumpOutputHtmlSafe = extension_loaded('xdebug')
// false means that it was not set (and the default is on) or it explicitly enabled
&& (false === ini_get('xdebug.overload_var_dump') || ini_get('xdebug.overload_var_dump'))
// false means that it was not set (and the default is on) or it explicitly enabled
// xdebug.overload_var_dump produces HTML only when html_errors is also enabled
&& (false === ini_get('html_errors') || ini_get('html_errors'))
|| 'cli' === php_sapi_name()
;
return array(
new Twig_SimpleFunction('dump', 'twig_var_dump', array('is_safe' => $isDumpOutputHtmlSafe ? array('html') : array(), 'needs_context' => true, 'needs_environment' => true)),
);
}
/**
* Returns the name of the extension.
*
* @return string The extension name
*/
public function getName()
{
return 'debug';
}
}
function twig_var_dump(Twig_Environment $env, $context)
{
if (!$env->isDebug()) {
return;
}
ob_start();
$count = func_num_args();
if (2 === $count) {
$vars = array();
foreach ($context as $key => $value) {
if (!$value instanceof Twig_Template) {
$vars[$key] = $value;
}
}
var_dump($vars);
} else {
for ($i = 2; $i < $count; $i++) {
var_dump(func_get_arg($i));
}
}
return ob_get_clean();
}

View File

@ -1,107 +0,0 @@
<?php
/*
* This file is part of Twig.
*
* (c) 2009 Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
class Twig_Extension_Escaper extends Twig_Extension
{
protected $defaultStrategy;
public function __construct($defaultStrategy = 'html')
{
$this->setDefaultStrategy($defaultStrategy);
}
/**
* 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_TokenParser_AutoEscape());
}
/**
* Returns the node visitor instances to add to the existing list.
*
* @return array An array of Twig_NodeVisitorInterface instances
*/
public function getNodeVisitors()
{
return array(new Twig_NodeVisitor_Escaper());
}
/**
* 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('raw', 'twig_raw_filter', array('is_safe' => array('all'))),
);
}
/**
* Sets the default strategy to use when not defined by the user.
*
* The strategy can be a valid PHP callback that takes the template
* "filename" as an argument and returns the strategy to use.
*
* @param mixed $defaultStrategy An escaping strategy
*/
public function setDefaultStrategy($defaultStrategy)
{
// for BC
if (true === $defaultStrategy) {
$defaultStrategy = 'html';
}
$this->defaultStrategy = $defaultStrategy;
}
/**
* Gets the default strategy to use when not defined by the user.
*
* @param string $filename The template "filename"
*
* @return string The default strategy to use for the template
*/
public function getDefaultStrategy($filename)
{
// disable string callables to avoid calling a function named html or js,
// or any other upcoming escaping strategy
if (!is_string($this->defaultStrategy) && is_callable($this->defaultStrategy)) {
return call_user_func($this->defaultStrategy, $filename);
}
return $this->defaultStrategy;
}
/**
* Returns the name of the extension.
*
* @return string The extension name
*/
public function getName()
{
return 'escaper';
}
}
/**
* Marks a variable as being safe.
*
* @param string $string A PHP variable
*/
function twig_raw_filter($string)
{
return $string;
}

View File

@ -1,35 +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_Extension_Optimizer extends Twig_Extension
{
protected $optimizers;
public function __construct($optimizers = -1)
{
$this->optimizers = $optimizers;
}
/**
* {@inheritdoc}
*/
public function getNodeVisitors()
{
return array(new Twig_NodeVisitor_Optimizer($this->optimizers));
}
/**
* {@inheritdoc}
*/
public function getName()
{
return 'optimizer';
}
}

View File

@ -1,112 +0,0 @@
<?php
/*
* This file is part of Twig.
*
* (c) 2009 Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
class Twig_Extension_Sandbox extends Twig_Extension
{
protected $sandboxedGlobally;
protected $sandboxed;
protected $policy;
public function __construct(Twig_Sandbox_SecurityPolicyInterface $policy, $sandboxed = false)
{
$this->policy = $policy;
$this->sandboxedGlobally = $sandboxed;
}
/**
* 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_TokenParser_Sandbox());
}
/**
* Returns the node visitor instances to add to the existing list.
*
* @return array An array of Twig_NodeVisitorInterface instances
*/
public function getNodeVisitors()
{
return array(new Twig_NodeVisitor_Sandbox());
}
public function enableSandbox()
{
$this->sandboxed = true;
}
public function disableSandbox()
{
$this->sandboxed = false;
}
public function isSandboxed()
{
return $this->sandboxedGlobally || $this->sandboxed;
}
public function isSandboxedGlobally()
{
return $this->sandboxedGlobally;
}
public function setSecurityPolicy(Twig_Sandbox_SecurityPolicyInterface $policy)
{
$this->policy = $policy;
}
public function getSecurityPolicy()
{
return $this->policy;
}
public function checkSecurity($tags, $filters, $functions)
{
if ($this->isSandboxed()) {
$this->policy->checkSecurity($tags, $filters, $functions);
}
}
public function checkMethodAllowed($obj, $method)
{
if ($this->isSandboxed()) {
$this->policy->checkMethodAllowed($obj, $method);
}
}
public function checkPropertyAllowed($obj, $method)
{
if ($this->isSandboxed()) {
$this->policy->checkPropertyAllowed($obj, $method);
}
}
public function ensureToStringAllowed($obj)
{
if (is_object($obj)) {
$this->policy->checkMethodAllowed($obj, '__toString');
}
return $obj;
}
/**
* Returns the name of the extension.
*
* @return string The extension name
*/
public function getName()
{
return 'sandbox';
}
}

View File

@ -1,113 +0,0 @@
<?php
/*
* This file is part of Twig.
*
* (c) 2012 Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Internal class.
*
* This class is used by Twig_Environment as a staging area and must not be used directly.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class Twig_Extension_Staging extends Twig_Extension
{
protected $functions = array();
protected $filters = array();
protected $visitors = array();
protected $tokenParsers = array();
protected $globals = array();
protected $tests = array();
public function addFunction($name, $function)
{
$this->functions[$name] = $function;
}
/**
* {@inheritdoc}
*/
public function getFunctions()
{
return $this->functions;
}
public function addFilter($name, $filter)
{
$this->filters[$name] = $filter;
}
/**
* {@inheritdoc}
*/
public function getFilters()
{
return $this->filters;
}
public function addNodeVisitor(Twig_NodeVisitorInterface $visitor)
{
$this->visitors[] = $visitor;
}
/**
* {@inheritdoc}
*/
public function getNodeVisitors()
{
return $this->visitors;
}
public function addTokenParser(Twig_TokenParserInterface $parser)
{
$this->tokenParsers[] = $parser;
}
/**
* {@inheritdoc}
*/
public function getTokenParsers()
{
return $this->tokenParsers;
}
public function addGlobal($name, $value)
{
$this->globals[$name] = $value;
}
/**
* {@inheritdoc}
*/
public function getGlobals()
{
return $this->globals;
}
public function addTest($name, $test)
{
$this->tests[$name] = $test;
}
/**
* {@inheritdoc}
*/
public function getTests()
{
return $this->tests;
}
/**
* {@inheritdoc}
*/
public function getName()
{
return 'staging';
}
}

View File

@ -1,64 +0,0 @@
<?php
/*
* This file is part of Twig.
*
* (c) 2012 Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
class Twig_Extension_StringLoader extends Twig_Extension
{
/**
* {@inheritdoc}
*/
public function getFunctions()
{
return array(
new Twig_SimpleFunction('template_from_string', 'twig_template_from_string', array('needs_environment' => true)),
);
}
/**
* {@inheritdoc}
*/
public function getName()
{
return 'string_loader';
}
}
/**
* Loads a template from a string.
*
* <pre>
* {{ include(template_from_string("Hello {{ name }}")) }}
* </pre>
*
* @param Twig_Environment $env A Twig_Environment instance
* @param string $template A template as a string
*
* @return Twig_Template A Twig_Template instance
*/
function twig_template_from_string(Twig_Environment $env, $template)
{
$name = sprintf('__string_template__%s', hash('sha256', uniqid(mt_rand(), true), false));
$loader = new Twig_Loader_Chain(array(
new Twig_Loader_Array(array($name => $template)),
$current = $env->getLoader(),
));
$env->setLoader($loader);
try {
$template = $env->loadTemplate($name);
} catch (Exception $e) {
$env->setLoader($current);
throw $e;
}
$env->setLoader($current);
return $template;
}

View File

@ -1,83 +0,0 @@
<?php
/*
* This file is part of Twig.
*
* (c) 2009 Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Interface implemented by extension classes.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
interface Twig_ExtensionInterface
{
/**
* Initializes the runtime environment.
*
* This is where you can load some file that contains filter functions for instance.
*
* @param Twig_Environment $environment The current Twig_Environment instance
*/
public function initRuntime(Twig_Environment $environment);
/**
* 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();
/**
* Returns the node visitor instances to add to the existing list.
*
* @return array An array of Twig_NodeVisitorInterface instances
*/
public function getNodeVisitors();
/**
* Returns a list of filters to add to the existing list.
*
* @return array An array of filters
*/
public function getFilters();
/**
* Returns a list of tests to add to the existing list.
*
* @return array An array of tests
*/
public function getTests();
/**
* Returns a list of functions to add to the existing list.
*
* @return array An array of functions
*/
public function getFunctions();
/**
* Returns a list of operators to add to the existing list.
*
* @return array An array of operators
*/
public function getOperators();
/**
* Returns a list of global variables to add to the existing list.
*
* @return array An array of global variables
*/
public function getGlobals();
/**
* Returns the name of the extension.
*
* @return string The extension name
*/
public function getName();
}

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,134 +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('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'),
);
}
/**
* 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

@ -1,81 +0,0 @@
<?php
/*
* This file is part of Twig.
*
* (c) 2009 Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Represents a template filter.
*
* Use Twig_SimpleFilter instead.
*
* @author Fabien Potencier <fabien@symfony.com>
* @deprecated since 1.12 (to be removed in 2.0)
*/
abstract class Twig_Filter implements Twig_FilterInterface, Twig_FilterCallableInterface
{
protected $options;
protected $arguments = array();
public function __construct(array $options = array())
{
$this->options = array_merge(array(
'needs_environment' => false,
'needs_context' => false,
'pre_escape' => null,
'preserves_safety' => null,
'callable' => null,
), $options);
}
public function setArguments($arguments)
{
$this->arguments = $arguments;
}
public function getArguments()
{
return $this->arguments;
}
public function needsEnvironment()
{
return $this->options['needs_environment'];
}
public function needsContext()
{
return $this->options['needs_context'];
}
public function getSafe(Twig_Node $filterArgs)
{
if (isset($this->options['is_safe'])) {
return $this->options['is_safe'];
}
if (isset($this->options['is_safe_callback'])) {
return call_user_func($this->options['is_safe_callback'], $filterArgs);
}
}
public function getPreservesSafety()
{
return $this->options['preserves_safety'];
}
public function getPreEscape()
{
return $this->options['pre_escape'];
}
public function getCallable()
{
return $this->options['callable'];
}
}

View File

@ -1,37 +0,0 @@
<?php
/*
* This file is part of Twig.
*
* (c) 2009 Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Represents a function template filter.
*
* Use Twig_SimpleFilter instead.
*
* @author Fabien Potencier <fabien@symfony.com>
* @deprecated since 1.12 (to be removed in 2.0)
*/
class Twig_Filter_Function extends Twig_Filter
{
protected $function;
public function __construct($function, array $options = array())
{
$options['callable'] = $function;
parent::__construct($options);
$this->function = $function;
}
public function compile()
{
return $this->function;
}
}

View File

@ -1,39 +0,0 @@
<?php
/*
* This file is part of Twig.
*
* (c) 2009 Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Represents a method template filter.
*
* Use Twig_SimpleFilter instead.
*
* @author Fabien Potencier <fabien@symfony.com>
* @deprecated since 1.12 (to be removed in 2.0)
*/
class Twig_Filter_Method extends Twig_Filter
{
protected $extension;
protected $method;
public function __construct(Twig_ExtensionInterface $extension, $method, array $options = array())
{
$options['callable'] = array($extension, $method);
parent::__construct($options);
$this->extension = $extension;
$this->method = $method;
}
public function compile()
{
return sprintf('$this->env->getExtension(\'%s\')->%s', $this->extension->getName(), $this->method);
}
}

View File

@ -1,39 +0,0 @@
<?php
/*
* This file is part of Twig.
*
* (c) 2011 Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Represents a template filter as a node.
*
* Use Twig_SimpleFilter instead.
*
* @author Fabien Potencier <fabien@symfony.com>
* @deprecated since 1.12 (to be removed in 2.0)
*/
class Twig_Filter_Node extends Twig_Filter
{
protected $class;
public function __construct($class, array $options = array())
{
parent::__construct($options);
$this->class = $class;
}
public function getClass()
{
return $this->class;
}
public function compile()
{
}
}

View File

@ -1,23 +0,0 @@
<?php
/*
* This file is part of Twig.
*
* (c) 2012 Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Represents a callable template filter.
*
* Use Twig_SimpleFilter instead.
*
* @author Fabien Potencier <fabien@symfony.com>
* @deprecated since 1.12 (to be removed in 2.0)
*/
interface Twig_FilterCallableInterface
{
public function getCallable();
}

View File

@ -1,42 +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 template filter.
*
* Use Twig_SimpleFilter instead.
*
* @author Fabien Potencier <fabien@symfony.com>
* @deprecated since 1.12 (to be removed in 2.0)
*/
interface Twig_FilterInterface
{
/**
* Compiles a filter.
*
* @return string The PHP code for the filter
*/
public function compile();
public function needsEnvironment();
public function needsContext();
public function getSafe(Twig_Node $filterArgs);
public function getPreservesSafety();
public function getPreEscape();
public function setArguments($arguments);
public function getArguments();
}

View File

@ -1,71 +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 template function.
*
* Use Twig_SimpleFunction instead.
*
* @author Fabien Potencier <fabien@symfony.com>
* @deprecated since 1.12 (to be removed in 2.0)
*/
abstract class Twig_Function implements Twig_FunctionInterface, Twig_FunctionCallableInterface
{
protected $options;
protected $arguments = array();
public function __construct(array $options = array())
{
$this->options = array_merge(array(
'needs_environment' => false,
'needs_context' => false,
'callable' => null,
), $options);
}
public function setArguments($arguments)
{
$this->arguments = $arguments;
}
public function getArguments()
{
return $this->arguments;
}
public function needsEnvironment()
{
return $this->options['needs_environment'];
}
public function needsContext()
{
return $this->options['needs_context'];
}
public function getSafe(Twig_Node $functionArgs)
{
if (isset($this->options['is_safe'])) {
return $this->options['is_safe'];
}
if (isset($this->options['is_safe_callback'])) {
return call_user_func($this->options['is_safe_callback'], $functionArgs);
}
return array();
}
public function getCallable()
{
return $this->options['callable'];
}
}

View File

@ -1,38 +0,0 @@
<?php
/*
* This file is part of Twig.
*
* (c) 2009 Fabien Potencier
* (c) 2010 Arnaud Le Blanc
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Represents a function template function.
*
* Use Twig_SimpleFunction instead.
*
* @author Arnaud Le Blanc <arnaud.lb@gmail.com>
* @deprecated since 1.12 (to be removed in 2.0)
*/
class Twig_Function_Function extends Twig_Function
{
protected $function;
public function __construct($function, array $options = array())
{
$options['callable'] = $function;
parent::__construct($options);
$this->function = $function;
}
public function compile()
{
return $this->function;
}
}

View File

@ -1,40 +0,0 @@
<?php
/*
* This file is part of Twig.
*
* (c) 2009 Fabien Potencier
* (c) 2010 Arnaud Le Blanc
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Represents a method template function.
*
* Use Twig_SimpleFunction instead.
*
* @author Arnaud Le Blanc <arnaud.lb@gmail.com>
* @deprecated since 1.12 (to be removed in 2.0)
*/
class Twig_Function_Method extends Twig_Function
{
protected $extension;
protected $method;
public function __construct(Twig_ExtensionInterface $extension, $method, array $options = array())
{
$options['callable'] = array($extension, $method);
parent::__construct($options);
$this->extension = $extension;
$this->method = $method;
}
public function compile()
{
return sprintf('$this->env->getExtension(\'%s\')->%s', $this->extension->getName(), $this->method);
}
}

View File

@ -1,39 +0,0 @@
<?php
/*
* This file is part of Twig.
*
* (c) 2011 Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Represents a template function as a node.
*
* Use Twig_SimpleFunction instead.
*
* @author Fabien Potencier <fabien@symfony.com>
* @deprecated since 1.12 (to be removed in 2.0)
*/
class Twig_Function_Node extends Twig_Function
{
protected $class;
public function __construct($class, array $options = array())
{
parent::__construct($options);
$this->class = $class;
}
public function getClass()
{
return $this->class;
}
public function compile()
{
}
}

View File

@ -1,23 +0,0 @@
<?php
/*
* This file is part of Twig.
*
* (c) 2012 Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Represents a callable template function.
*
* Use Twig_SimpleFunction instead.
*
* @author Fabien Potencier <fabien@symfony.com>
* @deprecated since 1.12 (to be removed in 2.0)
*/
interface Twig_FunctionCallableInterface
{
public function getCallable();
}

View File

@ -1,39 +0,0 @@
<?php
/*
* This file is part of Twig.
*
* (c) 2010 Fabien Potencier
* (c) 2010 Arnaud Le Blanc
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Represents a template function.
*
* Use Twig_SimpleFunction instead.
*
* @author Arnaud Le Blanc <arnaud.lb@gmail.com>
* @deprecated since 1.12 (to be removed in 2.0)
*/
interface Twig_FunctionInterface
{
/**
* Compiles a function.
*
* @return string The PHP code for the function
*/
public function compile();
public function needsEnvironment();
public function needsContext();
public function getSafe(Twig_Node $filterArgs);
public function setArguments($arguments);
public function getArguments();
}

View File

@ -1,408 +0,0 @@
<?php
/*
* This file is part of Twig.
*
* (c) 2009 Fabien Potencier
* (c) 2009 Armin Ronacher
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Lexes a template string.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class Twig_Lexer implements Twig_LexerInterface
{
protected $tokens;
protected $code;
protected $cursor;
protected $lineno;
protected $end;
protected $state;
protected $states;
protected $brackets;
protected $env;
protected $filename;
protected $options;
protected $regexes;
protected $position;
protected $positions;
protected $currentVarBlockLine;
const STATE_DATA = 0;
const STATE_BLOCK = 1;
const STATE_VAR = 2;
const STATE_STRING = 3;
const STATE_INTERPOLATION = 4;
const REGEX_NAME = '/[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/A';
const REGEX_NUMBER = '/[0-9]+(?:\.[0-9]+)?/A';
const REGEX_STRING = '/"([^#"\\\\]*(?:\\\\.[^#"\\\\]*)*)"|\'([^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\'/As';
const REGEX_DQ_STRING_DELIM = '/"/A';
const REGEX_DQ_STRING_PART = '/[^#"\\\\]*(?:(?:\\\\.|#(?!\{))[^#"\\\\]*)*/As';
const PUNCTUATION = '()[]{}?:.,|';
public function __construct(Twig_Environment $env, array $options = array())
{
$this->env = $env;
$this->options = array_merge(array(
'tag_comment' => array('{#', '#}'),
'tag_block' => array('{%', '%}'),
'tag_variable' => array('{{', '}}'),
'whitespace_trim' => '-',
'interpolation' => array('#{', '}'),
), $options);
$this->regexes = array(
'lex_var' => '/\s*'.preg_quote($this->options['whitespace_trim'].$this->options['tag_variable'][1], '/').'\s*|\s*'.preg_quote($this->options['tag_variable'][1], '/').'/A',
'lex_block' => '/\s*(?:'.preg_quote($this->options['whitespace_trim'].$this->options['tag_block'][1], '/').'\s*|\s*'.preg_quote($this->options['tag_block'][1], '/').')\n?/A',
'lex_raw_data' => '/('.preg_quote($this->options['tag_block'][0].$this->options['whitespace_trim'], '/').'|'.preg_quote($this->options['tag_block'][0], '/').')\s*(?:end%s)\s*(?:'.preg_quote($this->options['whitespace_trim'].$this->options['tag_block'][1], '/').'\s*|\s*'.preg_quote($this->options['tag_block'][1], '/').')/s',
'operator' => $this->getOperatorRegex(),
'lex_comment' => '/(?:'.preg_quote($this->options['whitespace_trim'], '/').preg_quote($this->options['tag_comment'][1], '/').'\s*|'.preg_quote($this->options['tag_comment'][1], '/').')\n?/s',
'lex_block_raw' => '/\s*(raw|verbatim)\s*(?:'.preg_quote($this->options['whitespace_trim'].$this->options['tag_block'][1], '/').'\s*|\s*'.preg_quote($this->options['tag_block'][1], '/').')/As',
'lex_block_line' => '/\s*line\s+(\d+)\s*'.preg_quote($this->options['tag_block'][1], '/').'/As',
'lex_tokens_start' => '/('.preg_quote($this->options['tag_variable'][0], '/').'|'.preg_quote($this->options['tag_block'][0], '/').'|'.preg_quote($this->options['tag_comment'][0], '/').')('.preg_quote($this->options['whitespace_trim'], '/').')?/s',
'interpolation_start' => '/'.preg_quote($this->options['interpolation'][0], '/').'\s*/A',
'interpolation_end' => '/\s*'.preg_quote($this->options['interpolation'][1], '/').'/A',
);
}
/**
* Tokenizes a source code.
*
* @param string $code The source code
* @param string $filename A unique identifier for the source code
*
* @return Twig_TokenStream A token stream instance
*/
public function tokenize($code, $filename = null)
{
if (function_exists('mb_internal_encoding') && ((int) ini_get('mbstring.func_overload')) & 2) {
$mbEncoding = mb_internal_encoding();
mb_internal_encoding('ASCII');
}
$this->code = str_replace(array("\r\n", "\r"), "\n", $code);
$this->filename = $filename;
$this->cursor = 0;
$this->lineno = 1;
$this->end = strlen($this->code);
$this->tokens = array();
$this->state = self::STATE_DATA;
$this->states = array();
$this->brackets = array();
$this->position = -1;
// find all token starts in one go
preg_match_all($this->regexes['lex_tokens_start'], $this->code, $matches, PREG_OFFSET_CAPTURE);
$this->positions = $matches;
while ($this->cursor < $this->end) {
// dispatch to the lexing functions depending
// on the current state
switch ($this->state) {
case self::STATE_DATA:
$this->lexData();
break;
case self::STATE_BLOCK:
$this->lexBlock();
break;
case self::STATE_VAR:
$this->lexVar();
break;
case self::STATE_STRING:
$this->lexString();
break;
case self::STATE_INTERPOLATION:
$this->lexInterpolation();
break;
}
}
$this->pushToken(Twig_Token::EOF_TYPE);
if (!empty($this->brackets)) {
list($expect, $lineno) = array_pop($this->brackets);
throw new Twig_Error_Syntax(sprintf('Unclosed "%s"', $expect), $lineno, $this->filename);
}
if (isset($mbEncoding)) {
mb_internal_encoding($mbEncoding);
}
return new Twig_TokenStream($this->tokens, $this->filename);
}
protected function lexData()
{
// if no matches are left we return the rest of the template as simple text token
if ($this->position == count($this->positions[0]) - 1) {
$this->pushToken(Twig_Token::TEXT_TYPE, substr($this->code, $this->cursor));
$this->cursor = $this->end;
return;
}
// Find the first token after the current cursor
$position = $this->positions[0][++$this->position];
while ($position[1] < $this->cursor) {
if ($this->position == count($this->positions[0]) - 1) {
return;
}
$position = $this->positions[0][++$this->position];
}
// push the template text first
$text = $textContent = substr($this->code, $this->cursor, $position[1] - $this->cursor);
if (isset($this->positions[2][$this->position][0])) {
$text = rtrim($text);
}
$this->pushToken(Twig_Token::TEXT_TYPE, $text);
$this->moveCursor($textContent.$position[0]);
switch ($this->positions[1][$this->position][0]) {
case $this->options['tag_comment'][0]:
$this->lexComment();
break;
case $this->options['tag_block'][0]:
// raw data?
if (preg_match($this->regexes['lex_block_raw'], $this->code, $match, null, $this->cursor)) {
$this->moveCursor($match[0]);
$this->lexRawData($match[1]);
// {% line \d+ %}
} elseif (preg_match($this->regexes['lex_block_line'], $this->code, $match, null, $this->cursor)) {
$this->moveCursor($match[0]);
$this->lineno = (int) $match[1];
} else {
$this->pushToken(Twig_Token::BLOCK_START_TYPE);
$this->pushState(self::STATE_BLOCK);
$this->currentVarBlockLine = $this->lineno;
}
break;
case $this->options['tag_variable'][0]:
$this->pushToken(Twig_Token::VAR_START_TYPE);
$this->pushState(self::STATE_VAR);
$this->currentVarBlockLine = $this->lineno;
break;
}
}
protected function lexBlock()
{
if (empty($this->brackets) && preg_match($this->regexes['lex_block'], $this->code, $match, null, $this->cursor)) {
$this->pushToken(Twig_Token::BLOCK_END_TYPE);
$this->moveCursor($match[0]);
$this->popState();
} else {
$this->lexExpression();
}
}
protected function lexVar()
{
if (empty($this->brackets) && preg_match($this->regexes['lex_var'], $this->code, $match, null, $this->cursor)) {
$this->pushToken(Twig_Token::VAR_END_TYPE);
$this->moveCursor($match[0]);
$this->popState();
} else {
$this->lexExpression();
}
}
protected function lexExpression()
{
// whitespace
if (preg_match('/\s+/A', $this->code, $match, null, $this->cursor)) {
$this->moveCursor($match[0]);
if ($this->cursor >= $this->end) {
throw new Twig_Error_Syntax(sprintf('Unclosed "%s"', $this->state === self::STATE_BLOCK ? 'block' : 'variable'), $this->currentVarBlockLine, $this->filename);
}
}
// operators
if (preg_match($this->regexes['operator'], $this->code, $match, null, $this->cursor)) {
$this->pushToken(Twig_Token::OPERATOR_TYPE, $match[0]);
$this->moveCursor($match[0]);
}
// names
elseif (preg_match(self::REGEX_NAME, $this->code, $match, null, $this->cursor)) {
$this->pushToken(Twig_Token::NAME_TYPE, $match[0]);
$this->moveCursor($match[0]);
}
// numbers
elseif (preg_match(self::REGEX_NUMBER, $this->code, $match, null, $this->cursor)) {
$number = (float) $match[0]; // floats
if (ctype_digit($match[0]) && $number <= PHP_INT_MAX) {
$number = (int) $match[0]; // integers lower than the maximum
}
$this->pushToken(Twig_Token::NUMBER_TYPE, $number);
$this->moveCursor($match[0]);
}
// punctuation
elseif (false !== strpos(self::PUNCTUATION, $this->code[$this->cursor])) {
// opening bracket
if (false !== strpos('([{', $this->code[$this->cursor])) {
$this->brackets[] = array($this->code[$this->cursor], $this->lineno);
}
// closing bracket
elseif (false !== strpos(')]}', $this->code[$this->cursor])) {
if (empty($this->brackets)) {
throw new Twig_Error_Syntax(sprintf('Unexpected "%s"', $this->code[$this->cursor]), $this->lineno, $this->filename);
}
list($expect, $lineno) = array_pop($this->brackets);
if ($this->code[$this->cursor] != strtr($expect, '([{', ')]}')) {
throw new Twig_Error_Syntax(sprintf('Unclosed "%s"', $expect), $lineno, $this->filename);
}
}
$this->pushToken(Twig_Token::PUNCTUATION_TYPE, $this->code[$this->cursor]);
++$this->cursor;
}
// strings
elseif (preg_match(self::REGEX_STRING, $this->code, $match, null, $this->cursor)) {
$this->pushToken(Twig_Token::STRING_TYPE, stripcslashes(substr($match[0], 1, -1)));
$this->moveCursor($match[0]);
}
// opening double quoted string
elseif (preg_match(self::REGEX_DQ_STRING_DELIM, $this->code, $match, null, $this->cursor)) {
$this->brackets[] = array('"', $this->lineno);
$this->pushState(self::STATE_STRING);
$this->moveCursor($match[0]);
}
// unlexable
else {
throw new Twig_Error_Syntax(sprintf('Unexpected character "%s"', $this->code[$this->cursor]), $this->lineno, $this->filename);
}
}
protected function lexRawData($tag)
{
if (!preg_match(str_replace('%s', $tag, $this->regexes['lex_raw_data']), $this->code, $match, PREG_OFFSET_CAPTURE, $this->cursor)) {
throw new Twig_Error_Syntax(sprintf('Unexpected end of file: Unclosed "%s" block', $tag), $this->lineno, $this->filename);
}
$text = substr($this->code, $this->cursor, $match[0][1] - $this->cursor);
$this->moveCursor($text.$match[0][0]);
if (false !== strpos($match[1][0], $this->options['whitespace_trim'])) {
$text = rtrim($text);
}
$this->pushToken(Twig_Token::TEXT_TYPE, $text);
}
protected function lexComment()
{
if (!preg_match($this->regexes['lex_comment'], $this->code, $match, PREG_OFFSET_CAPTURE, $this->cursor)) {
throw new Twig_Error_Syntax('Unclosed comment', $this->lineno, $this->filename);
}
$this->moveCursor(substr($this->code, $this->cursor, $match[0][1] - $this->cursor).$match[0][0]);
}
protected function lexString()
{
if (preg_match($this->regexes['interpolation_start'], $this->code, $match, null, $this->cursor)) {
$this->brackets[] = array($this->options['interpolation'][0], $this->lineno);
$this->pushToken(Twig_Token::INTERPOLATION_START_TYPE);
$this->moveCursor($match[0]);
$this->pushState(self::STATE_INTERPOLATION);
} elseif (preg_match(self::REGEX_DQ_STRING_PART, $this->code, $match, null, $this->cursor) && strlen($match[0]) > 0) {
$this->pushToken(Twig_Token::STRING_TYPE, stripcslashes($match[0]));
$this->moveCursor($match[0]);
} elseif (preg_match(self::REGEX_DQ_STRING_DELIM, $this->code, $match, null, $this->cursor)) {
list($expect, $lineno) = array_pop($this->brackets);
if ($this->code[$this->cursor] != '"') {
throw new Twig_Error_Syntax(sprintf('Unclosed "%s"', $expect), $lineno, $this->filename);
}
$this->popState();
++$this->cursor;
}
}
protected function lexInterpolation()
{
$bracket = end($this->brackets);
if ($this->options['interpolation'][0] === $bracket[0] && preg_match($this->regexes['interpolation_end'], $this->code, $match, null, $this->cursor)) {
array_pop($this->brackets);
$this->pushToken(Twig_Token::INTERPOLATION_END_TYPE);
$this->moveCursor($match[0]);
$this->popState();
} else {
$this->lexExpression();
}
}
protected function pushToken($type, $value = '')
{
// do not push empty text tokens
if (Twig_Token::TEXT_TYPE === $type && '' === $value) {
return;
}
$this->tokens[] = new Twig_Token($type, $value, $this->lineno);
}
protected function moveCursor($text)
{
$this->cursor += strlen($text);
$this->lineno += substr_count($text, "\n");
}
protected function getOperatorRegex()
{
$operators = array_merge(
array('='),
array_keys($this->env->getUnaryOperators()),
array_keys($this->env->getBinaryOperators())
);
$operators = array_combine($operators, array_map('strlen', $operators));
arsort($operators);
$regex = array();
foreach ($operators as $operator => $length) {
// an operator that ends with a character must be followed by
// a whitespace or a parenthesis
if (ctype_alpha($operator[$length - 1])) {
$regex[] = preg_quote($operator, '/').'(?=[\s()])';
} else {
$regex[] = preg_quote($operator, '/');
}
}
return '/'.implode('|', $regex).'/A';
}
protected function pushState($state)
{
$this->states[] = $this->state;
$this->state = $state;
}
protected function popState()
{
if (0 === count($this->states)) {
throw new Exception('Cannot pop state without a previous state');
}
$this->state = array_pop($this->states);
}
}

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